MoeCTF 2025

学习逆向一年了,重温Moe

speed

动调即可

1
2
3
4
enc = [0x6D,0x6F,0x65,0x63,0x74,0x66,0x7B,0x4A,0x75,0x73,0x74,0x5F,0x64,0x79,0x6E,0x40,0x6D,0x69,0x63,0x5F,0x64,0x33,0x62,0x75,0x67,0x67,0x31,0x6E,0x67,0x7D]

for i in range(len(enc)):
print(chr(enc[i]),end="")

moectf{Just_dyn@mic_d3bugg1ng}

image-20250915161716947

reverse

moectf{open_your_IDA_and_start_reverse_engineering!!}

image-20250915161902215

base

image-20250915162038181

catch

假的xor实则凯撒密码

1
2
3
4
5
6
7
8
9
10
11
12
13
def solve():
s2 = "zbrpgs{F4z3_Ge1px_jvgu_@sybjre_qrfhjn}"
decrypted_s2 = ""
for char in s2:
if 'a' <= char <= 'z':
decrypted_s2 += chr((ord(char) - ord('a') + 13) % 26 + ord('a'))
elif 'A' <= char <= 'Z':
decrypted_s2 += chr((ord(char) - ord('A') + 13) % 26 + ord('A'))
else:
decrypted_s2 += char
print(f"flag: {decrypted_s2}")

solve()

image-20250915170131925

moectf{S4m3_Tr1ck_with_@flower_desuwa}

upx

upx脱壳后解密很简单,但是发现不大对,后来跟moe的头异或了下发现还要异或0xa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enc = [35,43,39,54,51,60,3,72,100,11,29,118,123,16,11,58,63,101,118,41,21,55,28,10,8,33,62,60,61,22,11,36,0x29,0x24,0x56]
flag = [0] * 35
n = len(enc)

flag[n-1] = enc[n-1] ^ 0x21

for i in range(n-2, -1, -1):
flag[i] = enc[i] ^ 0x21 ^ flag[i+1]

for i in range(n):
flag[i] ^= 0xa

result = "".join(map(chr, flag))
print(f"The flag is: {result}")

moectf{Y0u_c4n_unp4ck_It_vvith_upx}

ez3

直接解开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from z3 import *

a = [0xB1B0,0x5678,0x7FF2,0xA332,0xA0E8,0x364C,0x2BD4,0xC8FE,0x4A7C,0x18,0x2BE4,0x4144,0x3BA6,0xBE8C,0x8F7E,0x35F8,0x61AA,0x2B4A,0x6828,0xB39E,0xB542,0x33EC,0xC7D8,0x448C,0x9310,0x8808,0xADD4,0x3CC2,0x796,0xC940,0x4E32,0x4E2E,0x924A,0x5B5C]


flag_chars = [BitVec(f'flag_{i}', 32) for i in range(34)]

b_actual = [BitVec(f'b_actual_{i}', 32) for i in range(34)]

solver = Solver()

for i in range(34):
solver.add(flag_chars[i] >= 32)
solver.add(flag_chars[i] < 127)

temp_val = 47806 * (flag_chars[i] + i)

if i > 0:
temp_val = temp_val ^ b_actual[i-1] ^ 0x114514

solver.add(b_actual[i] == URem(temp_val, 51966))
solver.add(b_actual[i] == a[i])

if solver.check() == sat:
model = solver.model()
flag = ""
for i in range(34):
flag += chr(model[flag_chars[i]].as_long())
print('moectf{' + flag + '}')
else:
print("Failed to find a solution.")

moectf{Y0u_Kn0w_z3_S0Iv3r_N0w_a1f2bdce4a9}

ezandroid

1
bW9lY3Rme2FuZHJvaWRfUmV2ZXJzZV9JNV9lYXN5fQ==

解码得到moectf{android_Reverse_I5_easy}

flower

花指令

1
2
3
jz short Label
jnz short Label
call near ptr Label+1
1
2
把jz、jnz→jmp;
call Label+1直接NOP掉。

对方法u,c,p即可

