首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
KCTF 2026 第5关 cm.exe Write-up
发表于: 2026-8-17 13:14
12
KCTF 2026 第5关 cm.exe Write-up
awaxiaoyu
2026-8-17 13:14
12
# KCTF 2026 第5关 cm.exe Write-up ## 0x00 结论 最终 Key: ```text 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B ``` 原始未 patch 程序验证: ```text Enter your key: verify success. ``` 样本 SHA1 与 readme 一致: ```text d1c8124c5964af1531c6e311328854a05ca40bca ``` ## 0x01 基本信息 先做事实表,不直接相信可见字符串。这个样本里确实有不少诱导内容,比如 `admin123`、`password`、`r3v3rs3!`、`n0_4i_c4n_r34d_th1s!`,以及 FIXME/TODO/NOTE 风格的“作者提示”。这些都只作为 bait 处理,必须通过 xref 和运行结果验证。 静态信息: - PE:32-bit x86 - ImageBase:`0x400000` - Entry:`0x404d00` - 输入函数:`gets_s`,IAT `0x4060ec` - 输出函数封装:`0x4033a0` - 成功字符串:`verify success.`,VA `0x406258` - 失败字符串:`verify fail.retry it...`,VA `0x406240` 关键 xref: ```asm 403f60 push 40622Ch ; "Enter your key:\n" 403f81 call dword ptr [4060ECh] ; gets_s(input, 0x3e8) 404586 push 406258h ; "verify success." 40458b call 4033A0h 4045a5 push 406240h ; "verify fail.retry it..." 4045aa call 4033A0h 4046e6 push 406240h ; fail path 4046eb call 4033A0h ``` 一次性 smoke test 结果: ```text admin123 -> verify fail.retry it... password -> verify fail.retry it... r3v3rs3! -> verify fail.retry it... n0_4i_c4n_r34d_th1s! -> verify fail.retry it... ``` 因此可见字符串路线证伪,不再重复尝试。 ## 0x02 主判定路径 主函数在 `0x403d90` 附近。输入读取后,程序把输入按 hex 字符串解析成大整数/字节数组。长度要求来自下面逻辑: ```asm 404070 mov al, [ebp+ecx-858h] 40407b cmp al, '0' 40407f cmp al, '9' 404083 sub al, 'A' 404087 ja 4046E6h ; 非 0-9/A-Z fail ... 404092 cmp edx, 58h ; 88 hex chars 404095 jne 4046E6h ``` 所以 Key 是 88 个大写 hex 字符,对应 44 bytes。 后续有两个 guard: ### XOR guard ```asm 404112 mov esi, [ebp-938h] 40411c lea edx, [esi*4] ... 4041b0 xor al, [ebp+ecx-470h] ... 4041bc cmp al, 8Fh 4041be jne 4046E6h ``` 即 44 bytes 的 XOR 必须为 `0x8f`。 ### rolling checksum guard ```asm 404218 mov edx, [ebp-930h] 404226 mov eax, [ebp-93Ch] 404226 and ecx, 7Fh 404229 inc ecx 40422a movzx eax, byte ptr [eax] 40422d imul ecx, eax 404230 add edx, ecx ... 40425f mov eax, 0BEFFh 404264 cmp [ebp-930h], ax 40426b jne 4046E6h ``` 等价伪代码: ```python s = 0 for b in raw44: s = (s + (((s & 0x7f) + 1) * b)) & 0xffff assert s == 0xbeff ``` ## 0x03 末 16 字节的 RSA/powmod 变换 一开始我把 44 bytes 全部直接按最终表反查,能得到目标映射输入,但 XOR/checksum 对不上。这说明中间还有覆盖或变换。继续看 `0x403500`,发现它会生成末尾 16 bytes。 主流程: ```asm 404271 lea edx, [ebp-820h] 404277 lea ecx, [ebp-92Ch] 40427d call 403500h ; 生成 16-byte 结果 ... 40431f..404348 ; 把 0x403500 输出转为 16 bytes 40437f..4043d0 ; 覆盖 final buffer 的末 16 bytes ``` `0x403500` 内部可归约为: ```python out = pow(tail_plain, 0x10001, n) ``` 其中 modulus 来自静态 BigInt 表: ```text n = 0x8c91cb79ec693f3ed6519c945dd67371 e = 0x10001 ``` 分解: ```text n = 13636154180376482939 * 13702465297157554691 ``` 最终比较目标位于 `0x416380`: ```text Welcome to KCTF2026! Come and give it a try. ``` 程序在 `0x4044f8..0x40457c` 把 44 bytes 逐字节通过表 `0x4163b0/0x4263b0` 映射,再和 `0x416380` 比较。 表反查后,需要被映射的 44 bytes 为: ```text 323c47184b0d3c44254b445842552f365c362c1144424b0d3c4416430f4439374e3c44372544164425151d1b ``` 前 28 bytes 是原始输入前缀;后 16 bytes 是 RSA 输出。因此先 RSA 私钥反解 tail: ```text required_rsa_output = 0f4439374e3c44372544164425151d1b tail_plain = 3b0dd6b12a0d3d95fa65b5e0ade5e11b ``` 组合得到原始 44 bytes: ```text 323c47184b0d3c44254b445842552f365c362c1144424b0d3c4416433b0dd6b12a0d3d95fa65b5e0ade5e11b ``` 转大写 hex 即最终 Key。 ## 0x04 求解脚本核心 ```python import pefile, struct, sympy as sp EXE = 'cm.exe' IMAGE_BASE = 0x400000 EXPECTED_VA = 0x416380 INDEX_TABLE_VA = 0x4163B0 BYTE_TABLE_BASE_VA = 0x4263B0 data = open(EXE, 'rb').read() pe = pefile.PE(data=data) def va_data(va, n): off = pe.get_offset_from_rva(va - pe.OPTIONAL_HEADER.ImageBase) return data[off:off+n] def u32(va): return struct.unpack('<I', va_data(va, 4))[0] def cstr(va): off = pe.get_offset_from_rva(va - pe.OPTIONAL_HEADER.ImageBase) end = data.index(b'\x00', off) return data[off:end] def mapped_byte(b): off = u32(INDEX_TABLE_VA + (b - 1) * 4) return va_data(BYTE_TABLE_BASE_VA + off, 1)[0] def static_bigint(count, base_va, index_va): limbs = [] for i in range(count): idx = u32(index_va + i * 4) limbs.append(u32(base_va + idx * 4)) return sum(x << (32 * i) for i, x in enumerate(limbs)) def checksum16(bs): s = 0 for b in bs: s = (s + (((s & 0x7f) + 1) * b)) & 0xffff return s expected = cstr(EXPECTED_VA) need = [] for ch in expected: cand = [b for b in range(1, 256) if mapped_byte(b) == ch] assert len(cand) == 1 need.append(cand[0]) need = bytes(need) prefix = need[:28] rsa_output = need[28:44] n = static_bigint(4, 0x436018, 0x42E018) e = static_bigint(1, 0x430018, 0x434018) factors = sp.factorint(n) phi = 1 for p, k in factors.items(): phi *= (int(p) - 1) * (int(p) ** (int(k) - 1)) d = pow(e, -1, phi) tail = pow(int.from_bytes(rsa_output, 'big'), d, n).to_bytes(16, 'big') raw = prefix + tail assert checksum16(raw) == 0xbeff x = 0 for b in raw: x ^= b assert x == 0x8f assert prefix + pow(int.from_bytes(tail, 'big'), e, n).to_bytes(16, 'big') == need print(raw.hex().upper()) ``` ## 0x05 验证 模型输出: ```text xor=0x8f checksum16=0xbeff mapped=b'Welcome to KCTF2026! Come and give it a try.' ``` 原始程序运行: ```text Enter your key: verify success. ``` 最终 Key: ```text 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
awaxiaoyu
3
发帖
4
回帖
20
RANK
关注
私信
他的文章
KCTF 2026 第5关 cm.exe Write-up
12
[原创]2026 KCTF 第二题:巳时·绿光幽语 Writeup
14
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部