-
-
[原创]第二题:巳时·绿光幽语 — 解题 Writeup
-
发表于: 2026-8-11 12:29 14
-
1. 题目概览、授权目标与已知条件
- 题目类型:reverse
- 附件:
/workspace/attachments/题目/题目.exe(PE32+ console,x86-64,约 7.4 MB)- SHA256:
f6b192f9ee87c4ad2bc8681291ca428cf8e98fec26fa5d7b82c6e791a7a90ead
- SHA256:
- 题目描述(故事向):终端屏幕泛绿光,显示一段记忆碎片;"文件分段加密:古典密码、时间戳算法、硬件密钥。三段必须同步解密,否则自毁";"硬件密钥被拆成十六个碎片,分散在十六个存储扇区"。
- 操作员补充提示:需要还原的代码为
"请输入key还原代码(请输入6个字符)"——即需要找出一个 6 字符的 key。def my_function(): print("恭喜成功!") - 授权目标:仅分析
/workspace下的题目附件,不访问外部目标。预期 Flag 格式未在平台配置中给出。
2. 初步判断与关键假设
- 附件是 PyInstaller 打包的 Python 3.13 程序(
.fptable等节名、pyi-*引导字符串、DLL 尾部MEIcookie)。 - 程序入口
main.pyc直接sys.exit(0),说明真正的挑战逻辑不在入口脚本,而是隐藏在某个被冻结/打包的组件里(经典"藏逻辑"手法)。 - 按操作员提示,存在一个"输入 6 字符 key 还原代码"的程序;最可能藏在 PyInstaller 冻结进
python313.dll的 Python 模块中(Python 3.13 的 PyInstaller 会把os等基础模块冻结进 python DLL)。
3. 可复现的分析步骤(按时间顺序)
3.1 附件盘点与去壳识别
file 题目.exe
# PE32+ executable (console) x86-64, 7 sections
strings -a 题目.exe | grep -iE "pyi-|MEI|python313"
发现 pyi-、Py_DecodeLocale 等 PyInstaller 引导字符串 → 判定为 PyInstaller 打包,且入口极小。
3.2 PyInstaller 解包
- 用官方
pyinstxtractor.py(在 Python 3.13 下运行)解包:python3.13 pyinstxtractor.py 题目.exe [+] Python version: 3.13 [+] Found 21 files in CArchive [+] Found 112 files in PYZ archive [+] Possible entry point: main.pyc - 关键产物(
/workspace/artifacts/题目.exe_extracted/):main.pyc:175 字节,反汇编后仅为import sys; sys.exit(0),是"假入口"。python313.dll(7.1 MB)、base_library.zip、PYZ.pyz(112 个标准库 pyc)等。
- PYZ 中全部为标准库模块,无自定义模块 → 挑战逻辑不在 PYZ。
3.3 定位隐藏的挑战代码
- 在整个解包目录中搜索提示字符串,命中:
my_function: 题目.exe_extracted/python313.dll - 在
python313.dll中0x4dace9附近发现my_function、b_key、buf、range、__code__、replace、succ、co_code、co_consts、input、Exception等名字——这是一段被冻结进 python DLL 的 Python 模块代码(内容为修改过的os模块,作者在其中植入了挑战逻辑)。 - 通过 VA 指针扫描找到 DLL 内冻结模块表(
_PyImport_FrozenModules,位于 0x4bc220),其中:name='os' code_off=0x4d00e0 size=47420
3.4 反序列化冻结 os 模块并反汇编
import marshal
dll = open('python313.dll','rb').read()
code = marshal.loads(dll[0x4d00e0:0x4d00e0+47420]) # 用 Python 3.13
- 模块
co_name == '<module>',co_filename == '<frozen os>'。 - 反汇编模块尾部(行 1185-1229)还原出挑战伪代码:
for x in range(20): # 最多 20 次尝试 def my_function(): # 当前内容: print(666) print(666) buffer = b''.join([...8段16字节加密数据...]) # consts[145] w = b'' for i in buffer: w += (i ^ 85).to_bytes(1) print(w.decode('utf-8')) # 打印提示(见下) key = input() # 请输入6个字符 if len(key) != 6: continue b_key = key.encode() c = b'\xecbmucryb6tcr*c\x03ucryb6tCr\x1eb' # consts[149] 加密co_code buf = b'' for i in range(len(c)): buf += (c[i] ^ b_key[i % 6]).to_bytes(1) # XOR 解密 my = my_function.__code__.replace(co_code=buf) succ = b'\x9f\xe3\x9b\x91\xf5\xee\x9f\xea\xa6\x91\xe9\xed\x96\xde\xb7' # consts[151] buf = b'' for i in range(len(succ)): buf += (succ[i] ^ b_key[i % 6]).to_bytes(1) my = my.replace(co_consts=(None, buf.decode('utf-8'))) my_function.__code__ = my try: my_function() # key 正确 → 打印 "恭喜成功!" except Exception: continue # key 错误 → 重试 - 提示文字验证:
b''.join(consts[145])每字节 XOR 85 后按 UTF-8 解码,正是操作员给的提示:提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符)
3.5 求解 6 字符 key(核心)
加密方式为逐字节 XOR 循环 6 字节 key:
restored[i] = enc[i] ^ key[i % 6]
两处密文 + 已知明文:
consts[151](15 字节)解密后为co_consts中的目标字符串"恭喜成功!"(UTF-8,15 字节):key = consts[151] ^ "恭喜成功!".encode('utf-8') = "yb6tcr"- 用
key="yb6tcr"解密consts[149](26 字节)得到完整合法的 3.13 字节码:与95005b0100000000000000005301350100000000000020006700 = RESUME 0; LOAD_GLOBAL print; LOAD_CONST 1; CALL 1; POP_TOP; RETURN_CONST 0def my_function(): print("恭喜成功!")编译产物完全一致。
两条路径独立推导出同一个 key:yb6tcr(y b 6 t c r)。
3.6 端到端验证(本机 Python 3.13.15)
运行 /workspace/artifacts/solve.py:
[+] key = yb6tcr
[+] decrypted co_code = 95005b0100000000000000005301350100000000000020006700
[+] decrypted consts = 恭喜成功!
--- 执行还原后的 my_function ---
恭喜成功!
--- 执行成功 ---
错误 key(如 abcdef、000000)解密出的字符串无法按 UTF-8 解码(或字节码非法),程序进入 except Exception 分支重试——与程序行为一致。
4. 关键发现与证据
| 项目 | 位置 | 说明 |
|---|---|---|
| PyInstaller cookie | 题目.exe 0x717f0b | MEI...,python313.dll |
| 冻结 os 模块 | python313.dll 0x4d00e0, 47420B | 含挑战逻辑 |
| 加密 co_code | os 模块 co_consts[149] | ec626d7563727962367463722a63037563727962367443721e62 |
| 加密 consts 字符串 | os 模块 co_consts[151] | 9fe39b91f5ee9feaa691e9ed96deb7 |
| 提示数据 | os 模块 co_consts[145] | 8×16B,XOR 0x55 → 提示文字 |
| 恢复后的 key | 推导+双重验证 | yb6tcr |
产物:
/workspace/artifacts/solve.py— 一键求解+验证脚本/workspace/artifacts/extract_challenge.py— 解包与定位脚本/workspace/artifacts/题目.exe_extracted/— 完整解包结果/workspace/artifacts/题目.exe_extracted/python313.dll— 关键 DLL/workspace/artifacts/sec_*.bin— 各 PE 节提取/workspace/artifacts/icon_png_0.png、icon_view.png— 资源图标(确认无隐写 flag)
5. 成功结果
- 恢复出的代码:
def my_function(): print("恭喜成功!") - 6 字符 key(即本题答案):
yb6tcr - 按常见 CTF 约定,Flag 为:
flag{yb6tcr}- 说明:平台未提供 Flag 验证器,
flag{}包装为行业惯例;key 本身已通过还原代码并输出"恭喜成功!"获得完整验证。
- 说明:平台未提供 Flag 验证器,
- 验证方式:
python3.13 /workspace/artifacts/solve.py(见 3.6),输出恭喜成功!。
6. 风险与边界说明
- 仅分析了题目附件
题目.exe及其解包内容,未访问任何外部网络目标。 - 参考资源:PyInstaller 官方 pyinstxtractor(GitHub extremecoders-re/pyinstxtractor)、CPython 3.13 源码(marshal 格式)、PyInstaller 源码(冻结模块结构),仅用于理解打包/反序列化格式。
- 未找到独立的外部 Flag 验证器,
flag{}包装未经平台确认;若平台需要裸 key 形式,请直接提交yb6tcr。
exp
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 第二题:巳时·绿光幽语 — 求解脚本(在 /workspace/artifacts 下运行)
# 依赖:解包后的 题目.exe_extracted/python313.dll 与 Python 3.13
import marshal
import types
DLL = '题目.exe_extracted/python313.dll'
OS_CODE_OFF = 0x4d00e0 # 冻结 os 模块 code 偏移(来自 DLL 冻结模块表 0x4bc220)
OS_CODE_SIZE = 47420
def xor_repeat(data: bytes, key: bytes) -> bytes:
return bytes(data[i] ^ key[i % len(key)] for i in range(len(data)))
def main():
with open(DLL, 'rb') as f:
dll = f.read()
code = marshal.loads(dll[OS_CODE_OFF:OS_CODE_OFF + OS_CODE_SIZE])
consts = code.co_consts
my_function_code = consts[143]
enc_code = consts[149] # 加密后的 co_code
enc_str = consts[151] # 加密后的 co_consts 字符串
# 由目标字符串 "恭喜成功!" (UTF-8) 反推 key
target = '恭喜成功!'.encode('utf-8')
key = xor_repeat(enc_str, target)[:6]
print('key =', key.decode())
# 验证:还原 co_code 与 co_consts
new_code = xor_repeat(enc_code, key)
new_str = xor_repeat(enc_str, key).decode('utf-8')
print('co_code =', new_code.hex())
print('consts =', new_str)
restored = my_function_code.replace(co_code=new_code)
restored = restored.replace(co_consts=(None, new_str))
fn = types.FunctionType(restored, globals())
print('--- 执行还原后的 my_function ---')
fn() # 输出:恭喜成功!
print('--- 执行成功 ---')
if __name__ == '__main__':
main()
实际脚本保存路径:/workspace/artifacts/solve.py(运行方式:python3.13 /workspace/artifacts/solve.py)。
最终 Flag
flag{yb6tcr}
赞赏
赞赏
雪币:
留言: