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

news/2025/2/22 19:47:05/

目录

  • 一.思维导图
  • 二.加密原理
  • 三.算法识别与解密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的倍数elsestr_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就是6encry[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组成8encry[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;elsestr_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向上取整后乘4encry = (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/2if (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) {// 初始化解密映射表,将所有元素置为 0memset(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_295">六.python解密代码

1.base64通用(处理变表和变索引)解密脚本

python">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]  # 减去加密时加上的keyif 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  # 添加范围检查,为未处理部分设为0d = _index[i + 3] if i + 3 < len(_index) else 0sum = a << 18 | b << 12 | c << 6 | dfor j in range(3):if i * 6 + j * 8 < len(cipher) * 8:  # 检查是否超出原始编码长度print(chr((sum >> ((2 - j) * 8)) & 0xff), end="")

2.再附上一个利用map映射解决变表问题的解密脚本

python">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解密函数解密

http://www.ppmy.cn/news/1572480.html

相关文章

【3.Git与Github的历史和区别】

目录 Git的历史和Github的区别本质和功能 Git的历史和Github的区别 Git是由Linux内核的创造者Linus Torvalds于2005年创建的。当时&#xff0c;Linux内核开源项目使用BitKeeper作为版本控制系统&#xff0c;但2005年BitKeeper的商业公司终止了与Linux社区的合作&#xff0c;收…

kafka介绍,kafka集群环境搭建,kafka命令测试,C++实现kafka客户端

目录 kafka介绍kafka集群环境搭建zookeeper安装与配置kafka安装与配置 kafka命令测试C实现kafka客户端librdkafka库编译新版本cmake编译cppkafka库编译C实现kafka生产者和消费者客户端 kafka介绍 定义与概述 Apache Kafka 是一个开源的分布式流处理平台&#xff0c;最初由 Lin…

Navicat导入海量Excel数据到数据库(简易介绍)

目录 前言正文 前言 此处主要作为科普帖进行记录 原先Java处理海量数据的导入时&#xff0c;由于接口超时&#xff0c;数据处理不过来&#xff0c;后续转为Navicat Navicat 是一款功能强大的数据库管理工具&#xff0c;支持多种数据库系统&#xff08;如 MySQL、PostgreSQL、…

嵌入式AI革命:DeepSeek开源如何终结GPU霸权,开启单片机智能新时代?

2025年&#xff0c;全球AI领域最震撼的突破并非来自算力堆叠的超级模型&#xff0c;而是中国团队DeepSeek通过开源策略&#xff0c;推动大模型向微型化、低功耗场景的跨越。相对于当人们还在讨论千亿参数模型的训练成本被压缩到600万美金而言&#xff0c;被称作“核弹级别”的操…

嵌入式软件 —— 单片机上电后地址如何跳转

目 录 地址跳转一、程序存储起始地址二、main函数地址 地址跳转 单片机上电或复位后&#xff0c;地址跳转流程&#xff1a; 从复位向量获取启动代码的地址执行启动代码&#xff0c;来初始化硬件启动代码执行完成后才跳转到main函数入口地址执行用户程序 假设复位向量位于0x000…

消息队列之-RabbitMq 学习

生产者服务A /消费者服务B 服务A和服务B配置: 依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency> yaml spring: rabbitmq:host: ${RABBITMQ_HOST:local…

Redis——优惠券秒杀问题(分布式id、一人多单超卖、乐悲锁、CAS、分布式锁、Redisson)

#想cry 好想cry 目录 1 全局唯一id 1.1 自增ID存在的问题 1.2 分布式ID的需求 1.3 分布式ID的实现方式 1.4 自定义分布式ID生成器&#xff08;示例&#xff09; 1.5 总结 2 优惠券秒杀接口实现 3 单体系统下一人多单超卖问题及解决方案 3.1 问题背景 3.2 超卖问题的…

Netease Youdao BCE-Reranker-Base_v1:重新定义中文语义理解新高度

在人工智能飞速发展的今天&#xff0c;自然语言处理&#xff08;NLP&#xff09;作为人机交互的核心技术&#xff0c;正经历着前所未有的变革。而语义理解作为 NLP 的基石&#xff0c;其重要性不言而喻。近日&#xff0c;网易有道推出的 BCE-Reranker-Base_v1 模型&#xff0c;…