【Reverse】Base64魔改逆向及例题Ezbase(编码表、索引值)(附C、python解密脚本)
目录
一.思维导图
先附上我自己的对于逆向题中Base64算法的思维导图
二.加密原理
Base64加解密原理这里就不说了
大概就是这张图
三.算法识别与解密C代码
Base64的C代码目前发现两种有区别的写法,区别在8位一组和6位一组的互相转换时
- 一种格式是结合前后元素利用按需移位然后截断,C语言代码如下
#define _CRT_SECURE_NO_WARNINGS 1
// base64可以自定义表加密解密
#include<stdio.h>
#include<stdint.h>
#include<string.h>
#include<stdlib.h>
char* base64encry(char* input) //base64 加密函数
{
int len = 0, str_len = 0;
char* encry;
char table64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//获取加密表单
len = strlen(input); //获取输入长度
if (len % 3 == 0)
str_len = (len / 3) * 4; //长度是否是3的倍数
else
str_len = ((len / 3) + 1) * 4;
encry = (char*)malloc(sizeof(char) * str_len + 1); //控制申请的密文空间长度
for (int i = 0, j = 0; i < len; i += 3, j += 4) //明文3个一组 3*8 = 24位
{ //密文4个一组 4*6 = 24位
encry[j] = table64[input[i] >> 2]; //8位改6位 第1个字符8位右移位2就是6
encry[j + 1] = table64[((input[i] & 0x3) << 4) | ((input[i + 1]) >> 4)]; //第2个字符的前4与第一个的后2组成8
//&截位符 input[i] & 0x3 这个操作会把 input[i] 的二进制表示中除了最右边 2 位以外的其他位都置为 0,也就是只保留最右边的 2 位。
encry[j + 2] = table64[((input[i + 1] & 0xf) << 2) | (input[i + 2] >> 6)]; //第3个字符的前2与第2个字符的后4组成8
encry[j + 3] = table64[input[i + 2] & 0x3f]; //第3个字符的后6组成8
}
switch (len % 3) //根据3倍数余数确定添加'='个数
{
case 1: encry[str_len - 1] = '=';
encry[str_len - 2] = '=';
break;
case 2: encry[str_len - 1] = '=';
break;
}
encry[str_len] = '\0';
return encry;
}
char* base64decry(char* input)
{
//映射表 换表就自己生成,8位只有后6位有效
//根据base64表,以字符找到对应的十进制数据 ,这里是int类型,移位的时候要转换成char地址。
//主要是没用下标索引类的函数,这里的ascii表示从0开始的
int table[] =
{
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,63,
52,53,54,55,56,57,58,59,60,61,0,0,
0,0,0,0,0,0,1,2,3,4,5,6,
7,8,9,10,11,12,13,14,15,16,17,18,
19,20,21,22,23,24,25,0,0,0,0,0,
0,26,27,28,29,30,31,32,33,34,35,36,
37,38,39,40,41,42,43,44,45,46,47,48,
49,50,51,
};
int len = 0, str_len = 0;
char* decry;
len = strlen(input);
if (strstr(input, "==")) //由==个数确定字符个数与3倍数关系
str_len = (len / 4) * 3 - 2;
else if (strchr(input, '='))
str_len = (len / 4) * 3 - 1;
else
str_len = (len / 4) * 3;
decry = (char*)malloc(sizeof(char) * str_len + 1);
for (int i = 0, j = 0; i < len; i += 4, j += 3)
{
decry[j] = (table[input[i]] << 2) | (table[input[i + 1]] >> 4); //剔除前2位还剩6位加上后面8位的前2位,虽然8-4 = 4,但是前2位无效
decry[j + 1] = (table[input[i + 1]] << 4) | (table[input[i + 2]] >> 2); //第2的4加第3的前4组成8位
decry[j + 2] = (table[input[i + 2]] << 6) | (table[input[i + 3]]); //第3的前2位于第4的6位组成8位
}
decry[str_len] = '\0';
return decry;
}
int main()
{
char buff[100], * encry, * decry;
printf("请输入字符:");
scanf("%s", buff); //获取用户输入
encry = base64encry(buff);
printf("\n加密后的字符:%s", encry);
//decry = base64decry(buff);
//printf("\n解密后的字符:%s", decry);
return 0;
}
2.另一种非是先将每一大组(24位)生成,然后等差移位,0、6、12、18这样就6位一小组,C代码如下(如果不偏移索引值将key改为{0,0,0,0})
#define _CRT_SECURE_NO_WARNINGS 1
//修改索引模式
//关键区别26行理解
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
char* base64encry_alt(char* input) //Base64加密函数(组合24位方式)
{
char* encry;
char table64[] = "CDABGHEFKLIJOPMNSTQRWXUVabYZefcdijghmnklqropuvstyzwx23016745+/89"; //Base64编码表
int len = strlen(input);
int encry_len = 4 * ((len + 2) / 3); //计算加密后长度,(len+2)/3向上取整后乘4
encry = (char*)malloc(encry_len + 1); //分配空间,+1存放终止符
uint32_t a, b, c, triplet; //存储三个字节和组合后的24位整数
int i, j;
int key[4] = { 1,2,3,4 };
//每次处理3个输入字节,生成4个Base64字符
for (i = 0, j = 0; i < len; i += 3, j += 4)
{
//获取三个字节,超出部分补零
a = (uint8_t)input[i];
b = (i + 1 < len) ? (uint8_t)input[i + 1] : 0;
c = (i + 2 < len) ? (uint8_t)input[i + 2] : 0;
triplet = (a << 16) | (b << 8) | c; //合并为24位整数 关键区别
//拆分24位为四个6位索引并查表
encry[j] = table64[((triplet >> 18) + key[0]) & 0x3F]; //前6位 3f是 00111111刚好截6位
encry[j + 1] = table64[((triplet >> 12) + key[1]) & 0x3F]; //中前6位
encry[j + 2] = table64[((triplet >> 6) + key[2]) & 0x3F]; //中后6位
encry[j + 3] = table64[(triplet + key[3]) & 0x3F]; //后6位
}
//处理末尾填充等号
int pad = (3 - (len % 3)) % 3; //计算填充数:0/2/1对应余数0/1/2
if (pad > 0)
{
//从末尾向前填充等号(pad=2时补两个,pad=1补一个)
for (int k = 0; k < pad; k++)
{
encry[encry_len - 1 - k] = '=';
}
}
encry[encry_len] = '\0'; //字符串终止符
return encry;
}
// Base64 解密函数
char* base64decry_alt(char* input) {
int table[] = {
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,60,0,0,0,61,
54,55,52,53,58,59,56,57,62,63,0,0,
0,0,0,0,0,2,3,0,1,6,7,4,
5,10,11,8,9,14,15,12,13,18,19,16,
17,22,23,20,21,26,27,0,0,0,0,0,
0,24,25,30,31,28,29,34,35,32,33,38,
39,36,37,42,43,40,41,46,47,44,45,50,
51,48,49,
};
int key[4] = { 1,2,3,4 };
int len = strlen(input);
int pad_count = 0;
// 安全计算填充数量
if (len > 0 && input[len - 1] == '=') pad_count++;
if (len > 1 && input[len - 2] == '=') pad_count++;
int decry_len = (len * 3) / 4 - pad_count;
char* decry = (char*)malloc(decry_len + 1);
memset(decry, 0, decry_len + 1);
for (int i = 0, j = 0; i < len; i += 4, j += 3) {
// 处理等号情况
uint32_t sextet_a = (i < len) ? (table[(uint8_t)input[i]] - key[0] + 64) % 64 : 0;
uint32_t sextet_b = (i + 1 < len) ? (table[(uint8_t)input[i + 1]] - key[1] + 64) % 64 : 0;
uint32_t sextet_c = (i + 2 < len && input[i + 2] != '=') ? (table[(uint8_t)input[i + 2]] - key[2] + 64) % 64 : 0;
uint32_t sextet_d = (i + 3 < len && input[i + 3] != '=') ? (table[(uint8_t)input[i + 3]] - key[3] + 64) % 64 : 0;
uint32_t triplet = (sextet_a << 18) | (sextet_b << 12) | (sextet_c << 6) | sextet_d;
// 严格长度控制
if (j < decry_len) decry[j] = (triplet >> 16) & 0xFF;
if (j + 1 < decry_len) decry[j + 1] = (triplet >> 8) & 0xFF;
if (j + 2 < decry_len) decry[j + 2] = triplet & 0xFF;
}
return decry;
}
int main()
{
char buff[100], * encry, * decry;
printf("请输入字符:");
scanf("%s", buff); // 获取用户输入
//encry = base64encry_alt(buff);
//printf("\n加密后的字符:%s", encry);
decry = base64decry_alt(buff);
printf("\n解密后的字符:%s", decry);
}
加密函数可以用来对找到的Base64加密对照
解密函数按照识别的加密函数反向用来解密
用加密密码表生成解密映射表函数C代码:
#define _CRT_SECURE_NO_WARNINGS 1
//生成映射表
#include <stdio.h>
#include <string.h>
#define TABLE_SIZE 128
// 生成解密映射表的函数
void generate_decoding_table(const char* encoding_table, int* decoding_table) {
// 初始化解密映射表,将所有元素置为 0
memset(decoding_table, 0, TABLE_SIZE * sizeof(int));
// 遍历编码表中的每个字符
for (int i = 0; i < 64; i++) {
// 获取当前字符的 ASCII 码值
int ascii_value = (int)encoding_table[i];
// 将该字符在编码表中的索引值存入解密映射表对应位置
decoding_table[ascii_value] = i;
}
}
// 打印解密映射表的函数
void print_decoding_table(int* decoding_table) {
printf("{\n");
for (int i = 0; i < TABLE_SIZE - 5; i++) {
if (i % 12 == 0 && i != 0) {
printf("\n");
}
printf("%d,", decoding_table[i]);
}
printf("\n};\n");
}
int main() {
// 标准的 Base64 编码字符表
const char standard_encoding_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
int decoding_table[TABLE_SIZE];
// 生成解密映射表
generate_decoding_table(standard_encoding_table, decoding_table);
// 打印解密映射表
print_decoding_table(decoding_table);
return 0;
}
四.魔改方式
魔改主要有两个方面:
1.修改编码表
这个一般在可见主函数中穿插修改函数。
逆向方式就是先找到新的编码表,可以通过函数逻辑识别写脚本生成新的编码表也可以通过调试。然后解密可以通过随波逐流将新的编码表作为key进行Base64解密;也可以通过上述C代码,只需要修改对应编码表和映射表就可以
2.修改索引值
这种魔改一般发生在第二种C代码的样式中,对下标进行额外偏移。
逆向方式就是通过已知函数魔改逻辑逆向。
五.魔改例题Ezbase
程序Ezbase.exex用IDA打开,没找到main函数,查找字符串,发现Base64编码表和出口信息,追踪查看
阅读程序可以发现经过两次处理函数(堆栈复检不解释了)
这里v9[]在第二次处理时才用到
进入第一个处理函数:修改编码表
(只传表,与输入无关)可以发现是修改编码表,这里解密当然需要找到修改后的编码表,1.可以写脚本生成,2.因为这里的修改与可变输入flag无关,所以直接调试到下一条语句就可以知道新的编码表
补充:可调式的条件就是与程序外的因素无关,所以无论我们输入的什么,走到这一步就会执行,进而查看
注意:调试时flag的长度必须是45不然走不到这一步
进入第二个函数:修改索引值
这个函数传了很多参数所以不可以调试获得信息
这里对比发现就是多了a[]的偏移也就是v9[]数组
然后我们根据C代码的第二种来修改-v9[]的值就可以了
然后就可以求得flag
六.python解密代码
1.base64通用(处理变表和变索引)解密脚本
table = "CDABGHEFKLIJOPMNSTQRWXUVabYZefcdijghmnklqropuvstyzwx23016745+/89" # base64当前表
cipher = "TqK1YUSaQryEMHaLMnWhYU+Fe0WPenqhRXahfkV6WE2fa3iRW197Za62eEaD" # 密文
cipher = cipher.rstrip('=') #去除密文多余的'='
_index = []
key = [1, 2, 3, 4] #正常设置为[0,0,0,0]就可以
for i in range(len(cipher)):
tmp = table.index(cipher[i]) - key[i % 4] # 减去加密时加上的key
if tmp >= 0:
_index.append(tmp)
else: # 因为减去key会导致索引变成负数,+64保证在正常索引范围
_index.append(tmp + 64)
#print(_index)
for i in range(0, len(_index), 4):
a = _index[i]
b = _index[i + 1]
c = _index[i + 2] if i + 2 < len(_index) else 0 # 添加范围检查,为未处理部分设为0
d = _index[i + 3] if i + 3 < len(_index) else 0
sum = a << 18 | b << 12 | c << 6 | d
for j in range(3):
if i * 6 + j * 8 < len(cipher) * 8: # 检查是否超出原始编码长度
print(chr((sum >> ((2 - j) * 8)) & 0xff), end="")
2.再附上一个利用map映射解决变表问题的解密脚本
import base64 # 导入base64模块用于解密
#这里是先把密文映射替换相当于原表还是标准表
s1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' # 标准表
s2 = 'qvEJAfHmUYjBac+u8Ph5n9Od17FrICL/X0gVtM4Qk6T2z3wNSsyoebilxWKGZpRD' # base64换表
en_text = '5Mc58bPHLiAx7J8ocJIlaVUxaJvMcoYMaoPMaOfg15c475tscHfM/8==' # 密文
map = str.maketrans(s2, s1) # 用str类中的maketrans建立映射,注意第一个参数是需要映射的字符串,第二个参数是映射的目标
map_text = en_text.translate(map) # 映射实现替换密文,替换前是base64换表加密,替换后则是base64标准表加密
print(map_text) # 可以先看看标准表加密的原base64密文
print(base64.b64decode(map_text)) # 直接使用提供的base64解密函数解密
作者:Power++