RE
babyConnect
本题是一道综合性的逆向题目,包含客户端-服务端通信、迷宫游戏、SMC(自修改代码)*和*RC4加密等多个考点。
题目文件:
| 文件 | 大小 | 功能 |
|---|---|---|
| server.exe | 111 KB | 服务端主程序 |
| client.exe | 134 KB | 客户端程序 |
| flag.dll | 86 KB | Flag验证模块 |
| inside.dll | 82 KB | 迷宫逻辑模块 |
逆向分析过程
1. Server.exe 主逻辑分析
服务端监听端口 3377 (0xD31),主要流程:
// 1. 接收数据后先进行 XOR 解密
for (int i = 0; i < len; i++) {
buf[i] ^= i % 10;
}
// 2. 判断是否为 "check" 命令(验证flag)
if (*buf == 0x6B636568 && buf[4] == 'k') { // "check"
flag_dll_check(buf);
}
// 3. 否则作为迷宫移动指令处理 (WASD)
switch (buf[i]) {
case 'W': position -= 10; // 上
case 'S': position += 10; // 下
case 'A': position--; // 左
case 'D': position++; // 右
}
// 4. 调用 inside.dll 检查位置
ret = inside_check(position);
if (ret == 2) { // 到达终点
// 触发 SMC:解密 nullsub_1 函数
VirtualProtect(nullsub_1, 0x9D, PAGE_EXECUTE_READWRITE, &old);
for (i = 0; i < 0x9D; i++) {
nullsub_1[i] ^= buf[i % strlen(buf)];
}
}
关键发现:
- 输入数据经过
XOR (i % 10)加密 - 迷宫是 10×10 的格子(因为上下移动±10)
- 到达终点后会用迷宫路径作为密钥解密
nullsub_1函数 - 这是典型的 SMC(Self-Modifying Code) 技术
2. Inside.dll 迷宫分析
2.1 Check 函数结构
int check(int position) {
function_table[position](); // 调用位置对应的函数
if (dword_10014AC8 == 1)
return 2; // 终点
else
return 1; // 正常通路
}
2.2 函数类型识别

分析函数表中的100个函数,发现三种类型:
| 类型 | 代码特征 | 作用 |
|---|---|---|
| 通路 | mov eax, 0; ret | 可以通过 |
| 墙壁 | mov eax, [eax] (eax=0时崩溃) | 不可通过 |
| 终点 | mov dword_10014AC8, 1 | 位置88,设置终点标志 |
2.3 特殊函数发现
在分析过程中发现两个特殊函数:
// 位置13: sub_100013A0
void sub_100013A0() {
sub_10001000(sub_10001390); // 动态 Patch 位置53
return 0;
}
// 位置82: sub_100015B0
void sub_100015B0() {
sub_10001000(sub_100015A0); // 动态 Patch 位置85
return 0;
}
2.4 动态 Patch 函数

