一道 Reverse 类题目,给出了一个 PE32+ x86-64 的 Windows 可执行文件。通过静态逆向分析,发现程序使用 RC4 加密用户输入并与硬编码密文比对。提取密钥和密文后,解密即可得到 flag。

免责声明:本文仅为CTF网络安全竞赛技术分析,旨在帮助安全从业者提升逆向工程与密码分析能力。文中所有技术方法仅适用于合法授权的CTF竞赛场景,严禁用于任何非法用途。请遵守《中华人民共和国网络安全法》及相关法律法规。

一、文件初步分析

解压后得到单个文件 crypt.exe。先用 file 命令查看基本信息:

$ file crypt.exe
crypt.exe: PE32+ executable (GUI) x86-64, 3 sections
  .text .rdata .data

进一步提取 PDB 调试符号路径:

$ strings crypt.exe | grep ".pdb"
D:\Remember Crypt 4\c++_rc4.pdb

关键发现:PDB 路径 D:\Remember Crypt 4\c++_rc4.pdb 暴露了两个重要信息:

  • 源文件名是 c++_rc4.cpp — 程序使用了 RC4 加密
  • 项目目录是 "Remember Crypt 4" — 说明这已经是第 4 个版本的加密程序
属性 值
文件类型 PE32+ (64-bit Windows)
架构 x86-64
段 .text (代码) .rdata (只读数据) .data (全局变量)
入口点 0x38630 (RVA)
调试符号 c++_rc4.pdb

二、提取关键字符串

用 strings 提取可打印字符串,过滤出有意义的:

$ strings -t x crypt.exe | grep -E "nice|error|12345678"

  fb600  12345678abcdefghijklmnopqrspxyz
  fb624  error
  fb630  nice job
文件偏移 字符串 推测用途
0xfb600 12345678abcdefghijklmnopqrspxyz RC4 密钥(32 字节)
0xfb624 error 验证失败输出
0xfb630 nice job 验证成功输出

细节观察:密钥字符串中 abcdefghijklmnopqrspxyz 注意到 s 后面直接是 p,跳过了 t~w 和 {。这不是标准的字母表序列,说明密钥是人为精心构造的,而非自动生成。

三、定位主函数

使用 Python + Capstone 反汇编引擎,在 .text 段中搜索引用 "12345678..." 字符串地址的 lea 指令:

# 字符串 "12345678..." 的虚拟地址 (VA)
# 文件偏移 0xfb600 在 .rdata 段 (VA=0x1400fc000, 文件偏移=0xfb200)
# VA = 0x1400fc000 + (0xfb600 - 0xfb200) = 0x1400fc400

# 搜索结果:
VA 0x140001381: lea rcx, [rip + 0xfb078]  -> "12345678..."
VA 0x140001454: lea rdx, [rip + 0xfafc9]  -> "error"
VA 0x14000147c: lea rdx, [rip + 0xfafad]  -> "nice job"

三条引用都在 0x140001360 ~ 0x1400014b9 范围内——这就是 main 函数。

四、反汇编分析加密流程

反汇编 main 函数后,逻辑非常清晰:

程序执行流程
    1. 初始化 RC4 Key (32 字节)
    → 2. 清零 S-box (96 字节 = 24 个 int)
    → 3. 清零输入缓冲区 (23 字节)
    → 4. scanf 读取用户输入
    → 5. RC4_KSA(key, keylen=32) 初始化 S-box
    → 6. RC4_PRGA(plaintext, strlen) 加密输入
    → 7. for i in range(22): encrypted[i] ^= 0x22
    → 8. for i in range(22): if encrypted[i] != ciphertext[i] → "error"
    → 9. 全部匹配 → "nice job"

4.1 关键汇编代码解读

Step 1:复制 RC4 Key

0x140001381: lea  rcx, [rip + 0xfb078]    ; rcx -> "12345678..."
0x140001388: mov  rdi, rax                   ; rdi -> 栈缓冲区 [rsp+0x50]
0x14000138e: mov  ecx, 0x20                  ; 复制 32 字节
0x140001393: rep movsb                        ; 复制 key 到栈上

Step 2:RC4 加密

0x1400013dc: call strlen            ; r8d = keylen = 32
0x1400013ee: call 0x140001120       ; RC4_KSA(S, key, keylen)
0x1400013fd: call strlen            ; r8d = inputlen
0x14000140a: call 0x140001240       ; RC4_PRGA(S, plaintext, inputlen)

Step 3:额外 XOR 0x22 + 比较

0x140001423: cmp  dword ptr [rsp + 0x24], 0x16  ; 循环 22 次
0x140001434: xor  eax, 0x22                       ; 加密结果 ^= 0x22
0x140001445: lea  rdx, [rip + 0x139bb4]           ; rdx -> .data 段密文
0x140001450: cmp  eax, byte ptr [rdx + rcx]       ; 逐字节比较
0x140001452: je   0x14000147a                     ; 匹配则继续
0x140001454: lea  rdx, [rip + 0xfafc9]            ; "error"

注意:这里有一个额外的 XOR 0x22。程序不是单纯使用 RC4,而是在 RC4 加密后又对每个字节异或了 0x22(ASCII 双引号 ")。这是一个常见的防逆向混淆手段。

五、提取密文

在 0x140001445 处,lea rdx, [rip + 0x139bb4] 指向 .data 段的密文。计算实际地址:

target VA = 0x14000144c + 0x139bb4 = 0x14013b000
文件偏移 = 0x139800

提取 .data 段前 22 字节:

hex: 9e e7 30 5f a7 01 a6 53 59 1b 0a 20 f1 73 d1 0e ab 09 84 0e 8d 2b

六、RC4 原理简述

RC4 是一种经典的流密码算法,分为两个阶段:

6.1 KSA(Key Scheduling Algorithm)

初始化 S-box: S[i] = i (i = 0..255)
for i = 0 to 255:
    j = (j + S[i] + key[i % keylen]) % 256
    swap(S[i], S[j])

6.2 PRGA(Pseudo-Random Generation Algorithm)

i = 0, j = 0
for each byte of plaintext:
    i = (i + 1) % 256
    j = (j + S[i]) % 256
    swap(S[i], S[j])
    k = S[(S[i] + S[j]) % 256]
    output = plaintext_byte XOR k

RC4 的对称性:RC4 的加密和解密操作完全相同。用同一个 key 对密文再执行一次 RC4,就能还原明文。这是因为 RC4 本质是生成伪随机密钥流与明文异或,异或操作是自逆的。

七、编写解密脚本

根据逆向分析,解密流程是加密的逆操作:

解密流程
    密文 (.data段 22字节)
    → XOR 0x22(撤销额外的异或)
    → RC4(key, data)(RC4 对称解密)
    → 明文 = flag
def rc4(key, data):
    # KSA
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    # PRGA
    i, j = 0, 0
    result = bytearray()
    for byte in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) % 256]
        result.append(byte ^ k)
    return bytes(result)

