欧卡2中文社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

需要三步,才能开始

只需两步,慢速开始

欧卡2入门方向盘选莱仕达V9莱仕达折叠便携游戏方向盘支架欢迎地图Mod入驻
查看: 8301|回复: 5
收起左侧

test

[复制链接]
知行 发表于 2020-7-7 17:58 | 显示全部楼层 |阅读模式
 for (; index < 64; index++) {
                                if (cpystr[i] == base64[index])
                                {
                                        temp1 = index;
                                }
                                if (cpystr[i + 1] == base64[index])
                                {
                                        temp2 = index;
                                }
                                if (cpystr[i + 2] == base64[index])
                                {
                                        temp3 = index;
                                }
                                if (cpystr[i + 3] == base64[index])//一轮密文4个字节已取出,退出循环
                                {
                                        temp4 = index;
                                }
Hmily 发表于 2020-7-7 18:02 | 显示全部楼层
[ 本帖最后由 小菜鸟一枚 于 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[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//定义密码表

  2.编码实现:

char str[5] = "zxcv";//定义明文
char cpystr[10];//定义密文

/*
第一个参数:明文字符串
第二个参数:明文字符串长度
第三个参数:密文字符串分配的内存的地址
第四个参数:密文字符串所占内存空间大小
返回值:
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[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//定义base64码表
        int i = 0;//明文下标
        int j = 0;//密文下标
        while (i < strLen)//判断是否加密完毕
        {
                if (j < cpystrLen - 1)//判断下标是否超过密文可存储范围
                {
                        if (i + 3 <= strLen)//读取到3个字节
                        {
                                cpystr[j] = base64[str[i] >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 1] = base64[(str[i] & 3) << 4 | (str[i + 1] >> 4)];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 2] = base64[(str[i + 1] & 15) << 2 | (str[i + 2] >> 6)];//取第二个字节的后4位再加上第三个字节的前2位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 3] = base64[str[i + 2] & 63];//取第三个字节的后6位,将其加密为base64[]码表中对应的字符
                        }
                        else if (i + 2 <= strLen)//读取到2个字节
                        {
                                cpystr[j] = base64[str[i] >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 1] = base64[(str[i] & 3) << 4 | (str[i + 1] >> 4)];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 2] = base64[(str[i + 1] & 15) << 2];//取第二个字节的后4位再加上第三个字节的前2位,将其加密为base64[]码表中对应的字符
                                                                                                                                                                   //末尾补一个等于号
                                cpystr[j + 3] = '=';
                        }
                        else if (i + 1 <= strLen)//只读取到1个字节
                        {
                                cpystr[j] = base64[str[i] >> 2];//取第1个字节的前6位,将其加密为base64[]码表中对应的字符
                                cpystr[j + 1] = base64[(str[i] & 3) << 4];//取第一个字节的后2位再加上第二个字节的前4位,将其加密为base64[]码表中对应的字符
                                                                                                                                                          //末尾补两个等于号
                                cpystr[j + 2] = '=';
                                cpystr[j + 3] = '=';
                        }
                        else
                        {
                                cout << "为什么循环还在执行,程序逻辑错误!!!" << endl;
                                return 1;
                        }
                        i += 3;//明文每次循环向后移动3位
                        j += 4;//密文每次循环向后移动4位
                }
                else
                {
                        cout << "存储密文的内存不足,请重新分配!!!" << endl;
                        return 2;
                }
        }
        cpystr[j] = '\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[65] = "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[i] == base64[index])
                                {
                                        temp1 = index;
                                }
                                if (cpystr[i + 1] == base64[index])
                                {
                                        temp2 = index;
                                }
                                if (cpystr[i + 2] == base64[index])
                                {
                                        temp3 = index;
                                }
                                if (cpystr[i + 3] == base64[index])//一轮密文4个字节已取出,退出循环
                                {
                                        temp4 = index;
                                }

                                //如果已经查到四个下标就退出循环!
                                if (temp1 != -1 && temp2 != -1 && temp3 != -1 && temp4 != -1) break;
                        }

                        if (temp3 != -1 && temp4 != -1)//完整的读取到了4个字节,直接解密
                        {
                                str[j] = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
                                str[j + 1] = ((temp2 & 15) << 4) | (temp3 >> 2);//取第二个密文后4个字符再加上第三个密文前4个字符
                                str[j + 2] = ((temp3 & 3) << 6) | temp4;//取第三个密文后2个字符再加上第四个密文6个字符
                        }
                        else if (temp3 == -1)//只取到了2个字节
                        {
                                str[j] = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
                                j -= 2;//下标前移2位,不然补‘\0’的位置不对
                        }
                        else if (temp4 == -1)//只取到了3个字节
                        {
                                str[j] = (temp1 << 2) | (temp2 >> 4);//取第一个密文6个字符再加上第二个密文前2个字符
                                str[j + 1] = ((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[j] = '\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:[0x429564]     ;  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:[esp+0x74]          ;  取出01234567890给edx
00401F9A   .  83C4 0C       add esp,0xC                              ;  销毁局部变量,平衡堆栈
00401F9D   .  8D4A 01       lea ecx,dword ptr ds:[edx+0x1]           ;  指针右移1位,将1234567890保存给ecx
00401FA0   >  8A02          mov al,byte ptr ds:[edx]           ;接下来这几行就是计算字符串长度给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:[esp+0x70]          ;  将字符串给ecx
00401FBF   .  E8 CC000000   call demo01.00402090         ;处理我们输入的字符串,加密函数

00401FE2   .  8D4C24 08     lea ecx,dword ptr ss:[esp+0x8]          ;将最开始看到的ZDNkM0xqVXljRzlxYVdVdVkyND0=给ecx
00401FE6   .  E8 A5020000   call demo01.00402290         ;处理ZDNkM0xqVXljRzlxYVdVdVkyND0=,解密函数

00401FEE   .  8D8C24 D00000>lea ecx,dword ptr ss:[esp+0xD0]          ;看到这里就知道是strcmp函数了,如果不知道自己写个strcmp看看反编译的结果是不是这样
00401FF5   .  8D8424 380100>lea eax,dword ptr ss:[esp+0x138]
00401FFC   .  0f1f40 00     nop dword ptr ds:[eax]
00402000   >  8A10          mov dl,byte ptr ds:[eax]
00402002   .  3A11          cmp dl,byte ptr ds:[ecx]
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:[eax+0x1]
0040200D   .  3A51 01       cmp dl,byte ptr ds:[ecx+0x1]
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:[esp+0x68]          ;接下来四行是将输入的字符串显示到屏幕上
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,[arg.1]                          ;  局部变量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:[ebx]             ;  取字符串第一个字母0,对应ASCII,0x30
00402115  |. |C1F9 02       |sar ecx,0x2                             ;  算术右移2位,也就是除以2
00402118  |. |0FB64C0D B0   |movzx ecx,byte ptr ss:[ebp+ecx-0x50]    ;  将M赋值给ecx,为什么是M呢?第十六个字母是M, ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
0040211D  |. |880C07        |mov byte ptr ds:[edi+eax],cl            ;  再将M存储到edi中
00402120  |. |0FBE13        |movsx edx,byte ptr ds:[ebx]             ;  将这个0给edx
00402123  |. |8B5D AC       |mov ebx,[local.21]                      ;  还是将字符串给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:[esi+ebx+0x1]     ;  再取出字符串第二个字符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:[ebp+edx-0x50]    ;  这里很明显就是取第四个字符D
0040213B  |. |884C07 01     |mov byte ptr ds:[edi+eax+0x1],cl        ;  将D存储到edi+1中
0040213F  |. |0FBE541E 01   |movsx edx,byte ptr ds:[esi+ebx+0x1]     ;  将这个1给edx
00402144  |. |0FBE4C1E 02   |movsx ecx,byte ptr ds:[esi+ebx+0x2]     ;  再取出字符串第三个字符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:[ebp+edx-0x50]    ;  这里很明显就是取第五个字符E
00402159  |. |884C07 02     |mov byte ptr ds:[edi+eax+0x2],cl        ;  将E存储到edi+2中
0040215D  |. |0FBE4C1E 02   |movsx ecx,byte ptr ds:[esi+ebx+0x2]     ;  将这个2给ecx
00402162  |. |83E1 3F       |and ecx,0x3F                            ;  将ecx和0x3F进行与运算,得到0x32,也就是50
00402165  |. |0FB64C0D B0   |movzx ecx,byte ptr ss:[ebp+ecx-0x50]    ;  ebp+0x32对应第51个字符y
0040216A  |. |884C07 03     |mov byte ptr ds:[edi+eax+0x3],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-7 18:13 | 显示全部楼层
cpystr[j] = base64[str[i] >> 2];
 楼主| 知行 发表于 2020-7-7 18:14 | 显示全部楼层
cpystr[j] = base64[str[i] >> 2];
 楼主| 知行 发表于 2020-7-7 18:15 | 显示全部楼层
cpystr[j] = base64[str[i] >> 2];
 楼主| 知行 发表于 2020-7-7 18:15 | 显示全部楼层
cpystr[j] = base64[str[i] >> 2];
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

联系我们|手机版|欧卡2中国 ( 湘ICP备11020288号-1 )

GMT+8, 2024-12-27 23:46 , Processed in 0.045801 second(s), 8 queries , Redis On.

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表