1
2
3
4
5
6
enc2 = [79, 26, 89, 31, 91, 29, 93, 111, 123, 71, 126, 68, 106, 7, 89, 103, 14, 82, 8, 99, 92, 26, 82, 31, 32, 123, 33, 119, 112, 37, 116, 43]
key = 0x23 ^ 0xa

for i in range(len(enc2)):
print(chr(key ^ enc2[i]), end='')
key += 1

key偷偷做了修改

image-20250916111403946

moectf{f0r3v3r_JuMp_1n_7h3_a$m_a9b35c3c}

2048_master_re

image-20250916190155331

一个xxtea动调得到n即可写代码解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <stdint.h>

#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))

void xxtea_decrypt(uint32_t* v, int n, uint32_t* key) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) {
rounds = 6 + 52 / n;
sum = rounds * 0x3E9779B9;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= 0x3E9779B9;
} while (--rounds);
}
}

int main() {
unsigned int* key = (uint32_t*)"2048master2048ma";
unsigned char encrypted[] = {0x35,0x79,0x77,0xCC,0x1B,0x13,0x41,0x34,0xF9,0xFF,0x9F,0x91,0xFF,0x5B,0x94,0x78,0x86,0x2A,0xAF,0xAE,0xD7,0x9E,0x31,0x4D,0x7A,0xC4,0xA5,0x51,0xD1,0xD9,0x6E,0x44,0x18,0x52,0x86,0x1B,0x42,0x8A,0xC9,0x63};
xxtea_decrypt((uint32_t*)encrypted, 0xa, key);
for (int i = 0; i < 48; i++) printf("%c", encrypted[i]);
printf("\n");
return 0;
}

moectf{@_N1c3_cup_0f_XXL_te4_1n_2O48}

tea

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdint.h>
#include <iostream>

using namespace std;

void decrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x114514;
uint32_t sum = (32) * delta;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
for (uint32_t i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}
v[0] = v0; v[1] = v1;
}

int main() {
unsigned int enc[10] = { 0x78C594AB, 578894681, 1193947460, -229306230, 73202484, 961145356 ,-881456792 ,358205817,-554069347 ,119347883 };
unsigned int key[4] = { 0x11451419,0x19810114,0x51419198,0x10114514 };
for (int i = 0; i < 5; i++) {
decrypt(&enc[i * 2], key);
}
printf("%.40s\n", enc);
}

ezPy

image-20250916200025347

1
2
3
4
ciphertext = "wyomdp{I0e_Ux0G_zim}"
shift = 114514 % 26 # 只需取模 26
plaintext = caesar_cipher_decrypt(ciphertext, shift)
print("Decrypted flag:", plaintext)

moectf{Y0u_Kn0W_pyc}

have_fun

没看出来想考察什么

1
2
3
s = "GEOI^LQbj\\" + "".join(map(chr, [0x001E, 0x0075, 0x004C, 0x007F, 0x0044, 0x0057]))
plain = "".join(chr(ord(c) ^ 0x2A) for c in s)
print(plain) # moectf{H@v4_fUn}

moectf{H@v4_fUn}

mazegame

