-
-
[推荐]第二题:巳时·绿光幽语 WP
-
发表于: 3小时前 117
-
下面从拿到样本开始还原完整过程。
随便输入错误值,程序不会输出成功信息。空输入会再次请求输入,达到内部尝试次数后退出。
这一轮只能确认三件事:
第三点后来正好成为秒解 XOR key 的突破口。
对样本做离线分诊,可以看到 PyInstaller cookie、pyi_ 运行时组件和 Python DLL 等特征。复现命令:
解析 CArchive 后得到:
关键 TOC 项:
这里有几个立即可用的结论:
选择性解包:
本题真正需要关注的只有:
这里的提取器不执行样本,只从文件尾定位 cookie,计算 CArchive 起点、解析 TOC,再按压缩标志选择性解压。
按 Python 3.13 的 marshal/code object 结构检查 main,能看到:
它的逻辑等价于:
这和实际运行明显冲突:
因此不能把“解出 main.pyc”当作完成 PyInstaller 逆向。业务代码一定在 main 之前已经执行,或者藏在启动阶段自动导入的对象中。
搜索范围由此压缩为:
PyInstaller onefile 运行时会出现两个同名进程:
最初使用 Frida 附加时命中了父进程。父进程中能看到解包和文件写入,却看不到真正的 Python 输入、XOR 和 code object 修改。
改为同时观察父子进程后确认:
原因是校验并非 C 层明文比较,而是在 Python 字节码中逐字节循环 XOR,再替换函数的 code。继续扩大 CRT Hook 范围收益很低,因此动态路线只保留为最终验证,主线切回静态提取。
检查 PYZ 模块索引,没有发现与题目交互相匹配的自定义业务模块,入口 main 也没有导入它们。
转向理由:继续反编译整个 PYZ 只会淹没在标准库代码中。
运行时文件写入曾让 base_library.zip 看起来可疑,但直接解包后仍是启动所需标准模块,没有能解释完整交互的业务逻辑。
转向理由:程序行为发生在假 main 之前,而 PyInstaller 还会从 Python DLL 加载 built-in frozen modules。
pyiboot01_bootstrap 和 pyi_rth_inspect 与标准 PyInstaller 启动逻辑基本一致,没有 6 字符 key、密文数组或 code object 替换逻辑。
转向理由:剩下最值得查的目标就是 python313.dll。
对解压出的 python313.dll 搜索异常 Python 标识符,出现了一组不应存在于标准 os 模块尾部的名字:
本样本中,异常 marshal 根对象位于:
该偏移只适用于本文 SHA-256 对应的样本,不能当作其他 Python 3.13 DLL 的固定偏移。
使用精确匹配的 Python 3.13 解析该对象:
关键输出:
题目作者没有把代码放在普通 .pyc 中,而是修改了 python313.dll 里内置的 frozen os。Python 启动时很早就会导入 os,因此附加逻辑会在假 main 之前执行。
这里还有一个版本坑:用 Python 3.11 读取 Python 3.13 code object 时,部分常量可能看起来正常,但 opcode 和 code object 布局不兼容,反汇编会错位。最终必须使用 Python 3.13,或明确支持 3.13 的字节码工具。
异常代码整理后如下。变量名保留原 code object 中的名字,控制流做等价简化:
原始字节码还将 8 段 bytes 与 0x55 异或后做 UTF-8 解码,以隐藏中文提示。这只影响字符串可见性,不影响 key 求解。
真正的校验条件不是:
而是:
这解释了为什么 Hook 比较函数或简单修改成功分支没有直接得到可提交答案。
题面已经打印需要还原的目标:
第二段密文长度为 15 字节。UTF-8 的“恭喜成功!”也正好是 15 字节:
将第二段密文与已知明文逐字节 XOR:
XOR 结果呈现严格的 6 字节周期:
所以候选 key 是:
这并非只靠中文语义猜答案。把同一 key 用于第一段密文:
得到:
它能被 Python 3.13 正确解释为 my_function 的有效 co_code,并与打印一个常量字符串后返回的短函数结构吻合。这是第二个独立验证条件。
下面 solver :
运行结果:
也可用通用已知明文 XOR 工具:
输出:
赞赏
- [推荐]第二题:巳时·绿光幽语 WP 116
- KCTF2025题目提交- 第七题 危局初现 设计思路 3709
- [推荐]KCTF2024题目提交 1709
- KCTF2023 第六题设计思路 5721