学破解第110天,《C++之base64编码解码》学习
[ 本帖最后由 小菜鸟一枚 于 2020-7-6 12:33 编辑 ]\n\n# 学破解第110天,《C++之base64编码解码》学习前言:
一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程。随着破解学习的深入,楼主这个半吊子迷失了自我,日渐沉迷水贴装X,不能自拔。
==========申明:从第71天楼主开始水贴装X,帖子不再具有连续性,仅供参考,后续帖子为楼主YY专用贴!!!==========
立帖为证!--------记录学习的点点滴滴
**部分bug已更正,其余bug未知,本文视频讲解:
(https://www.52pojie.cn/thread-1212676-1-1.html)**
## 0x1base64算法加解密原理
1.首先利用万能的百度搜索相关知识,得到如下知识:
1)Base64使用A--Z,a--z,0--9,+,/ 这64个字符.
2)编码原理:将3个字节转换成4个字节( (3 X 8) = 24 = (4 X 6) )先读入3个字节,每读一个字节,左移8位,再右移四次,每次6位,这样就有4个字节了.
3)解码原理:将4个字节转换成3个字节.先读入4个6位(用或运算),每次左移6位,再右移3次,每次8位.这样就还原了.
2.通过百度知道的知识知道的密码表是由上述64字符组成的,接下来看看编码原理将3个字节转换成4个字节,这句话有点糊涂。
3.解码原理是将4个字节转换成3个字节,反过来就是解码,后面的左移,右移运算又是什么了,还是有些糊涂,接下来用代码模拟一遍。
## 0x2base64算法实现
1.密码表:
```
char base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//定义密码表
```
2.编码实现:
```
char str = "zxcv";//定义明文
char cpystr;//定义密文
/*
第一个参数:明文字符串
第二个参数:明文字符串长度
第三个参数:密文字符串分配的内存的地址
第四个参数:密文字符串所占内存空间大小
返回值:
0:表示加密完成
1:程序逻辑错误
2:密文字符串所占内存空间大小保存不下明文
其他值:未知错误
*/
int encodeBase64(const char *str, int strLen, const char *cpystr, int cpystrLen);
int encodeBase64(const char *str, int strLen, char *cpystr, int cpystrLen)
{
//读取3个字节zxc,转换为二进制01111010 01111000 01100011
//转换为4个6位字节,011110 100111 100001 100011
//不足8位在前补0,变成00011110 00100111 00100001 00100011
//再将4个字节转换为十进制,30 39 33 35
//通过下标找到码表中对应的,e n h j
//zxc加密后的密文就为,enhj
//接下来第二轮加密,只剩1个字母v,转换成二进制01110110
//转换成2个6位字节,011101 100000(10不足6位末尾补0)
//不足8位在前补0,变成 00011101 00100000
//转换成十进制,29 32
//通过下标找到码表中对应的d g,还缺两个才构成四字节,这时补等于号即可
//v加密后的密文为dg==
//最后整个明文被base64加密完成,得到密文enhjdg==
char base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//定义base64码表
int i = 0;//明文下标
int j = 0;//密文下标
while (i < strLen)//判断是否加密完毕
{
if (j < cpystrLen - 1)//判断下标是否超过密文可存储范围
{
if (i + 3 <= strLen)//读取到3个字节
{
cpystr = base64 >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
cpystr = base64[(str & 3) << 4 | (str >> 4)];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
cpystr = base64[(str & 15) << 2 | (str >> 6)];//取第二个字节的后4位再加上第三个字节的前2位,将其加密为base64[]码表中对应的字符
cpystr = base64 & 63];//取第三个字节的后6位,将其加密为base64[]码表中对应的字符
}
else if (i + 2 <= strLen)//读取到2个字节
{
cpystr = base64 >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
cpystr = base64[(str & 3) << 4 | (str >> 4)];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
cpystr = base64[(str & 15) << 2];//取第二个字节的后4位再加上第三个字节的前2位,将其加密为base64[]码表中对应的字符
//末尾补一个等于号
cpystr = '=';
}
else if (i + 1 <= strLen)//只读取到1个字节
{
cpystr = base64 >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
cpystr = base64[(str & 3) << 4];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
//末尾补两个等于号
cpystr = '=';
cpystr = '=';
}
else
{
cout << "为什么循环还在执行,程序逻辑错误!!!" << endl;
return 1;
}
i += 3;//明文每次循环向后移动3位
j += 4;//密文每次循环向后移动4位
}
else
{
cout << "存储密文的内存不足,请重新分配!!!" << endl;
return 2;
}
}
cpystr = '\0';//补一个字符串结束符
return 0;
}
```
3.解码实现:
```
/*
base64解密函数
第一个参数:密文字符串
第二个参数:密文字符串长度
第三个参数:明文字符串分配的内存的地址
第四个参数:明文字符串所占内存空间大小
返回值:
0:表示解密完成
1:程序逻辑错误
2:明文字符串所占内存空间大小保存不下明文
其他值:未知错误
*/
int decodeBase64(const char *cpystr, int cpystrLen, char *str, int strLen);
int decodeBase64(const char *cpystr, int cpystrLen, char *str, int strLen)
{
//解密就是将加密的过程倒过来即可!
char base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//定义base64码表
int i = 0;//密文下标
int j = 0;//明文下标
while (i < cpystrLen)//判断是否解密完毕
{
int index = 0;//码表下标
int temp1 = -1;//保存第一个密文下标
int temp2 = -1;//保存第二个密文下标
int temp3 = -1;//保存第三个密文下标
int temp4 = -1;//保存第四个密文下标
if (j < strLen - 1)//判断下标是否超过明文可存储范围
{
//通过码表反查密文对应的下标,然后分别获得十进制数字
for (; index < 64; index++) {
if (cpystr == base64)
{
temp1 = index;
}
if (cpystr == base64)
{
temp2 = index;
}
if (cpystr == base64)
{
temp3 = index;
}
if (cpystr == base64)//一轮密文4个字节已取出,退出循环
{
temp4 = index;
}
//如果已经查到四个下标就退出循环!
if (temp1 != -1 && temp2 != -1 && temp3 != -1 && temp4 != -1) break;
}
if (temp3 != -1 && temp4 != -1)//完整的读取到了4个字节,直接解密
{
str = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
str = ((temp2 & 15) << 4) | (temp3 >> 2);//取第二个密文后4个字符再加上第三个密文前4个字符
str = ((temp3 & 3) << 6) | temp4;//取第三个密文后2个字符再加上第四个密文6个字符
}
else if (temp3 == -1)//只取到了2个字节
{
str = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
j -= 2;//下标前移2位,不然补‘\0’的位置不对
}
else if (temp4 == -1)//只取到了3个字节
{
str = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
str = ((temp2 & 15) << 4 | (temp3 >> 2));//取第二个密文后4个字符第三个密文前4个字符
j--;//下标前移1位,不然补‘\0’的位置不对
}
else
{
cout << "下标取值不对,程序逻辑错误!!!" << endl;
return 1;
}
i += 4;//密文每次循环向后移动4位
j += 3;//明文每次循环向后移动3位
}
else
{
cout << "存储明文的内存不足,请重新分配!!!" << endl;
return 2;
}
}
str = '\0';//补一个字符串结束符
return 0;
}
```
## 0x3base64算法的变形及识别
1.base64可以对中文加密,当然了我这段代码并没有对中文处理,在处理前判断是中文或者英文字符就可以了,例如在ASCII码,32位平台中,一个中文有两个字节表示,一般是负数,为了base64能够对其处理,可以将其加上128,然后再行处理,这里就不在重复编码了。
2.然后我们看,这个加密方式是有一个码表的,那么我们就可以自由的更改码表,让在线的解密平无法解密,或者解密出的明文不对。
3.那么如何识别base64,==号可以换成别的,码表也可以换,但是加密逻辑是固定的(换了就不叫base64加密了)。
例如:3个字符加密后是4个,4个字符加密后是8个,且末尾两个字符相同,无论密文位数怎么改变,都是4的倍数,并且相同字符加密后的密文绝对一样,例如输入6个1,输出每三个字符必然一样!
### 0x4通过OD分析base64加密
1.首先还是常规思路,准备一个base64加密的cm程序!
编译器vs2015,xp可运行。成功会得到压缩包的解压密码,失败就直接退出了。
2.打开OD载入程序,这里我输入00401000,转过去
```
00401000 .B9 F8CD4200 mov ecx,demo01.0042CDF8
00401005 .E8 21270000 call demo01.0040372B
0040100A .68 61D74100 push demo01.0041D761
0040100F .E8 4C500000 call demo01.00406060
00401014 .59 pop ecx ;kernel32.7C817077
00401015 .C3 retn
```
3.那么我们该怎么找程序入口点呢?
方法一:搜字符串,缺点如果字符串太多不太容易分辨
方法二:直接让程序跑起来,下API断点回溯过去,找到段首
方法三:直接F8让程序跑起来,然后丢到IDA F5分析,如果还是系统api,那么记下跑飞的地方,F7进去然后继续F8,重复以上步骤
4.跑一遍,随便输入个123456,然后发现经过一个与0xA的比较后,提示输入的字符要大于10,那我就输入01234567890,开始分析
```
00401F2F .0F1005 649542>movups xmm0,dqword ptr ds: ;ZDNkM0xqVXljRzlxYVdVdVkyND0=
00401F81 .E8 09900000 call demo01.0040AF8F ;这里就要输入字符串了
00401F8B .E8 F00E0000 call demo01.00402E80
00401F90 .50 push eax ;这些都是干扰指令
00401F91 .E8 DA110000 call demo01.00403170
00401F96 .8D5424 74 lea edx,dword ptr ss: ;取出01234567890给edx
00401F9A .83C4 0C add esp,0xC ;销毁局部变量,平衡堆栈
00401F9D .8D4A 01 lea ecx,dword ptr ds: ;指针右移1位,将1234567890保存给ecx
00401FA0 >8A02 mov al,byte ptr ds: ;接下来这几行就是计算字符串长度给edx
00401FA2 .42 inc edx
00401FA3 .84C0 test al,al
00401FA5 .^ 75 F9 jnz short demo01.00401FA0
00401FA9 .83FA 0A cmp edx,0xA ;如果小于等于10就跳向失败
00401FAC .0F86 AA000000 jbe demo01.0040205C
00401FBB .8D4C24 70 lea ecx,dword ptr ss: ;将字符串给ecx
00401FBF .E8 CC000000 call demo01.00402090 ;处理我们输入的字符串,加密函数
00401FE2 .8D4C24 08 lea ecx,dword ptr ss: ;将最开始看到的ZDNkM0xqVXljRzlxYVdVdVkyND0=给ecx
00401FE6 .E8 A5020000 call demo01.00402290 ;处理ZDNkM0xqVXljRzlxYVdVdVkyND0=,解密函数
00401FEE .8D8C24 D00000>lea ecx,dword ptr ss: ;看到这里就知道是strcmp函数了,如果不知道自己写个strcmp看看反编译的结果是不是这样
00401FF5 .8D8424 380100>lea eax,dword ptr ss:
00401FFC .0f1f40 00 nop dword ptr ds:
00402000 >8A10 mov dl,byte ptr ds:
00402002 .3A11 cmp dl,byte ptr ds:
00402004 .75 1A jnz short demo01.00402020
00402006 .84D2 test dl,dl
00402008 .74 12 je short demo01.0040201C
0040200A .8A50 01 mov dl,byte ptr ds:
0040200D .3A51 01 cmp dl,byte ptr ds:
00402010 .75 0E jnz short demo01.00402020
00402012 .83C0 02 add eax,0x2
00402015 .83C1 02 add ecx,0x2
00402018 .84D2 test dl,dl
0040201A .^ 75 E4 jnz short demo01.00402000
0040201C >33C0 xor eax,eax
0040201E . /EB 05 jmp short demo01.00402025 ;如果上面比较的两个字符串相等会到这里
00402020 > |1BC0 sbb eax,eax
00402022 . |83C8 01 or eax,0x1
00402025 > \85C0 test eax,eax ;因为eax被清空了,所以这里显然不会跳转
00402027 .75 46 jnz short demo01.0040206F
00402029 .8D5424 68 lea edx,dword ptr ss: ;接下来四行是将输入的字符串显示到屏幕上
0040202D .E8 4E0E0000 call demo01.00402E80
00402032 .50 push eax
00402033 .E8 38110000 call demo01.00403170
00402038 .83C4 04 add esp,0x4
0040203B .68 B4954200 push demo01.004295B4 ;pause
00402040 .E8 628D0000 call demo01.0040ADA7
```
5.还剩下那加密函数和解密函数,然而
```
004020BF|.8B7D 08 mov edi, ;局部变量1是我输入的那个字符串
004020E9|.85D2 test edx,edx ;edx是字符串到长度,没看到哪里运算啊
004020EB|.0F8E 2F010000 jle demo01.00402220 ;显然不为0,不会跳转
004020F1|.B9 02000000 mov ecx,0x2 ;ecx的值为2
00402100|> /83F8 63 /cmp eax,0x63 ;初始eax=0,大于等于63时会跳走
00402103|. |0F8D 57010000 |jge demo01.00402260
00402109|. |03DE |add ebx,esi
0040210B|. |41 |inc ecx
0040210C|. |03CB |add ecx,ebx
0040210E|. |3BCA |cmp ecx,edx ;初始ecx是3,大于11则下面跳走
00402110|. |7F 61 |jg short demo01.00402173
00402112|. |0FBE0B |movsx ecx,byte ptr ds: ;取字符串第一个字母0,对应ASCII,0x30
00402115|. |C1F9 02 |sar ecx,0x2 ;算术右移2位,也就是除以2
00402118|. |0FB64C0D B0 |movzx ecx,byte ptr ss: ;将M赋值给ecx,为什么是M呢?第十六个字母是M, ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
0040211D|. |880C07 |mov byte ptr ds:,cl ;再将M存储到edi中
00402120|. |0FBE13 |movsx edx,byte ptr ds: ;将这个0给edx
00402123|. |8B5D AC |mov ebx, ;还是将字符串给ebx
00402126|. |83E2 03 |and edx,0x3 ;将edx和0x3进行与运算
00402129|. |C1E2 04 |shl edx,0x4 ;将edx逻辑左移4位,也就是乘以16
0040212C|. |0FBE4C1E 01 |movsx ecx,byte ptr ds: ;再取出字符串第二个字符1,对应ascii,0x31
00402131|. |C1F9 04 |sar ecx,0x4 ;将其算术右移2位,也就是除以4
00402134|. |0BD1 |or edx,ecx ;edx和ecx进行或运算,值为3
00402136|. |0FB64C15 B0 |movzx ecx,byte ptr ss: ;这里很明显就是取第四个字符D
0040213B|. |884C07 01 |mov byte ptr ds:,cl ;将D存储到edi+1中
0040213F|. |0FBE541E 01 |movsx edx,byte ptr ds: ;将这个1给edx
00402144|. |0FBE4C1E 02 |movsx ecx,byte ptr ds: ;再取出字符串第三个字符2,对应ascii,0x32
00402149|. |83E2 0F |and edx,0xF ;将edx和0xF进行与运算
0040214C|. |C1F9 06 |sar ecx,0x6 ;将其算术右移6位,也就是除以64
0040214F|. |C1E2 02 |shl edx,0x2 ;将edx逻辑左移2位,也就是乘以4
00402152|. |0BD1 |or edx,ecx ;edx和ecx进行或运算,值为4
00402154|. |0FB64C15 B0 |movzx ecx,byte ptr ss: ;这里很明显就是取第五个字符E
00402159|. |884C07 02 |mov byte ptr ds:,cl ;将E存储到edi+2中
0040215D|. |0FBE4C1E 02 |movsx ecx,byte ptr ds: ;将这个2给ecx
00402162|. |83E1 3F |and ecx,0x3F ;将ecx和0x3F进行与运算,得到0x32,也就是50
00402165|. |0FB64C0D B0 |movzx ecx,byte ptr ss: ;ebp+0x32对应第51个字符y
0040216A|. |884C07 03 |mov byte ptr ds:,cl ;将y存储到edi+3中
```
上面是取到第一轮也就是三个字符时是这么处理的,在看代码的时候不能只关注值,看esi+ebx+0x1,esi+ebx+0x2,我们就要想到数组或者连续存储结构,这里通过移动指针访问我们输入的字符串中的每一个字符,edi+eax+0x1,edi+eax+0x2这里连续存储着处理后(加密)的字符串,ebp+edx-0x50这里为什么知道取的是哪一个字符呢,直接数据窗口中跟随过去就能看到码表,像这种类似的结构多次出现时,我们要多加关注,知道它的结构,可能的含义。
4.解密部分同样按照上面的方法逐步分析出来,这里我就不再贴分析代码了,一种字符串变成另一种字符串我们要观察规律,根据经验简单判断可能的加密方式,如果特征都对得上,但是解密出来的不对,有可能就是码表等地方动了手脚,我们就需要像这样去分析算法。
## 0x5总结
1.这篇帖子是我学的最吃力的一次,花费了数天的时间,算法真的是让人头脑爆炸,虽然base64实现起来并不困难。
2.看原理看了很多,始终不明白base64咋回事,然后自己照着步骤一步一步去实现,最终终于知道base64是怎么加密和解密的了。
3.二进制移位是难点,光看左移一下,右移一下就加密了,完全不懂,只有把每一个字母变成二进制,模拟运算过程,才容易理解。
4.自学的,所以编码可能不规范,算法的实现有没有bug也不知道,发现主要还是缺乏二进制的概念。
5.这个我尽可能的写的很详细了,一是怕我过段时间又忘了base64咋回事,另一方面方便论坛和我一样基础差的坛友们参考学习。
参考资料:
(https://zhidao.baidu.com/question/87965568.html)
(https://www.cnblogs.com/qianjinyan/p/9541368.html)
(https://blog.csdn.net/lazyer_dog/article/details/82628076)
(https://www.cnblogs.com/onroad/archive/2009/07/13/1522670.html)
总结:楼主是个小菜鸟,离了教程啥都不会! 大家共同学习呀! 本帖最后由 小菜鸟一枚 于 2020-7-3 21:48 编辑
@alittlebear 小熊熊,看到帮帮忙,听说你说MD高手呢,请问这个格式怎么调,不知道为什么后面的字都变成斜体了,难受,好好的帖子变成这样了 打卡,学习 打卡,学习 小菜鸟一枚 发表于 2020-7-3 21:37
@alittlebear 小熊熊,看到帮帮忙,这个格式怎么调,不知道为什么后面的字都变成斜体了,
*这是斜体*
```
*这是斜体*
```
这是正体
```
这是正体
```
看看源代码,菜鸟大佬 本帖最后由 小菜鸟一枚 于 2020-7-3 21:54 编辑
alittlebear 发表于 2020-7-3 21:48
*这是斜体*
```
*这是斜体*
https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面
拜托大佬了!{:1_893:}
PS:*斜体*或者_斜体_,我找了半天没找到在哪少写了*或者_,我代码里没用下划线,可能哪里*号少了,或者与论坛的代码冲突了! 小菜鸟一枚 发表于 2020-7-3 21:52
https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面
{:301_1004:}您才是大佬,我去看看哈 小菜鸟一枚 发表于 2020-7-3 21:52
https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面
MD文件在我这里是正常的
1.重新清空帖子,粘贴一下源代码试试?
2.是不是代码框里的*误判成MD语法了,是bug吗。。。
总结:我只是个菜鸟