首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创]KCTF 2026 第五题:申时·忆海倒带 writeup
发表于: 2026-8-18 11:40
1127
[原创]KCTF 2026 第五题:申时·忆海倒带 writeup
number_Z
2
2026-8-18 11:40
1127
一、程序逻辑梳理 题目是一个 32 位的 Windows PE 文件,要求输入一串 44 字节(88 个十六进制字符)的密钥。 1. 基础校验:对输入的 44 字节进行全局 XOR 校验和自定义的 Checksum 递推校验。 2. 密码学变换:输入的前 28 字节作为固定前缀保持不变,后 16 字节作为大整数进行一次 128 位的 RSA 加密。 3. 查表比对:将经过 RSA 处理后的 44 字节逐一通过一个大小为 0x4000 的自定义查找表进行字节置换,置换后的结果必须与内存中硬编码的终态字符串完全一致。终态字符串为 "Welcome to KCTF2026! Come and give it a try."。 二、前置校验绕过 程序早期的 XOR 校验要求整体异或和等于 0x8F,Checksum 校验要求递推结果为 0xBEFF。 由于这两个校验仅仅是放行条件,不参与后续的数据变换,处理起来非常简单。在实际分析时,既可以在 IDA 中将对应的条件跳转指令直接 Patch 掉,也可以用脚本随机生成一组符合异或和 Checksum 规律的垃圾数据作为跳板,强制让程序走到终点的查表逻辑处。 三、查表逻辑与终态逆推 程序在启动时会通过 sub_4033D0 函数初始化一张查表矩阵。 分析汇编可以发现,该表所需的核心数据全部在 PE 文件中静态写死:数据基址 data 位于 0x4263B0,置换表 permutation 位于 0x4163B0,逻辑索引范围是 0x4000。 既然数据是静态固定的,就可以直接放弃动态调试,利用 pefile 库离线读取这两个内存段的内容,在本地重现程序的字节映射逻辑。遍历 1 到 127 的有效输入字符建立反查字典,然后拿硬编码的目标字符串去字典里逐个查表,就可以得到查表前内存中真正需要的 44 字节中间状态: ``` 323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416430F4439374E3C44372544164425151D1B ``` 四、RSA 128 解密 拿到中间状态后,按照程序逻辑将其切分为两部分: 前 28 字节为固定前缀: ``` 323C47184B0D3C44254B445842552F365C362C1144424B0D3C441643 ``` 后 16 字节为 RSA 密文: ``` 0F4439374E3C44372544164425151D1B ``` 提取程序中的公钥数据,N = 0x8C91CB79EC693F3ED6519C945DD67371,e = 65537。由于 N 只有 128 位,可以直接用常规工具分解出两个素数: ``` p = 13636154180376482939 q = 13702465297157554691 ``` 有了 p 和 q 即可算出私钥 d,对后 16 字节密文进行标准 RSA 解密,最后将解密结果与前 28 字节拼接,就是最终的 Flag。 五、完整求解脚本 结合 pefile 静态解析、逆向查表和 RSA 解密,完整的纯静态求解代码如下: ```python import pefile def get_flag(): binary_path = "cm.exe" IMAGE_BASE = 0x400000 DATA_VA = 0x4263B0 PERM_VA = 0x4163B0 COUNT = 0x4000 pe = pefile.PE(binary_path) def va_to_offset(va): return pe.get_offset_from_rva(va - IMAGE_BASE) data_offset = va_to_offset(DATA_VA) perm_offset = va_to_offset(PERM_VA) def u8_static(offset): return pe.__data__[offset] def u32_static(offset): return int.from_bytes(pe.__data__[offset : offset + 4], "little") def lookup_index_static(index): if index >= COUNT: return u8_static(data_offset) offset_val = u32_static(perm_offset + index * 4) return u8_static(data_offset + offset_val) def map_raw_byte(raw): signed_value = raw if raw < 0x80 else raw - 0x100 index = (signed_value - 1) & 0xFFFFFFFF return lookup_index_static(index) T_raw = bytes(map_raw_byte(raw) for raw in range(256)) inverse = {} for raw, mapped in enumerate(T_raw): inverse.setdefault(mapped, []).append(raw) target = b"Welcome to KCTF2026! Come and give it a try." chosen = [] for pos, target_byte in enumerate(target): candidates = [raw for raw in inverse.get(target_byte, []) if 1 <= raw <= 0x7F] chosen.append(candidates[0]) desired_v84 = bytes(chosen) prefix = desired_v84[:28] rsa_ciphertext = desired_v84[28:] N = 0x8C91CB79EC693F3ED6519C945DD67371 e = 65537 p = 13636154180376482939 q = 13702465297157554691 phi = (p - 1) * (q - 1) d = pow(e, -1, phi) c = int.from_bytes(rsa_ciphertext, "little") m = pow(c, d, N) rsa_plaintext = m.to_bytes(16, "little") final_key = prefix + rsa_plaintext print("最终 Key:", final_key.hex().upper()) if __name__ == "__main__": get_flag() ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
number_Z
2
6
发帖
16
回帖
125
RANK
关注
私信
他的文章
[原创]KCTF 2026 第七题:戌时·暗能潜流 writeup
32
[原创]KCTF 2026 第五题:申时·忆海倒带 writeup
1127
[原创]KCTF 2026 第二题:巳时·绿光幽语 writeup
750
[原创]看雪2020 KCTF秋季赛 第九题 命悬一线writeup
8054
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部