首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
AI 助力安全
发新帖
1
20
[原创]看雪 CTF《巳时·绿光幽语》逆向:用 r0crawl-skills 穿透 PyInstaller 诱饵
发表于: 2026-8-14 15:07
413
[原创]看雪 CTF《巳时·绿光幽语》逆向:用 r0crawl-skills 穿透 PyInstaller 诱饵
manyuegong33
2026-8-14 15:07
413
## 0x00 前言 这道题表面上是一个普通的 Windows 可执行文件,题面却反复提示“古典密码、时间戳算法、硬件密钥”和“十六个碎片”。直接解包后,入口脚本又只剩一句 `sys.exit(0)`,很容易让分析陷入两个方向:要么怀疑解包失败,要么转头硬啃 PyInstaller 启动器。 本文使用 <a href="elink@b64K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6E0j5h3&6&6N6h3g2Y4L8$3&6Y4x3K6y4Q4x3V1k6J5x3r3y4J5j5i4N6D9i4K6g2X3M7$3E0A6L8r3I4K6">`manyuegong33/r0crawl_skills`</a> 组织完整分析过程。 项目地址:<<mark class="encrypted">efeK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6E0j5h3&6&6N6h3g2Y4L8$3&6Y4x3K6y4Q4x3V1k6J5x3r3y4J5j5i4N6D9i4K6g2X3M7$3E0A6L8r3I4K6</mark>> `r0crawl-skills` 在本题中的价值不是替代逆向工具,而是持续回答三个问题: 1. 当前观察属于容器、加载器、语言运行时,还是应用校验器? 2. 哪个最小实验能够区分现有假设? 3. 什么证据足以把“看起来像答案”提升为“已验证答案”? 最终恢复出的原始 key 为: ```text yb6tcr ``` 将它输入原程序后,程序输出: ```text 恭喜成功! ``` ## 0x01 样本与目标 样本信息如下: | 项目 | 结果 | |---|---| | 题目类型 | Reverse | | 文件 | `题目.exe` | | 文件大小 | 7,438,179 字节 | | SHA-256 | `f6b192f9ee87c4ad2bc8681291ca428cf8e98fec26fa5d7b82c6e791a7a90ead` | | 容器 | PyInstaller one-file | | Python 运行时 | CPython 3.13.1 x64 | | 最终目标 | 恢复并验证程序接受的 6 字符 key | 这里的“破解”不能只停留在找到一段可读字符串。按照 `r0crawl-skills` 的质量门槛,最终结果必须同时满足: - 能解释真实执行链; - 能从密文确定性地推导 key; - 能重建校验逻辑; - 能通过原始程序的输入路径验证。 ## 0x02 r0crawl-skills 路由过程 本题实际使用到的模块如下: | 模块 | 在本题中的作用 | |---|---| | `r0crawl-skills` | 根据 PyInstaller、CTF、Python 3.13 等信号选择分析路线 | | `triage-and-route` | 记录样本、架构、入口行为和期望结果 | | `evidence-collection` | 固化哈希、时间线、假设和派生文件 | | `python-frozen-app-reversing` | 处理 PyInstaller、诱饵入口和修改版 `python313.dll` | | `ctf-reverse-pivot-control` | 在入口脚本失效后控制分析转向,避免盲目反编译 | | `ctf-key-recovery` | 利用已知明文和周期 XOR 恢复 6 字节 key | | `reconstruction-and-parity` | 重建校验器并与原程序做一致性验证 | 对应的分析主线可以概括为: ```text 题目.exe -> PyInstaller 容器 -> CPython 3.13.1 运行时 -> main.pyc 诱饵 -> 修改版 python313.dll -> 被篡改的冻结 os 模块 -> 动态替换 code object -> 6 字节循环 XOR -> 输出“恭喜成功!” ``` 这个分层很重要。若不先确认行为位于哪一层,很容易在 PyInstaller bootloader 或 CPython 解释器内部花费大量时间,却始终碰不到真正的题目逻辑。 ## 0x03 建立证据目录 `r0crawl-skills` 推荐先建立独立 case,而不是边试命令边覆盖样本: ```text case/ case.yaml evidence/ raw/ derived/ notes/ timeline.md hypotheses.md repro/ tests/ fixtures.jsonl report.md ``` 本题保留原始 `题目.exe` 不变,所有 DLL、marshal、反汇编和求解脚本都放进 `evidence/derived` 或 `repro`。这样做的好处是每个结论都能回溯到具体文件、哈希和命令。 ## 0x04 初步分诊:这是 PyInstaller,不是普通原生校验器 解包目录中出现了典型 PyInstaller 文件: ```text PYZ.pyz base_library.zip pyiboot01_bootstrap.pyc pyimod01_archive.pyc pyimod02_importers.pyc main.pyc python313.dll ``` `python313.dll` 的版本资源显示为 CPython 3.13.1。入口 `main.pyc` 的文件头也是 Python 3.13 对应的 magic: ```text f3 0d 0d 0a ``` 因此第一层结论是: > 外层 PE 主要负责释放并启动 Python 运行时,题目逻辑更可能位于 Python 字节码、冻结模块或修改后的运行库中。 ## 0x05 入口脚本是诱饵 用 Python 3.13 对 `main.pyc` 反汇编后,逻辑等价于: ```python import sys sys.exit(0) ``` 这与题目描述、交互界面和加密提示完全不匹配。 `python-frozen-app-reversing` 对这种情况有一条非常关键的规则: > 抽取出的入口脚本没有行为,不代表抽取失败;应依次检查 runtime hook、冻结模块、修改版 Python 运行库、bootloader 前置逻辑和运行时生成的 code object。 此时建立两个竞争假设: ### 假设 H1:逻辑藏在修改版 CPython 运行时 预测:题目内置的 `python313.dll` 与官方 3.13.1 DLL 明显不同,并包含被修改的冻结模块。 最小实验:下载官方同版本、同架构运行库,比较文件、PE 节和冻结模块。 ### 假设 H2:逻辑藏在 PyInstaller bootloader 预测:外层 EXE 在执行 `main.pyc` 之前直接运行校验逻辑。 转向条件:只有 H1 被否定后,才把主要精力放到 bootloader。 这就是 `ctf-reverse-pivot-control` 的作用:保留多个假设,但先执行成本最低、区分度最高的实验。 ## 0x06 扫描全部解包产物,但不被字符串带偏 技能自带的 `scan_frozen_artifacts.py` 可以扫描整个解包树,而不是只搜索 `main.pyc`: ```powershell python scan_frozen_artifacts.py ` .\题目.exe_extracted ` flag correct wrong input timestamp hardware key ``` 宽泛关键字会在标准库、OpenSSL 和扩展模块中产生许多噪声。因此这里不能把某个 `flag` 字符串直接当作调用链证据。 更有价值的事实是: - `PYZ.pyz` 中只有标准库模块; - `main.pyc` 明确是退出诱饵; - `python313.dll` 比官方运行库大很多; - 决定性证据应来自同版本差分,而不是字符串命中。 ## 0x07 与官方 CPython 3.13.1 做基线对比 下载官方嵌入式发行包: ```powershell Invoke-WebRequest ` https://www.python.org/ftp/python/3.13.1/python-3.13.1-embed-amd64.zip ` -OutFile python-3.13.1-embed-amd64.zip ``` 两份 DLL 的信息如下: | 文件 | 大小 | SHA-256 | |---|---:|---| | 官方 `python313.dll` | 6,093,816 | `78b1dd211c0e66a0603df48da2c9b67a915ab3258701b9285d3faa255ed8dc25` | | 题目 `python313.dll` | 7,127,552 | `af3dae93d87cba3ab60d47a1512304d5e0985808e1264107c437e99beaf23729` | PE 节对比进一步显示: | 节 | 官方版本 | 题目版本 | |---|---:|---:| | `.text` | `0x2b4bb6` | `0x3a7046` | | `.rdata` | `0x25a0fc` | `0x2641b8` | 题目版 `.text` 增加了约 1 MB。这不是简单在文件尾部追加数据,而是重新编译过的 CPython 运行时。 至此 H1 的置信度显著上升,分析焦点从外层 PE 转向 CPython 冻结模块。 ## 0x08 静态恢复冻结模块表 CPython 在 `python313.dll` 中导出了以下符号: ```text _PyImport_FrozenBootstrap _PyImport_FrozenStdlib _PyImport_FrozenTest PyImport_FrozenModules ``` CPython 3.13 的 `_frozen` 结构为: ```c struct _frozen { const char *name; const unsigned char *code; int size; int is_package; }; ``` 因此可以解析 PE 导出表,读取三个 `_frozen` 数组,再按 `code` 和 `size` 导出原始 marshal 数据。 本文的提取器调用方式为: ```powershell python case\repro\extract_frozen.py ` .\题目.exe_extracted\python313.dll ` .\case\evidence\derived\frozen-challenge ``` 同样提取官方 DLL 后,对 29 个冻结模块逐一比较 SHA-256,结果非常集中: | 比较结果 | 数量 | 说明 | |---|---:|---| | 完全一致 | 27 | 可直接排除 | | `_frozen_importlib` 不同 | 1 | 仅行号表和起始行号变化,执行字节码相同 | | `os` 不同 | 1 | 题目版增加 1672 字节,且执行字节码发生变化 | `os` 模块的关键数据如下: ```text 官方大小:45748 题目大小:47420 题目 SHA-256:68b7a8bc5b29f69392f9109d6bdaa61a2b7a92c3e70f65052288b691c0eea02a ``` 这一轮差分把约 7 MB 的运行库压缩成了一个 47 KB 的 marshal 代码对象。 ## 0x09 为什么隐藏在 os 模块里 PyInstaller 启动阶段会很早导入 `os`。题目的真实执行链为: ```text pyimod01_archive.py -> import os -> FrozenImporter.exec_module -> 执行被修改的 <frozen os> -> 在 main.pyc 之前启动 key 交互 ``` 错误 key 触发的真实运行时栈也验证了这条链: ```text File "<frozen os>", line 1188 in my_function File "<frozen os>", line 1226 in <module> File "<frozen importlib._bootstrap>", line 1176 in exec_module File "pyimod01_archive.py", line 17 in <module> ``` 这构成了两个独立证据: 1. 静态差分确认只有 `os` 的执行代码被扩展; 2. 运行时栈确认交互和动态函数确实从 `<frozen os>` 执行。 ## 0x0A 恢复隐藏校验器 用官方 Python 3.13.1 对题目版 `os.marshal` 执行 `marshal.loads()` 和递归 `dis.dis()`,可以看到标准库代码末尾被加入了以下逻辑。 整理后的伪代码如下: ```python x = 0 while x < 20: def my_function(): print(666) try: x += 1 prompt_buffer = b"".join(FRAGMENTS) prompt = bytes(value ^ 0x55 for value in prompt_buffer) print(prompt.decode("utf-8")) key = input() if len(key) != 6: continue b_key = key.encode() encrypted_code = bytes.fromhex( "ec626d7563727962367463722a63037563727962367443721e62" ) code = bytes( value ^ b_key[index % len(b_key)] for index, value in enumerate(encrypted_code) ) new_code = my_function.__code__.replace(co_code=code) encrypted_success = bytes.fromhex( "9fe39b91f5ee9feaa691e9ed96deb7" ) success = bytes( value ^ b_key[index % len(b_key)] for index, value in enumerate(encrypted_success) ) new_code = new_code.replace( co_consts=(None, success.decode("utf-8")) ) my_function.__code__ = new_code my_function() return except Exception: # 原程序进入下一轮或结束异常路径 pass ``` 第一组“十六个碎片”只是界面提示。全部拼接后逐字节 XOR `0x55`,得到: ```text 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) ``` 题面中的“硬件密钥”和“时间戳算法”在这里起到了叙事和干扰作用,真正决定结果的是 6 字符输入对两组数据进行的周期 XOR。 ## 0x0B 用已知明文恢复 key `ctf-key-recovery` 建议优先使用最便宜的可逆关系。对于周期为 6 的重复 XOR: ```text cipher[i] = plain[i] XOR key[i mod 6] key[i mod 6] = cipher[i] XOR plain[i] ``` 本题天然提供了已知明文。 程序先定义: ```python def my_function(): print(666) ``` 随后只替换该函数的 `co_code` 和 `co_consts`。原始 dummy 函数的 Python 3.13 字节码为: ```text 95005b0100000000000000005301350100000000000020006700 ``` 加密字节码为: ```text ec626d7563727962367463722a63037563727962367443721e62 ``` 使用技能自带脚本: ```powershell python recover_repeating_xor.py ` hex:ec626d7563727962367463722a63037563727962367443721e62 ` hex:95005b0100000000000000005301350100000000000020006700 ` 6 ``` 输出: ```text hex: 796236746372 text: yb6tcr ``` 26 个明文字节覆盖了 key 的每个槽位至少 4 次,所有重复约束完全一致,因此这不是靠可打印字符碰出的候选值。 再用该 key 解密成功消息: ```python key = b"yb6tcr" cipher = bytes.fromhex("9fe39b91f5ee9feaa691e9ed96deb7") plain = bytes(v ^ key[i % 6] for i, v in enumerate(cipher)) print(plain.decode("utf-8")) ``` 结果为: ```text 恭喜成功! ``` ## 0x0C 最小复现脚本 下面的脚本直接读取提取出的 `os.marshal`,自动定位 `my_function`、加密字节码和成功消息,并完成恢复: ```python import marshal import types PERIOD = 6 def xor_repeat(data, key): return bytes(v ^ key[i % len(key)] for i, v in enumerate(data)) def recover_key(cipher, plain, period): slots = [None] * period for i, (c, p) in enumerate(zip(cipher, plain)): value = c ^ p slot = i % period if slots[slot] is not None and slots[slot] != value: raise ValueError("inconsistent repeating-XOR constraint") slots[slot] = value if any(value is None for value in slots): raise ValueError("known plaintext is too short") return bytes(slots) module = marshal.loads(open("os.marshal", "rb").read()) dummy = next( item for item in module.co_consts if isinstance(item, types.CodeType) and item.co_name == "my_function" ) encrypted_code = next( item for item in module.co_consts if isinstance(item, bytes) and len(item) == len(dummy.co_code) ) encrypted_success = next( item for item in module.co_consts if isinstance(item, bytes) and len(item) == 15 ) key = recover_key(encrypted_code, dummy.co_code, PERIOD) assert xor_repeat(encrypted_code, key) == dummy.co_code print("key:", key.decode("ascii")) print("message:", xor_repeat(encrypted_success, key).decode("utf-8")) ``` 注意:marshal 格式和 Python 字节码都与版本强相关,必须使用题目对应的 Python 3.13.1 执行该脚本。 运行结果: ```text key: yb6tcr message: 恭喜成功! ``` ## 0x0D 原程序验证 ### 正确 key ```powershell 'yb6tcr' | .\题目.exe ``` 输出: ```text 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) 恭喜成功! ``` 进程退出码为 `0`。 ### 错误长度 连续输入 20 次 `bad`: ```text 成功文本出现:False 提示代码出现:20 次 退出码:0 ``` 说明 `len(key) != 6` 会进入下一轮,不会执行成功函数。 ### 单字节变异 将首字节改成 `xb6tcr` 后,解密出的字节码失效,原程序报错: ```text Fatal Python error: _PyEval_EvalFrameDefault: Executing a cache. ``` 调用栈落在 `<frozen os>` 的 `my_function`,没有出现成功文本。该结果既验证了 key 的敏感性,也再次确认了真实执行位置。 ## 0x0E 容易走偏的几个点 ### 1. 只看 `main.pyc` 入口脚本确实只有 `sys.exit(0)`,但真实行为在它之前已经由冻结 `os` 模块完成。把入口脚本当作全部逻辑会直接漏题。 ### 2. 直接反编译整个 `python313.dll` 题目版 `.text` 超过 3.8 MB。若不先比较官方基线,静态分析会淹没在 CPython 内部实现中。冻结模块哈希差分能把范围迅速压缩到 `os.marshal`。 ### 3. 相信宽泛字符串命中 `flag`、`key`、`input` 在 Python 标准库和 OpenSSL 中都很常见。字符串只能作为线索,不能单独证明调用链。 ### 4. 用错误 Python 版本加载 marshal 本机默认 Python 3.10 无法可靠解释 Python 3.13 的 code object。使用题目同版本的官方嵌入式 Python 可以避免错误反序列化和错误 opcode 解释。 ### 5. 看到可读 key 就停止 `yb6tcr` 可打印并不等于它一定正确。真正的确认来自三层一致性: - 26 字节已知明文对 6 个 key 槽位完全一致; - 成功密文解出合法 UTF-8 文本“恭喜成功!”; - 原始 EXE 的真实输入路径输出相同成功文本。 ## 0x0F 总结 本题真正有意思的地方不是 XOR 本身,而是藏逻辑的位置: - PyInstaller 外层负责制造熟悉的解包入口; - `main.pyc` 是明确的退出诱饵; - 修改版 `python313.dll` 携带被篡改的冻结 `os` 模块; - `os` 在 PyInstaller 启动早期被导入,因此能抢在入口脚本前执行; - 校验器将输入作为周期 XOR key,动态重建 Python 3.13 字节码和成功常量。 `r0crawl-skills` 在这里最关键的贡献,是让分析始终沿着可证伪的路径推进: ```text 先分类层次 -> 再建立假设 -> 用官方基线做差分 -> 从运行时回到高语义 code object -> 使用最便宜的可逆关系恢复 key -> 最后在原程序中验证 ``` 最终答案: ```text yb6tcr ```
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-8-14 15:09 被manyuegong33编辑 ,原因: 标题位置写错了
收藏
・
1
点赞
・
20
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_wdhsjycl
感谢你分享这么好的资源!
5天前
mb_wspiyiep
感谢你分享这么好的资源!
2026-10-5 15:49
git_99440qimingminimax
为你点赞!
2026-9-23 11:49
git_51951meggadf3df
非常支持你的观点!
2026-9-12 15:12
FinSectech
感谢你分享这么好的资源!
2026-9-5 19:44
mb_gatolgsc
感谢你分享这么好的资源!
2026-8-27 00:37
ruoyang
非常支持你的观点!
2026-8-23 12:19
vuu
感谢你的积极参与,期待更多精彩内容!
2026-8-21 11:28
mb_lthgjpwj
为你点赞!
2026-8-20 16:23
Lookchard
你的帖子非常有用,感谢分享!
2026-8-19 11:09
neilwu
谢谢你的细致分析,受益匪浅!
2026-8-18 13:25
阿捏利
期待更多优质内容的分享,论坛有你更精彩!
2026-8-17 16:26
hacker521
非常支持你的观点!
2026-8-17 12:28
墨穹呢
这个讨论对我很有帮助,谢谢!
2026-8-16 20:36
tfll
为你点赞!
2026-8-16 10:16
dsfa
期待更多优质内容的分享,论坛有你更精彩!
2026-8-15 14:13
cr_lgdx
感谢你的贡献,论坛因你而更加精彩!
2026-8-15 00:01
风兮木晓
谢谢你的细致分析,受益匪浅!
2026-8-14 16:45
mb_qtvylfog
你的帖子非常有用,感谢分享!
2026-8-14 16:02
pexillove
感谢你的贡献,论坛因你而更加精彩!
2026-8-14 15:18
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
8
)
mb_ldbucrik
雪 币:
6
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
709
粉丝
7
关注
私信
mb_ldbucrik
2
楼
感谢分享
2026-8-14 16:06
0
mb_dcnwdpsk
雪 币:
31
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
13
粉丝
0
关注
私信
mb_dcnwdpsk
3
楼
学习
2026-8-18 13:07
0
啥也不会2
雪 币:
35
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
28
粉丝
1
关注
私信
啥也不会2
4
楼
看看
2026-8-20 17:30
0
zhuangbx
雪 币:
335
活跃值:
(237)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
82
粉丝
0
关注
私信
zhuangbx
5
楼
看看
2026-8-21 10:57
0
mb_khbmgbzb
雪 币:
200
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
10
粉丝
0
关注
私信
mb_khbmgbzb
6
楼
看看
2026-8-28 17:25
0
菜鸟起飞666
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
2
粉丝
0
关注
私信
菜鸟起飞666
7
楼
看看
2026-9-7 16:40
0
Mix_Xuan
雪 币:
338
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
9
粉丝
0
关注
私信
Mix_Xuan
8
楼
学习一下
2026-9-11 17:31
0
mb_erzitfho
雪 币:
508
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
1
粉丝
0
关注
私信
mb_erzitfho
9
楼
很棒
2026-9-14 01:18
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
manyuegong33
4
发帖
59
回帖
10
RANK
关注
私信
他的文章
[原创]看雪 CTF《巳时·绿光幽语》逆向:用 r0crawl-skills 穿透 PyInstaller 诱饵
413
[原创] 我把散装逆向经验压成了一套 Agent 作战系统:r0crawl_skills 全面介绍
6362
[原创] 我让 Hermes 把古法 RPC 重新炼成了高并发调度平台:r0rpc 实战
7244
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部