-
-
[原创]KCTF 2026 · 第五题「申时·忆海倒带」Writeup
-
发表于: 2026-8-17 13:48 16
-
实机验证:
无 IDA / gdb,纯自建工具链静态解决:
踩坑:脚本一开始命名为 dis.py,遮蔽了标准库 dis,导致 import capstone 抛AttributeError: module 'dis' has no attribute 'COMPILER_FLAG_NAMES'。改名即好。
另外 capstone 的 md.disasm(code, base) 遇到数据字节会直接停止(只出 608 条),
必须逐字节 while off < len 续扫才能覆盖全部 .text。
main(sub_403d90)开头把三条"开发者注释"当成 base-16 大数 parse 进三个 vector,
之后再也没被使用过,纯粹是给人看的假线索:
不存在 strcmp("admin123"),不存在起作用的 __except,int3 也不在校验路径上。
另一层难读化是遍地的假跳转桩,.text 里到处是:
以及 ret / 0xff / push [ebx] 混入指令流,专门用来打乱线性反汇编。直接无视即可。
parse 把 Key 当成 base-16 大整数存进 std::vector<uint32_t>,每 limb 8 个 hex 字符(32 bit)。
字符集与长度校验:
→ size = 11 limbs,输入恰好 88 个大写十六进制字符 = 44 字节。
接着把大数按 limb 从高到低、每 limb 大端展开到 buf2 = ebp-0x470:
也就是说 buf2[i] = int(input[2i:2i+2], 16) —— 就是 88 个 hex 字符的朴素解码,44 字节。
编译器把它向量化成了 SSE(pxor + psrldq 折叠),尾巴再补标量循环:
这是个非线性、依赖顺序的校验和,无法像纯 XOR 那样随便调字节。
input+56 正是 buf2[28..43] 对应的那 32 个 hex 字符。
sub_403500 内部有大量自修改代码(往 0x4380xx 一个字节一个字节写对象指针),拨开之后是:
回到 main,结果字符串又被 parse 成大数,大端展开到 ebp-0x88,
然后 覆盖回 buf2 的第 28..43 字节:
两个常量都不是明文立即数,而是"模板对象 + 置换表间接寻址":BigNum 读第 i 个 limb
实际是 data[perm[i]],perm 由 sub_402da0 用固定种子做 Fisher–Yates 洗牌生成。
按模板里的 data / perm 指针把 limb 一个个捞出来(dump_const.py):
标准 RSA-128 公钥运算。
其中 obj(ebp-0x86c)先用种子 0xA5A5A5A5 建好,再被 sub_4033d0 的 SMC 改成:
lookup 的实质是 obj->data + perm[x],所以:
dump_tables.py 把 x ∈ 0..94 全跑一遍,发现 f 在 0x20..0x7e 上是双射:
于是每个目标字符唯一确定一个 buf2 字节(= x + 1)。
目标串 "Welcome to KCTF2026! Come and give it a try." 长度恰好 44,与 Key 字节数一致。
反查表把整个 44 字节的"变换后 buf2"钉死:
→ 必须分解 128 位模数 C。C 无 20 万以内小因子、Pollard p−1(B=5×10⁵)不通,
但它是个"课本级"128-bit 半素数,factordb 已有完整记录(status: FF):
于是常规 RSA 解密:
拼接:
把这 44 字节代回校验 ① ②:
XOR 和加权和是"免费"满足的 —— 我们完全没有为它们求解,它们自动成立。
这说明出题人是先选定 Key、再反推出 0x8F / 0xBEFF 这两个常量,
也反证了整条数据流的还原完全正确(否则不可能三个独立约束同时命中)。
| 脚本 | 作用 |
|---|---|
pe.py |
自写 PE32/PE32+ 解析器(rva2off / read_rva / cstr_rva / imports),不依赖 pefile |
disasm_text.py |
基于 capstone 5.0.7 的鲁棒 linear-sweep,反汇编整个 .text,遇非法字节跳过续扫,输出 dis_text.txt(7089 条指令 / 131 个函数入口) |
dump_tables.py |
重建被 SMC 写入的置换对象,dump PERM / DATA / INV / target 字符串,算出 f(x) 与反查表 |
dump_const.py |
恢复模幂的指数 B 与模数 C 两个大数常量 |
solve2.py |
一键求解 + 三重自校验 |
| size | data | perm | limbs | 值 | |
|---|---|---|---|---|---|
| B(指数) | 1 | 0x430018 |
0x434018 |
perm[0]=1374 → 0x00010001 |
65537 |
| C(模数) | 4 | 0x436018 |
0x42e018 |
perm=[145,6,101,2037] |
0x8C91CB79EC693F3ED6519C945DD67371 |