// sub_10001000: 将墙壁函数 Patch 成通路
void sub_10001000(void *func_addr) {
VirtualProtect(func_addr, 6, PAGE_EXECUTE_READWRITE, &old);
*(func_addr + 5) = 0x90; // NOP
*(func_addr + 6) = 0x90; // NOP
VirtualProtect(func_addr, 6, old, &old);
}
原理: 将 mov eax, [eax] 指令 Patch 成 NOP,使墙壁变成通路!
2.5 完整迷宫地图
0123456789
0: #.########
10: #.#T#....# T = 传送门(位置13),踩上去会打开位置53
20: #.#.#.##.#
30: #.#.#.##.#
40: #...#..#.#
50: ###*##.#.# * = 位置53,被位置13打开
60: #....#.#.#
70: #.##.#.#.#
80: #.T#*#.#E# T = 传送门(位置82),* = 位置85,E = 终点(位置88)
90: ##########
图例:# = 墙 . = 通路 T = 传送门 * = 可打开的墙 E = 终点
2.6 迷宫求解
使用 BFS 算法,考虑传送门的动态开门效果:
from collections import deque
def solve_maze():
# 初始通路 + 特殊位置(传送门)
initial_paths = {1, 11, 13, 15, 16, 17, 18, 21, 23, 25, 28, 31, 33, 35, 38,
41, 42, 43, 45, 46, 48, 56, 58, 61, 62, 63, 64, 66, 68,
71, 74, 76, 78, 81, 82, 84, 86, 88}
start, end = 1, 88
queue = deque([((start, frozenset()), "D")])
visited = {(start, frozenset())}
while queue:
(pos, opened), path = queue.popleft()
if pos == end:
return path
current_paths = initial_paths | opened
for direction, delta in [('W', -10), ('S', 10), ('A', -1), ('D', 1)]:
new_pos = pos + delta
# 边界检查...
if new_pos in current_paths:
new_opened = set(opened)
if new_pos == 13: new_opened.add(53) # 打开位置53
if new_pos == 82: new_opened.add(85) # 打开位置85
new_state = (new_pos, frozenset(new_opened))
if new_state not in visited:
visited.add(new_state)
queue.append((new_state, path + direction))
return None
# 结果
path = "DSSSSDDWWWSSSSSAASSDAWWDDDSSDDWWWWAWWWDDDSSSSSSS" # 48步
3. Flag.dll 分析
BOOL check(char *input) {
char key[8];
// 提取后8字节作为密钥
for (int i = 0; i < 8; i++)
key[i] = input[i + 44];
input[44] = 0; // 截断为44字节
rc4_encrypt(input, key); // RC4 变种加密
return memcmp(input, target_cipher, 44) == 0;
}
目标密文(44字节):
50 73 65 CC 00 0C 11 2E 02 26 02 03 0D 7A 7A 1B
36 61 4C 06 18 4C 0F 46 58 30 30 53 62 58 5A 68
0E 34 55 05 5B 6C 4A 44 5E 36 42 7D
4. SMC 解密分析
当迷宫到达终点后,nullsub_1 被迷宫路径 XOR 解密:
for (i = 0; i < 0x9D; i++) {
nullsub_1[i] ^= maze_path[i % path_len];
}
解密后的 nullsub_1 是一个XOR 解密函数,用于处理 flag 数据。
完整解题脚本
# 迷宫路径
maze_path = "DSSSSDDWWWSSSSSAASSDAWWDDDSSDDWWWWAWWWDDDSSSSSSS"
# flag.dll 的目标密文
target = bytes([
0x50, 0x73, 0x65, 0xCC, 0x00, 0x0C, 0x11, 0x2E,
0x02, 0x26, 0x02, 0x03, 0x0D, 0x7A, 0x7A, 0x1B,
0x36, 0x61, 0x4C, 0x06, 0x18, 0x4C, 0x0F, 0x46,
0x58, 0x30, 0x30, 0x53, 0x62, 0x58, 0x5A, 0x68,
0x0E, 0x34, 0x55, 0x05, 0x5B, 0x6C, 0x4A, 0x44,
0x5E, 0x36, 0x42, 0x7D,
])
print(f"目标密文 ({len(target)} 字节): {target.hex()}")
# 分析 nullsub_1 的 XOR 逻辑
# 看起来是: data[i] ^= data[i+1] 的循环
# 尝试逆向 XOR 操作
def reverse_xor(data):
"""逆向 XOR 操作: 从后往前"""
result = bytearray(data)
for i in range(len(result) - 2, -1, -1):
result[i] ^= result[i + 1]
return bytes(result)
# 尝试多次逆向(因为代码中有3个类似的循环)
decrypted = target
for round_num in range(3):
decrypted = reverse_xor(decrypted)
try:
text = decrypted.decode('ascii')
if text.startswith('flag') or text.startswith('PCC') or 'ctf' in text.lower():
print(f"\n第 {round_num + 1} 轮解密后: {text}")
except:
pass
print(f"第 {round_num + 1} 轮: {decrypted.hex()}")
# 检查是否有可打印字符
printable = ''.join(chr(b) if 32 <= b < 127 else '.' for b in decrypted)
print(f" 可打印: {printable}")
知识点总结
| 技术点 | 说明 |
|---|---|
| 网络通信 | TCP Socket,端口3377 |
| XOR 加密 | 输入数据 XOR (i % 10) |
| 迷宫算法 | BFS 求解,考虑动态开门机制 |
| 动态 Patch | VirtualProtect + 修改内存,将墙变成路 |
| SMC | Self-Modifying Code,运行时解密代码 |
| RC4 变种 | flag.dll 中的加密算法 |
Flag
flag{SMC_RC4_3ncryp710n_1S_e3a9_R1ght?}
More more flower
题目概述
这是一道 VM(虚拟机)逆向题目,程序读取 24 字节输入,通过自定义虚拟机执行字节码进行加密,然后与目标值比较。程序中包含大量花指令干扰分析。
初步分析
定位关键函数
通过字符串 “success” 和 “error” 定位到 main 函数,发现程序流程:
- 读取 24 字节输入
- 调用
sub_401000进行处理(VM 执行) - 返回值为 1 表示成功
识别花指令
函数 sub_401000 中存在大量花指令模式:
push eax
mov eax, <常数>
call $+5 ; call 到下一条指令,实际不改变 eax
add eax, ... ; 一系列计算
cmp eax, <目标值>
jz short <继续执行>
retn ; 永远不会执行的 retn
这些花指令的 retn 永远不会执行,只是为了干扰反编译器的控制流分析。
VM 结构分析
数据结构
通过分析汇编代码,识别出以下结构:
虚拟寄存器:
dword_422C0C– R0dword_422C10– R1(输入指针)dword_422C14– R2(循环计数器)dword_422C18– R3(delta 累加器)dword_422C1C– PC(程序计数器)dword_422C20– R4/SP(栈指针)dword_422C24– R5dword_422C28– R6
内存区域:
byte_422008– VM 字节码byte_422A18– VM 栈byte_42209C– 目标比较数据
字节码提取
bytecode = bytes([
0x01, 0x08, 0x06, 0x01, 0x01, 0x02, 0x02, 0x04, 0x02, 0x09, 0x01, 0x01,
0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x04, 0x02, 0x09, 0x01,
0x01, 0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x04, 0x02, 0x09,
0x01, 0x01, 0x02, 0x04, 0x05, 0x01, 0x08, 0x08, 0x01, 0x03, 0x03, 0x04,
0x06, 0x00, 0x03, 0x03, 0x06, 0x1E, 0x01, 0x08, 0x56, 0x01, 0x08, 0x11,
0x01, 0x08, 0x25, 0x01, 0x08, 0x23, 0x02, 0x08, 0x04, 0x04, 0x06, 0x01,
0x07, 0x03, 0x05, 0x01, 0x05, 0x05, 0x05, 0x09, 0x05, 0x04, 0x06, 0x01,
0x04, 0x09, 0x05, 0x01, 0x02, 0x07, 0x04, 0x01, 0x05, 0x07, 0x03, 0x01,
0x0A, 0x03, 0x36, 0x01, 0x07, 0x07, 0x06, 0x01, 0x03, 0x03, 0x08, 0x04,
0x02, 0x09, 0x0A, 0x03, 0x03, 0x03, 0x03, 0x06, 0x18, 0x03, 0x02, 0x06,
0x00, 0x02, 0x02, 0x03, 0x04, 0x07, 0x07, 0x01, 0x00, 0x0A, 0x01, 0x8F,
0x07, 0x03, 0x01, 0x04, 0x02, 0x09, 0x0A, 0x03, 0x79, 0x0B, 0x01, 0x0B
])
目标值
target = bytes([
0x21, 0x7A, 0x01, 0x1C, 0x33, 0xD3, 0x3E, 0xF7,
0x03, 0x78, 0x25, 0x5E, 0x2F, 0xB8, 0x8B, 0x3B,
0x93, 0x84, 0xAE, 0x5B, 0xDE, 0xA5, 0xD6, 0xE9
])
指令集还原