key = b"12345678abcdefghijklmnopqrspxyz"
ct  = bytes.fromhex("9ee7305fa701a653591b0a20f173d10eab09840e8d2b")

# 解密: 先去掉 XOR 0x22,再 RC4
after_xor = bytes([b ^ 0x22 for b in ct])
flag = rc4(key, after_xor)
print(flag)  # b'flag{nice_to_meet_you}'

八、结果验证

flag{nice_to_meet_you}

正向验证:用解密得到的明文重新执行加密流程,确认与原始密文一致。

re_encrypted = rc4(key, plaintext)
re_xored = bytes([b ^ 0x22 for b in re_encrypted])
assert re_xored == ct  # True ✓

九、逆向思路总结

这道题的解题路径可以总结为以下步骤:

  1. 文件识别 — file 确认为 PE32+ x86-64,strings 提取 PDB 路径,确认是 RC4 加密程序。
  2. 字符串提取 — 用 strings 找到 RC4 密钥、成功/失败提示字符串。
  3. 定位关键代码 — 通过 Capstone 反汇编引擎搜索引用字符串地址的 lea 指令,定位 main 函数。
  4. 还原加密逻辑 — 阅读 main 函数汇编,识别出 RC4 KSA、PRGA 调用和额外的 XOR 0x22 操作。
  5. 提取密文 — 通过 RIP-relative 寻址计算,找到 .data 段中硬编码的 22 字节密文。
  6. 编写解密脚本 — 实现 RC4 解密 + XOR 还原,得到 flag。

工具清单:本题使用的逆向工具包括 file(文件类型识别)、strings(可打印字符串提取)、objdump(PE 段信息、反汇编)、Python + Capstone(精确反汇编与 RIP-relative 寻址计算)。


免责声明:本文内容仅供网络安全技术学习和CTF竞赛交流使用。文中涉及的逆向分析方法与解密技术,仅供读者在法律授权的范围内进行安全研究。未经授权的软件逆向、系统入侵等行为均属违法行为,作者不承担任何由此产生的法律责任。