📌 题目信息
| 项目 | 内容 |
|---|---|
| 题目名称 | [GWCTF 2019]re3 |
| 题目来源 | BUUCTF Reverse |
| 题目类型 | Reverse(逆向工程) |
| 子分类 | SMC自修改代码 / 密码学逆向 |
| 考点 | SMC自修改代码、AES-ECB加密、MD5密钥派生、动态调试 |
| 难度 | ⭐⭐⭐ |
| 最终 Flag | flag{924a9ab2163d390410d0a1f670} |
这道题虽然是逆向题,但是有用到AES,还是放到密码学里面。
🛠️ 使用工具
- IDA Pro(静态分析 + 远程动态调试)
- FindCrypt插件(识别加密算法特征)
- Python 3 + PyCryptodome(AES解密脚本)
📝 解题思路
这是一道结合了 SMC(Self-Modifying Code,自修改代码) 和 AES加密 的逆向题。程序运行时会先对自身代码进行异或解密,然后使用AES-ECB模式加密输入并与密文比对。
第 1 步:初步分析——发现SMC自修改代码
将程序拖入IDA 64位分析,查看 main 函数伪代码,步骤如下:
- 在函数列表里,找到
main这个函数,双击它。 - 这时主视图会跳转到
main函数的汇编代码处。 - 按下键盘上的
F5键(这是接下来要用的最核心快捷键)。如果弹出一个提示框说“Pseudo-code viewer”,点确定就行。
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-48h]
char s[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+48h] [rbp-8h]
v5 = __readfsqword(0x28u);
__isoc99_scanf("%39s", s);
if ( (unsigned int)strlen(s) != 32 )
{
puts("Wrong!");
exit(0);
}
// 修改内存权限:将 0x400000 开始的 0xF000 字节设为可读可写可执行
mprotect(&dword_400000, 0xF000uLL, 7);
// SMC自修改:将 sub_402219 处的 224 个字节逐字节与 0x99 异或
for ( i = 0; i <= 223; ++i )
*((_BYTE *)sub_402219 + i) ^= 0x99u; // SMC自修改:逐字节异或0x99
sub_40207B(&unk_603170); // 生成AES密钥
sub_402219(s); // 加密输入并校验
}
这里有两个关键点:
- mprotect(&dword_400000, 0xF000uLL, 7):将
0x400000开始的0xF000(60KB)内存区域设置为**可读、可写、可执行。这是SMC自修改代码能够运行的前提。 - for循环异或0x99:将
sub_402219函数地址处的224个字节逐字节与0x99异或。程序运行时会先执行这个异或操作,把加密的代码还原成真正的函数代码,然后再调用它。
💡 SMC(Self-Modifying Code):程序在运行时动态修改自身代码的一种技术,常用于反调试和增加逆向难度。本题中,
sub_402219在运行前是一堆"乱码",异或0x99后才变成真正的加密函数。
第 2 步:编写IDC脚本解密SMC代码
在IDA中按 Shift + F2 打开脚本编辑器,选择 IDC 语言,运行以下脚本对 sub_402219 进行解密:
#include <idc.idc>
static main()
{
auto addr = 0x402219;
auto i = 0;
for ( i = 0; i <= 223; ++i ) {
PatchByte(addr + i, Byte(addr + i) ^ 0x99);
}
}

