小菜鸟一枚 发表于 2020-7-3 21:30

学破解第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:46

大家共同学习呀!

Deuez 发表于 2020-7-3 21:45

小菜鸟一枚 发表于 2020-7-3 21:37

本帖最后由 小菜鸟一枚 于 2020-7-3 21:48 编辑

@alittlebear 小熊熊,看到帮帮忙,听说你说MD高手呢,请问这个格式怎么调,不知道为什么后面的字都变成斜体了,难受,好好的帖子变成这样了

qiang0606 发表于 2020-7-3 21:44

打卡,学习

yushaomeng 发表于 2020-7-3 21:45

打卡,学习

alittlebear 发表于 2020-7-3 21:48

小菜鸟一枚 发表于 2020-7-3 21:37
@alittlebear 小熊熊,看到帮帮忙,这个格式怎么调,不知道为什么后面的字都变成斜体了,

*这是斜体*
```
*这是斜体*
```

这是正体
```
这是正体
```

看看源代码,菜鸟大佬

小菜鸟一枚 发表于 2020-7-3 21:52

本帖最后由 小菜鸟一枚 于 2020-7-3 21:54 编辑

alittlebear 发表于 2020-7-3 21:48
*这是斜体*
```
*这是斜体*

https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面

拜托大佬了!{:1_893:}

PS:*斜体*或者_斜体_,我找了半天没找到在哪少写了*或者_,我代码里没用下划线,可能哪里*号少了,或者与论坛的代码冲突了!

alittlebear 发表于 2020-7-3 21:53

小菜鸟一枚 发表于 2020-7-3 21:52
https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面



{:301_1004:}您才是大佬,我去看看哈

alittlebear 发表于 2020-7-3 21:56

小菜鸟一枚 发表于 2020-7-3 21:52
https://wwa.lanzouj.com/itP91ea16cd
md源文件和txt形式的都放在里面



MD文件在我这里是正常的

1.重新清空帖子,粘贴一下源代码试试?
2.是不是代码框里的*误判成MD语法了,是bug吗。。。

总结:我只是个菜鸟
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 学破解第110天,《C++之base64编码解码》学习