-
-
[原创]KCTF2026 第5题 解析
-
发表于: 23小时前 130
-
题目:cm.exe(看雪 / KCTF 2026,规则 5.1.1)
样本 SHA1:d1c8124c5964af1531c6e311328854a05ca40bca(与 readme.txt 一致)
判胜条件:运行程序,输入序列号后回车,输出 verify success.
结论状态:已在原版 cm.exe 与工作副本上独立验证通过
本地复现:
期望输出:
一键脚本见同目录 solve.py。
程序中嵌入大量误导信息,不是真校验路径:
真逻辑在主校验函数(约 0x403D90 起)的线性路径中,并带有控制流花指令(ret; mov edi,edi; jmp; stc 垫片)。
整体流水线:
对解码得到的 44 字节 b[0..43]:
动态确认方式:Frida 挂钩模幂例程 0x402510,转储 limb,得到 e=65537 与 n 的 4 个 32-bit limb,并用已知 (m,c) 对验证 pow(m,e,n)==c。
覆盖完成后:
静态表(ImageBase 0x400000):
因此可对 Welcome 每个字符反查最小 idx,令 key[i] = idx + 1,得到「查表成功」所需的最终 44 字节 final。
注意:final[0..27] 来自输入前缀(RSA 不改);final[28..43] 必须等于 RSA 输出 c,故输入后缀应取:
碰巧本样本在该构造下 XOR / 加权约束自动满足,无需再搜索多解。
完整实现:答案提交/solve.py。
详见同目录 工具与环境.md。摘要:
本题是「自定义大整数 + 128-bit RSA + 置换查表」组合的序列号校验。表面 SEH/strcmp 注释为诱饵;核心是还原 44 字节最终形态,再对 RSA 段求逆。模数仅 128 位,分解成本极低,适合作为 CTF 可解的 RSA 变体。
一键求解脚本 solve.py 仅需:
| 项目 | 结论 |
|---|---|
| 格式 | PE32 控制台程序,MSVC(VCRUNTIME140) |
| 入口 | EP RVA 0x4D00,ImageBase 0x400000(有 ASLR) |
| 加壳 | 无明显壳 / 无 overlay |
| 输入 | gets_s,提示 Enter your key: |
| PDB | ...\rsa_tf-20260802\Release\ca_tf6.pdb(暗示 RSA) |
| 成功串 | verify success. |
| 目标明文 | Welcome to KCTF2026! Come and give it a try.(44 字节) |
| VA | 内容 |
|---|---|
0x4163B0 |
置换 P,为 0..0x3FFF 的排列 |
0x4263B0 |
字节表 DATA(可打印 ASCII 等) |
0x416380 |
目标串 Welcome… |
| 类别 | 工具 |
|---|---|
| 静态 | Python、pefile、Capstone(去花指令反汇编) |
| 动态 | Frida(校验点 / 模幂 / keybuf 覆盖观测) |
| 密码 | sympy.factorint(128-bit RSA) |
| 辅助 | z3(构造过校验的测试向量) |
| 验证 | subprocess 调用原 cm.exe |
| 文件 | 说明 |
|---|---|
答案提交/解题分析.md |
本文(分析文章) |
答案提交/工具与环境.md |
工具、依赖、关键地址 |
答案提交/solve.py |
一键求 key 并调用原程序验证 |
答案提交/README.md |
提交材料目录说明 |
FLAG_RESULT.md |
简洁结论与复现命令(题根目录) |
work/ |
分析过程脚本与反汇编草稿(可选附录,非提交必需) |
| 项目 | 说明 |
|---|---|
| 系统 | Windows 10/11 x64 |
| 样本 | cm.exe(SHA1 d1c8124c5964af1531c6e311328854a05ca40bca,与题目 readme 一致) |
| Python | 3.10+(本机实测 3.14 可用) |
| 工具 | 用途 |
|---|---|
| pefile | PE 节区、导入表、RVA/文件偏移、字符串定位 |
| Capstone | x86 反汇编;配合脚本去除 ret; mov edi,edi; jmp; stc 花指令 |
| Frida | 动态挂钩:XOR/加权校验、RSA 模幂入口、limb 转储、覆盖前后 keybuf |
| sympy | 分解 128-bit 模数 n,求 RSA 私钥指数 d |
| z3-solver | 早期构造满足 XOR/加权约束的测试向量(辅助动态分析) |
| Python 标准库 | subprocess 喂入序列号验证 verify success. |
| 地址 | 含义 |
|---|---|
0x403D90 |
主校验函数附近 |
0x402100 |
十六进制字符串 → 大整数 |
0x402510 |
模幂 m^e mod n |
0x403500 |
RSA 包装(后缀 hex → 加密 → 写回) |
0x4033D0 |
安装查表对象(指向 P/DATA) |
0x404870 |
查表 DATA[P[idx]] |
0x416380 |
目标串 Welcome to KCTF2026! ... |
0x4163B0 |
置换表 P(0..16383 的排列) |
0x4263B0 |
字节表 DATA(可打印 ASCII 等) |
323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B
echo 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B| cm.exe
Enter your key:
verify success.
88 个十六进制字符
│ parse(base16 → 大整数 → 44 字节缓冲)
▼
keybuf[0..43](44 字节)
│ ① XOR 全字节 == 0x8F
│ ② 递推加权低 16 位 == 0xBEFF
▼
取后 16 字节 m,做 RSA:c = m^e mod n(e=65537)
│ 用 c 的大端 16 字节覆盖 keybuf[28..43]
▼
对最终 44 字节逐字节查表:
out[i] = DATA[ P[ (int8)keybuf[i] - 1 ] ]
(有效 key 字节 ∈ [1,127])
▼
out == "Welcome to KCTF2026! Come and give it a try."
▼
打印 verify success.
sum = 0
for each byte b:
sum = sum + ((sum & 0x7F) + 1) * b
要求 (sum & 0xFFFF) == 0xBEFF
n = 0x8c91cb79ec693f3ed6519c945dd67371
e = 65537
p = 0xbd3d59fac7cc547b
q = 0xbe28ef96c8318203
d = 0x721a022d849de5cc5ad9bbb701f9fb65
idx = (signed char)keybuf[i] - 1
out[i] = *(DATA + P[idx]) // idx 需落在合法范围;key∈[1,127]
m = pow(final[28..43]_as_int, d, n)
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。