maze.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
11111111111111111111111111111111111111111111111111111111
10100000000000000010000011011101011111111101011100000111
10111010111111111010111011000001000001000001000101110111
10000010000010000010001011011111111101110111011101110111
10111111111011101110111011010000000000010100010001110111
10100000001000101000100011010101111111011101110101110111
10101011111110111011101011010101000001000000010101110111
10101010000010100000101011110101110101111101111111110111
10111010111010101111101011100101000100000101000101110111
10000010001010001000001011001111011111010101011101110111
11111011101011111011111111101000100000101100101001110111
10001010001000100010000010001010011000100010010011000001
10111010111110101010111011011001011111010101011101011101
10001010001000001010001011000101000100000101000101011101
11101011101111111011101011110101110111111101110101011101
10001000101000001010001011000100010100000101000101011101
10111111101011101110111011011111110101110111011101011101
10001000001000100000001011000100000100010000000101011001
11101011111011111111101011110101111101111111110101011011
10101000000010001000101011010100000001000100010101011011
10101111111110101010101011010111111111010101010101011011
10100000000000100010101011010000000000010001010101011011
10111111111111111110111011011111111111111111011101011011
10000000001111000000000011110111010000111100011111011011
11101111100000011011011111111010110111011101100001011011
11101111111111111011011111111101110111101101100001011011
10001000111111000010000011111010110111011101100001011011
10111010111111111010111011110111010000111101100001010011
10000010000010000010001011111111111111111101100001010111
10111111111011101110111011110001000110001101100001010001
10100000001000101000100011110111011101111101100001011101
10101011111110111011101011110001000101111101100001011101
10101010000010100000101011111101011101111101100001011101
10111010111010101111101011110001000110001101100001011101
10000010001010101000001011111111111111111101100001011101
11111011101011111011111110000000000000001101100001011101
10001010001000100010000011111111111111111100110011011101
10111010111110101010111010010000000011111110001111011101
10001010001000001010001010110111000001111110100101011101
11101011101111111011101000110011001111111100110111011101
10001000101000001010001011111111111111111111110111010001
10111111101011101110111010100001001100000000000011011011
10001000001000100000001011111111111101011101111001011011
10101011111011111111101011000000000001000100010111011011
10101000000010001000101010010111111111111111111111011011
10101111111110101010101010110111111111111111111101011011
10100000000000100010101011100000000000000000000011011011
10111111111111111110011011111111111111111111111011011011
10000011111111111111000010000000000000000000000000011001
11111011111111111111111111111111111111111111111111111101
11111011100001100110110111000000000000000000000111111101
11111011101111011010000111011111111111111111110111111101
11111011100001000010110110000111111111111111110000000001
11111011101111011010110111101111111111111111111111111111
11110000000000011000110000000000000000000000000000000011

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def load_maze_txt(path="maze.txt"):
with open(path, "r", encoding="utf-8") as f:
lines = [ln.strip() for ln in f if ln.strip()]
# 校验只含0/1
for i,ln in enumerate(lines,1):
if any(ch not in "01" for ch in ln):
raise ValueError(f"第{i}行含非0/1字符: {ln!r}")
# 转二维整型
maze = [[int(ch) for ch in ln] for ln in lines]
# 可选:校验等宽
w = len(maze[0])
if any(len(row)!=w for row in maze):
raise ValueError("各行长度不一致")
return maze

maze = load_maze_txt("maze.txt")
print(maze)

start_point_x = 1 # 行
start_point_y = 1 # 列
end_point_x = 15 # 行
end_point_y = 32 # 列

directions = [
(-1, 0, 'w'), # 上
(0, -1, 'a'), # 左
(1, 0, 's'), # 下
(0, 1, 'd') # 右
]


def DFS_maze(maze, start_x, start_y, end_x, end_y,directions):
rows = len(maze)
cols = len(maze[0]) if rows > 0 else 0

visited = [[False for _ in range(cols)] for _ in range(rows)]

stack = [(start_x, start_y, "")]
visited[start_x][start_y] = True

while stack:
x, y, path = stack.pop()

if x == end_x and y == end_y:
return path

for dx, dy, direction in directions:
nx, ny = x + dx, y + dy

if 0 <= nx < rows and 0 <= ny < cols and not visited[nx][ny] and maze[nx][ny] == 0:
visited[nx][ny] = True
print(f"{nx},{ny} walked")
stack.append((nx, ny, path + direction))

return "no such path found"

def main():
result = DFS_maze(maze, start_point_x, start_point_y, end_point_x, end_point_y, directions)
print(result)

if __name__ == "__main__":
main()

moectf{ssddddwwddssddddssddssssddwwddwwddwwwwddddssssaassssaaaassaassaawwaawwwwaaaassddssaassddssssaaaassddddddwwwwddddssddddwwddwwaawwddddssssssssssssaaasssdddssssaassssaaaassaassaawwaawwwwaaaassddssaassddssssaaaassddddddwwwwddddssddddwwddwwaawwddddssssssssssssaaawawwwaassaawwaassaaaaaaaaaawwwwaassssssddddssssssdddddddddwwdddssddwwwdddsssdddddwwawwddddddddddddddddddddssddddddddwwwwawwwwwwwwdwwwwwwwwwwwaawwdwwwwwwwwwwdwwwwwwaaaasssssssssssssssssssssssssssssssssssssaaawwaaaaaaaaaaaaaaaaaaawwwddddddddwwddddddddddwwwawaawawwwwwwwwwwwwwddwwwwaassaawwaassaaaaaaaaaawwwwaawwddwwaawwdwwwdwwwwddddddddddssddddssssdsdssddssaassaaaawwaaaassssaaaaaawwddddwwwwaawwawaassdsssdd}

