02 逆向工程入门指北
文件提示:
现在,用 IDA 打开本题的附件,开始我们的第一个挑战吧!
P.S. 附件使用C++编写,有很多正常人看不懂的东西,不过或许 shift+F12 能帮到你?

moectf{open_your_IDA_and_start_reverse_engineering!!}
03 base
IDA打开,找到main函数

查看sub_140001070函数

是一个 完全遵循Base64编码的标准算法流程
使用strcmp函数,将 sub_140001070 返回的字符串v7与一个硬编码的字符串**“bW9lY3Rme1kwdV9DNG5fRzAwZF9BdF9CNDVlNjQhIX0=“**进行比较。

moectf{Y0u_C4n_G00d_At_B45e64!!}
10 A cup of tea
搜索main函数无果,shift6+F12搜索字符串

我们看到主函数在sub_1400162E0里,继续点击

v5是密钥,v6是数组,加密逻辑在sub_14001109B里

每一轮使用 delta(这里是 1131796)和 key 进行混淆 ,逆解密
from typing import List, Tuple
DELTA = 1131796 # 0x114514 as seen in the binary (decimal 1131796)
def u32(x: int) -> int: return x & 0xFFFFFFFF
def tea_decrypt_block(v0: int, v1: int, k: List[int], rounds: int = 32) -> Tuple[int, int]: v0 = u32(v0) v1 = u32(v1) k = [u32(x) for x in k] sum_ = u32(DELTA * rounds) for _ in range(rounds): v1 = u32(v1 - (((v0 >> 5) + k[3]) ^ (sum_ + v0) ^ ((v0 << 4) + k[2]))) v0 = u32(v0 - (((v1 >> 5) + k[1]) ^ (sum_ + v1) ^ ((v1 << 4) + k[0]))) sum_ = u32(sum_ - DELTA) return v0, v1
def tea_encrypt_block(v0: int, v1: int, k: List[int], rounds: int = 32) -> Tuple[int, int]: v0 = u32(v0) v1 = u32(v1) k = [u32(x) for x in k] sum_ = 0 for _ in range(rounds): sum_ = u32(sum_ + DELTA) v0 = u32(v0 + (((v1 >> 5) + k[1]) ^ (sum_ + v1) ^ ((v1 << 4) + k[0]))) v1 = u32(v1 + (((v0 >> 5) + k[3]) ^ (sum_ + v0) ^ ((v0 << 4) + k[2]))) return v0, v1
def words_to_bytes_le(words: List[int]) -> bytes: return b"".join(w.to_bytes(4, "little") for w in words)
def main(): key = [289739801, 427884820, 1363251608, 269567252] v6 = [ 2026214571, 578894681, 1193947460, -229306230, 73202484, 961145356, -881456792, 358205817, -554069347, 119347883, 0, ] v6_u = [u32(x) for x in v6] ciphertext_words = v6_u[:10] plaintext_words = [] for i in range(0, len(ciphertext_words), 2): p0, p1 = tea_decrypt_block(ciphertext_words[i], ciphertext_words[i+1], key) plaintext_words.extend([p0, p1]) pt_bytes = words_to_bytes_le(plaintext_words) flag = pt_bytes.split(b"\x00", 1)[0].decode('utf-8', errors='replace') print("Recovered flag string:", flag)
re_cipher_words = [] for i in range(0, len(plaintext_words), 2): c0, c1 = tea_encrypt_block(plaintext_words[i], plaintext_words[i+1], key) re_cipher_words.extend([c0, c1]) print("Verification success?", re_cipher_words == ciphertext_words)
if __name__ == '__main__': main()moectf{h3r3_4_cuP_0f_734_f0R_y0U!!!!!!}
11 ezpy
简单的pyc逆向
通过命令
uncompyle6 ezpy.pyc >ezpy.py
看到生成了ezpy.py

打开ezpy.py文件

