-
-
[原创]巳时·绿光幽语 WP
-
发表于: 1天前 69
-
巳时·绿光幽语 WP
题目简介
附件只有一个 64 位 Windows 可执行文件 题目.exe。程序表面上是一个几乎为空的 PyInstaller 单文件程序,真正逻辑并不在入口 main.py,而是隐藏在被篡改的 CPython 运行时中。需要穿过 PyInstaller 引导层,定位 python313.dll 内嵌的 frozen module,再恢复自修改 Python 字节码所用的 6 字节密钥。
结论
可验证的 6 字符 key 为:
yb6tcr
输入后程序输出:
恭喜成功!
附件内没有存放带包裹的 flag{...}/KCTF{...} 字符串;若赛题平台规定了统一格式,则按平台规则包裹 yb6tcr,例如 flag{yb6tcr}。
1. 文件初检
SHA-256 f6b192f9ee87c4ad2bc8681291ca428cf8e98fec26fa5d7b82c6e791a7a90ead 格式 PE32+ / x86-64 / Windows CUI 节数 7 ImageBase 0x140000000 OEP 0x14000d4c0
字符串中有 PyMarshal_ReadObjectFromString、_PYI_PARENT_PROCESS_LEVEL 和 MEI\x0c\x0b\x0a\x0b\x0e,可确认为 PyInstaller one-file 程序。
PyInstaller cookie 位于文件偏移 0x717f0b:
package length 0x6c3963 archive start 0x54600 TOC file offset 0x717bab TOC length 0x360 Python version 313 Python DLL python313.dll
CArchive 共 21 项,只有一个用户入口 main。其压缩数据在 0x59f9e,121 字节,zlib 解压后为 159 字节的 Python 3.13 marshal code object。
2. main.py 是诱饵
恢复 Python 3.13 opcode 后,唯一入口等价于:
import sys sys.exit(0)
co_names 只有 ('sys', 'exit'),没有输入、比较或解密逻辑。PYZ.pyz 的 112 个项也全部是标准库。如果在这里就停止,会错误判定附件为空。
3. 定位异常 python313.dll
归档中的 .pyd 和 OpenSSL 文件与官方 Python 3.13.13 发行件逐字节一致,但 python313.dll 明显异常:
SHA-256 af3dae93d87cba3ab60d47a1512304d5e0985808e1264107c437e99beaf23729 版本资源 3.13.1 大小 0x6cc200 (7,127,552 bytes) 签名 无 PDB H:\样本分析\789456\Python-3.13.1\PCbuild\amd64\python313.pdb
它不是 python.org 的原版 DLL,而是重新编译且修改过的 CPython。PDB 路径中的 789456 是误导,并不是正确 key。
4. 提取 frozen os
CPython DLL 导出 _PyImport_FrozenStdlib。该导出处存放的指针指向 _frozen 表,x64 下每项可按以下结构解析:
struct _frozen {
const char *name;
const unsigned char *code;
int size;
int is_package;
};题目 DLL 中的定位结果:
_PyImport_FrozenStdlib storage VA 0x180625c38 frozen stdlib table VA 0x1804bdc20 os code VA 0x1804d1ae0 os code file offset 0x4d00e0 os marshal size 0xb93c (47,420) os marshal SHA-256 68b7a8bc5b29f69392f9109d6bdaa61a2b7a92c3e70f65052288b691c0eea02a
官方 3.13.1 的 frozen os 只有 45,748 字节。递归比对 frozen modules 后,真正的语义差异只在 os:顶层 co_code 由 2,858 增至 3,572 字节,并新增 my_function、b_key、__code__、replace 和 succ 等名称。
PyInstaller 的第一个引导模块 pyimod01_archive 在第 17 行执行 import os,因此植入逻辑在诱饵 main.py 之前就会触发。
5. 还原植入逻辑
frozen os 末尾的逻辑等价于:
def my_function():
print(666)
key = input()
if len(key) == 6:
b_key = key.encode()
c = b'\xecbmucryb6tcr*c\x03ucryb6tCr\x1eb'
new_code = bytes(c[i] ^ b_key[i % 6] for i in range(len(c)))
my = my_function.__code__.replace(co_code=new_code)
succ = bytes.fromhex('9fe39b91f5ee9feaa691e9ed96deb7')
text = bytes(succ[i] ^ b_key[i % 6] for i in range(len(succ))).decode()
my = my.replace(co_consts=(None, text))
my_function.__code__ = my
my_function()外层最多循环 20 次,长度错误或字节码/解码异常都会重试。提示文本被分成 8 段,每字节异或 0x55 后才显示。
6. 利用已知字节码恢复 key
原始 my_function 的 co_code 可直接从 code object 中取出:
95005b0100000000000000005301350100000000000020006700
密文 c 与该已知明文异或,得到:
yb6tcryb6tcryb6tcryb6tcryb
循环节长是 6,所以 key 为 yb6tcr。仅看前 6 字节也可验证:
ec 62 6d 75 63 72 encrypted code 95 00 5b 01 00 00 known co_code ----------------- 79 62 36 74 63 72 "yb6tcr"
再用此 key 解密 succ,得到 UTF-8 文本 恭喜成功!。
7. 求解脚本
#!/usr/bin/env python3
"""Recover the six-byte key from the modified frozen ``os`` payload."""
from hashlib import sha256
from pathlib import Path
EXPECTED_EXE_SHA256 = (
"f6b192f9ee87c4ad2bc8681291ca428cf8e98fec26fa5d7b82c6e791a7a90ead"
)
# Constants recovered from the added tail of <frozen os>.
ENCRYPTED_CODE = b"\xecbmucryb6tcr*c\x03ucryb6tCr\x1eb"
ENCRYPTED_SUCCESS = bytes.fromhex("9fe39b91f5ee9feaa691e9ed96deb7")
# co_code of the original function: def my_function(): print(666)
KNOWN_CODE = bytes.fromhex(
"95005b0100000000000000005301350100000000000020006700"
)
def xor_repeat(data: bytes, key: bytes) -> bytes:
return bytes(value ^ key[index % len(key)] for index, value in enumerate(data))
def main() -> None:
target = Path("题目.exe")
if target.exists():
digest = sha256(target.read_bytes()).hexdigest()
if digest != EXPECTED_EXE_SHA256:
raise SystemExit(f"目标 SHA-256 不匹配: {digest}")
keystream = bytes(a ^ b for a, b in zip(ENCRYPTED_CODE, KNOWN_CODE))
key = keystream[:6]
assert keystream == (key * 5)[: len(keystream)]
assert xor_repeat(ENCRYPTED_CODE, key) == KNOWN_CODE
success = xor_repeat(ENCRYPTED_SUCCESS, key).decode("utf-8")
print(f"key: {key.decode('ascii')}")
print(f"decrypted success text: {success}")
if __name__ == "__main__":
main()python3 solve.py
预期输出:
key: yb6tcr decrypted success text: 恭喜成功!
8. 动态验证
在 Windows 终端或带真实 PTY 的 Wine 中运行,输入 key:
提示,需要还原的代码
def my_function():
print("恭喜成功!")
请输入key还原代码(请输入6个字符)
yb6tcr
恭喜成功!实测退出码为 0。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。