小菜鸟一枚 发表于 2020-5-17 14:51

学破解第103天,《攻防世界reverse练习区Windows_Reverse1》分析

# 学破解第103天,《攻防世界reverse练习区Windows_Reverse1》分析
前言:
  一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程。随着破解学习的深入,楼主这个半吊子迷失了自我,日渐沉迷水贴装X,不能自拔。
==========申明:从第71天楼主开始水贴装X,帖子不再具有连续性,仅供参考,后续帖子为楼主YY专用贴!!!==========

立帖为证!--------记录学习的点点滴滴

## 0x1查壳
  1.程序下载后,查壳UPX 0.89 - 3.xx,ESP定律脱壳。
  2.运行程序,提示:please input code:要我们输入正确的字符串。

## 0x2动态调试分析(OD)
  1.搜索字符串,定位到关键跳。
```
004010FB   /75 27         jnz short dumped_.00401124
004010FD   |8D4C24 04       lea ecx,dword ptr ss:
00401101   |51            push ecx
00401102   |68 20214000   push dumped_.00402120                  ; You've got it!!%s\n
00401107   |FFD6            call esi
00401109   |83C4 08         add esp,0x8
0040110C   |33C0            xor eax,eax
0040110E   |5E            pop esi                                  ; kernel32.7C817077
0040110F   |8B8C24 00080000 mov ecx,dword ptr ss:
00401116   |33CC            xor ecx,esp
00401118   |E8 29000000   call dumped_.00401146
0040111D   |81C4 04080000   add esp,0x804
00401123   |C3            retn
00401124   \68 34214000   push dumped_.00402134                  ; Try again later.\n
```

  2.当然了,咋们的目的是追码,爆破这里毫无意义。关键跳上面必有关键call,找到它:
```
0040109D    68 08214000   push dumped_.00402108                  ; please input code:
004010A2    FFD6            call esi
004010A4    8D9424 20040000 lea edx,dword ptr ss:
004010AB    52            push edx                                 ; ntdll.KiFastSystemCallRet
004010AC    68 1C214000   push dumped_.0040211C                  ; %s
004010B1    FF15 A4204000   call dword ptr ds:[<&msvcr90.#1343>]   ; msvcr90.scanf
004010B7    8D8424 28040000 lea eax,dword ptr ss:
004010BE    50            push eax
004010BF    8D4C24 2C       lea ecx,dword ptr ss:
004010C3    E8 38FFFFFF   call dumped_.00401000
```

&emsp;&emsp;3.我F4到004010C3这里,程序运行起来,随便输入一个字符串123456,可以清晰的看到,此时eax就是我输入的123456,作为参数传递给call dumped_.00401000。


&emsp;&emsp;4.接下来就F7进去,开始F8单步向下走,给它都分析出来
```
00401000    51            push ecx
00401001    55            push ebp
00401002    8B6C24 0C       mov ebp,dword ptr ss:         ; 123456赋值给ebp
00401006    56            push esi                                 ; msvcr90.printf
00401007    8BC5            mov eax,ebp                              ; ebp(123456)赋值给eax
00401009    57            push edi                                 ; dumped_.004033D4
0040100A    33FF            xor edi,edi                              ; dumped_.004033D4
0040100C    8D70 01         lea esi,dword ptr ds:         ; 将eax+1后指向的地址给esi
0040100F    90            nop
00401010    8A10            mov dl,byte ptr ds:               ; 从eax中低位取一个字节,也就是1
00401012    40            inc eax                                  ; eax+1,指针右移一位
00401013    84D2            test dl,dl                               ; 判断dl是否为NULL
00401015^ 75 F9         jnz short dumped_.00401010               ; 循环判断
00401017    2BC6            sub eax,esi                              ; eax-esi,计算123456的长度,保存在eax中
00401019    74 26         je short dumped_.00401041                ; 相等就跳,也就是字符串长度为0
0040101B    53            push ebx
0040101C    8BDD            mov ebx,ebp                              ; ebp(123456)赋值给ebx
0040101E    2BD9            sub ebx,ecx                              ; ECX的值哪来的?相减后ebx为400
00401020    0FBE040B      movsx eax,byte ptr ds:          ; ebx+ecx那不就是还原ebp(123456),从中取一个字节(1)给eax
00401024    8A90 F82F4000   mov dl,byte ptr ds:      ; 从常量值中取出m赋值为edx的低八位给dl
0040102A    8BC5            mov eax,ebp                              ; ebp(123456)赋值给eax
0040102C    8811            mov byte ptr ds:,dl               ; 将dl(m)存入ecx指向的内存中
0040102E    47            inc edi                                  ; edi+1=1
0040102F    41            inc ecx                                  ; ecx内存地址+1
00401030    8D70 01         lea esi,dword ptr ds:         ; 将eax+1后指向的地址给esi
00401033    8A10            mov dl,byte ptr ds:               ; 从eax中低位取一个字节,也就是1
00401035    40            inc eax                                  ; eax+1,指针右移一位
00401036    84D2            test dl,dl                               ; 判断dl是否为NULL
00401038^ 75 F9         jnz short dumped_.00401033               ; 循环判断
0040103A    2BC6            sub eax,esi                              ; eax-esi,计算123456的长度,保存在eax中
0040103C    3BF8            cmp edi,eax                              ; edi=1,eax=6
0040103E^ 72 E0         jb short dumped_.00401020                ; edi < eax则跳转
00401040    5B            pop ebx                                  ; dumped_.004010C8
00401041    5F            pop edi                                  ; dumped_.004010C8
00401042    5E            pop esi                                  ; dumped_.004010C8
00401043    5D            pop ebp                                  ; dumped_.004010C8
00401044    59            pop ecx                                  ; 此时ecx值为mlkjih,0040102C 这一行循环写入
00401045    C3            retn
```

