小菜鸟一枚 发表于 2020-11-28 21:30

学破解第132天,《攻防世界reverse练习区Replace》学习

本帖最后由 小菜鸟一枚 于 2020-11-28 21:34 编辑

## 学破解第132天,《攻防世界reverse练习区Replace》学习
前言:
  从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。

  2017年12月16日,通过19元注册码注册论坛账号,开始做伸手党,潜水一年多,上来就是找软件。(拿论坛高大上的软件出去装X)

  2018年8月某一天,报名了华中科技大学网络教育本科(计算机科学与技术专业)2018级秋季。(开始提升学历)

  2019年6月17日,不愿再做小菜鸟一枚,开始零基础学习破解。(感谢小糊涂虫大哥在我刚开始学习脱壳时,录制视频解答我的问题)

  2020年7月7日,感谢H大对我的鼓励,拥有了第一篇获得优秀的文章。(接下来希望学习逆向,逆天改命)

  坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:(https://www.52pojie.cn/thread-1278021-1-1.html)
立帖为证!--------记录学习的点点滴滴

### 0x1下载文件
  1.是一个压缩包,下载解压出来后是一个exe程序。

  2.看到exe小菜鸟就放心,论坛的xp虚拟机太强大了,看小菜鸟如何用OD+lordPE+IRI脱UPX壳。

  3.小菜鸟将程序放进exe程序,发现居然打不开?小菜鸟告诉自己不慌,在win7真机环境下下载一个吾爱OD,运行程序居然自己退出了。

  4.小菜鸟只好直接打开这个程序,观察观察,发现就是要求输入正确的key,否则退出:


### 0x2新工具X32dbg的学习
  1.经过尝试,小菜鸟发现x32dbg工具能正常打开exe程序,虽然是一个陌生的工具,小菜鸟还是照着吾爱OD的操作,单步一次
```
013D7D20 | 60                     | pushad                                  |
013D7D21 | BE 00703D01            | mov esi,replace.13D7000               |
013D7D26 | 8DBE 00A0FFFF            | lea edi,dword ptr ds:         |
013D7D2C | 57                     | push edi                              |
013D7D2D | 83CD FF                  | or ebp,FFFFFFFF                         |
013D7D30 | EB 10                  | jmp replace.13D7D42                     |
```

  2.右键-》数据窗口中跟随,然而x32dbg工具中我找了半天,也没有找到数据窗口,最左下角是内存窗口,于是小菜鸟就跟随到内存1窗口,在内存1窗口中设置硬件断点,运行,取消硬件断点,单步几次到达OEP,vc++程序的入口特征就不用说了吧,就它了
```
013D13B0 | E8 DC020000            | call replace.13D1691                  |
013D13B5 | E9 91FEFFFF            | jmp replace.13D124B                     |
013D13BA | 55                     | push ebp                              |
```

  3.接下来怎么办呢?lordpe只能显示60个进程,看不到这个,没办法,小菜鸟又去下载了PEtool,都是英文,小菜鸟琢磨不透,最后只能试试x32dbg自带的scylla工具,还好,界面比较简单,能看懂,还能自己识别我找到的OEP,挺方便的,自动查找IAT,获取输入表,先dump,然后Fix Dump修复,程序脱壳成功,然后运行程序,程序异常了???


  4.这个问题打了小菜鸟一个措手不及,对x32dbg小菜鸟也不熟,完全不知道问题到底是出在哪了?UPX脱壳步骤应该也是没有问题的啊?

  5.小菜鸟没办法,百度c0000005异常也没找到什么有用的知识,说的都是兼容性什么的。异常偏移0000169f这个值引起了我的注意,想去x32dbg重新跑一下程序看看,这一看,小菜鸟发现程序的每次入口点都不一样:
```
01357D20 | 60                     | pushad                                  |
00DB7D20 | 60                     | pushad                                  |
013A7D20 | 60                     | pushad                                  |
00B77D20 | 60                     | pushad                                  |
```

  6.小菜鸟这下就知道是动态基址的问题的了,于是就去论坛搜索相关帖子,找到了用studype固定基址的办法(见文章末尾参考资料),固定基址后,程序正常运行。


### 0x3使用IDA分析程序
  1.程序已经被脱壳了,那就好办了,丢到IDA里面走起来,main函数已经被自动识别出来了,直接F5
```
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Buf; //
char Dst; //

Buf = 0;
memset(&Dst, 0, 0x27u);
printf("Welcome The System\nPlease Input Key:");
gets_s(&Buf, 0x28u);
if ( strlen(&Buf) - 35 <= 2 )
{
    if ( sub_13D1090(&Buf) == 1 )
      printf("Well Done!\n");
    else
      printf("Your Wrong!\n");
}
return 0;
}
```

  2.跟着代码看一看,首先从键盘接收长度为40(0x28u)的字符串,然后计算字符串的长度减去35要小于等于2才会进入内层if,然后满足sub_13D1090(&Buf) == 1才是正确。

  3.进入sub_13D1090这个函数一探究竟,代码如下:
```
signed int __fastcall sub_13D1090(int a1, int a2)
{
int v2; // ebx
int v4; // edx
char v5; // al
int v6; // esi
int v7; // edi
char v8; // al
int v9; // eax
char v10; // cl
int v11; // eax
int v12; // ecx

v2 = a1;
if ( a2 != 35 )
    return -1;
v4 = 0;
while ( 1 )
{
    v5 = *(_BYTE *)(v4 + v2);
    v6 = (v5 >> 4) % 16;
    v7 = (16 * v5 >> 4) % 16;
    v8 = byte_13D2150;
    if ( v8 < 48 || v8 > 57 )
      v9 = v8 - 87;
    else
      v9 = v8 - 48;
    v10 = byte_13D2151;
    v11 = 16 * v9;
    if ( v10 < 48 || v10 > 57 )
      v12 = v10 - 87;
    else
      v12 = v10 - 48;
    if ( (unsigned __int8)byte_13D21A0 != ((v11 + v12) ^ 0x19) )
      break;
    if ( ++v4 >= 35 )
      return 1;
}
return -1;
}
```

  4.还是继续使用我们的逆向思维,倒着看,要使if等于1,也就是必须return 1,要想return 1,v4要大于等于34,然后上面一堆的if else,小菜鸟已经懵圈了。

### 0x4换x32dbg动态调试
  1.首先,没得说,直接13D1090下断点,构造长度为35位的字符串12345678912345678912345678912345678,输入,运行:
```
013D1090 | 53                     | push ebx                              | ebx:"12345678912345678912345678912345678"
013D1091 | 8BD9                     | mov ebx,ecx                           | ebx:"12345678912345678912345678912345678", ecx:"12345678912345678912345678912345678"
013D1093 | 83FA 23                  | cmp edx,23                              | 23:'#'
013D1096 | 74 05                  | je 1.13D109D                            | 如果字符串长度不为35(0x23)直接返回-1
```

  2.看的小菜鸟头皮发麻,继续看,进入了while循环,根据前面的推导,必须return 1退出循环才行,那么v4显然就是控制循环退出的条件!再看看if ( v8 < 48 || v8 > 57 )这一句,我们判断是不是数字经常用的,如果是数字减去48,否则减去87,v8和v10是从两个数组中取数据。
**既然如此,那么这两个数组的元素是什么呢?**
直接x32dbg内存中就能看到:
```
byte ptr ==32 '2'
byte ptr ==61 'a'
```
真的是看的清清楚楚,明明白白啊!

  3.再看v5 = v4 + v2,v2是ebx,看看x32dbg,ebx就是我们输入的字符串12345678912345678912345678912345678,接着就看v6和v7是在干啥了,右移4位对16取余, v7先乘16在右移四位对16取余,按我的理解就是v6取v5的低4位,v7取v5的高4位?

  4.接着也就是看最重要一句代码,跳向失败的条件:if ( (unsigned \_\_int8)byte\_13D21A0 != ((v11 + v12) ^ 0x19) ),每一次循环必须让这个等式成立。
**13D21A0这个数组的值呢?**
我姑且当二维数组来看,由于v7是对16取余,所以这个数组应该有16*16=256个元素,关键是这些元素的值是多少,真的是要人命啊,有没有技巧?

......
一小时后,小菜鸟通过16\*0+0,一直计算到16\*15+15,通过基址+偏移量终于得到了这256个元素,小菜鸟真是太傻了,百度一下就有(不知道这些大佬们怎么算的):
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16


  5.接下来就开始写代码爆破了:
```
// demo_ctf1.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "stdlib.h"


int main()
{
      int v4 = 0, v5 = 0, v6 = 0, v7 = 0, v8 = 0, v9 = 0, v10 = 0, v11 = 0, v12 = 0;

      char arr1[] = "2a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6";
      char arr2[] = "a49f69c38395cde96d6de96d6f4e025484954d6195448def6e2dad67786e21d5adae6";

      char flag = "";

      char arr3[] = {
                0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
                0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
                0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
                0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
                0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
                0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
                0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
                0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
                0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
                0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
                0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
                0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
                0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
                0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
                0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
                0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
      };

      while (1)//前面的代码直接照搬IDA反编译的结果
      {
                v8 = arr1;
                if (v8 < 48 || v8 > 57)
                        v9 = v8 - 87;
                else
                        v9 = v8 - 48;
                v10 = arr2;
                v11 = 16 * v9;
                if (v10 < 48 || v10 > 57)
                        v12 = v10 - 87;
                else
                        v12 = v10 - 48;
                for (v5 = 32; v5 < 127; v5++)//代入一个个去试等式成立的条件
                {
                        v6 = (v5 >> 4) % 16;
                        v7 = (16 * v5 >> 4) % 16;
                        if ((unsigned __int8)arr3 == ((v11 + v12) ^ 0x19))//找到正确的字符就加入flag
                        {
                              flag = v5;
                              break;
                        }
                }

                v4++;
                if (v4 >= 35)
                {
                        flag = 0;//flag字符串末尾补\0
                        break;
                }
      }

      printf("%s\n", flag);
      system("pause");
      return 0;
}
```


### 0x5总结
  1.x32dbg手脱UPX那里浪费了时间,就是个随机基址的问题。
  2.这个程序的逻辑并不难,脱壳后放入IDA可以清晰的看到爆破条件,即使看不懂那个判断函数,也可以把代码copy出来爆破。
  3.主要复习了一维数组和二维数组的识别,认识了一下x32dbg的界面和简单使用。
  4.找最后一个二维数组时,没想到什么好办法,也不会脚本,纯体力活给它一个个找出来,从前到后花了6个小时才做出来。
  5.逆向还是要耐心和技巧,逆向逆向就是倒着思考问题,由果寻因的过程。

### 0x6参考资料
1.[我的代码传记]upx脱壳重定位表修复](https://www.52pojie.cn/thread-1295193-1-1.html)
2.(https://www.cnblogs.com/DirWang/p/12236700.html)

  **PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!**

18073698066 发表于 2020-11-30 08:18

杰诺斯 发表于 2020-11-28 21:46

学习,谢谢

枫叶飞向海 发表于 2020-11-28 21:52

学习最重要的就是坚持,坚持必然会成功的

astarl 发表于 2020-11-28 22:10

开始java的的日常第34天路过,一起加油

我吃小朋友 发表于 2020-11-28 22:19

学历怎么样了

look1232000 发表于 2020-11-28 22:30

有高清视频学基础么?

aibgds 发表于 2020-11-28 23:26


支持一下

5524152 发表于 2020-11-28 23:33

支持支持

safer 发表于 2020-11-28 23:45

支持支持,学习一下。

吾心随风 发表于 2020-11-29 00:10

支持一下
页: [1] 2 3 4 5 6 7 8
查看完整版本: 学破解第132天,《攻防世界reverse练习区Replace》学习