最近做了一道BUUCTF的Misc板块的Base64隐写的题目,发现不了解清楚Base64编码的根本原理的话,好像也难以理清Base64隐写的原理,从接触CTF以来Base64就是接触得最多的编码方式,无论是Misc、Crypto、Web、Reverse反正几乎所有CTF方向的入门题里都有它的身影。可恨的是我还没搞清楚它的编码原理,所以就抽空在B站和CSDN上的文章里面学习了一下。
首先Base64编码当然是可逆的,所以不能依赖它进行加密。
Base64编码过程
首先,要让原始的待编码数据每三个字节作为一组,每个字节是8个bit,所以一共是 24 个 bit,我感觉理解这一步在理解Base64编码步骤里面十分重要。
接着,再将待编码数据的每一个字符的Ascii值转化为二进制,比如字母’A’的Ascii值为65,转化为二进制之后为1000001,因为每个字节是8个bit,所以要在1前面在补个0方便后续编码,所以就是01000001。
三个字节的数据进行这一步操作后就是24个bit的二进制数据,我们对此从前往后,重新以每6个bit的二进制数据为一组,得到四组,然后将每组的二进制数据转化为十进制,再对应Base64编码表,这个十进制数据在码表中对应的值即为编码后的数据,也就是说三个字符数据Base64之后会得到4个字符数据,实验一下,如图,确实如此,Man在经过编码后变成了TWFu
那么问题又来了,什么是Base64码表?
Base64编码表(table)
也没什么,就是用来将以6个bit位为一组的二进制数据重新转换为十进制之后,对照来获得编码后的数据的对照编码表,它的常规编码表一般是下面这个,网上的在线Base64编码默认通常也是这个编码表
可以看到,总共有64个字符,是英文字母的大写和小写,加上1234567890+/
比如010000转化为十进制为16,对应的就是编码表中的Q。
而在CTF的题目中,Base64的码表可能会被修改,无论是Misc还是Reverse。比如下面这题
[WUSTCTF2020]level3 – Arnold’s Blog (arnold66.top)
上面我介绍的文字太多可能还不够清楚,接下来是我对 ‘Arnold’ 6个字符的手动进行Base64编码,看完这个应该就会明白一些。如图,第二行为每个字符在Ascii码表对应的值
第三行我将字符对应的Ascii表中对应的值转化为8个bit的二进制数据后,又以每6个bit为一组得到第四行
然后再转化回十进制,在Base64编码表中寻找对应的字符即可得到Base64编码后的数据,QXJub2xk
但是Base64强调以3个字节为一组,像Arnold总共6个字节,为三的倍数刚刚好,那么不是三的倍数的情况下又会如何编码呢?比如woman、hello,这时候就会出现一个问题就是将二进制数据以6个为一组时,到最后的数据不够6个了,如下图所示
当二进制数据以6个bit为一组分完后,会最后剩下一个4个bit的数据1110,这时候我们就要在这些数据后面补上两个0,使得其为6个bit为一组的二进制数据,并且再补6个0作为一组,为什么要再补6个0呢?因为之前强调了Base64要让原始的待编码数据中每三个字节作为一组,每三个字节以6个bit分组会得到4组,因此最后要补全四组,再加上一组000000,而在Base64中000000代表的就是等于号,这就是为啥Base64编码中经常会出现等于号,我们也不难发现当待编码数据的字节数为3n+1时,Base64编码后会出现2个‘=’,而当带编码的数据字节数为3n+2时,Base64编码会出现1个‘=’,例如woman进行Base64编码之后是d29tYW4=,life进行Base64编码之后是则是bGlmZQ==。
我们再来看看再Python中如何借助库实现对base64的编码和解码,代码如下
import base64
strings=b'Arnold666666'
en=base64.b64encode(strings)
print(en)
de=base64.b64decode(en)
print(de)
然后是再逆向过程中经常遇到的base64换表
import base64
import string
str1 = "NUGry2uhKgV2KgV2"
string1 = "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/"
# string1是改过之后的base64表
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
# string2是原始的base64表
print (base64.b64decode(str1.translate(str.maketrans(string1,string2))))
然后看看Base64加密在C语言当中的实现,我们在逆向工程中通常面对的是IDA中C语言的Base64加密,可以参考文章
Base64加密算法以及在IDA中的识别 – Qsons – 博客园 (cnblogs.com)
C语言实现Base64编码/解码_c语言base64库-CSDN博客
unsigned char *base64_encode(unsigned char *str)
{
long len;
long str_len;
unsigned char *res;
int i,j;
//定义base64编码表
unsigned char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//计算经过base64编码后的字符串长度
str_len=strlen(str);
if(str_len % 3 == 0)
len=str_len/3*4;
else
len=(str_len/3+1)*4;
res=malloc(sizeof(unsigned char)*len+1);
res[len]='\0';
//以3个8位字符为一组进行编码
for(i=0,j=0;i<len-2;j+=3,i+=4)
{
res[i]=base64_table[str[j]>>2]; //取出第一个字符的前6位并找出对应的结果字符
res[i+1]=base64_table[(str[j]&0x3)<<4 | (str[j+1]>>4)]; //将第一个字符的后位与第二个字符的前4位进行组合并找到对应的结果字符
res[i+2]=base64_table[(str[j+1]&0xf)<<2 | (str[j+2]>>6)]; //将第二个字符的后4位与第三个字符的前2位组合并找出对应的结果字符
res[i+3]=base64_table[str[j+2]&0x3f]; //取出第三个字符的后6位并找出结果字符
}
switch(str_len % 3)
{
case 1:
res[i-2]='=';
res[i-1]='=';
break;
case 2:
res[i-1]='=';
break;
}
return res;
}
反正要在IDA中能识别出Base64加密算法,我已经识别不出来好几次了😅
这就是Base64的编码原理了,而在Base64解码的过程中因为解码的过程的性质会出现Base64隐写的题目,后续再写。