题目信息
- 题目名称: 特殊的Base64
- 类型: Reverse Engineering
- 考点: Base64变种算法识别、静态分析
分析流程
步骤1:程序基本信息识别
使用IDA Pro加载程序后,首先确认程序基本信息:
| 项 | 值 |
| 文件名 | 特殊的Base64.exe |
| 架构 | x86 (32位) |
| 编译器 | MinGW GCC |
| 基地址 | 0x400000 |
步骤2:定位主函数和关键逻辑
在函数列表中识别到两个关键函数:
main- 主函数 (0x401530)_Z12base64EncodeSs- Base64编码函数 (0x4016c9)
main函数反编译代码分析:
int __fastcall main(int argc, const char **argv, const char **envp)
{
// 目标密文 - 正确flag编码后的结果
std::string::string(&rightFlag, "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==", &v9);
// 提示用户输入
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "Please input your flag!!!!");
// 读取用户输入
std::operator>><char>(refptr__ZSt3cin, &str);
// 对用户输入进行特殊Base64编码
base64Encode(&result);
// 比较编码结果与目标密文
if ( std::operator==<char>(&result, &rightFlag) )
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "The flag is right!!!!!!!!!");
else
std::operator<<<std::char_traits<char>>(refptr__ZSt4cout, "This is a wrong flag!!!!!!!!");
}
关键点: 程序要求我们输入的flag经过特殊Base64编码后等于 mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==
步骤3:分析特殊Base64编码算法
反编译 base64Encode 函数 (0x4016c9):
std::string __cdecl base64Encode(std::string *p_decode)
{
// 关键:编码表初始化在静态构造函数中完成
// unk_489084 就是 baseKey
len = std::string::length(p_decodea);
// 标准Base64分组处理:3字节 -> 4字符
for ( i = 0; len / 3 > i; ++i )
{
// 第1个字符: 取第1字节高6位
v2 = (char *)std::string::operator[](p_decodea, 3 * i);
std::string::operator[](&baseKey, *v2 >> 2);
std::string::operator+=(p_decode);
// 第2个字符: 第1字节低2位 + 第2字节高4位
v3 = 16 * (*(_BYTE *)std::string::operator[](p_decodea, 3 * i) & 3);
v4 = (char *)std::string::operator[](p_decodea, 3 * i + 1);
std::string::operator[](&baseKey, v3 | (*v4 >> 4));
std::string::operator+=(p_decode);
// 第3个字符: 第2字节低4位 + 第3字节高2位
v5 = 4 * (*(_BYTE *)std::string::operator[](p_decodea, 3 * i + 1) & 0xF);
v6 = (char *)std::string::operator[](p_decodea, 3 * i + 2);
std::string::operator[](&baseKey, v5 | (*v6 >> 6));
std::string::operator+=(p_decode);
// 第4个字符: 第3字节低6位
v7 = (_BYTE *)std::string::operator[](p_decodea, 3 * i + 2);
std::string::operator[](&baseKey, *v7 & 0x3F);
std::string::operator+=(p_decode);
}
// 填充处理逻辑与标准Base64完全一致
// ...
}
重要发现: 移位、分组算法完全等同于标准Base64!唯一的区别就是编码表不同。
步骤4:查找自定义编码表
通过交叉引用找到 baseKey 的初始化位置在静态构造函数 __static_initialization_and_destruction_0 (0x401b2b):
void __cdecl __static_initialization_and_destruction_0(int __initialize_p, int __priority)
{
if ( __initialize_p == 1 && __priority == 0xFFFF )
{
std::ios_base::Init::Init(&std::__ioinit);
atexit(_tcf_0);
// 关键:特殊Base64编码表!!!
std::string::string(&baseKey,
"AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+",
v2);
atexit(_tcf_1);
}
}
步骤5:编码表对比分析
标准Base64编码表:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
|--------26大写--------||--------26小写--------||----10数字----|
特殊Base64编码表:
AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+
|-----------------52字母大小写交替------------------||--倒序数字--|
具体差异对比:
| 索引 | 标准Base64 | 特殊Base64 | 说明 |
| 0 | A | A | 相同 |
| 1 | B | a | 不同! |
| 2 | C | B | 不同! |
| 3 | D | b | 不同! |
| ... | ... | ... | ... |
| 51 | z | Zz末尾 | 字母顺序完全重排 |
| 52 | 0 | 0 | 数字起始相同 |
| 53 | 1 | 9 | 数字倒序! |
| 54 | 2 | 8 | 数字倒序! |
| ... | ... | ... | ... |
| 62 | + | / | 交换位置 |
| 63 | / | + | 交换位置 |
步骤6:编写解密脚本
解密思路:
- 将特殊Base64字符映射回标准Base64字符
- 使用标准Base64算法解密
import base64
# 特殊Base64编码表
custom_table = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+'
# 标准Base64编码表
std_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
# 目标密文
ciphertext = 'mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI=='
# 1. 建立字符映射表:特殊表 -> 标准表
translation = str.maketrans(custom_table, std_table)
# 2. 将密文转换为标准Base64格式
standard_b64 = ciphertext.translate(translation)
# 3. 标准Base64解密
flag = base64.b64decode(standard_b64).decode()
print(f"FLAG: {flag}")
步骤7:运行解密脚本
FLAG: flag{Special_Base64_By_Lich}
最终Flag
flag{Special_Base64_By_Lich}
解题总结
难点分析
- 识别变种: 不要被"特殊Base64"的名称迷惑,核心算法其实和标准Base64完全一致
- 编码表定位: 编码表不在编码函数内,而是在静态初始化函数中,需要通过交叉引用找到
- 细微差异: 数字倒序、字母交替、最后两位交换这些细节容易忽略
解题技巧
- Base64变种题优先找编码表,不要浪费时间分析移位算法
- 全局变量的初始化往往在静态构造函数中
- 字符串交叉引用是快速定位关键代码的好方法
- 编码表的差异可以通过字符映射轻松转换,不需要重新实现Base64算法
经验总结
- 90%的Base64变种题都是编码表替换,算法不变
- 静态分析优先,能静态解就不要动态调试
- 养成写Writeup的习惯,有助于整理思路和知识积累