实际就是 shift = 10 的凯撒加密, 只要把目标字符串 只要把目标字符串 “wyomdp{I0e_Ux0G_zim}” 逆向 Caesar -10 就行。
def caesar_cipher_decrypt(text, shift): result = [] for char in text: if char.isalpha(): if char.islower(): new_char = chr((ord(char) - ord("a") - shift) % 26 + ord("a")) else: if char.isupper(): new_char = chr((ord(char) - ord("A") - shift) % 26 + ord("A")) result.append(new_char) else: result.append(char) return "".join(result)
if __name__ == "__main__": encrypted = "wyomdp{I0e_Ux0G_zim}" shift = 114514 % 26 # 实际等效移位量 = 10 flag = caesar_cipher_decrypt(encrypted, shift) print("解密结果:", flag)moectf{Y0u_Kn0W_pyc}
13 mazegame
IDA打开,定位到主函数,是一个迷宫, 需要找到从起始点 **(1, 1)** 到终点 **(32, 15)** 的一条有效路径

迷宫地图在sub_1400010E0函数里

提取出,并按顺序排好
1111111111111111111111111111111111111111111111111111111110100000000000000010000011011101011111111101011100000111101110101111111110101110110000010000010000010001011101111000001000001000001000101101111111110111011101110111011110111111111011101110111011010000000000010100010001110111101000000010001010001000110101011111110111011101011101111010101111111011101110101101010100000100000001010111011110101010000010100000101011110101110101111101111111110111101110101110101011111010111001010001000001010001011101111000001000101000100000101100111101111101010101110111011111111011101011111011111111101000100000101100101001110111100010100010001000100000100010100110001000100100110000011011101011111010101011101101100101111101010101110101110110001010001000001010001011000101000100000101000101011101111010111011111110111010111101011101111111011101010111011000100010100000101000101100010001010000010100010101110110111111101011101110111011011111110101110111011101011101100010000010001000000010110001000001000100000001010110011110101111101111111110101111010111110111111111010101101110101000000010001000101011010100000001000100010101011011101011111111101010101010110101111111110101010101010110111010000000000010001010101101000000000001000101010101101110111111111111111110111011011111111111111111011101011011100000000011110000000000111101110100001111000111110110111110111110000001101101111111101011011101110110000101101111101111111111111011011111111101110111101101100001011011100010001111110000100000111110101101110111011000010110111011101011111111101011101111011101000011110110000101001110000010000010000010001011111111111111111101100001010111101111111110111011101110111100010001100011011000010100011010000000100010100010001111011101110111110110000101110110101011111110111011101011110001000101111101100001011101101010100000101000001010111111010111011111011000010111011011101011101010111110101111000100011000110110000101110110000010001010101000001011111111111111111101100001011101111110111010111110111111100000000000000011011000010111011000101000100010001000001111111111111111110011001101110110111010111110101010111010010000000011111110001111011101100010100010000010100010101101110000011111101001010111011110101110111111101110100011001100111111110011011101110110001000101000001010001011111111111111111111110111010001101111111010111011101110101000010011000000000000110110111000100000100010000000101111111111110101110111100101101110101011111011111111101011000000000001000100010111011011101010000000100010001010100101111111111111111111110110111010111111111010101010101011011111111111111111110101101110100000000000100010101011100000000000000000000011011011101111111111111111100110111111111111111111111110110110111000001111111111111100001000000000000000000000000001100111111011111111111111111111111111111111111111111111111101111110111000011001101101110000000000000000000001111111011111101110111101101000011101111111111111111111011111110111111011100001000010110110000111111111111111110000000001111110111011110110101101111011111111111111111111111111111111000000000001100011000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111用BFS算法寻路
from collections import deque
def find_maze_path(maze, start, end): # maze: 56x56的迷宫地图('0'和'1'的二维列表) # start: 起点坐标 (x, y) # end: 终点坐标 (x, y)
rows = len(maze) cols = len(maze[0])
# 移动方向:上、下、左、右 directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] # (dx, dy)
# 队列用于BFS queue = deque([start])
# 集合用于记录已访问的节点 visited = {start}
# 字典用于回溯路径 parent = {start: None}
while queue: current_x, current_y = queue.popleft() current_node = (current_x, current_y)
# 检查是否到达终点 if current_node == end: return reconstruct_path(parent, start, end)
# 探索所有可能的移动方向 for dx, dy in directions: next_x, next_y = current_x + dx, current_y + dy next_node = (next_x, next_y)
# 检查移动是否有效 if 0 <= next_x < cols and 0 <= next_y < rows and \ maze[next_y][next_x] == '0' and next_node not in visited:
visited.add(next_node) queue.append(next_node) parent[next_node] = current_node
return None
def reconstruct_path(parent_map, start, end): path = [] current = end while current: path.append(current) current = parent_map[current]
path.reverse()
instructions = [] for i in range(len(path) - 1): x1, y1 = path[i] x2, y2 = path[i+1]
if x2 > x1: instructions.append('D') elif x2 < x1: instructions.append('A') elif y2 > y1: instructions.append('S') elif y2 < y1: instructions.append('W')
return "".join(instructions)
maze_map_strings = [ "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", "11111111111111111111111111111111111111111111111111111111",]maze_map = [list(row) for row in maze_map_strings]
start_point = (1, 1)end_point = (32, 15)
path_instructions = find_maze_path(maze_map, start_point, end_point)
if path_instructions: print("找到了路径!") print(path_instructions)else: print("没有找到路径。")moectf{SSDDDDWWDDSSDDDDSSDDSSSSDDWWDDWWDDWWWWDDDDSSSSAASSSSAAAASSAASSAAWWAAWWWWAAAASSDDSSAASSDDSSSSAAAASSDDDDDDWWWWDDDDSSDDDDWWDDWWAAWWDDDDSSSSSSSSSSSSAAASSSDDDSSSSAASSSSAAAASSAASSAAWWAAWWWWAAAASSDDSSAASSDDSSSSAAAASSDDDDDDWWWWDDDDSSDDDDWWDDWWAAWWDDDDSSSSSSSSSSSSAAAWAWWWAASSAAWWAASSAAAAAAAAAAWWWWAASSSSSSDDDDSSSSSSDDDDDDDDDWWDDDSSDDWWWDDDSSSDDDDDWWAWWDDDDDDDDDDDDDDDDDDDDSSDDDDDDDDWWWWAWWWWWWWWDWWWWWWWWWWWAAWWDWWWWWWWWWWDWWWWWWAAAASSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSAAAWWAAAAAAAAAAAAAAAAAAAWWWDDDDDDDDWWDDDDDDDDDDWWWAWAAWAWWWWWWWWWWWWWDDWWWWAASSAAWWAASSAAAAAAAAAAWWWWAAWWDDWWAAWWDWWWDWWWWDDDDDDDDDDSSDDDDSSSSDSDSSDDSSAASSAAAAWWAAAASSSSAAAAAAWWDDDDWWWWAAWWAWAASSDSSSDD}
17 Two cup of tea
IDA打开定位主函数