&emsp;&emsp;5.从这个call出来后,继续往下跟,详细分析如下:
```
004010C3    E8 38FFFFFF   call dumped_.00401000
004010C8    83C4 28         add esp,0x28
004010CB    B9 F4204000   mov ecx,dumped_.004020F4               ; DDCTF{reverseME}字符串赋值给eax
004010D0    8D4424 04       lea eax,dword ptr ss:         ; esp+4就是ecx(mlkjih)
004010D4    8A10            mov dl,byte ptr ds:               ; 从eax中取出第一个字节m存放在dl
004010D6    3A11            cmp dl,byte ptr ds:               ; 判断m == D
004010D8    75 1A         jnz short dumped_.004010F4
004010DA    84D2            test dl,dl
004010DC    74 12         je short dumped_.004010F0
004010DE    8A50 01         mov dl,byte ptr ds:
004010E1    3A51 01         cmp dl,byte ptr ds:             ; 比较第二个字符
004010E4    75 0E         jnz short dumped_.004010F4
004010E6    83C0 02         add eax,0x2
004010E9    83C1 02         add ecx,0x2
004010EC    84D2            test dl,dl
004010EE^ 75 E4         jnz short dumped_.004010D4
004010F0    33C0            xor eax,eax                              ; 清空eax
004010F2    EB 05         jmp short dumped_.004010F9
004010F4    1BC0            sbb eax,eax
004010F6    83D8 FF         sbb eax,-0x1
004010F9    85C0            test eax,eax                           ; 判断eax是否为NULL
004010FB    75 27         jnz short dumped_.00401124
004010FD    8D4C24 04       lea ecx,dword ptr ss:
00401101    51            push ecx                                 ; msvcr90.7855215C
00401102    68 20214000   push dumped_.00402120                  ; You've got it!!%s\n
00401107    FFD6            call esi                                 ; msvcr90.printf
```

显然必须要让ecx等于DDCTF{reverseME}它,才能成功,所以要从关键call中ecx的赋值寻找突破口。

&emsp;&emsp;6.然而我没学过汇编,依靠百度而来的意思,并不能让我知道ecx的每一次取值之间的关系,那么就运用数学知识,多测试几组数据,观察变量之间的关系。
输入:123456
输出:mlkjih

输入:654321
输出:hijklm

输入:abcdef
输出:=<;:98

输入:fedbca
输出:89:;<=

观察上面,我可以猜测字符串输入的顺序与输出的字符串没有关系,每一个输入的字符有唯一对应的输出。

&emsp;&emsp;7.接下来显然就是要找到密码表和key,找出对应关系,重新跑一边程序,梳理前面的整理出来的注释,发现决定ecx值的为这一行:00401024    8A90 F82F4000   mov dl,byte ptr ds:
密码表显然就是0x402FF8处的字符串了。
输入:123456
循环取每一个字符
&emsp;&emsp;第一次取1的的ASCII 0x31 + 0x402FF8处字符就是m,如图所示!


