简介
base64编码根据编码表将一段二进制数据映射成64个可显示字母和数字组成的字符集合,主要用于传送图形、声音等非文本数据。
标准 base64 编码表
编码原理
原理
下面我们通过将明文 “abc”
进行 base64 编码来讲解 base64 编码原理。
1.首先将明文每三个字节分为一组,每个字节8bit,共24bit。
2.将24bit划分为四组,每组6bit,4组共24bit
3.将每组用0补齐为8bit,4组共32bit。
黄色部分就是补齐的0。
将补齐后的二进制数的十进制值作为编码表的下标获取编码表中的对应值。
编码结果就是 “YWJj”
如果编码后的字符不是4的倍数,后面用 “=” 填充补齐。
代码实现
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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| const char * const table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
char * encode( const unsigned char * data, char * base64, int length ) { int i, j; unsigned char current; for ( i = 0, j = 0 ; i < length ; i += 3 ){ current = (data[i] >> 2) ; current &= (unsigned char)0x3F; base64[j++] = table[(int)current]; current = ( (unsigned char)(data[i] << 4 ) ) & ( (unsigned char)0x30 ) ;
if ( i + 1 >= length ) { base64[j++] = table[(int)current]; base64[j++] = '='; base64[j++] = '='; break; } current |= ( (unsigned char)(data[i + 1] >> 4) ) & ( (unsigned char) 0x0F ); base64[j++] = table[(int)current]; current = ( (unsigned char)(data[i + 1] << 2) ) & ( (unsigned char)0x3C ) ;
if ( i + 2 >= length ) { base64[j++] = table[(int)current]; base64[j++] = '='; break; }
current |= ( (unsigned char)(data[i + 2] >> 6) ) & ( (unsigned char) 0x03 ); base64[j++] = table[(int)current];
current = ( (unsigned char)data[i + 2] ) & ( (unsigned char)0x3F ) ; base64[j++] = table[(int)current]; }
base64[j] = '\0'; return base64; }
|
解码原理
原理
解码就是编码的逆过程。
获取密文 “YWJI”
每一个字符在 base64 编码表中的下标。
然后将这些下标对应的值的二进制值连接起来,重新划分为8位为一组。
之后转换为每字节对应的ASCII码即可得到编码的内容。
在CTF比赛中一般都是以python编写解密脚本。
代码实现
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 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 52 53 54 55 56 57 58 59 60
|
int decode( const char * base64, unsigned char * data ) { int i, j; unsigned char k; unsigned char temp[4]; for ( i = 0, j = 0; base64[i] != '\0' ; i += 4 ) { memset( temp, 0xFF, sizeof(temp) ); for ( k = 0 ; k < 64 ; k ++ ) { if ( table[k] == base64[i] ) temp[0] = k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( table[k] == base64[i + 1] ) temp[1] = k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( table[k] == base64[i + 2] ) temp[2] = k; } for ( k = 0 ; k < 64 ; k ++ ) { if ( table[k] == base64[i + 3] ) temp[3] = k; }
data[j++] = ((unsigned char)(((unsigned char)(temp[0] << 2)) & 0xFC)) | ((unsigned char)((unsigned char)(temp[1] >> 4) & 0x03));
if ( base64[i + 2] == '=' ) break; data[j++] = ((unsigned char)(((unsigned char)(temp[1] << 4)) & 0xF0)) | ((unsigned char)((unsigned char)(temp[2] >> 2) & 0x0F)); if ( base64[i + 3] == '=' ) break; data[j++] = ((unsigned char)(((unsigned char)(temp[2] << 6)) & 0xF0)) | ((unsigned char)(temp[3] & 0x3F)); } return j; }
|
逆向中的base64
特征识别
- 字符串编码表识别
- 加密填充符识别(一般为=号)
- bit:3 * 8变4 * 6
- 输入参数会被移位拼接,移位位数为 2、4、6位,将3字节拆成4字节
- 理解编码原理,编码时通常都会用3个字节一组来处理比特位数据,这些特征都可以用来分析识别。
移位运算中左移1位等于乘以2,右移1位等于除以2
常规魔改:编码表(TLS、SMC等各种反调试位置)
魔改
1.修改编码表
2.修改下标
将base64的编码查表下标对应关系修改,对于这种修改,我们只需要推导出下标逆计算即可。
例题
常规
BUUCTF-reverse3
拿到程序查壳,发现无壳
之后运行一下,随意输入一串字符
判定为字符串比较
ida打开分析一下,发现主函数中只调用了一个main_0函数
进入查看
先将函数和变量改一下名,提高代码可读性。
·sub_41132f
用于显示字符串,判断为printf
利用快捷键n改名为printf
sub_411375
根据其后参数判定为scanf,用于向str数组输入最多20个字符。
将其函数改为scanf,将str数组改为input。
j_strlen
函数判断为获取字符串长度的函数strlen
,将获取结果的变量v3改为input_length便于阅读。
可以看到函数sub_4110be
对我们输入的数据进行了处理
无法判断函数sub_4110be
的功能,所以进入查看。
发现调用了一个函数,继续进入查看
分析主要代码逻辑
进行了很多移位拼接操作,很明显的base64加密
更简单的方法是打开字符串窗口查看一下
发现base64编码表字符串,判断为base64加密
所以sub_4110be
为加密函数,变量v4为返回的密文。
根据逻辑重命名一下
接下来继续分析
strncmp
函数将密文复制0x28个字节到destination中
接下来计算密文长度
然后对密文做了移位运算
然后获取运算后的密文长度
通过strncmp
函数对密文和str2变量比较密文长短的内容
如果相等则输入flag正确
所以str2就是加密后的密文
我们通过密文和加密逻辑逆向构造exp。
exp
通过python的base64库来构造解密exp
1 2 3 4 5 6 7 8 9 10 11
| import base64
s="e3nifIH9b_C@n@dH" flag=""
for i in range(len(str)): flag+=chr(ord(str[i])-i)
print(base64.b64decode(flag))
|
换表
BUUCTF 特殊的 BASE64
原理
换表就是将映射的编码表修改掉,但是加密过程是仍然不变的。
前面讲过base64编码最关键的点在于根据值取编码表中的下标对应的字符。
魔改编码表同样如此,所以我们可以获取密文在魔改编码表中的下标。然后获取下标在原编码表中的值之后。
再进行常规解密。
拿到程序后一般流程查壳,发现无壳
ida打开分析,发现为c++程序
先查看一下字符串表
第一行是很明显的base64编码字符串
选中那行可能就是魔改后的编码表
分析main函数代码逻辑
根据前面发现的魔改编码表则使用常规解密的方法一定是不行的,所以我们必须换种方法。
在构造exp之前还是先看一下加密函数
分析base64加密函数
发现在初始化a1的时候用的是unk_489084,进入查看一下
发现为字符串窗口查看到的字符串,则确实为换表加密。
根据逻辑构造exp
exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import base64 import string
enc = "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI=="
table1 = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0987654321/+"
table2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
a=enc.translate(str.maketrans(table1,table2))
print(base64.b64decode(a).decode())
|
后言
参考链接:彻底弄懂base64的编码与解码原理 - 掘金 (juejin.cn)
参考链接:reverse逆向算法之base64和RC4_base64”和 rc4-CSDN博客