upx_revenge

upx无法秒了,上秘密武器也不行,只能手动脱壳了

插入

image-20250916230213093

动调发现意思Base64编码

image-20250916231107553

Base64原理简单补充:

原始字节:b'A' (0x41)

二进制:01000001

Base64 编码流程:

  1. 拆成 6 比特:010000 (16),010000 (16) …
  2. 查码表:索引 16 是 Q,再查索引 16 还是 Q → 得到 "QQ=="

解码时

拿到字符串 "QQ=="

  1. 看第一个字符 'Q',在码表中的索引是 16 → 6 比特 010000
  2. 第二个 'Q',索引也是 16 → 6 比特 010000
  3. 把这些 6 比特拼起来,再截掉填充,还原出原始的 01000001 → 就是字节 0x41

这是自己设计的Base64因此要重新构建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
table = [0x4f,0x4c,0x4d,0x4a,0x4b,0x48,0x49,0x46,0x47,0x44,0x45,0x42,0x43,0x40,0x41,0x5e,0x5f,0x5c,0x5d,0x5a,0x5b,0x58,0x59,0x56,0x57,0x54,0x6f,0x6c,0x6d,0x6a,0x6b,0x68,0x69,0x66,0x67,0x64,0x65,0x62,0x63,0x60,0x61,0x7e,0x7f,0x7c,0x7d,0x7a,0x7b,0x78,0x79,0x76,0x77,0x74,0x3e,0x3f,0x3c,0x3d,0x3a,0x3b,0x38,0x39,0x36,0x37,0x25,0x21]

enc = "lY7bW=\\ck?eyjX7]TZ\\}CVbh\\tOyTH6>jH7XmFifG]H7"

rev = {chr(ch):i for i,ch in enumerate(table)}
enc_bytes = bytearray()

mid = []
for i in enc:
mid.append(rev[i])
if len(mid) == 4:
b1,b2,b3,b4 = mid
enc_bytes.append(((b1 << 2) | (b2 >> 4)))
enc_bytes.append(((b2 & 0xf) << 4 | (b3 >> 2)))
enc_bytes.append(((b3 & 0x3) << 6 | b4))
mid = []

print(enc_bytes.decode('latin1'))

这三步把四个6位索引b1b2b3b4拼回三个8位原字节;有“=”填充时按规则省略相应输出字节即可。

moectf{Y0u_Re4l1y_G00d_4t_Upx!!!}

latin1 的特点

  • 单字节编码:latin1 是一种单字节字符集,每个字符占用 1 个字节,编码范围为 0x00-0xFF。
  • 兼容 ASCII:latin1 向下兼容 ASCII 编码,0x00-0x7F 范围与 ASCII 完全一致。
  • 局限性:仅支持西欧语言字符,不支持中文、表情符号等复杂字符。
  • 性能优势:由于其单字节特性,处理效率较高,适合对字符集要求不高的场景。

utf8mb4 的特点

  • 多字节编码:utf8mb4 是变长字符集,每个字符占用 1 至 4 个字节。
  • 支持 Unicode:能够存储几乎所有语言的字符,包括中文、阿拉伯文、表情符号等。
  • 国际化支持:适用于需要多语言支持的应用,尤其是现代化的全球化系统。
  • 性能开销:由于多字节特性,utf8mb4 在存储和处理上会占用更多的内存和 CPU 资源。

应用场景

  • latin1:适用于仅需支持西欧语言的系统,如传统的英文网站或应用。
  • utf8mb4:适用于需要支持多语言、表情符号或特殊字符的系统,如社交媒体、国际化电商平台等。

guess

