首页
社区
课程
招聘
[原创]第二题:巳时·绿光幽语 — 解题 Writeup
发表于: 2026-8-11 12:29 14

[原创]第二题:巳时·绿光幽语 — 解题 Writeup

2026-8-11 12:29
14

1. 题目概览、授权目标与已知条件

  • 题目类型:reverse
  • 附件:/workspace/attachments/题目/题目.exe(PE32+ console,x86-64,约 7.4 MB)
    • SHA256:f6b192f9ee87c4ad2bc8681291ca428cf8e98fec26fa5d7b82c6e791a7a90ead
  • 题目描述(故事向):终端屏幕泛绿光,显示一段记忆碎片;"文件分段加密:古典密码、时间戳算法、硬件密钥。三段必须同步解密,否则自毁";"硬件密钥被拆成十六个碎片,分散在十六个存储扇区"。
  • 操作员补充提示:需要还原的代码为
    def my_function():
        print("恭喜成功!")
    
    "请输入key还原代码(请输入6个字符)"——即需要找出一个 6 字符的 key。
  • 授权目标:仅分析 /workspace 下的题目附件,不访问外部目标。预期 Flag 格式未在平台配置中给出。

2. 初步判断与关键假设

  1. 附件是 PyInstaller 打包的 Python 3.13 程序(.fptable 等节名、pyi-* 引导字符串、DLL 尾部 MEI cookie)。
  2. 程序入口 main.pyc 直接 sys.exit(0),说明真正的挑战逻辑不在入口脚本,而是隐藏在某个被冻结/打包的组件里(经典"藏逻辑"手法)。
  3. 按操作员提示,存在一个"输入 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]

两处密文 + 已知明文:

  1. consts[151](15 字节)解密后为 co_consts 中的目标字符串 "恭喜成功!"(UTF-8,15 字节):
    key = consts[151] ^ "恭喜成功!".encode('utf-8')
        = "yb6tcr"
    
  2. 用 key="yb6tcr" 解密 consts[149](26 字节)得到完整合法的 3.13 字节码:
    95005b0100000000000000005301350100000000000020006700
    = RESUME 0; LOAD_GLOBAL print; LOAD_CONST 1; CALL 1; POP_TOP; RETURN_CONST 0
    
    与 def 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 本身已通过还原代码并输出"恭喜成功!"获得完整验证。
  • 验证方式: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}

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

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