数组v15是加密后的数据,v10是key,但经过函数处理的才是我们需要的

打开sub_140001070函数

它的作用就是:
生成/混淆出 XXTEA 所需的前半部分 key。
key = [final_low, final_high, 0x12345678, 0x9ABCDEF0];之后是加密函数sub_1400015E0,是一个改造过的 XXTEA 解密实现,固定迭代 11 轮。

解密脚本
def xxtea_decrypt_uint32(v, key): n = len(v) delta = 0x9E3779B9 q = 11 # 题里固定跑 11 轮 sum_ = (q * delta) & 0xFFFFFFFF
while q > 0: e = (sum_ >> 2) & 3 for p in range(n-1, -1, -1): z = v[p-1] if p > 0 else v[-1] y = v[p] v[p] = (y - (((z >> 5 ^ (v[(p+1) % n] << 2)) + (v[(p+1) % n] >> 3 ^ (z << 4))) ^ ((sum_ ^ v[(p+1) % n]) + (key[(p & 3) ^ e] ^ z)))) & 0xFFFFFFFF sum_ = (sum_ - delta) & 0xFFFFFFFF q -= 1 return v
# v15: main 函数里的常量数组(10个 32-bit)v15 = [ 1566723124, -2044068179, -1659816037, -53136879, 1175413710, -981373336, -28114771, 167777774, -1744380997, -280353208]
v15 = [x & 0xFFFFFFFF for x in v15]
# key 的两个部分key_low = 0x12345678key_high = 0x9ABCDEF0final_low = 1667592045final_high = 555837044
key = [final_low, final_high, key_low, key_high]
# 解密dec = xxtea_decrypt_uint32(v15[:], key)plain = b''.join(x.to_bytes(4, 'little') for x in dec)
print("解密结果:", plain.decode(errors="ignore"))moectf{X7e4_And_xx7EA_I5_BeautifuL!!!!!}
19 rusty_sudoku
shift+F12 查看字符串

