首页
社区
课程
招聘
[原创]巳时·绿光幽语 WP
发表于: 1天前 69

[原创]巳时·绿光幽语 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内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 1天前 被kanxue编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回