&emsp;&emsp;8.现在知道key了,将“DDCTF{reverseME}”字符串一个个还原也能得出密码了吧!
D = 0x403052, C = 0x403053, T = 0x403042, F = 0x403050
{ = 0x40301B, r = 0x403024, e = 0x403031, v = 0x403020
s = 0x403023, M = 0x403049, E = 0x403051, } = 0x403019
然后将每一个地址与0x402FF8相减即可得到对应的ASCII:ZZ[JX#,9(9,+9QY!,成功得到flag。

## 0x3静态调试分析(IDA)
&emsp;&emsp;1.尝试完累死人的OD追码,现在试试IDA放松一下吧。
```
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@2
char v4; // @1
char v5; // @1
char v6; // @1
char Dst; // @1

v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
printf("please input code:");
scanf("%s", &v6);
sub_401000(&v6);
if ( !strcmp(&v4, "DDCTF{reverseME}") )
{
    printf("You've got it!!%s\n", &v4);
    result = 0;
}
else
{
    printf("Try again later.\n");
    result = 0;
}
return result;
}
```
很清晰的看到,v6是我们输入的值,调用00401000函数,接着比较v4和DDCTF{reverseME}是否相等。

&emsp;&emsp;2.那么我就进去看一下00401000函数内部啥样子。
```
unsigned int __cdecl sub_401000(const char *a1)//
{
_BYTE *v1; // ecx@0
unsigned int v2; // edi@1
unsigned int result; // eax@1
int v4; // ebx@2

v2 = 0;
result = strlen(a1);//计算输入字符串的长度
if ( result )
{
    v4 = a1 - v1;
    do
    {
      *v1 = byte_402FF8];
      ++v2;
      ++v1;
      result = strlen(a1);
    }
    while ( v2 < result );
}
return result;//返回字符串长度
}
```
然后有点懵,外面v4没有传进来,里面怎么改变它的值???

&emsp;&emsp;3.不过我可是已经OD把它掀了个底朝天,重点看循环部分。
```
    do
    {
      *v1 = byte_402FF8];这个v1其实就是V4了,看注释是ecx,OD调试时我就知道了ecx保存计算后的字符串
      ++v2;//+1控制循环
      ++v1;//+1赋值
      result = strlen(a1);//a1也就是输入字符串的长度
    }
    while ( v2 < result );//循环控制条件
```
byte_402FF8是个常量字符串:前面部分不知道是啥,从00403018开始为:
~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!
然后出去这里面的v1(外面的&v4)与DDCTF{reverseME}比较就完事了。

## 0x3逆算法
&emsp;&emsp;1.因为字符数组有部分显示不过来,我就不用上面那个字符串了,直接二进制复制。
00 00 00 00 00 00 00 00 64 CC 3D CC 9B 33 C2 33 FF FF FF FF FF FF FF FF FE FF FF FF 01 00 00 00
7E 7D 7C 7B 7A 79 78 77 76 75 74 73 72 71 70 6F 6E 6D 6C 6B 6A 69 68 67 66 65 64 63 62 61 60 5F
5E 5D 5C 5B 5A 59 58 57 56 55 54 53 52 51 50 4F 4E 4D 4C 4B 4A 49 48 47 46 45 44 43 42 41 40 3F
3E 3D 3C 3B 3A 39 38 37 36 35 34 33 32 31 30 2F 2E 2D 2C 2B 2A 29 28 27 26 25 24 23 22 21

&emsp;&emsp;2.整理一下,逆算法如下:
```
int main()
{
        //密码表
        int arr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xCC, 0x3D, 0xCC, 0x9B, 0x33,
                0xC2, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0x01, 0x00,
                0x00, 0x00, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71,
                0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 0x63, 0x62, 0x61,
                0x60, 0x5F, 0x5E, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51,
                0x50, 0x4F, 0x4E, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41,
                0x40, 0x3F, 0x3E, 0x3D, 0x3C, 0x3B, 0x3A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
                0x30, 0x2F, 0x2E, 0x2D, 0x2C, 0x2B, 0x2A, 0x29, 0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21 };

        char ch = "DDCTF{reverseME}";//密码


        for (int i = 0; ch != 0;i++)//DDCTF{reverseME},逐个比较
        {
                for (int j = 0; j < 126; j++)//查找下标
                {
                        if (arr == ch)//找到下标,替换成ASCII字符
                        {
                                ch = j;
                                break;//开始下一次循环
                        }
                }
        }
       
        printf("%s",ch);//输出解密后的字符串

        system("pause");
        return 0;
}
```
成功解密字符串:ZZ[JX#,9(9,+9QY!

## 0x4总结
&emsp;&emsp;1.我个人不太会用IDA,因为一开始我是从论坛零基础学脱壳教程入门,所以习惯先OD。
&emsp;&emsp;2.因为看不懂汇编,很多时候只能靠百度,解密的时候习惯通过输入和输出寻找变量之间的关系,建立数学模型,也就是靠猜了。
&emsp;&emsp;3.建议坛友们也可以练练手,这个程序就一个函数,OD反汇编出来也就那么几行代码,一句一句的对照百度解释的指令含义,既能学习汇编知识,又能锻炼调试能力。
&emsp;&emsp;4.虽然花了大半天很累,但是很值得,最终提交的flag:flag{ZZ

总结:楼主是个小菜鸟,离了教程啥都不会!

魅惑灬 发表于 2020-5-17 14:52

深爱我的女孩 发表于 2020-5-17 14:58

楼主加油,动力源于自己,前来支持!

hmily65 发表于 2020-5-17 15:26

学习了,感谢分享

白长青丶 发表于 2020-5-17 15:54

学习了 楼主加油!

Ryan袁奥 发表于 2020-5-17 16:34

wangzhiguo9807 发表于 2020-5-17 17:35

真羡慕你有这个毅力学习,我都是断断续续的。求帮助,

堕落怪物 发表于 2020-5-17 17:39

很棒的文章,仔细读了

zjf123456 发表于 2020-5-17 21:52

感谢分享!

努力的小七 发表于 2020-5-17 22:00

技术贴,可以慢慢学习了
页: [1] 2
查看完整版本: 学破解第103天,《攻防世界reverse练习区Windows_Reverse1》分析