点进去看到

通过交叉引用,定位到rusty_sudoku::main函数

分析函数以及在字符串中看到的提示,就是找到一个正确的9*9数独,再回到字符串视图页面,发现有一个未补全的数独,点进去看到完整的数独
![]()

使用在线工具

将 369184572185327694274956831632879415897541263541632789756213948918465327423798156 作为程序的输入

moectf{a8c79927d4e830c3fe52e79f410216a0}
01 speed
IDA定位主函数,发现返回到WinMain函数里

打开该函数,这个函数注册了一个窗口,只显示1秒,就立即销毁,真正的逻辑在WndProc里

打开WndProc函数

加密算法为RC4,密钥 (key): “mylittlepony”
密文


def rc4_crypt(key: bytes, data: bytes) -> bytes: # simple RC4 (KSA + PRGA) S = list(range(256)) j = 0 # KSA for i in range(256): j = (j + S[i] + key[i % len(key)]) & 0xFF S[i], S[j] = S[j], S[i] # PRGA i = 0 j = 0 out = bytearray(len(data)) for idx in range(len(data)): i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) & 0xFF] out[idx] = data[idx] ^ K return bytes(out)
def main(): key = b"mylittlepony" str_part = (0x07F1B3E885EF9160).to_bytes(8, "little")
v25 = (0x2CD336BCB0464A89).to_bytes(8, "little")
v26 = bytearray((0xEF5FC91642917EE1).to_bytes(8, "little"))
overwrite = (0x739D40A4E356EF5F).to_bytes(8, "little")
v26[6:6+8] = overwrite
cipher_bytes = str_part + v25 + bytes(v26)
plaintext = rc4_crypt(key, cipher_bytes)
print("raw decrypted bytes:", plaintext) try: s = plaintext.decode("utf-8") except UnicodeDecodeError: s = plaintext.decode("utf-8", errors="ignore") print("decoded (utf-8, ignoring errors):") print(s)
if __name__ == "__main__": main()
moectf{Just_dyn@mic_d3bugg1ng}
04 catch
定位主函数,

打开solve函数

看到提示,打开sub_114514()函数

发现是根据enc函数进行加密
flag字符串为

enc函数就是一个简单的异或

异或结果为

显然不是我么们的flag,回到刚才的提示flag就藏在项目里,我们查看字符串看到可疑对象

使用解码工具一键解码

Rot13解码: moectf{S4m3_Tr1ck_with_@flower_desuwa}
12 have_fun
点开WinMain函数,是一个典型的 Win32 GUI 程序入口(WinMain)的反编译版

入口 WinMain 很模板化,实质逻辑都在 sub_140001210

分别查看sub_7FF681721420函数与DialogFunc函数,发现前者是空壳,真正的加密逻辑在DialogFunc里

输入长度为16

对每个字符做异或

把异或后的结果 v23 与目标常量比较

目标常量为[‘G’,‘E’,‘O’,‘I’,’^’,‘L’,‘Q’,‘b’,‘j’,’\’, 0x1E, ‘u’,‘L’, 0x7F,‘D’,‘W’]

解密
# 只需改这里:把 .rdata 里目标串的 16 个字节(低 8 位)填进来target = [ 0x47, 0x45, 0x4F, 0x49, 0x5E, 0x4C, 0x51, 0x62, 0x6A, 0x5C, 0x1E, 0x75, 0x4C, 0x7F, 0x44, 0x57]
flag = ''.join(chr((b ^ 0x2A) & 0xFF) for b in target)print(flag) # -> moectf{H@v4_fUn}moectf{H@v4_fUn}
07 ezandroid
在AndroidManifest.xml找到android

在源代码里按照路径打开com.example.ezandroid.MainActivity

是一个base64编码,字符串为bW9lY3Rme2FuZHJvaWRfUmV2ZXJzZV9JNV9lYXN5fQ==

moectf{android_Reverse_I5_easy}
18 ezandroid.pro
用jdax打开这个apk文件
定位到AndroidManifest.xml,在其中找到android

按照此路径在源代码中打开

Java 层只做了长度检查(32)和 check(str) 的调用;真正判定逻辑在 native 库 libezandroidpro.so
把apk后缀名改为zip
在ida中打开libezandroidpro.so
定位到Java_com_example_ezandroidpro_MainActivity_check函数

