首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
0
[原创]第二题:巳时·绿光幽语 WP
发表于: 2026-8-16 11:00
12
[原创]第二题:巳时·绿光幽语 WP
mxym_
2
2026-8-16 11:00
12
### 题目 #### 初步分析 附件只有一个 Windows x64 可执行文件: ```bash $ file 题目.exe 题目.exe: PE32+ executable (console) x86-64, for MS Windows ``` 字符串中可以直接看到大量 PyInstaller bootloader 的报错文本,例如: ```text PyMarshal_ReadObjectFromString PyRun_SimpleStringFlags _MEIPASS Failed to start embedded python interpreter! Failed to unmarshal code object for %s ``` 使用 `pyinstxtractor-ng` 解包: ```bash python3 pyinstxtractor_ng.py 题目.exe ``` 可以得到: ```text Pyinstaller version: 2.1+ Python version: 3.13 Possible entry point: pyiboot01_bootstrap.pyc Possible entry point: pyi_rth_inspect.pyc Possible entry point: main.pyc ``` 其中 `main.pyc` 只有 175 字节。用 Python 3.13 的 `dis` 查看: ```text 0 RESUME 0 2 LOAD_CONST 0 4 LOAD_CONST None 6 IMPORT_NAME sys 8 STORE_NAME sys 10 LOAD_NAME sys 12 LOAD_ATTR exit 34 LOAD_CONST 0 36 CALL 1 44 POP_TOP 46 RETURN_CONST None ``` 对应代码就是: ```python import sys sys.exit(0) ``` PYZ 中也只有常规标准库,没有正常意义上的题目主程序。 #### 检查 PyInstaller 外壳 为了确认原生 PE 部分是否藏了额外校验,将题目程序的代码段与 PyInstaller 6.20.0 Windows x64 `run.exe` 比较。 题目程序入口为 `0x14000d4c0`,`.text` 大小为 `0x2d8c0`,与 PyInstaller 6.20.0 对应 bootloader 完全一致: ```text .text same = True .data same = True .pdata same = True .fptable same = True .reloc same = True ``` `.rdata` 只有构建时间戳造成的少数字节变化。Ghidra 反编译 `0x1400040e0` 后也是标准 PyInstaller bootloader 主流程。 因此真正值得检查的是打包进去的运行时文件,尤其是: ```text python313.dll ``` #### python313.dll 题目中的 DLL 信息: ```text PE32+ executable (DLL) x86-64 size = 7127552 version = 3.13.1 compiler = MSC v.1941 64 bit (AMD64) ``` CodeView/PDB 中还能看到本地构建路径: ```text H:\样本分析\789456\Python-3.13.1\PCbuild\amd64\python313.pdb ``` 它是作者自行编译的 CPython 3.13.1,而不是 python.org 发布的官方 DLL。 导出表共有 1647 项,与官方 Python 3.13.1 的导出符号集合完全一致。普通 WinAPI import 也没有多出明显的校验函数。因此继续检查 DLL 内嵌的 frozen Python modules。 #### 导出 frozen module 准备一套 Python 3.13.1 Windows embeddable 环境,将其中的 `python313.dll` 替换为题目解出的 DLL。随后运行下面的脚本,把 frozen module 的 code object 导出来: ```python # dump_frozen.py import importlib.util import marshal for name, out_name in [ ("_frozen_importlib", "importlib.mar"), ("os", "os.mar"), ]: spec = importlib.util.find_spec(name) co = spec.loader.get_code(name) with open(out_name, "wb") as f: f.write(marshal.dumps(co)) ``` 对题目 DLL 和官方 Python 3.13.1 的 frozen modules 分别计算 code object 哈希,只有两个模块不同: ```text _frozen_importlib os ``` 继续递归比较 code object 后可以发现: ```text _frozen_importlib : co_code 语义一致,只是构建元数据不同 os : 模块级 co_code、co_names、co_consts 均存在真实差异 ``` 题目版 frozen `os` 额外出现一个 code object: ```text my_function @ line 1188 ``` 以及以下新增名称: ```text x my_function buffer w i b input key b_key c buf succ __code__ replace ... ``` 这就是题目的实际校验逻辑。 #### 还原 frozen os 中的逻辑 额外函数本身非常简单: ```python def my_function(): print(666) ``` 其 Python 3.13 `co_code` 为: ```text 95005b0100000000000000005301350100000000000020006700 ``` 模块级新增代码可以整理成下面的等价逻辑: ```python x = 0 while x < 20: def my_function(): print(666) try: x += 1 # 原程序这里由 8 段 bytes 拼接,再逐字节 XOR 0x55 # 解出来的是提示文本 print(prompt) key = input() if len(key) != 6: continue b_key = key.encode() c = bytes.fromhex( "ec626d7563727962367463722a63037563727962367443721e62" ) 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) succ = bytes.fromhex( "9fe39b91f5ee9feaa691e9ed96deb7" ) buf = b"" 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"))) my_function.__code__ = my my_function() except Exception: continue ``` 前面的提示字符串逐字节 XOR `0x55` 后为: ```text 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) ``` 校验没有直接执行 `key == xxx`。输入的 6 字节 key 会直接作为循环 XOR key 解密新的 `co_code`。错误 key 通常会让 `CodeType.replace()` 得到非法字节码,或者让后面的成功字符串无法以 UTF-8 解码,异常被外层 `except` 捕获后重新输入。 #### 直接恢复 key 这里不需要枚举 6 字节输入。 原始 `my_function` 的控制流是: ```python def my_function(): print(666) ``` 最终需要恢复的是: ```python def my_function(): print("恭喜成功!") ``` 两者的 Python bytecode 控制结构完全相同,区别只在 `co_consts[1]`。程序后面也确实单独执行: ```python my = my.replace(co_consts=(None, success_string)) ``` 因此第一阶段解密得到的 `co_code` 就是当前 `my_function.__code__.co_code` 本身。 令: ```text cipher = ec626d7563727962367463722a63037563727962367443721e62 code = 95005b0100000000000000005301350100000000000020006700 ``` 逐字节异或: ```text cipher XOR code = 79 62 36 74 63 72 79 62 36 74 63 72 79 62 36 74 63 72 ... = y b 6 t c r y b 6 t c r y b 6 t c r ... ``` 26 字节的完整 keystream 为: ```text yb6tcryb6tcryb6tcryb6tcryb ``` 周期正好为 6,因此: ```text key = yb6tcr ``` 再用它解密 `succ`: ```python succ = bytes.fromhex("9fe39b91f5ee9feaa691e9ed96deb7") key = b"yb6tcr" plain = bytes( succ[i] ^ key[i % len(key)] for i in range(len(succ)) ) print(plain.decode("utf-8")) ``` 输出: ```text 恭喜成功! ``` #### EXP 从导出的 `os.mar` 自动恢复 key: ```python import marshal import types with open("os.mar", "rb") as f: mod = marshal.loads(f.read()) my = next( x for x in mod.co_consts if isinstance(x, types.CodeType) and x.co_name == "my_function" ) # 题目版 frozen os 中对应常量 c = mod.co_consts[149] succ = mod.co_consts[151] assert len(c) == len(my.co_code) == 26 keystream = bytes( a ^ b for a, b in zip(c, my.co_code) ) key = keystream[:6] assert all( keystream[i] == key[i % 6] for i in range(len(keystream)) ) dec_code = bytes( c[i] ^ key[i % 6] for i in range(len(c)) ) assert dec_code == my.co_code success = bytes( succ[i] ^ key[i % 6] for i in range(len(succ)) ).decode("utf-8") print("key =", key.decode()) print("success =", success) # 按题目逻辑重建函数再执行一次 new_code = my.replace( co_code=dec_code, co_consts=(None, success), ) f = types.FunctionType(new_code, {"print": print}) f() ``` 运行结果: ```text key = yb6tcr success = 恭喜成功! 恭喜成功! ``` 最终答案: ```text yb6tcr ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mxym_
2
11
发帖
2
回帖
120
RANK
关注
私信
他的文章
[原创] 第十题:卯时·曦光初现 WP
1515
[原创]第九题:丑寅同墟·星海抉择 WP
64
[原创]第八题:亥子合辰·塔影迷楼 WP
44
[原创]第七题:戌时·暗能潜流
19
[原创]第六题:酉时·书院迷局 WP
1502
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部