导读: 上一篇文章我们成功将 CRACKME3.EXE 脱壳得到 dumped.bin。现在面对 229KB 的二进制文件,从哪下手?对于破解来说,最关键的一步就是找到验证函数的位置。 本文演示如何通过字符串搜索和交叉引用精准定位 CRACKME3 的验证逻辑,还原完整的 7 步验证流程,并揭示最易踩坑的用户名拼接逻辑。
关注我不迷路,最近会抽时间把自己十几年学习的东西逐步分享出来。
1. 搜索关键字符串
解包后的 dumped.bin 是一个完整的内存映像。第一步是用 Python 搜索成功/失败消息等字符串:
with open('dumped.bin', 'rb') as f:
data = f.read()
keywords = [b"join CORE", b"Welcome", b"Better luck",
b"first threshold", b"%lx%lx%lx", b"Failed"]
for kw in keywords:
off = data.find(kw)
if off >= 0:
print(f"0x{0x400000+off:06x}: {kw.decode()}")
这就是 CRACKME3 破解分析的起点——这些对话框里的文字,在代码中一定有对应的引用。找到引用这些字符串的代码,就等于找到了验证函数。
脱壳确认成功,关键字符串一览:
| 0x422520 | %lx%lx%lx | 序列号 sscanf 格式串 |
| 0x422560 | "… good enough to join CORE! …" | 成功消息 |
| 0x4225a0 | Welcome | 成功标题 |
| 0x422540 | "… Better luck next time …" | 失败消息 |
| 0x4224c0 | "… first threshold …" | 格式错误 |
| 0x422500 | Failed | 失败标题 |
这些字符串会被代码引用(xref)。例如 0x422560(成功消息)一定被某条 push 指令引用——找到所有这些引用,就能圈定验证函数的位置。
2. 交叉引用定位验证函数
通过 IDA Pro 或直接搜索二进制中对这些地址的引用,发现所有交叉引用都落在 0x4020a0 ~ 0x402408 区间。
这就是我们要分析的验证函数(validation function)。
3. 验证函数完整 7 步流程
通过反汇编,还原出完整的验证逻辑:
验证函数 0x4020a0 ~ 0x402408:
① 读取输入
GetDlgItemText(姓名框) → [ebp-0x608]
GetDlgItemText(序列号框) → [ebp-0x414]
② 用户名拼接
tmp = name → reverse(tmp) → name += tmp × 3 次
③-④ 注册表查询(对结果无影响)
RegQueryValueExA("Product") → NOT_FOUND
RegQueryValueExA("Registered") → NOT_FOUND
⑤ MD5 哈希计算
sub_4014e0 初始化上下文
sub_401500 分组处理 64 字节块
输出 A/B/C/D 四个 32 位哈希值
⑥ 解析序列号
sscanf(serial_buf, "%lx%lx%lx", &s0, &s1, &s2)
返回值必须 = 3,否则弹 "first threshold" 错误
⑦ 变换 + 比较
sub_401ad0 两次调用(线性变换)
s0' == A 且 s1'' == B 且 s2' == C → 成功
否则 → 失败

┌────────────────────────────────────────┐
│ ① 读取姓名和序列号 │
└────────────────┬───────────────────────┘
▼
┌────────────────────────────────────────┐
│ ② 用户名拼接(reverse + strcat × 3) │
└────────────────┬───────────────────────┘
▼
┌────────────────────────────────────────┐
│ ③-④ 查注册表(均不存在,无影响) │
└────────────────┬───────────────────────┘
▼
┌────────────────────────────────────────┐
│ ⑤ MD5 哈希(16 字节输入 → 4 个 dword)│
└────────────────┬───────────────────────┘
▼
┌────────────────────────────────────────┐
│ ⑥ sscanf 解析序列号(%lx%lx%lx) │
└────────────────┬───────────────────────┘
▼
┌────────────────────────────────────────┐
│ ⑦ 变换后比较:s0'==A, s1''==B, s2'==C│
│ 全部相等 → Welcome 否则 → Failed │
└────────────────────────────────────────┘
4. 最易踩坑:用户名拼接逻辑
哈希函数计算的不是原始用户名,而是一段经过变换的字符串。汇编级还原如下:
; 复制 name 到 tmp
0x402151: lea esi, [ebp-0x220] ; tmp
0x402157: lea edi, [ebp-0x608] ; name
; 手动实现 strcpy(tmp, name)
; 反转 tmp
0x402168: lea eax, [ebp-0x220]
0x40216f: call sub_408d70 ; reverse(tmp)
; "KCTF" → "FTCK"
; name += tmp(追加反转后的结果)
0x402174: lea edi, [ebp-0x608] ; name
0x40218d: … strcat(name, tmp)
; "KCTF" + "FTCK" = "KCTFFTCK"
接着代码查询注册表值 “Product” 和 “Registered”——这两个值在正常的 Windows 系统中都不存在。查询失败后,tmp 缓冲区内容不变(仍是 “FTCK”),name 继续追加:
初始 name = "KCTF" len=4
tmp = reverse("KCTF") = "FTCK"
name += "FTCK" → "KCTFFTCK" len=8
RegQuery("Product") → NOT_FOUND
name += "FTCK" → "KCTFFTCKFTCK" len=12
RegQuery("Registered") → NOT_FOUND
name += "FTCK" → "KCTFFTCKFTCKFTCK" len=16
最终哈希输入: "KCTFFTCKFTCKFTCK"(16 字节)

关键注意: 哈希的输入是 "KCTFFTCKFTCKFTCK",不是 "KCTF"!如果在后续分析中搞错了这一步,计算出的序列号一定不对。
汇编验证
注册表查询失败时返回 2(NOT_FOUND),但代码并未检查返回值就直接继续执行——这是 C 风格代码中常见的"偷懒"写法:
0x4021d5: call RegQueryValueExA(…)
0x4021db: lea eax, [ebp-0x220] ; 不管返回值,直接追加
0x4021e1: push eax
0x4021e2: lea eax, [ebp-0x608]
0x4021e8: push eax
0x4021e9: call strcat ; name += tmp(始终是 "FTCK")
我们也可以在 PowerShell 中确认注册表值不存在:
reg query "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" /v Product
# → 错误:系统找不到指定的注册表键或值
小结
破解 CRACKME3 的第二个关键节点已攻克。 我们已经:
- 通过字符串搜索 + xref,定位 CRACKME3 验证函数在 0x4020a0~0x402408
- 逆向还原了完整的 7 步验证流程:读输入→拼接→查注册表×2→MD5→sscanf→变换比较
- 破解过程中最容易踩坑的发现:哈希的输入是 "KCTFFTCKFTCKFTCK"(16 字节),不是原始 "KCTF"
- 注册表查询 “Product” 和 “Registered” 在正常系统上均不存在,不影响哈希输入
下篇预告:《MD5 哈希分析:标准算法背后的非标准细节》——CRACKME3 的哈希明明是标准 MD5,为什么算出的序列号在程序上全不对?一个导致所有前期工作白费的隐藏"quirk"即将揭晓。
参考文献与引用
- Capstone 反汇编引擎:github.com/capstone-engine/capstone——用于验证 MD5 分组函数 sub_401500
- pefile:github.com/erocarrera/pefile
- IDA Pro 数据库:CRACKME3.EXE.i64——辅助反汇编与 xref 分析
- Nolan Blender 分析(2000):fravia.accessroot.com/ccrack.htm——确认 CORE CrackMe3 的数学结构
- 本系列上一篇:[Unicorn 模拟解包实战与代码分析]
- 本系列下一篇:[MD5 哈希分析:标准算法背后的非标准细节]