是一个sm4—ECB加密,密钥是moectf2025!!!!!!
明文4EEB1EEF2914D79BFA8C5006332097ED2EF06C4A59CAE31C827A08D45CC649C0B971BF2EFBCB160E531A646DF7A6AC0B

moectf{SM4_Android_I5_Funing!!!}
05 upx
用die打开有upx加壳

用upx脱壳

脱壳完成在ida中打开,定位主函数

字符串长度为35

加密过程

要加密的数据(按顺序)


脚本
def reverse_decrypt(encrypted_data):
decrypted = []
for i in range(len(encrypted_data)): if i == 0: # 第一个字符已知是 'm' (moectf的开头) first_char = ord('m') decrypted.append(first_char) else: # 使用链式XOR解密公式 prev_encrypted = encrypted_data[i-1] # 前一个加密字节 prev_decrypted = decrypted[i-1] # 前一个解密字节 current_char = prev_encrypted ^ prev_decrypted ^ 0x21 decrypted.append(current_char)
return bytes(decrypted)
def encrypt_for_verification(plaintext):
plaintext_bytes = plaintext.encode('ascii') encrypted = []
for i in range(len(plaintext_bytes) - 1): # 加密公式: encrypted[i] = (plaintext[i] ^ 0x21) ^ plaintext[i+1] enc_byte = (plaintext_bytes[i] ^ 0x21) ^ plaintext_bytes[i+1] encrypted.append(enc_byte)
return encrypted
def solve_moe_exe():
print("=== moe.exe CTF题目解密算法 ===") print("题目类型: UPX脱壳 + 自定义XOR加密") print()
complete_encrypted_data = [ 0x23, 0x2B, 0x27, 0x36, 0x33, 0x3C, 0x03, 0x48, 0x64, 0x0B, 0x1D, 0x76, 0x7B, 0x10, 0x0B, 0x3A, 0x3F, 0x65, 0x76, 0x29, 0x15, 0x37, 0x1C, 0x0A, 0x08, 0x21, 0x3E, 0x3C, 0x3D, 0x16, 0x0B, 0x24, 0x29, 0x24 ]
print(f"加密数据长度: {len(complete_encrypted_data)} 字节") print("加密数据 (16进制):") for i in range(0, len(complete_encrypted_data), 16): chunk = complete_encrypted_data[i:i+16] hex_str = ' '.join(f'{b:02X}' for b in chunk) print(f" {hex_str}") print()
# 执行解密 print("开始解密...") decrypted_bytes = reverse_decrypt(complete_encrypted_data)
# 转换为字符串 try: decrypted_str = decrypted_bytes.decode('ascii') print(f"解密结果: {decrypted_str}") except UnicodeDecodeError: print("解密结果包含非ASCII字符,显示原始字节:") print(decrypted_bytes) return None
# 检查并修正flag格式 if decrypted_str.startswith('moectf{') and not decrypted_str.endswith('}'): # 手动添加结尾的'}' final_flag = decrypted_str + '}' print(f"修正后的flag: {final_flag}") else: final_flag = decrypted_str
# 验证flag格式 if final_flag.startswith('moectf{') and final_flag.endswith('}'): print("✅ Flag格式正确!")
if 'upx' in final_flag.lower(): print("🎯 包含UPX关键词,符合题目背景!")
return final_flag else: print("❌ Flag格式不正确") return None
def demo_step_by_step(): """ 演示解密过程的每一步 """ print("\n=== 逐步解密演示 ===")
# 使用前10字节进行演示 demo_data = [0x23, 0x2B, 0x27, 0x36, 0x33, 0x3C, 0x03, 0x48, 0x64, 0x0B]
print("演示数据:", ' '.join(f'{b:02X}' for b in demo_data)) print()
decrypted = []
for i in range(len(demo_data)): if i == 0: # 第一个字符 char = ord('m') decrypted.append(char) print(f"步骤 {i+1}: 第一个字符已知 = 'm' (0x{char:02X})") else: # 后续字符的解密 prev_encrypted = demo_data[i-1] prev_decrypted = decrypted[i-1] current_char = prev_encrypted ^ prev_decrypted ^ 0x21 decrypted.append(current_char)
char_display = chr(current_char) if 32 <= current_char <= 126 else f'\\x{current_char:02x}' print(f"步骤 {i+1}: {prev_encrypted:02X} ^ {prev_decrypted:02X} ^ 21 = {current_char:02X} ('{char_display}')")
# 显示部分结果 partial_result = bytes(decrypted).decode('ascii', errors='ignore') print(f"\n部分解密结果: {partial_result}")
def main(): """ 主函数 - 执行完整的解密流程 """ print("moe.exe CTF题目完整解密算法") print("=" * 50)
# 1. 执行解密 final_flag = solve_moe_exe()
if final_flag: print(f"\n🏆 最终Flag: {final_flag}")
# 2. 演示解密步骤 demo_step_by_step()
print("\n" + "=" * 50) print("解密完成!") print(f"Flag: {final_flag}") print("题目类型: UPX脱壳 + 自定义XOR加密算法逆向") print("=" * 50)
return final_flag else: print("\n❌ 解密失败") return None
if __name__ == "__main__": main()moectf{Y0u_c4n_unp4ck_It_vvith_upx}
06 ez3
ida中打开定位到主函数

