特殊的Base64 - Writeup

学习 · 8 天前

题目信息

  • 题目名称: 特殊的Base64
  • 类型: Reverse Engineering
  • 考点: Base64变种算法识别、静态分析

特殊的Base64 - Bugku CTF平台

分析流程

步骤1:程序基本信息识别

使用IDA Pro加载程序后,首先确认程序基本信息:

| 项 | 值 |

| 文件名 | 特殊的Base64.exe |

| 架构 | x86 (32位) |

| 编译器 | MinGW GCC |

| 基地址 | 0x400000 |


步骤2:定位主函数和关键逻辑

在函数列表中识别到两个关键函数:

  1. main - 主函数 (0x401530)
  2. _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:编写解密脚本

解密思路:

  1. 将特殊Base64字符映射回标准Base64字符
  2. 使用标准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}

解题总结

难点分析

  1. 识别变种: 不要被"特殊Base64"的名称迷惑,核心算法其实和标准Base64完全一致
  2. 编码表定位: 编码表不在编码函数内,而是在静态初始化函数中,需要通过交叉引用找到
  3. 细微差异: 数字倒序、字母交替、最后两位交换这些细节容易忽略

解题技巧

  1. Base64变种题优先找编码表,不要浪费时间分析移位算法
  2. 全局变量的初始化往往在静态构造函数中
  3. 字符串交叉引用是快速定位关键代码的好方法
  4. 编码表的差异可以通过字符映射轻松转换,不需要重新实现Base64算法

经验总结

  • 90%的Base64变种题都是编码表替换,算法不变
  • 静态分析优先,能静态解就不要动态调试
  • 养成写Writeup的习惯,有助于整理思路和知识积累