首页
社区
课程
招聘
[原创]KCTF2026 第5题 解析 Writeup5
发表于: 5天前 70

[原创]KCTF2026 第5题 解析 Writeup5

5天前
70

KCTF 2026 — cm.exe CrackMe 逆向 Writeup

  1. 题目信息
    项目 内容
    目标文件 cm.exe(32 位 Windows 控制台程序)
    编译环境 MSVC / MT(静态链接 UCRT),ASLR 开启
    题目要求 输入正确的 key,使程序输出 verify success.
    目录 E:\retoolkit\ctf\cm
    程序导入 gets_s、getchar、memset 等;PDB 路径含 rsa_tf,暗示存在 RSA 相关变换。
    字符串区埋有大量假开发者注释(// FIXME: author confirmed password is 'admin123'...、
    // TODO: exception handler contains real verification logic、// NOTE: the int3 in __try is just obfuscation 等),
    全部是干扰项,与真实校验无关(0x403c60 只是个把字符串长度相加的诱饵函数,返回值从未被使用)。

  2. 反混淆与主函数还原
    程序使用 jmp + 垃圾字节 控制流混淆(如 EB 06; <垃圾>; <真实代码>),线性反汇编会在垃圾字节处错位。
    编写递归下降反汇编器:对 jmp 只跟踪目标、不继续线性扫描(天然跳过垃圾);对 jcc/call 同时跟踪目标与顺序流。

还原后的主函数(0x403D90)流程:

gets_s(key, 1000) ; [ebp-0x858]


① hex 字符校验(0x404055~0x404092)

  • 长度:解码后向量 size × 8 == 0x58(88 个字符)
  • 只接受 '0'-'9' 和『大写』'A'-'F'(小写直接 verify fail!)


    ② hex 解码(0x402100)→ keybuf[ebp-0x470](44 字节)
    = 大数进制转换:value = Σ nibble_k × 16^(87-k),32 位 limb 小端存储
    0x401a90 做 ×16,0x401510 做 +nibble(带进位传播)


    ③ XOR 校验(0x4041BC):XOR(keybuf[0..44)) == 0x8F
    ④ 反馈式加权和(0x40426B):
    sum = 0
    for b in keybuf: sum += ((sum & 0x7F) + 1) * b
    (sum & 0xFFFF) == 0xBEFF


    ⑤ RSA 变换(0x403500,PDB 名 rsa_tf):
    取输入最后 32 字符 inputbuf[56..88) → 解码为 16 字节 msg
    result = msg^e mod n,转 hex 字符串后解码
    16 字节结果覆盖 keybuf[28..44) ; 即 [ebp-0x470]+0x1C


    ⑥ 查表(0x404870 / 0x402d70):
    out[i] = byte[ 0x4263B0 + buffer2[keybuf[i]-1] ]
    buffer2 = 0x4163B0:143 项 dword 偏移表(size = 0x8F)
    keybuf[i]-1 >= 143 或为负 → 返回 byte[0x4263B0](恒为 0)


    ⑦ strcmp(0x40454A):
    out == "Welcome to KCTF2026! Come and give it a try."(44 字节)
    关键点:

0x402bd0 是自定义大数 vector 类({size, data, perm, inv},data 为 0x800-limb 数组,
limb 经排列数组 perm 间接访问)。构造函数用 seed 0xcafebabe 洗牌,但写读对称,排列相互抵消,
因此 keybuf 就是 hex 解码的自然序字节。
校验③④在 RSA ⑤之前执行,作用于用户输入的 44 字节;查表⑥在⑤之后,后 16 字节已被 RSA 结果覆盖。
3. RSA 参数提取
0x403500 运行时在全局 0x438018 构造两个大数描述符,同样使用 {size, data, perm} 布局:

描述符 size data 数组 perm 数组
模数 n 4 0x436018 0x42E018
指数 e 1 0x430018 0x434018
真实 limb 值位于 data[perm[i]],读取排列指向的位置:

perm(n) = {0x91, 0x06, 0x65, 0x7F5}
n = 0x8C91CB79 EC693F3E D6519C94 5DD67371 ; 128 bit

perm(e) = {0x55E}
e = 0x10001 (65537)
即程序执行 RSA 加密:result = msg^65537 mod n,msg 为 key 最后 32 字符解码出的 16 字节。

  1. 求解
    4.1 查表候选(唯一)
    对每个目标字符 C[i],在 143 项查表中找所有 value[j] == C[i] 的 j:
    每个位置恰好一个候选,所以 keybuf[i] = j+1 唯一确定。

keybuf[0..27] = 50 60 71 24 75 13 60 68 37 75 68 88 66 85 47 54 92 54 44 17 68 66 75 13 60 68 22 67
R_bytes[0..16) = 0F 44 39 37 4E 3C 44 37 25 44 16 44 25 15 1D 1B ; 查表所需 RSA 密文
4.2 分解 n 并解密
n = 13636154180376482939 × 13702465297157554691 ; sympy factorint
φ(n) = (p-1)(q-1)
d = e⁻¹ mod φ(n) = 0x721A022D849DE5CC5AD9BBB701F9FB65

B[28..43] = R_bytes^d mod n = 3B0DD6B12A0D3D95FA65B5E0ADE5E11B
验证:pow(B[28..43], e, n) == R_bytes ✓

4.3 组装并校验
B = B[0..27] || B[28..43]
= 323C47184B0D3C44254B445842552F365C362C1144424B0D3C441643
3B0DD6B12A0D3D95FA65B5E0ADE5E11B
XOR(B) == 0x8F ✓
加权和低16位 == 0xBEFF ✓
查表 out == 目标串 ✓(用 B[0..28) + R_bytes 查表)
4.4 踩坑:大小写
hex 校验循环 sub al,0x41; cmp al,0x19; ja fail 只认大写 A-F。
最初用小写 key 运行始终 verify fail,转大写后立即通过。

  1. 最终 Key 与验证
    323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B
    $ echo 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B | ./cm.exe
    Enter your key:
    verify success.
  2. 工具与复现
    求解脚本:solve_cm.py(依赖 sympy 分解 n)
    反混淆方法:递归下降反汇编器(jmp 只跟目标、跳过垃圾字节)
    关键地址速查:
    地址 作用
    0x403D90 主函数
    0x402100 hex→大数解码(×16 与 +nibble 交替)
    0x403500 RSA 变换
    0x402510 模幂核心(平方-乘)
    0x404870 查表入口
    0x402d70 查表偏移 = buffer2[index]
    0x4163B0 buffer2:143 项偏移表
    0x4263B0 buffer1:字节池
    0x416380 目标字符串 "Welcome to KCTF2026!..."
    0x42E018/0x436018 RSA 模数 perm/data
    0x434018/0x430018 RSA 指数 perm/data

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 4天前 被kanxue编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回