flag长度为42

真正的加密逻辑在check()

点开

脚本
#!/usr/bin/env python3# -*- coding: utf-8 -*
MOD = 51966MUL = 47806XOR_VAL = 0x114514LEN = 34
# expected 数组(你给出的值)a = [ 0x0B1B0, 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]
def compute_b(ch: int, i: int, prev_b: int|None) -> int: tmp = MUL * (ch + i) if prev_b is not None: tmp ^= (prev_b ^ XOR_VAL) return tmp % MOD
# 优先级:'_' 最优,接着 数字,再字母,最后其他可打印字符def candidate_priority(ch: int): c = chr(ch) if c == '_': return 0 if c.isdigit(): return 1 if c.isalpha(): return 2 if 32 <= ch < 127: return 3 return 4
def gen_candidates(pos: int, prev_b: int|None, byte_range=range(32,127)): t = a[pos] cands = [] for ch in byte_range: if compute_b(ch, pos, prev_b) == t: cands.append(ch) # 按优先级排序(数字优先于字母) cands.sort(key=lambda x: (candidate_priority(x), x)) return cands
# DFS 回溯,返回若干候选解(按优先级)def dfs_find(byte_range=range(32,127), max_solutions=20): solutions = [] stack = [(0, None, [])] # pos, prev_b, bytes_list
while stack and len(solutions) < max_solutions: pos, prev_b, cur = stack.pop() if pos == LEN: solutions.append(bytes(cur)) continue
cands = gen_candidates(pos, prev_b, byte_range) # 逆序压栈,确保优先级高的先弹出 for ch in reversed(cands): new_b = compute_b(ch, pos, prev_b) stack.append((pos + 1, new_b, cur + [ch]))
return solutions
def main(): print("开始回溯求解(数字优先于字母)...") sols = dfs_find(range(32,127), max_solutions=20) if not sols: print("未在可打印 ASCII 找到解,请尝试全字节范围。") return
for i, s in enumerate(sols): try: text = s.decode('ascii') except: text = s.decode('latin1') print(f"[{i}] 候选:{text}") print(" 完整 flag:", "moectf{" + text + "}")
print("\n建议第一个候选通常为正确结果:") best = sols[0].decode('ascii', errors='replace') print("最终 flag:", "moectf{" + best + "}")
if __name__ == "__main__": main()moectf{Y0u_Kn0w_z3_S0Iv3r_N0w_a1f2bdce4a9}
16 A simple program
ida中打开,定位到main函数

输入到str1中,并与str2对比,相等就输出correct,否则输出wrong,但这个主函数并没有什么校验函数,根据题目提示“falg就在主函数里”,查看字符串

上面的moectf是假的flag,下面的数据组就是校验数组,点开这个函数

正如我们所料,对这个数组的数据进行异或,写个脚本
ARR = [ 0x4E, 0x4C, 0x46, 0x40, 0x57, 0x45, 0x58, 0x7A, 0x13, 0x56, 0x7C, 0x73, 0x17, 0x50, 0x50, 0x66, 0x47, 0x02, 0x02, 0x5E]
flag = bytes(b ^ 0x23 for b in ARR).decode('ascii')print(flag) # moectf{Y0u_P4ssEd!!}moectf{Y0u_P4ssEd!!}
08 flower
IDA定位到主函数

