-
-
[原创]看雪·2026 KCTF 第五题:申时·忆海倒带 WP (AI)
-
发表于: 2026-8-17 15:40 57
-
本文使用 Claude Opus 5(xhigh) + Claude Code + ida-pro-mcp, 完成时间为 9 分 41 秒
一个 224 KB 的 crackme:花指令、置换存储的大数库、运行时才补齐的查找表,外加三条专门写给自动化分析的假注释。真正的门槛只有一道 128 位 RSA——而它 1.4 秒就倒了。
88 个十六进制字符,回车即过:
字符串窗口里最扎眼的三条,是全场唯一带「解释」的文本:
这三条全是假的。
它们唯一的用途是被 sub_403C60 逐条喂给两个哈希函数:
阴险的地方在于——admin123 的比较真的存在,跟假注释说的一模一样。但 sub_403C60 的返回值在 main 里被直接丢弃,一个分支都不影响,整段是纯粹的死代码。
同类干扰还有主函数里成片的花指令:jmp short 后面塞 retn / stc / mov eax, eax,把线性反汇编打断。好在 IDA 的 F5 能完整恢复控制流,静态读伪代码基本无痛。
gets_s(Buffer, 0x3E8) 读一行,随后是六道关卡,全部通过才打印 verify success.:
第 5 步是全题的骨架:ArgList 的前 28 字节直接来自 Key 的前 56 个字符,后 16 字节则是 RSA 的输出——所以后 32 个字符既要满足 RSA,又要让原始明文通过第 3、4 步的校验。
那个 Buffer+56 的偏移很容易漏。伪代码里它只是个 char v40 局部变量,必须回汇编看这一句才知道传进去的不是整串:
题目自带一套大数类,容量固定 2048 肢。构造函数 sub_402BD0 一次 malloc 三块 0x2000:数据区、正向置换表、逆向置换表,再由 sub_402DA0 用 Fisher-Yates 洗牌把 0..n-1 打乱。
后果是:内存里 dump 出来的大数是乱序的,直接看数据区毫无意义,必须跟着 perm 还原。所有大数运算(sub_401A90 乘、sub_4013E0 加、sub_401D50 取模、sub_4012D0 赋值)都走这个访问器。
静态常量则藏在 .data 里六块 8 KB 的区域(0x42C018–0x436018,间隔正好 0x2000)。sub_403500 开头那堆逐字节写 byte_438018 的操作,实际是在手工拼一个对象头,再整体拷进大数对象——绕开了任何构造函数。
第 6 步查的表看上去无从下手:main 里先 malloc(1)、malloc(4)、malloc(4),再调 sub_402DA0 洗牌,随机数来自 sub_4011A0。
但此时 count 还是 1,洗牌函数的 if (count > 1) 直接跳过——那一整套随机化是演给人看的。真正生效的是紧接着的 sub_4033D0:它把 20 字节的对象头整个覆盖成指向 .rdata 的静态表。
于是查表退化成一个纯静态映射 f(k) = data[perm[k]]。索引来自 movsx 后的 ArgList[i] - 1,可用范围 k ∈ [0, 126]。在这 127 个值里扫一遍,目标串的 44 个字符各自只有唯一原像——44 字节明文当场全部确定:
IDA Python 一把梭:
sub_402510 是标准的从高位到低位二进制快速幂,平方用「逐肢乘 + 移位累加」实现,每步 sub_401D50 取模。参数从手工拼出的对象头里读,同样要过一层置换才能还原:
128 位模数在 2026 年不算门槛,sympy.factorint 1.4 秒就吐出两个 64 位素因子:
算出 d 后直接解密 R = 0x0F4439374E3C44372544164425151D1B,得到唯一的模 M 剩余类。因为 2^128 / M ≈ 1.82,落在 32 位十六进制表示范围内的候选只有两个。
第 3、4 步的异或与校验和作用在原始输入的 44 字节上,正好用来筛掉多余的那个候选:
三道彼此独立的校验同时命中,说明 L₀ 就是作者预埋的那把钥匙——出题人是先定死 44 字节明文,再反过来构造那张 16 KB 的查找表,让每个字节都映射到目标串上。
输出:
文件 SHA1 与 readme 中的 d1c8124c5964af1531c6e311328854a05ca40bca 一致,未做任何补丁。
静态分析 IDA Pro + Hex-Rays,求解 Python 3.13 / sympy 1.14