首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
KCTF 2026第二题「巳时·绿光幽语」Writeup
发表于: 2026-8-25 18:16
434
KCTF 2026第二题「巳时·绿光幽语」Writeup
烟雨.
2026-8-25 18:16
434
## 一、题目信息 - **题目文件**:`题目.exe`(7,438,179 字节) - **题目简介**:故事讲述了金融分析师发现一段被拆成 16 片的硬件密钥,需要还原旧终端机上的分段加密文件。程序提示"请输入 key 还原代码(请输入 6 个字符)"。 - **目标**:求出序列号(6 字符的 key) - **最终答案**:`yb6tcr` ## 二、初步分析 用 `file` / PE 头检查可知 `题目.exe` 是 **PyInstaller 打包的 Python 3.13 程序**。PyInstaller 程序通常把 Python 解释器(`python313.dll`)和打包的 `.pyc` 一起内置。使用 `pyinstxtractor.py` 解包: ``` 题目.exe_extracted/ ├── py313_mod.dll # 注意:被修改的 python313.dll(重命名) ├── base_library.zip └── ... ``` 关键点:`py313_mod.dll` 是**被作者修改过的解释器**。正常 PyInstaller 用的是原版 `python313.dll`,这里重命名为 `py313_mod.dll`,说明作者对解释器动了手脚。 ## 三、修改后的解释器分析 ### 3.1 字符串全部被加密 用 IDA 打开 `py313_mod.dll`(ImageBase `0x180000000`),发现程序要打印的提示文本在文件中**不是明文**,而是被 XOR 加密的数据块: - 8 个加密提示字符串块位于 `0x4dad10 - 0x4dadde`,均 XOR `0x55`(85) - 后面紧跟着一系列小数据:`INT 85`、`'utf-8'`、`INT 6`、块 9、`('co_code',)`、`INT 15`、块 10、`('co_consts',)` 这说明作者在**解释器层面**(DLL 内部)把字符串 marshal 数据加密了,运行时由解释器自行解密,标准反编译工具直接看是乱码。 ### 3.2 marshal 格式被修改 对比本机标准 Python 3.13.14 与 `py313_mod.dll` 的 marshal tag 编码: | 对象 | 标准 3.13.14 | 作者 DLL | |------|-------------|----------| | INT | `0xe9` | `0x69` | | SMALL_TUPLE(1 字节 count) | `0xa9` | `0x29` | | bytes(4 字节 len) | `0xf3` | `0x73` | | SMALL_STRING(1 字节 len) | `0xfa` | `0x7a` | | CODE | `0xe3` | `0x63` | 规律:标准 tag = 作者 tag + `0x80`(`FLAG_REF` 位)。作者 DLL 的 marshal 变体大多**不带 FLAG_REF 位**。而 `0x63` CODE tag 和 `0x81`/`0xa4` qualname tag 则是作者完全自定义的。 好消息:标准 3.13.14 的 `marshal.loads` 可以解析所有作者 DLL 的无 FLAG 变体(`0x29`、`0x73`、`0x69`、`0x7a` 均可正常 loads),只有 code/qualname tag 是自定义的。 ## 四、动态分析:加载作者解释器 IDA 静态分析该 DLL 流程繁琐且不稳定,改为**动态加载**:用 ctypes 加载 `py313_mod.dll`,调用它自己的 `Py_Initialize` 和 `PyMarshal_ReadObjectFromString`,让**作者自己的解析器**去解析 marshal 数据——它支持自定义 tag,并能自动 XOR 解密字符串。 ``` load_marshal.py / probe.py / scan.py ``` 一个坑:`Py_Initialize` 会立刻把解密后的提示文本打印 20 次(因为作者代码注入在 `os` 模块里,import os 时自动执行)。解决:加载 DLL 前把 fd 1/2 重定向到 devnull,初始化后再恢复。 ### 4.1 作者代码注入位置 `Py_Initialize` 后 `sys.modules` 里找到自定义函数 `my_function`,它位于 **`<frozen os>`** 模块中(`origin='frozen'`)。说明作者把自定义代码追加到了**标准 os 模块的字节码末尾**,import os 时即执行。 反汇编 os 模块主代码尾部(约 1185-1235 行)得到完整作者逻辑: ```python x = 0 while x < 20: def my_function(): print(666) # 占位函数,co_consts = (None, 666) x += 1 buffer = b''.join([8 个加密块]) # 拼接 8 段 XOR 0x55 的密文 w = b'' for i in buffer: b = i ^ 85 w += b.to_bytes(1) print(w.decode('utf-8')) # 打印解密后的提示 key = input() if len(key) != 6: continue # 长度不对就重来 b_key = key.encode() c = b'\xecbmucryb6tcr*c\x03ucryb6tCr\x1eb' # 块 9(26 字节) buf = b'' for i in range(len(c)): buf += (c[i] ^ b_key[i % len(b_key)]).to_bytes(1) my = my_function.__code__ my = my.replace(co_code=buf) # 块 9 解密结果 -> 新字节码 buf = b'' succ = b'\x9f\xe3\x9b\x91\xf5\xee\x9f\xea\xa6\x91\xe9\xed\x96\xde\xb7' # 块 10(15 字节) for i in range(len(succ)): buf += (succ[i] ^ b_key[i % len(b_key)]).to_bytes(1) my = my.replace(co_consts=(None, buf.decode('utf-8'))) # 块 10 解密结果 -> consts[1] my_function.__code__ = my my_function() # 调用还原后的函数 ``` ### 4.2 提示文本 8 个加密块 XOR `0x55` 拼接后是: ``` 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) ``` 即提示文本本身就明示了还原目标:让 `my_function` 打印 `恭喜成功!`。 ## 五、破解密钥:已知明文攻击 整体算法非常清晰: 1. **块 9**(26 字节)XOR key(UTF-8,循环重复)→ 还原成 `my_function` 的**新字节码**(co_code) 2. **块 10**(15 字节)XOR 同一 key → 还原成**字符串**,替换 `co_consts[1]` 而 `co_consts[1]` 最终要被 `my_function()` 打印出来。结合提示文本,"恭喜成功!" 就是要还原的目标明文! **"恭喜成功!" 的 UTF-8 编码**(15 字节): ``` e6 81 ad e5 96 9c e6 88 90 e5 8a 9f ef bc 81 ``` **块 10 密文**(15 字节): ``` 9f e3 9b 91 f5 ee 9f ea a6 91 e9 ed 96 de b7 ``` 因为密钥是**循环重复**的(`key[i % 6]`),取前 6 字节做 XOR: ``` k[0] = 9f ^ e6 = 79 = 'y' k[1] = e3 ^ 81 = 62 = 'b' k[2] = 9b ^ ad = 36 = '6' k[3] = 91 ^ e5 = 74 = 't' k[4] = f5 ^ 96 = 63 = 'c' k[5] = ee ^ 9c = 72 = 'r' ``` **key = `yb6tcr`**。其余 9 字节用同一 6 字节 key 循环验证,完全吻合。 ## 六、验证 ### 6.1 块 9 解密成合法字节码 ``` 块9 ^ 'yb6tcr' = 95 00 5b 01 00 00 00 00 00 00 00 00 53 01 35 01 00 00 00 00 00 00 3a 10 28 16 ``` 用 Python `dis` 反汇编(与原始 `my_function` 占位代码结构一致): ``` RESUME 0 LOAD_GLOBAL 1 (print) LOAD_CONST 1 CALL 1 POP_TOP RETURN_CONST 0 ``` 即"打印 consts[1]"的合法代码。 ### 6.2 块 10 解密成目标字符串 ``` 块10 ^ 'yb6tcr' = e6 81 ad e5 96 9c e6 88 90 e5 8a 9f ef bc 81 = "恭喜成功!" ``` ### 6.3 真实运行验证 **a)加载作者 DLL,手动还原并调用 `my_function()`**,实际打印 `恭喜成功!`。 **b)向原 `题目.exe` 管道输入 `yb6tcr`**,程序输出: ``` 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) 恭喜成功! ``` 验证通过 ✅
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
烟雨.
3
发帖
0
回帖
0
RANK
关注
私信
他的文章
KCTF 2026第二题「巳时·绿光幽语」Writeup
434
[分享][讨论]KCTF 2026 · Rosetta Calibration — Write-up
459
[分享]KCTF 2026第二题「巳时·绿光幽语」Writeup
450
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部