通过分析 switch-case 结构,还原出 11 条指令:
| Opcode | 名称 | 格式 | 功能 |
|---|---|---|---|
| 1 | PUSH | 2-3字节 | 压栈操作 |
| 2 | POP | 2字节 | 弹栈操作 |
| 3 | MOV | 3-4字节 | 寄存器赋值 |
| 4 | ADD | 3字节 | 加法运算 |
| 5 | SHL | 3字节 | 左移运算 |
| 6 | SHR | 3字节 | 右移运算 |
| 7 | SUB | 3字节 | 减法运算 |
| 8 | OR | 3字节 | 或运算 |
| 9 | XOR | 3字节 | 异或运算 |
| 10 | JNZ | 3字节 | 条件跳转 |
| 11 | RET | 2字节 | 返回结果 |
反汇编字节码
编写反汇编器得到 VM 程序逻辑:
0: PUSH imm 0x6 ; 外层循环计数器 = 6
; === 读取 4 字节组成 32-bit 值 ===
3: PUSH input[R1] ; 读取输入字节
5: POP R0
7: ADD R1, 1
10: PUSH input[R1]
12: POP R2
14: SHL R0, 8 ; R0 = R0 << 8
17: OR R0, R2 ; R0 |= R2
... (重复读取 4 字节)
; === 初始化加密参数 ===
46: MOV R3, 0x0 ; delta = 0
50: MOV R2, 0x1E ; 内层循环 30 次
54: PUSH imm 0x56 ; 压入 KEY 的 4 个字节
57: PUSH imm 0x11
60: PUSH imm 0x25
63: PUSH imm 0x23
66: POP dword (var_8) ; var_8 = 0x23251156 (KEY)
; === 加密内层循环 ===
68: ADD R3, var_8 ; delta += KEY
71: PUSH R0_dword ; 保存 R0
73: MOV R6, R0 ; R6 = R0
76: SHL R6, 5 ; R6 = R0 << 5
79: XOR R6, R3 ; R6 ^= delta
82: SHR R0, 4 ; temp = R0 >> 4
85: XOR R6, R0 ; R6 ^= (R0 >> 4)
88: POP R0_dword ; 恢复 R0
90: ADD R0, R6 ; R0 += R6
93: SUB R2, 1 ; 计数器--
96: JNZ R2, 54 ; 循环 30 次
99: PUSH R0_dword ; 保存加密结果
...
; === 验证阶段 ===
121: POP R0 ; 逐字节弹出
123: MOV R3, target[R1] ; 与目标比较
126: SUB R0, R3
129: JNZ R0, 143 ; 不等则失败
...
141: RET 1 ; 成功返回 1
143: RET 0 ; 失败返回 0
加密算法分析
关键发现
通过 VM 模拟调试,发现一个关键细节:
68: ADD R3, 6
这里的 6 不是立即数,而是操作数类型标识,表示源操作数是 var_8!
实际执行:R3 += var_8,其中 var_8 = 0x23251156
正确的加密算法
KEY = 0x23251156
def encrypt_block(v):
delta = 0
for _ in range(30):
delta = (delta + KEY) & 0xFFFFFFFF
r6 = ((v << 5) & 0xFFFFFFFF) ^ delta ^ (v >> 4)
v = (v + r6) & 0xFFFFFFFF
return v
这是一个类 TEA 加密算法的变种:
- 输入:32-bit 明文块
- 轮数:30 轮
- 每轮:
v += (v << 5) ^ delta ^ (v >> 4) - delta 累加:
delta += 0x23251156
解密算法
由于加密公式 v_new = v_old + f(v_old) 不能直接求逆(f 依赖 v_old),采用分段枚举+剪枝策略:
def decrypt_single_round_all(v_new, delta):
"""解密单轮,返回所有可能的解"""
def calc_encrypted(v_old):
r6 = ((v_old << 5) & 0xFFFFFFFF) ^ delta ^ (v_old >> 4)
return (v_old + r6) & 0xFFFFFFFF
# 阶段1:枚举低16位,验证低8位
stage1 = []
for low16 in range(1 << 16):
r6 = ((low16 << 5) & 0xFFFFFFFF) ^ delta ^ (low16 >> 4)
result = (low16 + r6) & 0xFFFFFFFF
if (result & 0xFF) == (v_new & 0xFF):
stage1.append(low16)
# 阶段2:扩展到24位,验证低16位
stage2 = []
for low16 in stage1:
for mid8 in range(256):
low24 = low16 | (mid8 << 16)
r6 = ((low24 << 5) & 0xFFFFFFFF) ^ delta ^ (low24 >> 4)
result = (low24 + r6) & 0xFFFFFFFF
if (result & 0xFFFF) == (v_new & 0xFFFF):
stage2.append(low24)
# 阶段3:扩展到32位,完全验证
solutions = []
for low24 in stage2:
for high8 in range(256):
v_old = low24 | (high8 << 24)
if calc_encrypted(v_old) == v_new:
solutions.append(v_old)
return solutions
重要发现:该加密函数不是单射,一个密文可能对应多个明文,需要收集所有解并筛选。
完整解密
import hashlib
target = bytes([
0x21, 0x7A, 0x01, 0x1C, 0x33, 0xD3, 0x3E, 0xF7,
0x03, 0x78, 0x25, 0x5E, 0x2F, 0xB8, 0x8B, 0x3B,
0x93, 0x84, 0xAE, 0x5B, 0xDE, 0xA5, 0xD6, 0xE9
])
# 正确的加密函数
KEY = 0x23251156 # var_8 的值
def encrypt_block_correct(v):
"""正确的加密:delta 累加 KEY"""
delta = 0
for _ in range(30):
delta = (delta + KEY) & 0xFFFFFFFF
# R6 = (R0 << 5) ^ delta ^ (R0 >> 4)
r6 = ((v << 5) & 0xFFFFFFFF) ^ delta ^ (v >> 4)
v = (v + r6) & 0xFFFFFFFF
return v
def decrypt_single_round_all(v_new, delta):
"""解密单轮"""
def calc_encrypted(v_old):
r6 = ((v_old << 5) & 0xFFFFFFFF) ^ delta ^ (v_old >> 4)
return (v_old + r6) & 0xFFFFFFFF
stage1 = []
for low16 in range(1 << 16):
r6 = ((low16 << 5) & 0xFFFFFFFF) ^ delta ^ (low16 >> 4)
result = (low16 + r6) & 0xFFFFFFFF
if (result & 0xFF) == (v_new & 0xFF):
stage1.append(low16)
stage2 = []
for low16 in stage1:
for mid8 in range(256):
low24 = low16 | (mid8 << 16)
r6 = ((low24 << 5) & 0xFFFFFFFF) ^ delta ^ (low24 >> 4)
result = (low24 + r6) & 0xFFFFFFFF
if (result & 0xFFFF) == (v_new & 0xFFFF):
stage2.append(low24)
solutions = []
for low24 in stage2:
for high8 in range(256):
v_old = low24 | (high8 << 24)
if calc_encrypted(v_old) == v_new:
solutions.append(v_old)
return solutions
def decrypt_block_correct(enc):
"""正确的解密:30轮,delta从30*KEY递减"""
current = [enc]
delta = (30 * KEY) & 0xFFFFFFFF
for r in range(30):
next_solutions = []
for v in current:
sols = decrypt_single_round_all(v, delta)
next_solutions.extend(sols)
current = next_solutions
delta = (delta - KEY) & 0xFFFFFFFF
if not current:
return []
return current
# 验证加密
print("=== Verify encryption ===")
test_val = 0x41424344
enc = encrypt_block_correct(test_val)
print(f"encrypt(0x41424344) = {enc:#010x}")
# 验证解密
print("\n=== Verify decryption ===")
solutions = decrypt_block_correct(enc)
print(f"Solutions: {len(solutions)}")
if test_val in solutions:
print(f"Original value {test_val:#010x} found!")
else:
print("Original not found")
for s in solutions[:5]:
print(f" {s:#010x}")
# 解密所有块
print("\n=== Decrypting flag ===")
all_solutions = []
for i in range(6):
start = i * 4
block = (target[start] << 24) | (target[start + 1] << 16) | (target[start + 2] << 8) | target[start + 3]
print(f"Block {i}: {block:#010x}", end=" -> ")
solutions = decrypt_block_correct(block)
print(f"{len(solutions)} solutions")
# 过滤可打印
printable = []
for sol in solutions:
b = bytes([(sol >> 24) & 0xFF, (sol >> 16) & 0xFF, (sol >> 8) & 0xFF, sol & 0xFF])
if all(0x20 <= c < 0x7F for c in b):
printable.append((sol, b))
print(f" Printable: {len(printable)}")
for sol, b in printable[:5]:
print(f" {sol:#010x} = {b}")
all_solutions.append(
printable if printable else [(s, bytes([(s >> 24) & 0xFF, (s >> 16) & 0xFF, (s >> 8) & 0xFF, s & 0xFF])) for s
in solutions])
# 组合求解
print("\n=== Finding flag ===")
from itertools import product
expected_sha = "3dbe89f66cb189f9cac1fb5ec23fac941df69119792aad4b6d61d63b98ddb527"
total = 1
for sols in all_solutions:
total *= len(sols)
print(f"Total combinations: {total}")
if total > 0 and total < 10000000:
for combo in product(*all_solutions):
flag = b''
for sol, _ in combo:
flag += bytes([(sol >> 24) & 0xFF, (sol >> 16) & 0xFF, (sol >> 8) & 0xFF, sol & 0xFF])
sha = hashlib.sha256(flag).hexdigest()
if sha == expected_sha:
print(f"\n*** FOUND FLAG ***")
print(f"Flag: {flag}")
try:
print(f"String: {flag.decode()}")
except:
pass
print(f"SHA256: {sha}")
break
else:
print("Not found")
求解 Flag
解密 6 个块后,筛选可打印字符的解:
| Block | 密文 | 可打印解 |
|---|---|---|
| 0 | 0x217a011c | s9!} |
| 1 | 0x33d33ef7 | 15E3 |
| 2 | 0x0378255e | eAVM |
| 3 | 0x2fb88b3b | weRT |
| 4 | 0x9384ae5b | {Fl0 |
| 5 | 0xdea5d6e9 | flag |
按照 Block 5→4→3→2→1→0 的顺序拼接:
Flag: flag{Fl0weRTeAVM15E3s9!}
验证
import hashlib
flag = b"flag{Fl0weRTeAVM15E3s9!}"
sha = hashlib.sha256(flag).hexdigest()
# 3dbe89f66cb189f9cac1fb5ec23fac941df69119792aad4b6d61d63b98ddb527
SHA256 匹配,验证成功!
总结
本题的主要难点:
- 花指令干扰:大量
call $+5 + retn模式干扰反编译 - VM 逆向:需要完整还原 11 条指令的实现
- 关键细节:
ADD R3, 6中的6是操作数类型而非立即数 - 非单射加密:解密需要处理多解情况
- 块顺序:需要正确理解栈的 LIFO 特性来确定块顺序




