2025鹏程杯wp——Re

RE

babyConnect

本题是一道综合性的逆向题目,包含客户端-服务端通信迷宫游戏SMC(自修改代码)*和*RC4加密等多个考点。

题目文件:

文件大小功能
server.exe111 KB服务端主程序
client.exe134 KB客户端程序
flag.dll86 KBFlag验证模块
inside.dll82 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 求解,考虑动态开门机制
动态 PatchVirtualProtect + 修改内存,将墙变成路
SMCSelf-Modifying Code,运行时解密代码
RC4 变种flag.dll 中的加密算法

Flag

flag{SMC_RC4_3ncryp710n_1S_e3a9_R1ght?}

More more flower

题目概述

这是一道 VM(虚拟机)逆向题目,程序读取 24 字节输入,通过自定义虚拟机执行字节码进行加密,然后与目标值比较。程序中包含大量花指令干扰分析。

初步分析

定位关键函数

通过字符串 “success” 和 “error” 定位到 main 函数,发现程序流程:

  1. 读取 24 字节输入
  2. 调用 sub_401000 进行处理(VM 执行)
  3. 返回值为 1 表示成功
识别花指令

函数 sub_401000 中存在大量花指令模式:

push    eax
mov     eax, <常数>
call    $+5          ; call 到下一条指令,实际不改变 eax
add     eax, ...     ; 一系列计算
cmp     eax, <目标值>
jz      short <继续执行>
retn                 ; 永远不会执行的 retn

这些花指令的 retn 永远不会执行,只是为了干扰反编译器的控制流分析。

VM 结构分析

数据结构

通过分析汇编代码,识别出以下结构:

虚拟寄存器:

  • dword_422C0C – R0
  • dword_422C10 – R1(输入指针)
  • dword_422C14 – R2(循环计数器)
  • dword_422C18 – R3(delta 累加器)
  • dword_422C1C – PC(程序计数器)
  • dword_422C20 – R4/SP(栈指针)
  • dword_422C24 – R5
  • dword_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名称格式功能
1PUSH2-3字节压栈操作
2POP2字节弹栈操作
3MOV3-4字节寄存器赋值
4ADD3字节加法运算
5SHL3字节左移运算
6SHR3字节右移运算
7SUB3字节减法运算
8OR3字节或运算
9XOR3字节异或运算
10JNZ3字节条件跳转
11RET2字节返回结果

反汇编字节码

编写反汇编器得到 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密文可打印解
00x217a011cs9!}
10x33d33ef715E3
20x0378255eeAVM
30x2fb88b3bweRT
40x9384ae5b{Fl0
50xdea5d6e9flag

按照 Block 5→4→3→2→1→0 的顺序拼接:

Flag: flag{Fl0weRTeAVM15E3s9!}

验证

import hashlib
flag = b"flag{Fl0weRTeAVM15E3s9!}"
sha = hashlib.sha256(flag).hexdigest()
# 3dbe89f66cb189f9cac1fb5ec23fac941df69119792aad4b6d61d63b98ddb527

SHA256 匹配,验证成功!

总结

本题的主要难点:

  1. 花指令干扰:大量 call $+5 + retn 模式干扰反编译
  2. VM 逆向:需要完整还原 11 条指令的实现
  3. 关键细节ADD R3, 6 中的 6 是操作数类型而非立即数
  4. 非单射加密:解密需要处理多解情况
  5. 块顺序:需要正确理解栈的 LIFO 特性来确定块顺序
如文章存在学术性错误,请联系penetr4t10n@163.com指出
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