-
-
[原创]【KCTF 2026】第五题:申时·忆海倒带 Writeup:"中间语言",把 RSA 诱饵从假死代码里挖出来
-
发表于: 2天前 149
-
这道 Windows crackme 输入 88 位 hex 序列号,对了打印 verify success.。表面看就是"大整数解析 + 查表 + 几重校验"的常规套路,可偏偏就是那几个校验对不上,让我卡了很久。最后逼我发明了一套压缩语言(我管它叫 Z-Lang),把整个 main 从头到尾翻译一遍、再做一次代数化简,才发现真正的机关藏在一个被当成"死代码"的地址里。
这篇文章就当分享这个思路,也希望能给同样卡在"校验矛盾"上的朋友一点启发。
程序没有加壳,main 在 0x403D90,纯 MSVC 编译。麻烦的是里面塞满了花指令,典型几种:
写了个脚本按"无条件 jmp 跳过紧邻死代码"的规则线性清理,得到 522 条有效指令(main_clean_full.asm)。花指令本身不是难点,跳过。
把 main 翻译完,逻辑其实不复杂。88 位 hex 先解析成 2048 位大整数,再字节反转成 44 字节缓冲区 V,然后要过三关:
查表对象 D/P 是静态表(D=0x4263B0 字节表,P=0x4163B0 偏移表),元素经大数对象的 datap[perm[k]] 间接寻址。程序里还有一大段 RSA 模幂(exp=0x10001)和费雪耶茨洗牌,看起来跟验证毫无关系——这就是后面最大的坑。
约束 C 对目标串的每个字符,在 D[P[j]] 里恰好找到一个 j 命中,所以 44 字节的 V 被 C 逐字节唯一确定:
可是拿这个 V 去算 A、B:
查表唯一确定的 V,过不了校验。 一个 CTF 题不可能无解,那一定是"三者作用于同一份 V"这个模型有问题。可汇编读来读去,A/B/C 都在读写同一个 [ebp-0x470](V),怎么看都是同一份。
卡了一下之后,我决定把所有内存写地址用符号代数化简一遍,不靠汇编直觉。重点看第 18 步——它要把 RSA 结果拷贝 16 字节,汇编里目标地址长得像"写到一个没用的地方":
把它写成 Z 记号再化简:
这一化简,整个题就通了——那条"拷贝 16 字节"根本不是死代码,它在执行 V[28..43] ← RSA 输出,把 V 的尾部 16 字节覆盖了!
重新梳理,三个约束其实针对的是两份不同的 V:
也就是说:
这解释了一切矛盾。RSA 看似是诱饵,其实是通过隐藏的缓冲区覆盖,把 base 解密的必要性接进了主流程。
sub_403500 从静态常量构造大数:
128 位模数直接分解:
求私钥 d = e⁻¹ mod φ(n) 后解出 base:
验证全部通过:
感谢阅读,欢迎交流。
; 模式A: jmp +6 跳过死代码
40435b: eb 06 jmp 0x404363
40435d: 8b c0 mov eax,eax ; ← 死代码开始
40435f: c3 ret
404360: ff (bad)
404361: ff 33 push [ebx]
404363: ... ; ← 真正代码
; 模式C: jmp +2 跳过两个 nop
403f6d: eb 02 jmp 0x403f71
403f6f: 8b c9 mov ecx,ecx
403f71: 8b 01 mov eax,[ecx]
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。