【Reverse】Base64魔改逆向及例题Ezbase(编码表、索引值)(附C、python解密脚本)

目录

  • 一.思维导图
  • 二.加密原理
  • 三.算法识别与解密C代码
  • 四.魔改方式
  • 五.魔改例题Ezbase
  • 六.python解密代码
  • 一.思维导图

    先附上我自己的对于逆向题中Base64算法的思维导图

    二.加密原理

    Base64加解密原理这里就不说了
    大概就是这张图

    三.算法识别与解密C代码

    Base64的C代码目前发现两种有区别的写法,区别在8位一组和6位一组的互相转换时

    1. 一种格式是结合前后元素利用按需移位然后截断,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++

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Reverse】Base64魔改逆向及例题Ezbase(编码表、索引值)(附C、python解密脚本)

    发表回复