RC4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def rc4_ksa(key):
key_length = len(key)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % key_length] + 42) % 256
S[i], S[j] = S[j], S[i]
return S

def rc4_prga(S, data):
i = 0
j = 0
output = bytearray()
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
output.append(byte ^ S[(S[i] + S[j]) % 256])
return output

def rc4_decrypt(ciphertext, key):
S = rc4_ksa(key)
return rc4_prga(S, ciphertext)

def hex_to_binary(hex_string):
return bytes.fromhex(hex_string)

if __name__ == "__main__":
encrypted_flag_hex = "464DCF81DE6F2E16BE203F10565CCDBFF18CCD6A45967D20DC558FB76C0CC3AE07D154"
key_string = "moectf2025"

ciphertext = hex_to_binary(encrypted_flag_hex)
key = key_string.encode('utf-8')

decrypted_flag = rc4_decrypt(ciphertext, key).decode('utf-8')
print(f"moectf{{{decrypted_flag}}}")

moectf{RrRRccCc44$$_w1th_fl0w3r!!_3c6a11b5}

A simple program

tls中hook了strcmp

1
2
3
4
5
6
7
8
9

BYTE_4031A4 = [0x4E,0x4C,0x46,0x40,0x57,0x45,0x58,0x7A,0x13,0x56,0x7C,0x73,0x17,0x50,0x50,0x66,0x47,0x02,0x02,0x5E]

def recover_a1():
return bytes([b ^ 0x23 for b in BYTE_4031A4]).decode('latin1')

if __name__ == "__main__":
a1 = recover_a1()
print(a1)

moectf{Y0u_P4ssEd!!}

Two cups of tea

KEY

image-20250917111503911

主逻辑是xxtea

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdint.h>

#define MX (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z)))
#define DELTA 0x61C88647

void xxtea_decrypt(uint32_t* v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) {
rounds = 6 + 52 / n;
sum = rounds * 0x9E3779B9;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= 0x9E3779B9;
} while (--rounds);
}
}

int main() {
uint32_t v[10] = { 0x5D624C34,0x8629FEAD,0x9D11379B,0xFCD53211,0x460F63CE, 0xC5816E68,0xFE5300AD,0x0A0015EE,0x9806DBBB,0xEF4A2648 };
unsigned char key[] = { 0x6D, 0x6F, 0x65, 0x63, 0x74, 0x66, 0x21, 0x21, 0x78, 0x56, 0x34, 0x12, 0xF0, 0xDE, 0xBC, 0x9A };
unsigned int* k = (unsigned int*)key;
xxtea_decrypt(v, 10, k);

unsigned char* flag = (unsigned char*)v;
for (int i = 0; i < 10; i++)
{
printf("%c%c%c%c", *((char*)&v[i] + 0), *((char*)&v[i] + 1), *((char*)&v[i] + 2), *((char*)&v[i] + 3));
}

return 0;
}

moectf{X7e4_And_xx7EA_I5_BeautifuL!!!!!}

ezandroid.pro

image-20250917114316949

1
2
3
4
5
from gmssl.sm4 import CryptSM4, SM4_DECRYPT

sm4_dec = CryptSM4()
sm4_dec.set_key(b'moectf2025!!!!!!', SM4_DECRYPT)
print(sm4_dec.crypt_ecb(bytes.fromhex('4EEB1EEF2914D79BFA8C5006332097ED2EF06C4A59CAE31C827A08D45CC649C0B971BF2EFBCB160E531A646DF7A6AC0B')))

moectf{SM4_Android_I5_Funing!!!}

rusty_sudoku

逻辑摘要:读入一行→去空白→必须81位且仅‘1..9’或‘.’→与题面a68718379181594比对(非‘.’位置不能改)→构盘→校验数独是否解完→对原始81字节答案做MD5(小写)→输出flag

题目:.6..8..7.18.3……7.9….1…8…15.9..4.2..54…2..9…..3948…..5..7..3….5.

解:369184572185327694274956831632879415897541263541632789756213948918465327423798156

MD5:a8c79927d4e830c3fe52e79f410216a0

moectf{a8c79927d4e830c3fe52e79f410216a0}