⚠️ 注意:运行脚本后操作顺序:
- 运行脚本后,回到 IDA 的主反汇编窗口,将光标定位到
0x402219(按下G键,在弹出的输入框中输入0x402219,回车跳转到该地址)。- 先按
U键(Undefine,取消当前地址的所有定义)。- 再按
C键(Code,强制将该区域识别为代码)。- 然后按
P键(Procedure,将其声明为函数)。- 最后按
F5即可生成伪代码。
如果跳过 U 直接按 C,IDA 可能会因为残留的数据定义而灰显 C 选项,导致无法转换。
第 3 步:分析核心加密逻辑
解密后,sub_402219 的伪代码如下:
// 验证函数:接收用户输入的32字节flag,返回1表示正确,0表示错误
__int64 __fastcall sub_402219(__int64 a1) // a1 指向用户输入的字符串
{
unsigned int v2; // 存放校验结果(默认为1正确)
int i; // 循环变量
_BYTE v4[200]; // 缓冲区,用于存放生成的AES密钥
unsigned __int64 v5; // 栈保护cookie(canary),防溢出
v5 = __readfsqword(0x28u); // 获取canary存入v5
// 【第1步】生成AES密钥
// sub_400A71内部对全局数据unk_603170(即Base64编码表)进行两次MD5运算,
// 计算出的16字节哈希值作为AES-128的密钥,保存到v4中
sub_400A71(v4, &unk_603170);
// 【第2步】AES-ECB加密
// sub_40196E是AES加密函数,采用ECB模式(无IV),每次加密16字节。
// 因为输入共32字节,所以分前后两段分别加密,加密结果直接覆盖原输入
sub_40196E(v4, a1); // 加密前16字节(a1[0] ~ a1[15])
sub_40196E(v4, a1 + 16); // 加密后16字节(a1[16] ~ a1[31])
// 【第3步】与硬编码密文对比
v2 = 1; // 先假设校验通过
for ( i = 0; i <= 31; ++i )
{
// 加密后的数据与全局数组byte_6030A0逐字节比较
// byte_6030A0中存储的是程序预设的正确flag加密后的32字节密文
if ( *(_BYTE *)(i + a1) != byte_6030A0[i] )
v2 = 0; // 有任何一字节不同,校验失败
}
return v2; // 返回1代表输入正确,0代表错误
}
逻辑分析:
sub_400A71使用&unk_603170作为密钥初始化AES上下文sub_40196E是AES加密函数,将输入的32位分成前后各16字节分别加密- 加密结果与
byte_6030A0处的硬编码密文逐字节比较
上面的如果不知道怎么分析可以使用FindCrypt插件。
识别加密算法:使用 FindCrypt插件 扫描(需要IDA pro版本),发现程序包含 MD5 和 AES 的特征。进一步分析 sub_40196E 中的 byte_4023A0 数组,这是AES的标准S盒,因此可以确定是 AES-128-ECB模式。
第 4 步:动态调试提取AES密钥
sub_40207B(&unk_603170) 函数负责生成AES密钥。分析发现该函数对 Base64密码表 进行了两次MD5加密,密钥与用户输入无关,是固定的。
因此通过 IDA远程动态调试 提取密钥:
- 在IDA中配置 Linux远程调试(需将程序拷贝到Linux虚拟机或WSL中运行)


- 在
sub_40207B函数执行完毕后下断点
- 运行程序,断下后查看
unk_603170地址处的内存(注意程序需要输入一个 32 字符的字符串,内容随意)


提取到的密钥为:
CB8D493521B47A4CC1AE7E62229266CE
第 5 步:提取密文
根据上面的分析可知,加密结果需要与 byte_6030A0 处的硬编码密文逐字节比较,因此 byte_6030A0 处其实就是密文,从 byte_6030A0 处提取硬编码的32字节密文:
BC0AADC0147C5ECCE0B140BC9C51D52B46B2B9434DE5324BAD7FB4B39CDB4B5B
第 6 步:编写Python解密脚本
有了密钥和密文,使用AES-ECB解密:
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
# 密钥(从动态调试中提取)
key = long_to_bytes(0xcb8d493521b47a4cc1ae7e62229266ce)
# 密文(从 byte_6030A0 提取)
ciphertext = long_to_bytes(0xbc0aadc0147c5ecce0b140bc9c51d52b46b2b9434de5324bad7fb4b39cdb4b5b)
# AES-ECB解密
aes = AES.new(key, AES.MODE_ECB)
flag = aes.decrypt(ciphertext)
print(flag.decode('utf-8'))
运行结果:
⚠️ 注意:密钥字符串长度为32位(16字节),对应 AES-128。
long_to_bytes()会自动将十六进制数转为字节串。
🏁 最终 Flag
flag{924a9ab2163d390410d0a1f670}
【AES逆向】GWCTF 2019 re3&spm=1001.2101.3001.5002&articleId=163878214&d=1&t=3&u=6723e25c290e4dcaa636b94cdfedfc50)
1805

被折叠的 条评论
为什么被折叠?