点击slove函数,有栈堆不平衡

定位到这个位置,发现有花指令,还有函数边界问题

先修改花指令

之后修改函数边界对着冒红行摁u

之后正常反汇编,查看slove函数,找到最主要的一步

encode函数以及enc和key,其中正确的初始key很关键


脚本
import string
# enc 常量表(32 个)enc = [ 0x4F, 0x1A, 0x59, 0x1F, 0x5B, 0x1D, 0x5D, 0x6F, 0x7B, 0x47, 0x7E, 0x44, 0x6A, 0x07, 0x59, 0x67, 0x0E, 0x52, 0x08, 0x63, 0x5C, 0x1A, 0x52, 0x1F, 0x20, 0x7B, 0x21, 0x77, 0x70, 0x25, 0x74, 0x2B,]
PRINTABLE = set(string.printable) - set("\r\n\t\x0b\x0c")
def decode_with_key(enc, key): # s[i] = enc[i] ^ (key + i) bs = [(e ^ ((key + i) & 0xFF)) & 0xFF for i, e in enumerate(enc)] return ''.join(chr(b) for b in bs)
def looks_good(s): # 1) 全可打印 2) 不含控制字符 3) 基本可读 return all(ch in PRINTABLE for ch in s)
candidates = []for key in range(256): inner = decode_with_key(enc, key) if looks_good(inner): candidates.append((key, inner))
# 输出候选 key 及解码结果for key, inner in candidates: print(f"key=0x{key:02X} -> {inner}")
# 通常只有极少数候选;从可读性判断最佳那个moectf{f0r3v3r_JuMp_1n_7h3_a$m_a9b35c3c}
14 upx_revenge
我们只需要在4.24.后面插入UPX!就可以正常脱壳了

脱壳

IDA打开,分析主函数,最主要的在这

去分析sub_7FF6BA531230 sub_7FF6BA531300两个函数
sub_7FF6BA531230 是对标准表进行异或14,sub_7FF6BA531300是调用这个表进行Base64编码

异或后的表
GMLQOJPI^DXHZSBUT[ARCYV-KWEN]@F\\(>JD=MLZSIWPHGYAQBEOU)TNVurqpcjgmlhfnadkbisoetv(zyx,0w.4-2863q1)5?9+7只需要输入的字符串用这个表进行Base64编码与硬字符串lY7bW=\ck?eyjX7]TZ\}CVbh\tOyTH6>jH7XmFifG]H7对比
脚本
standard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
encoded = "lY7bW=\\ck?eyjX7]TZ\\}CVbh\\tOyTH6>jH7XmFifG]H7".replace("\\\\", "\\") # 处理转义
# 生成自定义表custom_table = ''.join(chr(ord(c) ^ 0xE) for c in standard_table)
# 创建映射: char -> indexchar_to_index = {custom_table[i]: i for i in range(64)}
# 解码flag_bytes = []for i in range(len(encoded) // 4): group = encoded[i*4:i*4+4] indices = [char_to_index[c] for c in group] value = (indices[0] << 18) | (indices[1] << 12) | (indices[2] << 6) | indices[3] flag_bytes.append(value >> 16 & 0xFF) flag_bytes.append(value >> 8 & 0xFF) flag_bytes.append(value & 0xFF)
flag = ''.join(chr(b) for b in flag_bytes)
print("Flag:", flag)moectf{Y0u_Re4l1y_G00d_4t_Upx!!!}
15 guess
IDA打开,定位到主函数,无法反编译

定位到140001A5A,有花指令

改成单条无条件跳转,其余全部nop

ok,可以成功反编译

分析函数行为,是一个猜谜游戏,主要的逻辑在这里,看名字知道是RC4算法

点进去,主要逻辑在rc4_ksa和rc4_prga

分别点进去查看,先看ksa,比正常RC4相比密钥要加常数42

再看prga是一个标准的

接下来我们是找key和enc,shift+F12

