首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创] 看雪·2026 KCTF > 第五题:申时·忆海倒带
发表于: 2026-8-17 14:14
22
[原创] 看雪·2026 KCTF > 第五题:申时·忆海倒带
htg
7
2026-8-17 14:14
22
# KCTF2026 Reverse — cm.exe Writeup ## 1. 题目信息 - 程序:`cm.exe`(Windows 32 位) - SHA1:`d1c8124c5964af1531c6e311328854a05ca40bca` - 判定条件:打开 `cm.exe`,输入序列号 Key 后回车,若显示 `verify success.` 即成功 - 目标:求出可通过校验的 88 位十六进制 Key 程序运行时会对代码段进行解密(自解密/壳),静态 IDB 中已把运行时解密后的代码回填。运行时代码基址 `0x680000`,IDA 中以 `0x400000` 为基址显示,两者相差 `0x280000`;数据区立即数均为运行时绝对地址。 ## 2. 程序主流程 主函数(`0x404000` 附近)流程: 1. `gets_s` 读入输入,拷贝到栈上字符串对象(`var_8CC`)。 2. 合法性检查:长度必须恰为 88,且每个字符为 `0-9`/`A-F`。 3. `sub_402100` 将 88 位十六进制串解析成大整数(11 个 limb = 44 字节),随后在 `0x4040A3` 处“解压”成 44 字节大端序字节数组,存入 `ArgList`(`[ebp-0x470]`)。**该数组与输入十六进制串逐字节相同**(动态验证)。 4. 两个预校验(见下)。 5. `call sub_403500`(`0x40427D`):对 Key 尾部做 RSA 变换(见下)。 6. 解析 RSA 结果并写回 `ArgList[28..43]`(见下)。 7. 装载循环(`0x4044F0`):`out[i] = pool[table[keybyte-1]]`。 8. `0x404550` 处与目标串 `Welcome to KCTF2026! Come and give it a try.` 比较,相等则输出 `verify success.`。 ### 2.1 预校验一:异或和 `0x4041B0`:44 字节 `ArgList` 异或结果必须等于 `0x8F`。 ### 2.2 预校验二:加权和 `0x404218`: ```c acc = 0; for (i = 0; i < 44; i++) acc = (acc + ((acc & 0x7F) + 1) * key[i]) & 0xFFFF; // 结果必须等于 0xBEFF ``` ### 2.3 装载逻辑与 pool/table 结构 - `sub_4033D0(&v81)` 把全局 `0x6B8030` 处 20 字节拷入 `v81`(`[ebp-0x86C]`),动态抓取: ``` v81 = 8f 40 00 00 | b0 63 6a 00 | 01 2c b8 70 | b0 63 69 00 | 80 63 68 00 count=0x408F base=0x6A63B0 heap ptr off_table=0x6963B0 pool2=0x686380 ``` - 装载器(`0x404504` 起)逐字节:`idx = ArgList[i] - 1`,`sub_404870(&v81, idx)`: ```c int sub_404870(unsigned int *this, unsigned int idx) { if (idx < this[0]) // 0x408F,恒真 return this[1] + sub_402D70(idx); // 0x6A63B0 + off_table[idx] return this[1]; } int sub_402D70(unsigned int *this, int idx) { return *(DWORD*)(this[0xC] + 4*idx); // *(0x6963B0 + 4*idx) } ``` 即 `out[i] = *(0x6A63B0 + off_table[keybyte-1])`: - `off_table`:运行时 `0x6963B0`(RVA `0x163B0`),256 个 dword 偏移。 - pool:运行时 `0x6A63B0`(RVA `0x263B0`),0x4000 字节,内容为 95 个可打印字符 `0x20~0x7E` 打散存放。 - 动态 dump 后验证:**`keybyte -> out` 是双射**,目标串每个字符唯一对应一个 Key 字节。 ### 2.4 RSA 变换(sub_403500) `sub_403500(&var_92C, &var_820)` 的输入是 Key 尾部 16 字节(`ArgList[28..43]`)的 32 位大写十六进制串。函数内部: 1. 把输入串解析为大整数 `base`; 2. 用全局固定结构体构造指数与模数,调用 `sub_402510`(大整数模幂,标准平方-乘算法); 3. 把模幂结果转成 32 位大写十六进制串存到 `var_92C`。 动态抓取(在 `sub_403500` 内 `0x403A26` 冻结,读线程栈上的 `v44/v46/v29` 结构体,再按置换表读出 limb 值): ``` base = int(key[28..43]) // 大端序 16 字节 exp = 0x10001 // 65537 mod = 0x8C91CB79EC693F3ED6519C945DD67371 out = pow(base, exp, mod) ``` 验证:`pow(0x0F4439374E3C44372544164425151D1B, 0x10001, mod) == 0x2CDFE7C7F5FEE09E2CDF7CAB86775A85`(与运行观察一致)。即典型的 RSA 加密,`e = 65537`。 > 注:大整数 limb 通过置换表访问:`limb(idx) = *(limbs + 4*perm[idx])`,perm 表在 `0x6AE018`/`0x6B4018`(模数/指数)。 ### 2.5 RSA 结果写回 ArgList `sub_402100` 再次解析 `var_92C` 的十六进制串,解压成 16 字节大端序写入 `var_88`,随后 `0x40436B` 的拷贝循环把 `var_88[0..15]` 拷到 `ArgList[28..43]`(`[ebp-0x454..-0x445]`,16 字节)。 因此**装载器实际使用的 ArgList 为:`key[0..27]` 拼接 `RSA(key[28..43])` 的大端序结果**。动态验证:probe Key 的 ArgList 尾部被替换为 `2c df e7 c7 f5 fe e0 9e 2c df 7c ab 86 77 5a 85`,与 `var_88` 完全一致。 ## 3. 动态调试方案 采用 `DebugActiveProcess` + `WaitForDebugEvent` 的方式(`ctypes` 直接调用,64 位 Python 对 32 位进程用 `Wow64GetThreadContext` 取寄存器): - 首次 `0x80000003`(初始断点)后写入补丁: - `0x41BE`、`0x426B`:NOP 掉两个预校验跳转; - `0x4515`:把装载器存字节改成写向自建 scratch(`VirtualAllocEx` 的 `out_addr`):`88 86 <out_addr> 90`(`mov [esi+out_addr], al`,esi=循环下标); - 在关键点写自旋补丁 `C6 05 <mark> 41 EB FE` 冻结进程,再 `ReadProcessMemory` 读内存。 - 冻结点: - `0x404542`(装载循环之后、比较之前):dump `ArgList`、`var_88`、`v81`、pool、off_table; - `0x403A26`(`sub_403500` 内模幂返回后):dump `v44`(模数)、`v46`(指数)、`v29`(base/result) 结构体及其 limb/perm 表; - `0x40410A`(ArgList 构造后):确认 ArgList 初始等于输入十六进制字节。 - 关键内存: - pool:`0x6A63B0`(0x4000 字节,`pool2.bin`) - off_table:`0x6963B0`(0x400 字节,`table2.bin`) - 模数 limbs:`0x6B6018`,perm:`0x6AE018`;指数 limbs:`0x6B0018`,perm:`0x6B4018` ## 4. 求解 ### 4.1 由目标串反推“装载器所需字节” 目标串 44 字节。由双射关系 `out[i] = pool[table[b-1]]`,对每个目标字符求出唯一 Key 字节: ``` req[0..43] = 32 3c 47 18 4b 0d 3c 44 25 4b 44 58 42 55 2f 36 5c 36 2c 11 44 42 4b 0d 3c 44 16 43 0f 44 39 37 4e 3c 44 37 25 44 16 44 25 15 1d 1b ``` 前 28 字节即 `key[0..27]`;后 16 字节为 **RSA 输出必须等于** `0x0F4439374E3C44372544164425151D1B`。 ### 4.2 RSA 求逆 分解模数: ``` n = 0x8C91CB79EC693F3ED6519C945DD67371 = 13636154180376482939 × 13702465297157554691 ``` 求私钥指数: ``` e = 0x10001 phi(n) = (p-1)*(q-1) d = e^{-1} mod phi(n) = 0x721A022D849DE5CC5AD9BBB701F9FB65 key_tail = 0x0F4439374E3C44372544164425151D1B^d mod n = 0x3B0DD6B12A0D3D95FA65B5E0ADE5E11B ``` 验证:`pow(key_tail, e, n) == 0x0F4439374E3C44372544164425151D1B` ✓ ### 4.3 最终 Key ``` key = req[0..27] || key_tail(16 字节大端) = 323c47184b0d3c44254b445842552f365c362c1144424b0d3c441643 3b0dd6b12a0d3d95fa65b5e0ade5e11b ``` ### 4.4 校验 - 异或和:`0x8F` ✓ - 加权和:`0xBEFF` ✓ - 实跑 `cm.exe`:`verify success.` ✓ ## 5. Flag ``` 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B ``` ## 6. 关键地址/常量速查 | 项 | 值 | |---|---| | 运行时基址 | `0x680000` | | 目标串 | RVA `0x16380`(`Welcome to KCTF2026! ...`) | | off_table | RVA `0x163B0`(运行时 `0x6963B0`) | | pool | RVA `0x263B0`(运行时 `0x6A63B0`) | | v81 模板 | 运行时 `0x6B8030`(20 字节) | | RSA e | `0x10001` | | RSA n | `0x8C91CB79EC693F3ED6519C945DD67371` | | n = p×q | `13636154180376482939 × 13702465297157554691` | | 私钥指数 d | `0x721A022D849DE5CC5AD9BBB701F9FB65` |
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
htg
7
30
发帖
119
回帖
624
RANK
关注
私信
他的文章
[原创] 看雪·2026 KCTF > 第九题:丑寅同墟·星海抉择
40
[原创] 看雪·2026 KCTF > 第五题:申时·忆海倒带
22
看雪·2026 KCTF > 第二题:巳时·绿光幽语
138
[原创]Kanxue2026KCTF 第八题:亥子合辰·塔影迷楼 设计思路
57
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部