一道 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 ✓
九、逆向思路总结
这道题的解题路径可以总结为以下步骤:
- 文件识别 —
file确认为 PE32+ x86-64,strings提取 PDB 路径,确认是 RC4 加密程序。 - 字符串提取 — 用
strings找到 RC4 密钥、成功/失败提示字符串。 - 定位关键代码 — 通过 Capstone 反汇编引擎搜索引用字符串地址的
lea指令,定位 main 函数。 - 还原加密逻辑 — 阅读 main 函数汇编,识别出 RC4 KSA、PRGA 调用和额外的 XOR 0x22 操作。
- 提取密文 — 通过 RIP-relative 寻址计算,找到 .data 段中硬编码的 22 字节密文。
- 编写解密脚本 — 实现 RC4 解密 + XOR 还原,得到 flag。
工具清单:本题使用的逆向工具包括 file(文件类型识别)、strings(可打印字符串提取)、objdump(PE 段信息、反汇编)、Python + Capstone(精确反汇编与 RIP-relative 寻址计算)。
免责声明:本文内容仅供网络安全技术学习和CTF竞赛交流使用。文中涉及的逆向分析方法与解密技术,仅供读者在法律授权的范围内进行安全研究。未经授权的软件逆向、系统入侵等行为均属违法行为,作者不承担任何由此产生的法律责任。
浙公网安备 33010602011771号