首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
1
8
[原创]第二题「巳时·绿光幽语」解析
发表于: 2026-8-11 20:25
697
[原创]第二题「巳时·绿光幽语」解析
教教我吧~
1
2026-8-11 20:25
697
> **写给谁:** 刚接触逆向 / CTF 的同学,自己也学习一波。 > **阅读方式:** 少黑话;出现专业词会先用大白话解释。 > **目标:** 不依赖「死记 IDA 地址」,也能顺着思路得到 key,并理解「凭什么」。 > **AI:** grok 4.5 --- ## 目录 1. [最终答案](#一最终答案) 2. [题目在考什么](#二题目在考什么) 3. [先跑一遍:你会看到什么](#三先跑一遍你会看到什么) 4. [五个背景概念](#四五个背景概念) 5. [分析路线图](#五分析路线图) 6. [步骤 1:确认是 PyInstaller 打包](#六步骤-1确认是-pyinstaller-打包) 7. [步骤 2:拆包](#七步骤-2拆包) 8. [步骤 3:main 是烟雾弹](#八步骤-3main-是烟雾弹) 9. [步骤 4:定位真题到 python313.dll](#九步骤-4定位真题到-python313dll) 10. [步骤 5:两条加密线与算 key](#十步骤-5两条加密线与算-key) 11. [步骤 6:运行验证](#十一步骤-6运行验证) 12. [程序等价逻辑](#十二程序等价逻辑) 13. [捷径说明:文件里也能搜到 key](#十三捷径说明文件里也能搜到-key) 14. [常见疑问 FAQ](#十四常见疑问-faq) 15. [复现检查清单](#十五复现检查清单) 16. [知识点小结](#十六知识点小结) 17. [相关文件索引](#十七相关文件索引) --- ## 一、最终答案 | 项目 | 内容 | | ------------------- | -------------------------------------------------- | | **Key(6 个字符)** | `yb6tcr` | | **样本** | 工程根目录下的 `题目.exe` | | **验证方式** | 运行样本,输入 `yb6tcr`,应打印 **「恭喜成功!」** | 若平台要求 `flag{...}` 格式,可提交: ```text flag{yb6tcr} ``` 程序本身只校验 **6 字符** `yb6tcr`,是否再包一层以平台说明为准。 --- ## 二、题目在考什么 程序提示:有一段「被藏起来的代码」,需要你找到一个 **6 字符钥匙(key)** 正确还原,才会成功。 一句话: > **找出加密方式 + 算出钥匙。** 不是改程序、也不是攻击他人电脑,只是分析「程序如何检查答案」。 --- ## 三、先跑一遍:你会看到什么 ### 3.1 运行现象 在工程根目录运行: ```bat 题目.exe ``` 正常编码下大致显示: ```text 提示,需要还原的代码 def my_function(): print("恭喜成功!") 请输入key还原代码(请输入6个字符) ``` 含义: 1. **上方几行**:题面——正确逻辑长什么样(函数 `my_function` 打印成功信息)。 2. **最后一行**:要求输入 **正好 6 个字符** 的 key。 3. 输错会反复询问;输对会再打印 **恭喜成功!**。 ### 3.2 若出现中文乱码 这是 Windows 控制台编码问题,不是样本损坏。可先执行: ```bat chcp 936 题目.exe ``` `936` 表示按简体中文(GBK)显示。 ### 3.3 使用注意 - 仅在本地、自己的题目目录中分析与运行该样本。 - 输入时使用分析得到的 key 即可,不必长时间盲猜。 --- ## 四、五个背景概念 读后面步骤前,先建立这 5 个直觉。 ### 概念 1:`.exe` 不一定是纯 C 程序 有的 exe 是 C/C++ 直接编译;也有的是 **把 Python 程序打包进 exe**(方便双击运行)。本题属于后者,打包工具是 **PyInstaller**。 ```text ┌─────────────────────────────┐ │ 题目.exe(快递箱) │ │ ┌───────────────────────┐ │ │ │ Python 运行环境 │ │ │ │ + 真正的 Python 逻辑 │ │ │ └───────────────────────┘ │ └─────────────────────────────┘ ``` - 用 IDA 打开 **exe**,多半看到的是「开箱、启动 Python」的壳代码; - **真正出题逻辑**往往在箱子里面的 Python / DLL 数据里。 ### 概念 2:XOR(异或) - 对每个字节与「钥匙字节」做 XOR; - **同一把钥匙再 XOR 一次,会变回原文**(可逆)。 生活类比:同一把钥匙拧一下变密文,再拧一下变明文。 本题用了两种 XOR: | 用途 | 钥匙 | 作用 | | ------------ | -------------------------- | -------------------------------------- | | 显示提示文字 | 固定值 `0x55`(十进制 85) | 把展示密文变成你看到的中文和源码 | | 校验你的输入 | 你输入的 6 字符,循环使用 | 把 15 字节密文变成 UTF-8「恭喜成功!」 | ### 概念 3:6 字符循环 XOR key 为 `yb6tcr` 时,密文较长则钥匙循环使用: ```text 密文字节: 第0 第1 第2 第3 第4 第5 第6 第7 第8 ... key 字节: y b 6 t c r y b 6 ... ``` 每个位置:`明文 = 密文 XOR key[对应位置]`。 ### 概念 4:UTF-8 与 GBK 都是「文字如何变成字节」: - **UTF-8**:常见,中文通常 3 字节; - **GBK**:中文 Windows 控制台常用。 本题中: - 控制台打印时,不少中文按 **GBK** 显示; - 用 key 解密的「恭喜成功!」在算法上按 **UTF-8**(刚好 **15** 字节)。 记住:**同是中文,编码不同字节就不同**,分析时要对上编码。 ### 概念 5:Python 的 marshal(认路牌即可) **一句话:** marshal 是 Python 把「代码对象、常量、字符串、元组」写成一串字节 / 再读回来的官方格式。`.pyc`、frozen 模块、嵌在 DLL 数据区里的大段,都可能是 marshal。 ```text 源码 .py → 编译成 code 对象(字节码 + 常量表 + 名字表) → marshal.dumps → 磁盘/DLL 里的 0x73 0x29 ... → 运行时 marshal.loads → 解释器执行 ``` 本题只需会认几个「路牌」: | 字节 | 人话 | 后面跟什么 | | ---------------------- | --------------- | ---------------------------- | | **`73`**(`s`) | 字符串 / 字节串 | 4 字节长度(小端)+ 内容 | | **`29`**(`)`) | 小元组 | 1 字节元素个数 + 各元素 | | **`da` / `z` 等** | 短 ASCII 名字 | 名字字符(如 `my_function`) | | **`e3` / `63`(`c`)** | code 对象 | 字段多,初学可先跳过 | **例子 A:`73 10 00 00 00` + 16 字节** → 一段长度 16 的串(本题「展示密文」每块)。 **例子 B:`29 08` + 8 次「`73 10` + 16 字节」** → 8 元组,每个元素 16 字节 → 8 段展示密文的摆放方式。 **例子 C:`73 0F 00 00 00` + 15 字节** → 长度 15 的串 → 本题「用户密文」。 不必手写完整解析器;会「读标签拆货」即可。 --- ## 五、分析路线图 ```text 第 1 步 确认是 PyInstaller 打包的 Python ↓ 第 2 步 用 pyinstxtractor-ng 拆包 ↓ 第 3 步 发现 main 几乎是空的(烟雾弹) ↓ 第 4 步 在 python313.dll 中定位真题数据 ↓ 第 4.5 步(推荐)用 IDA 打开 DLL,按结构往下读 ↓ 第 5 步 两条线:展示用 0x55;校验用 6 字符 key ↓ 第 6 步 得到 key = yb6tcr,运行验证 ``` --- ## 六、步骤 1:确认是 PyInstaller 打包 ### 6.1 样本基本信息 | 项目 | 值 | | ------ | ---------------------------------- | | 文件名 | `题目.exe` | | 大小 | 约 7.4 MB(偏大,像带了运行环境) | | MD5 | `d2fc58976cac7403cee3a8b8f2560081` | | 类型 | Windows 64 位 PE | ### 6.2 为何怀疑 PyInstaller? 在 exe 的字符串中可看到类似: - `_MEIPASS` - `PYZ.pyz` - `python313.dll` - `Failed to extract script from archive` 这些是 PyInstaller 特征;`python313` 表示 **Python 3.13**。 ### 6.3 若用 IDA 打开 exe - 入口多在壳代码:解压、加载 Python、执行脚本; - **几乎看不到**「请输入 key」这类题目逻辑。 结论: > **题目逻辑不在壳里,在壳释放出来的内容里。** 初学者可先把 IDA 当「确认是 PyInstaller」的工具;解题主路径是 **拆包 + 分析 DLL 数据**。 --- ## 七、步骤 2:拆包 ### 7.1 推荐工具:<a href="elink@677K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3P5h3W2F1M7%4c8^5N6s2u0S2j5%4c8G2M7W2)9J5c8Y4m8&6K9h3&6K6N6s2S2@1M7X3q4U0N6r3!0J5i4K6u0V1L8X3N6Q4x3V1k6J5k6h3I4W2j5i4y4W2M7H3`.`.">pyinstxtractor-ng</a> 推荐使用 <a href="elink@7d6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3P5h3W2F1M7%4c8^5N6s2u0S2j5%4c8G2M7W2)9J5c8Y4m8&6K9h3&6K6N6s2S2@1M7X3q4U0N6r3!0J5i4K6u0V1L8X3M7`.">pyinstxtractor-ng</a>(可执行文件或 Python 脚本均可)。 优点:一键拆包、兼容较好、目录干净,适合初学者。 ### 7.2 操作示例 在工程根目录(与 `题目.exe` 同级)执行: ```bat :: 独立 exe 工具 pyinstxtractor-ng.exe 题目.exe ``` 成功后生成类似目录: ```text 题目.exe_extracted/ ``` 其中约有二十来个文件,例如:`main.pyc`、`PYZ.pyz`、`python313.dll` 等。 > **做题与写证据时,请只用 pyinstxtractor-ng 生成的拆包目录。** > 不要把运行日志、分析笔记 txt 混进搜索目录,以免「在文件中搜索」出现假命中。 ### 7.3 重点文件 | 文件 | 是什么 | 对本题的意义 | | -------------------- | -------------- | ------------------------ | | `main.pyc` | 程序入口字节码 | **几乎是空的**(烟雾弹) | | `PYZ.pyz` | 打包的标准库等 | 无自定义题目模块 | | **`python313.dll`** | Python 解释器 | **真题数据在这里** | | 其它 `.pyd` / 运行库 | 依赖 | 一般不必深挖 | 相对路径示例: ```text 题目.exe_extracted/python313.dll 题目.exe_extracted/main.pyc ``` --- ## 八、步骤 3:main 是烟雾弹 反编译 / 分析 `main.pyc` 后,逻辑大约只有: ```python import sys sys.exit(0) ``` 即:一运行就正常退出,没有输入 key、没有打印中文提示。 但实际运行 exe 时**明明有提示和输入**。因此: > 提示与校验 **不在 main 里**,而在 Python **启动过程中更早执行** 的地方。 Python 启动会自动导入内置模块,其中包括 **`os`**。出题人把挑战逻辑嵌进 **被修改过的 `python313.dll` 内 frozen 的 `os` 数据**。一旦启动并 `import os`,题目就先跑起来——即使 main 是空的。 ```text 1. 双击 题目.exe 2. PyInstaller 解压并启动 Python 3.13 3. 自动 import os → 【题目逻辑在这里执行】 4. 最后才执行 main → sys.exit(0) (几乎无作用) ``` **易错点:** 只反编译 main,会以为「题是空的」。必须结合运行现象,去找真正打印中文的位置。 --- ## 九、步骤 4:定位真题到 python313.dll 本节回答三个问题: 1. 为什么要搜 `my_function`? 2. 名字会不会被加密、搜不到? 3. 为何确定在 `python313.dll`,而不是其它拆包文件? ### 9.1 相关路径(均相对工程根目录) | 路径 | 说明 | | ---------------------------------- | ---------------------------------- | | `题目.exe` | 原始样本 | | `题目.exe_extracted/` | pyinstxtractor-ng 拆包结果(推荐) | | `题目.exe_extracted/python313.dll` | 真题所在 DLL | ### 9.2 为什么搜 `my_function`? 运行时屏幕上明文出现了英文标识符: ```text my_function ``` 特点: - 纯 ASCII,无 GBK/UTF-8 编码歧义; - 在二进制中常为连续字节 `6d 79 5f 66 75 6e 63 74 69 6f 6e`; - 比搜中文「恭喜」更稳。 分析顺序: ```text 运行现象 → 记下独特英文名 my_function → 在干净拆包目录全局搜索 → 谁命中,谁更可疑 ``` 不是先验「一定在 dll」,而是用运行提示反查文件。 ### 9.3 名字会不会加密? **本题中,标识符 `my_function` 未加密,可直接搜到。** | 内容 | 是否加密 | 说明 | | ------------------ | ----------------------------- | ---------------------------- | | 屏幕展示的整段提示 | **是**(固定 XOR `0x55`) | 密文在 DLL,运行时解密再打印 | | 校验用 15 字节 | **是**(6 字符 key 循环 XOR) | 验证 key | | 名字 `my_function` | **否(本题)** | ASCII 明文,可直接搜 | 注意区分: - 标识符 **`my_function` 这个词**:明文在 DLL 里; - 整段「提示… / def my_function(): / …」:**加密存放**,运行时解密打印。 其它题可能混淆名字或全串加密;**本题没有**,因此「搜运行时看见的英文函数名」有效。 ### 9.4 为何锁定 python313.dll? 在干净目录 `题目.exe_extracted/` 中搜索 `my_function`: **本题实测:仅 `python313.dll` 命中 1 次。** ```text √ python313.dll (匹配 1 次) ← 唯一真源 × main.pyc × PYZ.pyz × 启动相关 pyc / 各 .pyd / 运行库 dll ``` 这是 **全目录检索 + 排除法**,不是猜。 **操作(Notepad++ 示例):** 1. 打开 **搜索 → 在文件中查找**(或 `Ctrl+Shift+F`)。 2. 查找内容:`my_function`;目录:`题目.exe_extracted`;筛选:全部文件。 3. 全部查找 → 仅 DLL 命中 → 后续只分析该文件。 Notepad++ 只回答「在哪个文件」;打开后还要往下找 8 段密文与 15 字节(见下)。 ### 9.5 在 DLL 里,名字附近还有什么? 在 `python313.dll` 中,`my_function` 约在**文件偏移** `0x4DACE9`(十六进制编辑器 / 脚本查找可用)。 附近还能看到:`print`、`input`、`b_key`、`co_code` / `co_consts`、`__code__`、`replace`、`Exception`,以及 `os` 模块常见名字。 拼起来的逻辑大致是: 1. 解密并打印提示; 2. `input` 读 6 字符 key; 3. 用 key 还原一段数据,并 `replace` 到 code 相关字段; 4. 调用 `my_function`; 5. 失败则异常 / 继续循环。 ### 9.6 用 IDA 打开 python313.dll(推荐) > 下列 **VA(虚拟地址)** 针对本题该 DLL:ImageBase = **`0x180000000`**。 > 你本地若 ImageBase 一致,可用 **G** 跳转核对;不一致则以 **字符串搜索 + 结构阅读** 为准,不必死记地址。 #### (1)打开对象与注意点 | 项目 | 值(本题) | | --------- | --------------- | | 模块 | `python313.dll` | | 架构 | 64 位 | | ImageBase | `0x180000000` | **不要在 `.text` 里找名为 `my_function` 的 C 函数。** 本题逻辑是 **marshal 嵌在 `.rdata` 的 Python 字节码与常量**,不是原生导出函数。 #### (2)IDA 搜索顺序 **此时还不知道 key** 1. `Shift+F12` 打开 Strings。 2. `Ctrl+F` 搜索运行时见过的:`my_function`。 3. 双击跳到 `.rdata` 数据区。 4. **往下滚**,按步骤 A~C 认结构,用眼睛跟数据。 | 阶段 | 可以搜 | 不要搜 | | ----------- | -------------------------- | ---------------------------- | | 刚定位 | 运行时见过的 `my_function` | **`yb6tcr`(key 尚未算出)** | | 算完 key 后 | 可用 `yb6tcr` 回搜印证 | 把结论当定位手段提前抄 | #### (3)步骤 A:认出 marshal 结构 从 `my_function` 往下滚,会看到类似: ```text 29 08 ← 小元组,8 个成员 73 10 00 00 00 ← 串,长 16 [16 字节] ← 展示密文块 0 73 10 00 00 00 [16 字节] ← 展示密文块 1 ... 共 8 次 ... ``` 「展示密文块 0~7」是事后笔记名:结构来自 marshal 标签;内容是否为展示提示,要靠 XOR 实验与运行输出比对。 #### (4)步骤 B:证明 8×16 是展示提示密文 从第一段 16 字节(本题块 0 起址约 `0x1804DC71C`)抄出数据,试单字节 XOR。已知明文攻击后锁定钥匙为 **`0x55`**(过程见第十节): ```python b = bytes.fromhex("b3dac5b2f1efbae9d9bcc9d5bdf3d4bd") print(bytes(x ^ 0x55 for x in b)) ``` 按同样方法解全部 8 段并拼接,得到与运行窗口一致的整段提示 → 确认这就是「提示,需要.」密文。 #### (5)步骤 C:找到 15 字节用户密文 **不是** 8 段展示密文里的某一段,而是 **继续往下滚**,过 `co_code` 等名字后: ```text 73 0F 00 00 00 ← 长度 = 15 [15 字节] ← 用户密文(本题起址约 0x1804DC7FF) ``` | 名称 | 作用 | 钥匙 | | ---------- | --------------------------- | ----------- | | 展示密文块 | 给玩家看的提示(8×16) | 固定 `0x55` | | 用户密文 | 校验你输入的 key(15 字节) | 6 字符循环 | 二者地址、长度、密钥都不同,**不要混**。 #### (6)关键地址一览(本题 VA,供核对) | 含义 | VA | 如何自己定位 | | ---------------------- | ---------------------------- | ----------------------------------------------- | | 明文名 `my_function` | `0x1804DC6E9` | **先** Shift+F12 搜 | | 展示密文块 0 | `0x1804DC71C` | `my_function` 下方 `29 08` 后第一段 `73 10`+16B | | 展示密文块 2 | `0x1804DC746` | 第 3 段 16 字节(`31 30 33 75...`) | | 用户密文 15 字节 | `0x1804DC7FF` | 再往下 `73 0F 00 00 00` 后 15 字节 | | ASCII `yb6tcr`(泄漏) | `0x1804DC7DB` | **仅算出 key 后**回搜印证 | | 做题核心区(约) | `0x1804DC693`~`0x1804DCBCA` | 名字、两段密文、相关 names | **文件偏移与 VA(本题该 .rdata 片区粗算):** ```text 文件偏移 0x4DACE9 ≈ VA 0x1804DC6E9 VA ≈ 0x180000000 + 文件偏移 + 0x1A00 ``` 最稳妥仍是:**搜字符串 → 按结构往下读**,不必死记。 #### (7)为何几乎没有 xref? 这是数据区中的 Python marshal 串,不是 C 函数入口。静态做题以 **字符串/字节模式定位 + 算法还原** 即可,不必反编译整份 CPython。 #### (8)exe 与 dll 的分工 | 打开对象 | 你看到什么 | | --------------- | --------------------------------------------- | | `题目.exe` | PyInstaller 壳:解压、加载 DLL、跑 main | | `python313.dll` | 解释器 + **本题真正植入点**(frozen os 数据) | 业务逻辑(Python)主要在 **`.rdata` 的 marshal 数据**;**`.text`** 是解释器「厨师」。在 `.rdata` 看不到清晰的 `xor al, 55h` 很正常——算法靠 **已知明文 + 与运行输出比对** 断定。 ### 9.7 本节小结 1. 搜 `my_function`:运行窗口明文出现,ASCII 好搜。 2. 函数名本题未加密;加密的是提示串与 15 字节校验数据。 3. 干净拆包目录里,特征只集中在 `python313.dll`。 4. IDA:打开 DLL → 搜 `my_function` → 往下读 `29 08` / `73 10` / `73 0F`。 5. 业务在 `.rdata` 字节码数据里,由 `.text` 解释器执行。 --- ## 十、步骤 5:两条加密线与算 key ### 10.0 两条线必须分开 | 线 | 做什么 | 与输入的 key 有关吗 | | -------------- | ------------------------------------------- | ---------------------- | | **A 展示提示** | 8 段数据 XOR 固定 `0x55` → 屏幕中文提示 | **无关** | | **B 检查 key** | 15 字节 XOR 你输入的 6 字符 →「恭喜成功!」 | **有关(答案在这里)** | **算 key 只走线 B。** #### 问:程序都能解密展示,为什么还显示明文题面? | 东西 | 作用 | | ------------------------------------------------- | ------------------------------ | | 屏幕上的 `def my_function(): print("恭喜成功!")` | **题面**——告诉你还原后长什么样 | | 6 字符 key | **你要交的答案** | 线 A 由程序自动解密并打印;你静态推出 `0x55` 是为了看懂题面从哪来;**过关仍要线 B 的 `yb6tcr`**。 #### 问:明文和密文如何「对应」? **对应 = 等长 + 按下标对齐 + 同一种 XOR 能互变 + 与程序行为一致。** --- ### 10.1 线 A:展示提示(固定钥匙 0x55) DLL 中有 **8 段 × 16 字节**。对每个字节: ```text 明文字节 = 密文字节 XOR 0x55 ``` 拼起来即完整 UTF-8 提示(与运行时一致)。 #### 凭什么是 0x55?(发现过程,禁止倒背) 1. **运行**看到英文 `def my_function(`(到 `(` 为止共 16 字符)。 2. 在挑战区 8 段「长度 16」数据中,**块 2**(约 `0x1804DC746`)半可读。 3. **已知明文攻击**:假设块 2 对应 `def my_function(`: ```python 密文 = bytes.fromhex("31303375382c0a33203b36213c3a3b7d") # 块2 明文 = b"def my_function(" print(bytes(密文[i] ^ 明文[i] for i in range(16))) # b'UUUUUUUUUUUUUUUU' → 每一位都是 0x55 ``` 4. 用**同一个** `0x55` 解全部 8 段,拼接结果 = 运行输出 → **确认**。 **注意:** `0x1804DC6E9` 处的明文名字 `my_function` **不能**当这段密文;反推 `0x55` 用的是 **块 2**。 > 展示文字用固定钥匙 `0x55`,**不是**你要猜的 6 字符 key。 --- ### 10.2 线 B:15 字节用户密文 → 算出 key #### 凭什么是这一串密文? 1. **明文从哪来?** 题面/成功输出写明 **「恭喜成功!」**(题目给的)。 2. **编码后多长?** ```python print(len("恭喜成功!".encode("utf-8"))) # → 15 print("恭喜成功!".encode("utf-8").hex()) # e681ade5969ce68890e58a9fefbc81 ``` 3. **密文从哪找?** 在 `my_function` 挑战区、8 段展示密文**之后**,找「长度字段 = 15」: ```text 73 0F 00 00 00 9F E3 9B 91 F5 EE 9F EA A6 91 E9 ED 96 DE B7 ``` 依据:长度正好 15;贴在 `co_code` / `b_key` / `input` 旁;仍在挑战植入区。**不是**在整个 `.rdata` 里随便抓 15 字节。 4. **如何确认?** 密文 ⊕ 明文 → 钥匙流 `yb6tcryb6tcryb6`(**周期 6**)⇔ 题目「请输入 6 个字符」;再运行验证。 #### 反推 key ```text key 的第 i 个字节 = 密文[i] XOR 明文[i] ``` | 位置 | 密文 | 明文 (UTF-8) | XOR | 字符 | | ---- | ------ | ------------ | ------ | --------------- | | 0 | `0x9f` | `0xe6` | `0x79` | `y` | | 1 | `0xe3` | `0x81` | `0x62` | `b` | | 2 | `0x9b` | `0xad` | `0x36` | `6` | | 3 | `0x91` | `0xe5` | `0x74` | `t` | | 4 | `0xf5` | `0x96` | `0x63` | `c` | | 5 | `0xee` | `0x9c` | `0x72` | `r` | | 6 | `0x9f` | `0xe6` | `0x79` | `y`(开始重复) | 因此 6 字符 key 为: ```text yb6tcr ``` #### 一键脚本(可选) ```python c = bytes.fromhex("9fe39b91f5ee9feaa691e9ed96deb7") p = "恭喜成功!".encode("utf-8") print(bytes(c[i] ^ p[i] for i in range(15))) # b'yb6tcryb6tcryb6' → 前 6 个字符即 key ``` --- ## 十一、步骤 6:运行验证 ### 方法 A:手动输入 ```bat chcp 936 题目.exe ``` 输入:`yb6tcr` 看到 **恭喜成功!** 即通过。 --- ## 十二、程序等价逻辑 下面不是 100% 从编译器抠出的原文件,而是根据证据还原、与真题行为一致的逻辑: ```python # 1. 用固定 0x55 解密,打印「需要还原的代码」和输入提示 # 2. 循环: # key = input("请输入 6 个字符...") # 若长度不是 6,继续问 # 用 key 循环 XOR 那 15 字节密文 # 若得到合法的「恭喜成功!」 # 让 my_function 去 print 它,成功退出 # 否则重来 ``` --- ## 十三、捷径说明:文件里也能搜到 key 在 DLL 中约 `0x1804DC7DB` 附近,数据里夹着可读碎片: ```text ...bmucryb6tcr*c... ^^^^^^ yb6tcr ``` 于是有人问:**密钥都在文件里了,为何还要输入?** | | 程序运行时(设计意图) | 你在二进制里搜到 `yb6tcr` | | ---------- | ----------------------- | ------------------------------ | | key 从哪来 | `input()` 读键盘 | 数据布局**泄漏**出的 6 字符 | | 干什么 | XOR 15 字节,还原成功串 | **不是**程序从该地址自动读密码 | | 输错 | 解密失败,不进成功分支 | 与是否搜到无关 | **一句话:** > 程序只认你键入的 6 字节当钥匙。 > 文件里能搜到 `yb6tcr` = 出题不严谨带来的作弊通道,不是官方玩法。 **正确用法:** 用第十节 XOR 推出 key **之后**,再回搜印证。 **错误用法:** 一打开 DLL 就搜 `yb6tcr` 当定位手段,跳过算法。 --- ## 十四、常见疑问 FAQ ### Q1:不会 IDA 能做吗? 能。关键路径:确认 PyInstaller → pyinstxtractor-ng 拆包 → 在 `python313.dll` 找字符串与密文 → XOR 计算。IDA 用于核对,不是唯一路径。 ### Q2:什么是 `.pyc`? Python 源码编译后的字节码文件,可用工具反编译成接近源码的形式。 ### Q3:main 为空怎么还算题? 题藏在启动必加载的 frozen `os`(在 `python313.dll`)里,main 是掩护。 ### Q4:交 `yb6tcr` 还是 `flag{yb6tcr}`? 程序只认 6 字符;平台若要求 `flag{...}` 再包装。以平台说明为准。 ### Q5:XOR 不会手算? 使用工具:<a href="elink@d32K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4j5$3S2I4i4K6u0W2k6$3W2@1K9s2g2T1i4K6u0W2K9h3!0Q4x3V1k6o6P5h3u0W2M7V1y4Z5k6h3k6Q4x3V1k6Q4x3U0y4J5k6h3y4A6M7r3g2Q4x3@1c8j5e0#2u0Q4x3U0S2Q4x3U0f1%4b7W2)9J5y4$3!0H3N6r3W2G2L8W2)9J5y4#2)9K6b7g2)9J5y4@1S2W2P5q4)9J5y4#2)9J5b7#2)9J5y4%4y4@1M7X3W2F1k6#2)9J5y4#2)9K6b7g2)9J5y4#2)9J5y4#2)9J5y4e0N6p5i4K6u0o6i4K6t1%4f1%4c8S2L8X3c8S2M7X3c8Q4x3U0N6Q4x3V1y4X3j5h3I4K6k6b7`.`.">CyberChef</a>)。 ### Q6:`0x1804DC6E9` 与块 2 别混 | VA | 内容 | | ------------- | ----------------------------------- | | `0x1804DC6E9` | 明文**名字** `my_function`(路标) | | `0x1804DC746` | 展示密文**块 2**(用来反推 `0x55`) | ### Q7:凭什么 0x55?凭什么 15 字节是那一串? 见 **第十节「凭什么」**。延伸:`2_wp/6_零基础_如何算出key.md`。 ### Q8:没有 `my_function` 时怎么办? 不要盲扫整段 `.rdata`。更负责的优先级: 1. **动态**:在控制台输出明文出现时断点,回溯数据源; 2. **有干净对照则 diff**(官方 `python313.dll` vs 题目 DLL); 3. 从加载链缩小到某模块后,再对**小范围**做结构分析。 本题能快,是因为有运行提示 + 可搜 `my_function`(捷径)。通用时不要把「只搜字符串」当唯一方法。 ### Q9:`29 08`、`73 10` 一定要会 marshal 吗? 本题够用两个路牌:`73`+长度 = 串;`29`+个数 = 元组。见概念 5。 --- ## 十五、复现检查清单 - [ ] 运行 `题目.exe`,看懂需要 6 字符 key - [ ] 确认 PyInstaller + Python 3.13 - [ ] 用 pyinstxtractor-ng 得到 `题目.exe_extracted/` - [ ] 确认 `main` 几乎为空 - [ ] 干净目录搜 `my_function`,**仅** `python313.dll` 命中 - [ ] (推荐)IDA 打开 DLL → `Shift+F12` → `my_function` - [ ] 分清名字地址 vs 块 2(`31 30 33 75...`) - [ ] 块 2 ⊕ `def my_function(` → **0x55**;解 8 段得整段提示 - [ ] 15 字节 ⊕ UTF-8「恭喜成功!」→ **`yb6tcr`** - [ ] 运行输入 `yb6tcr`,看到「恭喜成功!」 - [ ] (可选)理解:业务在 `.rdata` 字节码;核心区约 `0x1804DC693`~`0x1804DCBCA` --- ## 十六、知识点小结 | 知识点 | 本题中的样子 | | ------------ | ------------------------------------------------------------ | | PyInstaller | exe 内嵌 Python;用 pyinstxtractor-ng 拆包 | | 烟雾弹 | main 为空 | | 藏逻辑 | 改 `python313.dll` 内 frozen **`os`**;数据在 **`.rdata`** | | 业务代码形态 | Python 字节码 + 常量被 marshal,不是 `.text` 里显眼的 C 函数 | | marshal 路牌 | `73`+长度=串;`29`+个数=元组 | | 线 A / 线 B | 展示用 `0x55`;校验用 6 字符 key(**算 flag 只走 B**) | | 固定 XOR | 块 2 ⊕ `def my_function(` → **`0x55`** | | 循环 XOR | 15 字节 ⊕ UTF-8「恭喜成功!」→ **`yb6tcr`** | | 无明文时 | 动态断输出 + DLL diff,禁止盲扫整段 `.rdata` | | 验证 | 动态输入看到「恭喜成功!」 | --- ## 十七、相关文件索引 **本题 IDA 路标(VA,ImageBase `0x180000000`):** | 内容 | VA | | ------------------ | ---------------------------- | | 核心区 | `0x1804DC693`~`0x1804DCBCA` | | 名字 `my_function` | `0x1804DC6E9` | | 展示块 2 | `0x1804DC746` | | 用户密文 15B | `0x1804DC7FF` | | key 泄漏串 | `0x1804DC7DB`(仅回搜印证) | --- ## 一句话 这题拆开后核心只有两步: 1. **找到藏数据的地方**(`python313.dll` 的 `.rdata` 植入区); 2. **做对 XOR**(线 A:`0x55` 展示;线 B:6 字符 key)。 跟着 **运行 → 拆包 → 定位 → 算异或 → 验证**,每一步问「凭什么」,就是完整的 CTF 逆向。 **Key:`yb6tcr`**
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-8-21 14:29 被教教我吧~编辑 ,原因: 对齐题名
#Reverse
上传的附件:
题目.zip
(6.93MB,4次下载)
6_零基础_如何算出key.md
(15.31kb,4次下载)
收藏
・
1
点赞
・
8
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
git_99440qimingminimax
为你点赞!
1天前
git_51951meggadf3df
非常支持你的观点!
2026-9-12 20:47
mb_dvqqrcce
你的分享对大家帮助很大,非常感谢!
2026-9-11 17:26
晨曦。
为你点赞!
2026-9-9 07:22
mb_lthgjpwj
为你点赞!
2026-8-21 07:34
huangyalei
这个讨论对我很有帮助,谢谢!
2026-8-17 16:18
只会逆一点点
非常支持你的观点!
2026-8-13 10:43
ONewTach
感谢你的贡献,论坛因你而更加精彩!
2026-8-13 09:05
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
xingbing
雪 币:
158
活跃值:
(5711)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
1381
粉丝
2
关注
私信
xingbing
2
楼
感谢分享
2026-8-12 14:40
0
mb_rqlksylg
雪 币:
96
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
74
粉丝
0
关注
私信
mb_rqlksylg
3
楼
good
2026-8-13 08:57
0
manyuegong33
雪 币:
427
活跃值:
(340)
能力值:
( LV2,RANK:10 )
在线值:
发帖
4
回帖
59
粉丝
11
关注
私信
manyuegong33
4
楼
111
2026-8-14 11:22
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
教教我吧~
1
12
发帖
63
回帖
80
RANK
关注
私信
他的文章
[原创]第九题「丑寅同墟·星海抉择」解析
412
[原创]第八题「亥子合辰·塔影迷楼」解析
38
[原创]第七题「戌时·暗能潜流」解析
24
[原创]第五题「申时·忆海倒带」解析
1258
[原创]第二题「巳时·绿光幽语」解析
697
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
ONewTach
mb_rqlksylg
mb_lthgjpwj
谁下载
×
huangyalei
ONewTach
mb_rqlksylg
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部