首页
社区
课程
招聘
[原创]KCTF 2026 第二题:巳时·绿光幽语 writeup
发表于: 14小时前 217

[原创]KCTF 2026 第二题:巳时·绿光幽语 writeup

14小时前
217

题目给了一个 Windows 程序,运行后要求输入 6 个字符来恢复下面的函数:

图片描述

用 Detect It Easy 看了一眼,样本是 64 位 PE,文件末尾带有很大的 overlay。基本可以确定是 PyInstaller one-file打包。

图片描述

先用 pyinstxtractor 解包:

解包后比较值得注意的只有几项:

图片描述

题目使用 Python 3.13,使用 pycdc 查看main.pyc
图片描述
main 内容等价于:

这个结果和实际运行现象明显矛盾:程序明明会打印提示并读取 key,入口脚本却直接退出。说明真正的逻辑不在 main,而是在启动过程中加载的其他组件里。

这里检查对比一下官方dll和题目dll的签名信息,可以明显看出dll是未进行官方签名的

官方3.13.15版本python313.dll
图片描述
题目python313.dll
图片描述

python313.dll 单独载入 IDA,在 Strings 窗口搜索:

图片描述
字符串位于 .rdata,附近还能看到一些信息:
图片描述

process_cpu_count 是 Python 3.13 os 模块中的函数。结合周围的 marshal 类型字节,可以判断题目把自定义逻辑塞进了冻结的 os 模块。

这里没有正常的代码交叉引用并不奇怪。my_function 不是 C 函数名,而是序列化 CodeObject 中的字符串,解释器通过冻结模块表和 marshal 解析器间接读取。

Python 3.13 marshal 中常见的类型标记如下:
参考如下资料 /3.13/Python/marshal.c

高位 0x80 是引用标记,所以 F3 & 0x7F == 73

沿着 process_cpu_count 向后看,可以确定它的 CodeObject 尾部结束于 0x1804DC68E 左右。随后出现一个新的 63,也就是 my_function CodeObject:

我用 IDAPython 把这一段直接导出:

这 125 字节虽然是完整的嵌套 CodeObject,但不能直接 marshal.load()。里面存在 72 xx xx xx xx 回引用,引用表是在更早的父 marshal 流中建立的。

按照 Python 3.13 CodeObject 的字段顺序手工读取,可以得到:

把这些字段装进一个最小 CodeType,再用 Python 3.13 自带的 dis 反汇编:

所以 DLL 中原本放着的是一个诱饵函数:

继续向后整理 marshal 数据,可以看到一组很不自然的常量:

这些数据连在一起,已经能提出几个比较靠谱的判断:

这时还只是猜测,需要用数据本身验证。

把 8 个 16 字节块拼起来,逐字节与 0x55 异或后得到:

这也解释了旁边为什么同时出现 0x55utf-86

注意,0x55 对应的是字符 U,但只是解开提示所用的掩码,不是最终 key。
图片描述

图片描述

当前诱饵函数的 co_code 长度正好是 26 字节:

后面紧挨着 co_code 字符串的可疑数据同样是 26 字节:

既然长度一致,而且旁边明确写着 co_code,最直接的验证就是把两段逐字节异或:

两者逐字节异或,前六字节为:

后续 20 字节继续以相同周期重复,所以 key 为:

再用这个周期 key 解密 15 字节数据:


冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回