[MoeCTF 2021] PEPEPE —— 利用 PE 文件头恢复 XOR 密钥
题目信息
| 项目 | 内容 |
|---|---|
| 赛事 | MoeCTF 2021 |
| 题目 | PEPEPE |
| 分值 | 443 分 |
| 类型 | Reverse / PE 文件还原 |
| 考点 | 已知明文攻击、XOR 流密码、PE 文件结构 |
| 平台 | NSSCTF (https://www.nssctf.cn/problem/3391) |
题目描述
题目提供了一个经过加密的 PE 文件和加密程序源码,需要分析加密算法,并恢复原始可执行文件。
加密后的文件头部已无 MZ 标志,用 file 命令识别为纯 data。
加密原理分析
查看加密器 main.c 的核心逻辑:
1 | scanf("%s", input); // 输入 key |
加密公式:
1 | enc[i] = ~(org[i] ^ key[i % key_len]) |
其中 key 是加密时输入的字符串,按 i % key_len 循环使用。这是一个循环 XOR 流密码(多了一步取反),强度完全取决于 key 的保密性。
解题思路:已知明文攻击
为什么能攻击?
PE 文件有固定不变的结构——文件头。任何 PE 文件都以 MZ(0x4D 0x5A)开头,后面跟着结构固定的 DOS Header(大量字节是固定的 0x00)。这就是”已知明文”。

前 64 字节的 DOS Header 中,大量字节是固定值或零值,非常适合作为已知明文。
Key 恢复
把加密公式变形:
1 | key[i] = ~enc[i] ^ org[i] |
用加密文件的前若干字节 + 已知的 PE 头部明文,恢复 key 的循环片段,检测周期得完整 key(其中 plain_head 取自原始文件 file_org 的前 64 字节,即标准 DOS Header):
1 | def recover_key(data: bytes, plain_head: bytes) -> bytes: |
运行结果:
1 | [+] recovered key = b'reverierwilllikeyou!' (len=20) |
key = reverierwilllikeyou!(20 字节),呼应了 file_org.c 中的 “I think ReverierXu will like you!”。
完整还原
1 | def decrypt(data: bytes, key: bytes) -> bytes: |
验证结果:
file_restored被识别为PE32 executable- 与原始文件对比,前 45559/45560 字节完全一致
最后一个字节差异:加密循环只处理
file_len - 1个字节,末字节为未初始化内存(垃圾值),不影响 PE 完整性。
运行验证
1 | $ cp file_restored file_restored.exe && echo "" | ./file_restored.exe |
Flag
注意:还原后的程序输出的是
moectf{}格式,而 NSSCTF 平台要求使用NSSCTF{}格式提交(题目描述中已注明)。
1 | NSSCTF{P3_Structur3_1s_r3ally_fUnnY!} |
总结
- 识别加密算法为循环 XOR(+取反)流密码
- 利用 PE 文件头固定结构 进行已知明文攻击恢复 key
- 检测 key 循环周期得到完整 key
- 解密整个文件,还原出可运行的 PE,运行得到 flag
- 固定格式的数据结构会降低简单流密码的安全性,因此在设计加密方案时不能仅依赖 XOR 与固定密钥。
参考
相关项目与联系
项目SnowSword —— Windows 10/11 x64 内核分析与 Anti-Rootkit 工具,源码与发布包在 Gitee。
技术合作Windows 内核 / 驱动开发 / 逆向分析 / 兼容性开发,范围与边界见 技术合作。
联系2160752730@qq.com · QQ 2160752730 · 微信 lst_3258110
