-
-
[讨论]第二题WriteUP
-
发表于: 1天前 237
-
一、最终结论
最终需要输入的 key 为:
yb6tcr
验证结果:向程序输入 yb6tcr 后,程序输出“恭喜成功!”,说明 key 正确。
二、所用工具
1. IDA Pro
用于静态查看 PE、python313.dll 以及隐藏在 frozen module 中的 Python marshal code object。
2. PyInstaller 解包思路 / CArchive 结构解析
用于确认题目.exe 是 PyInstaller 打包程序,并从包内定位 main、PYZ.pyz、base_library.zip、python313.dll 等条目。
3. 十六进制查看与字符串定位工具
用于在 python313.dll 中定位 my_function、key、hardware、timestamp 等字符串和附近数据。
4. Python marshal 结构分析思路
用于还原 python313.dll 中被篡改的 frozen os 模块 code object,查看其中的 consts、names、co_code 等字段。
5. Windows cmd / PowerShell
用于运行程序并验证最终输入。
三、整体思路
题面提示“文件分段加密:古典密码、时间戳算法、硬件密钥。三段必须同步解密,否则自毁”,并且运行程序时会提示需要输入 6 个字符的 key 来“还原代码”。
实际分析后发现,题目表面上是一个 PyInstaller 程序,但正常入口 main.py 是假的,真正的逻辑被藏在 PyInstaller 附带的 python313.dll 里面。出题人修改了 CPython 内置 frozen os 模块的 marshal code object,使程序在 Python 初始化 / 导入 os 模块阶段执行隐藏逻辑。
隐藏逻辑的核心不是常规的 C 层校验,而是动态修改一个 Python 函数 my_function 的 __code__:
1. 先定义一个假的 my_function,内容类似 print(666)。
2. 输入长度必须为 6 的 key。
3. 使用 key 循环异或一段加密后的 Python bytecode。
4. 用解出的 bytecode 替换 my_function.__code__.co_code。
5. 再用同一个 key 循环异或成功提示字符串。
6. 将解出的成功字符串放进 co_consts。
7. 调用 my_function。
因此只要能够得到“原始 my_function 的 co_code”和“加密后的目标 bytecode”,就可以通过异或关系反推出 6 字节 key。
四、初步运行现象
直接运行题目.exe,程序会打印类似如下提示:
提示,需要还原的代码
def my_function():
print("恭喜成功!")
请输入key还原代码(请输入6个字符)
这个提示已经暴露了几个关键点:
1. 程序关注的是 Python 代码还原。
2. 目标函数名是 my_function。
3. 正确执行结果应当打印“恭喜成功!”。
4. key 长度固定为 6。
因此后续重点放在“my_function 的代码对象从哪里来”和“6 字节 key 如何参与解密”上。
五、确认 PyInstaller 打包结构
题目.exe 中可以看到明显 PyInstaller 特征,例如:
PYI
PyMarshal_ReadObjectFromString
PyRun_SimpleStringFlags
PYZ.pyz
base_library.zip
python313.dll
解析 PyInstaller CArchive 后,主要条目包括:
main
PYZ.pyz
base_library.zip
python313.dll
其中 main 条目反序列化后只有非常短的逻辑:
import sys
sys.exit(0)
这说明 main.py 并不是实际校验入口。若只分析 main.py,会得到“程序直接退出”的错误结论。
由于 PyInstaller 程序启动时会初始化嵌入式 Python 运行时,并加载标准库 / frozen module,所以真正逻辑很可能藏在 python313.dll 或相关 Python 运行时数据中。
六、定位隐藏逻辑
继续在解包出的 python313.dll 中搜索与运行提示相关的字符串,发现以下关键字符串:
my_function
key
hardware
timestamp
其中 my_function 的位置如下:
文件偏移:0x4DACE9
RVA: 0x4DC6E9
VA: 0x1804DC6E9
这个位置并不像普通 C 字符串引用那样被正常函数交叉引用,而是落在 Python frozen module 的 marshal 数据区域。
继续向前回溯该区域的 code object 起始位置,可以定位到被修改的 frozen os 模块:
模块:<frozen os>
python313.dll 文件偏移:0x4D00E0
也就是说,出题人将隐藏 Python 代码追加 / 注入到了 python313.dll 内置的 frozen os 模块中。由于 os 是 Python 启动和标准库加载过程中非常常用的模块,所以 main.py 即使只是 sys.exit(0),隐藏逻辑依然可以在更早阶段执行。
七、还原出的关键逻辑
从 frozen os 模块的 marshal code object 中,可以看到尾部新增了大致如下逻辑。这里保留与解题相关的核心部分:
x = 0
while x < 20:
def my_function():
print(666)
x += 1
# 由 8 段 bytes 拼接出提示文字,然后每个字节异或 0x55
buffer = b''.join(prompt_parts)
w = b''
for i in buffer:
w += (i ^ 0x55).to_bytes(1)
print(w.decode("utf-8"))
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__.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)
赞赏
- [讨论]第二题WriteUP 199