-
-
[原创]KCTF2026 - 第八题:亥子合辰·塔影迷楼 题解(AI)
-
发表于: 5天前 24
-
27KB 的 x64 控制台 crackme。三层自解密,每层的密钥都是上一层代码自身的 FNV-1a 哈希——改一个字节,后续全部解成垃圾;反调试标志不走分支,而是直接异或进密钥种子,下断点不会报错,只会静默算出错误答案。核心是 GF(2^127−39) 上的 base-94 Horner 求值 + 五次多项式 + 20 轮 Feistel。
关键剪枝:输入在整个校验函数里只被引用两次——一次字节和、一次 Horner——于是 16 字节输入塌缩成单个 128 位未知量 h,逆 Feistel + 解五次方程即可直接反解。全程静态,零穷举。
.tgt 节区(0x14000A000,0x70 字节,明文未加密)是解包表:
第一层:start() 把 0x1800..0x1D10 逐字节 XOR 0x5A。
第二层:解出来的 sub_1400018D0 对自己所在的那 0x510 字节跑两遍 FNV-1a(basis 0xCBF29CE484222325 与 0x9E3779B97F4A7C15),得 h1/h2,再用它们生成 murmur3-finalizer 密钥流解出 stage-3:
实测 h1 = 0x73FB4F498AAB364F,h2 = 0xB157C7E044B966DF。

第三层:stage-3 的主体 sub_140004380 再对自己那 0x4962 字节求同样两个 FNV-1a,作为 SplitMix64 双流交叉反馈的种子,产出 86 个 qword 写进栈上的 CONTEXT 结构(Context+0x000 … +0x2B0)。所有后续常量都从这块出:
伪代码里的 Context.Dr1 / Legacy / Xmm5 等字段名全是误导——那块内存已被 PRNG 覆写,跟调试寄存器无关。
sub_140004380 有 0x22F2 字节、1900 行伪代码,但在里面 grep 输入指针 a1 只有两处命中:
引用一:字节和 gate(SIMD 横向求和)——必须 sum(input) == 0x500 = 1280,否则直接 return 0。
同一屏还能看到 stage-3 自校验和反调试位的收集:

反调试共四位,全部异或进 PRNG 种子:BeingDebugged、PEB+0x68 & 0x70、DR0-3/DR7、rdtsc 超时(阈值 5×10⁷)。
其中第二项是作者的 bug:NtGlobalFlag 在 x64 PEB 里位于 0xBC,0x68 是 32 位 PEB 的偏移,在 x64 上对应 ApiSetMap——一个页对齐指针,低位恒为 0,& 0x70 == 0x70 永远不成立。这条检测是死的。干净运行下四位全零。
引用二:base-94 Horner——h = (h*94 + (input[i] − 33)) mod (2^127−39),循环 16 次:

0x140004CB1 mov r9d, 5Eh 即基数 94;−33 把可打印 ASCII !` 映射到 0…93;+78 = 2×39是2^127 ≡ 39 (mod p)` 的高位折叠。
其余全是 h 的确定性函数: