首页
社区
课程
招聘
[讨论]第二题WriteUP
发表于: 1天前 237

[讨论]第二题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)


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回