脚本
from binascii import unhexlify
def rc4_with_plus42(data: bytes, key: bytes) -> bytes: # === KSA 带 +42 === S = list(range(256)) j = 0 for i in range(256): k = (key[i % len(key)] + 42) & 0xFF j = (j + S[i] + k) & 0xFF S[i], S[j] = S[j], S[i]
# === PRGA 标准 === i = j = 0 out = bytearray() for b in data: i = (i + 1) & 0xFF j = (j + S[i]) & 0xFF S[i], S[j] = S[j], S[i] K = S[(S[i] + S[j]) & 0xFF] out.append(b ^ K) return bytes(out)
enc_hex = "464DCF81DE6F2E16BE203F10565CCDBFF18CCD6A45967D20DC558FB76C0CC3AE07D154"key_str = "moectf2025"
cipher = unhexlify(enc_hex)plain = rc4_with_plus42(cipher, key_str.encode())
print("moectf{" + plain.decode(errors="ignore") + "}")moectf{RrRRccCc44$$_w1th_fl0w3r!!_3c6a11b5}
09 2048_master_re
这道题是一个窗口游戏题,有两种解题思路
一种是HOOK,另一种是直接逆向解密
先说逆向解密,在字符串中找到这个

点开,是一个flag校验函数, 它从 flag.txt 中读取字符串,经过某种加密/变换后,与内置的字节数组 byte_495280 进行比对,若完全一致则返回 0(正确),否则返回 1(错误)。

去查看加密函数sub_401A81

sub_401898:按 小端把明文每 4 字节打包成 uint32_t 数组,长度 Count=(len+3)//4,没有额外写入原始长度;

sub_401530:标准 BTEA/XXTEA,delta = 1050114489 (0x3E9779B9),key 为 16 字节,索引 (p^e)&3;

sub_40195B:把 uint32_t[] 原样拆回字节(每个 dword → 两个 _WORD),最终输出字节数 a3 = 4Count。

脚本
from typing import List
DELTA = 0x3E9779B9
def to_u32_le_blocks(b: bytes) -> List[int]: pad = (-len(b)) % 4 b += b'\x00' * pad return [int.from_bytes(b[i:i+4], 'little') for i in range(0, len(b), 4)]
def from_u32_le_blocks(v: List[int]) -> bytes: return b''.join(x.to_bytes(4, 'little') for x in v)
def btea(v: List[int], n: int, k: List[int]) -> None: """XXTEA/BTEA inplace; n>1 加密,n<-1 解密""" if n > 1: rounds = 6 + 52 // n _sum = 0 z = v[n - 1] for _ in range(rounds): _sum = (_sum + DELTA) & 0xFFFFFFFF e = (_sum >> 2) & 3 for p in range(n - 1): y = v[p + 1] v[p] = (v[p] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((_sum ^ y) + (k[(p ^ e) & 3] ^ z)))) & 0xFFFFFFFF z = v[p] p = n - 1 y = v[0] v[p] = (v[p] + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((_sum ^ y) + (k[(p ^ e) & 3] ^ z)))) & 0xFFFFFFFF z = v[p] elif n < -1: n = -n rounds = 6 + 52 // n _sum = (rounds * DELTA) & 0xFFFFFFFF y = v[0] while rounds > 0: e = (_sum >> 2) & 3 for p in range(n - 1, 0, -1): z = v[p - 1] v[p] = (v[p] - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((_sum ^ y) + (k[(p ^ e) & 3] ^ z)))) & 0xFFFFFFFF y = v[p] p = 0 z = v[n - 1] v[0] = (v[0] - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((_sum ^ y) + (k[(p ^ e) & 3] ^ z)))) & 0xFFFFFFFF y = v[0] _sum = (_sum - DELTA) & 0xFFFFFFFF rounds -= 1
# ==== 数据区 ====
# 全局密文 byte_495280(注意这里只取前 40 字节,其余是 0 padding,不参与校验)cipher_bytes = bytes([ 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,])
# key = "2048master2048ma" → 拆成 4 个小端 uint32key_words = [ int.from_bytes(b"2048", "little"), int.from_bytes(b"mast", "little"), int.from_bytes(b"er20", "little"), int.from_bytes(b"48ma", "little"),]
# ==== 解密流程 ====v = to_u32_le_blocks(cipher_bytes)btea(v, -len(v), key_words)plain = from_u32_le_blocks(v)
# 原始 flag 长度 = 37flag = plain[:37].decode(errors="ignore")print("Flag:", flag)moectf{@_N1c3_cup_0f_XXL_te4_1n_2O48}