学破解第140天,《攻防世界reverse练习区流浪者》学习
## 学破解第140天,《攻防世界reverse练习区流浪者》学习前言:
从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。
2017年12月16日,通过19元注册码注册论坛账号,开始做伸手党,潜水一年多,上来就是找软件。(拿论坛高大上的软件出去装X)
2018年8月10日,报名了华中科技大学网络教育本科(计算机科学与技术专业)2018级秋季。(开始提升学历)
2019年6月17日,不愿再做小菜鸟一枚,开始零基础学习破解。(感谢小糊涂虫大哥在我刚开始学习脱壳时,录制视频解答我的问题)
2020年7月7日,感谢H大对我的鼓励,拥有了第一篇获得优秀的文章。(接下来希望学习逆向,逆天改命)
2021年8月11日,华科学位英语2次不过,仅取得了毕业证书,学业提升失败,开始琢磨考注册类和职称类证书,谋求涨薪
坛友们,年轻就是资本,和我一起逆天改命吧,我的学习过程全部记录及学习资源:(https://www.52pojie.cn/thread-1278021-1-1.html)
立帖为证!--------记录学习的点点滴滴
### 0x1下载文件
1.下载CM,是一个压缩包,解压后发现是个exe文件。
2.通过图标来看是一个MFC程序
3.使用exeinfo工具查壳:
![https://z3.ax1x.com/2021/08/21/fvtLjJ.png](https://z3.ax1x.com/2021/08/21/fvtLjJ.png)
4.根据上述信息,知道这是一个32位无壳的MFC程序,可以使用OD和IDA来调试。
### 0x2寻找爆破点
1.对于window程序不像控制台程序,IDA找到main函数,一层层看就完了,这找到winmain函数,也不知道怎么找到逻辑,还是和我没有window编程基础有关。
2.还是先用OD找到爆破点,定位关键函数再去看代码对比。
2.那么我们OD载入程序,F9运行起来,随便输入123456,点击验证,弹出错误提示,再去401000处搜索一下字符串:
![https://z3.ax1x.com/2021/08/21/fvUoSU.png](https://z3.ax1x.com/2021/08/21/fvUoSU.png)
3.根据上图中错误提示和搜索到的字符串,定位错误弹窗的地方:
```
004017B0/$55 push ebp
004017B1|.8BEC mov ebp,esp
004017B3|.83EC 44 sub esp,0x44
004017B6|.53 push ebx
004017B7|.56 push esi
004017B8|.57 push edi
004017B9|.6A 00 push 0x0 ; /Style = MB_OK|MB_APPLMODAL
004017BB|.68 78354000 push cm.00403578 ; |错了!
004017C0|.68 70354000 push cm.00403570 ; |加油!
004017C5|.6A 00 push 0x0 ; |hOwner = NULL
004017C7|.FF15 00324000 call dword ptr ds:[<&USER32.MessageBoxA>>; \MessageBoxA
```
4.通过堆栈按回车键返回调用处:
```
00401857|> \8B55 FC mov edx,
0040185A|.C64415 DC 00mov byte ptr ss:,0x0
0040185F|.8B45 BC mov eax, ;cm.004035C0
00401862|.50 push eax ; /s2 = 00000001 ???
00401863|.8D4D DC lea ecx, ; |
00401866|.51 push ecx ; |s1 = "KanXueCTF2019JustForhappy"
00401867|.E8 84070000 call <jmp.&MSVCRT.strcmp> ; \strcmp
0040186C|.83C4 08 add esp,0x8
0040186F|.85C0 test eax,eax
00401871|.75 07 jnz short cm.0040187A ;两个字符串不相等,就跳向失败
00401873|.E8 F8FEFFFF call cm.00401770
00401878|.EB 05 jmp short cm.0040187F
0040187A|>E8 31FFFFFF call cm.004017B0 ;失败弹窗函数
```
5.接着去段首004017F0 处F2下上断点,重启跑过来看一看,这段汇编代码:
```
0040180A|.C745 BC C0354>mov ,cm.004035C0 ;KanXueCTF2019JustForhappy
00401811|.C785 50FFFFFF>mov ,cm.00403580 ;abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ
```
有两个可疑字符串,继续看:
```
0040181B|> /8B45 FC /mov eax, ;eax=0
0040181E|. |8B4D 08 |mov ecx, ;ecx=0018F700
00401821|. |833C81 3E |cmp dword ptr ds:,0x3E ;堆栈 ds:=00000001
00401825|. |7D 30 |jge short cm.00401857 ;大于等于62则跳转
00401827|. |8B55 FC |mov edx, ;edx=0
0040182A|. |8B45 08 |mov eax, ;eax=0018F700
0040182D|. |833C90 00 |cmp dword ptr ds:,0x0 ;堆栈 ds:=00000001
00401831|. |7C 24 |jl short cm.00401857 ;小于0则跳转
00401833|. |8B4D FC |mov ecx, ;ecx=0
00401836|. |8B55 08 |mov edx, ;edx=0018F700
00401839|. |8B048A |mov eax,dword ptr ds: ;eax=1
0040183C|. |8B4D FC |mov ecx, ;ecx=0
0040183F|. |8B95 50FFFFFF |mov edx, ;edx="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
00401845|. |8A0402 |mov al,byte ptr ds: ;al = b
00401848|. |88440D DC |mov byte ptr ss:,al ;存入一块内存
0040184C|. |8B4D FC |mov ecx, ;ecx=0
0040184F|. |83C1 01 |add ecx,0x1 ;ecx+1
00401852|. |894D FC |mov ,ecx ;变量1=ecx
00401855|.^\EB C4 \jmp short cm.0040181B ;开始下一轮循环
```
这是一个循环结构,每次我输入的处理一个我输入的变量存入某一块内存地址ebp+ecx-0x24,这个在学习数据结构时我们知道它大概率是数组,而且是4字节对齐的,从一个数组中取出数据al=edx+eax就是赋值的过程,a1=edx,i是我输入的值,并且我输入的值每一位取值必须在[0,62)这个区间。
6.F4跳过循环,可以看到我输入的123456,变成了bcdefg:
```
00401862|.50 push eax ; /s2 = "KanXueCTF2019JustForhappy"
00401863|.8D4D DC lea ecx, ; |
00401866|.51 push ecx ; |s1 = "bcdefg"
00401867|.E8 84070000 call <jmp.&MSVCRT.strcmp> ; \strcmp
```
再回过头来看看这两行的可疑的字符串:
KanXueCTF2019JustForhappy
abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ
第一个没得说,一会和我输入的字符串处理后作比较,第二个字符串我们看看,这个字符串所在的起始地址为:0x403580,再看前面赋值语句:
0040183F|.8B95 50FFFFFF |mov edx, ;edx="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
00401845|.8A0402 |mov al,byte ptr ds: ;al = b
edx的值就是0x403580,是不是一下子很清楚了,a1=arr=b,a2=arr=c,a1=arr=c,以此类推通过替换的方式得到bcdefg。
### 0x3代码分析
1.将程序用32位IDA打开,找到004017B0这个函数,打开看一看代码涨啥样:
```
int __cdecl sub_4017F0(int a1)
{
int result; // eax
char Str1; // BYREF
int v3; //
int v4; //
v4 = 0;
v3 = 0;
while ( *(int *)(a1 + 4 * v4) < 62 && *(int *)(a1 + 4 * v4) >= 0 )
{
Str1 = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
++v4;
}
Str1 = 0;
if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") )
result = sub_401770();
else
result = sub_4017B0();
return result;
}
```
aAbcdefghiabcde这个数组存的就是“abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ”,上述代码反过来就是遍历这个数组中的每一个元素和KanXueCTF2019JustForhappy"相等就保存下来,逆向的代码为:
```
char arr = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
while (v4 < strlen(arr))
{
if (flag == arr)
//取v4的值
}
```
为了循环判断,所以外层还需要加一个循环。
2.F5反编译的代码和我分析的差不多,接下来就写代码跑flag了,等等,似乎有什么不对?我输入123456是字符,为什么加密的时候作为数字下标呢???显然前面有将字符串如何变成数字下标的函数。
```
004019C2|.52 push edx
004019C3|.E8 28FEFFFF call cm.004017F0
```
通过OD查找调用过程,发现从004019C3这个地址调用的004017F0函数,向上翻找到段首00401890。
3.IDA中找到这个函数,F5反编译一下:
```
int __thiscall sub_401890(CWnd *this)
{
CWnd *v1; // eax
int v2; // eax
struct CString *v4; //
int v5; // BYREF
int i; //
char *Str; //
CWnd *v8; //
v8 = this;
v4 = (CWnd *)((char *)this + 100);
v1 = CWnd::GetDlgItem(this, 1002);
CWnd::GetWindowTextA(v1, v4);
v2 = sub_401A30((char *)v8 + 100);
Str = CString::GetBuffer((CWnd *)((char *)v8 + 100), v2);
if ( !strlen(Str) )
return CWnd::MessageBoxA(v8, "请输入pass!", 0, 0);
for ( i = 0; Str; ++i )
{
if ( Str > 57 || Str < 48 )
{
if ( Str > 122 || Str < 97 )
{
if ( Str > 90 || Str < 65 )
sub_4017B0();
else
v5 = Str - 29;
}
else
{
v5 = Str - 87;
}
}
else
{
v5 = Str - 48;
}
}
return sub_4017F0(v5);
}
```
找到了转换的函数,这种层层嵌套读起来真是要人命,同样逆向过来,大于变成小于等于,小于变成大于等于,-变成+,得到:
```
if (v4 + 48 >= 48 && v4 + 48 <= 57)
{
str = v4 + 48;
}
if (v4 + 87 >= 97 && v4 + 87 <= 122)
{
str = v4 + 87;
}
if (v4 + 29 >= 65 && v4 + 29 <= 90)
{
str = v4 + 29;
}
```
4.最终整理后的代码如下:
```
#include <iostream>
#include <thread>
using namespace std;
int main()
{
char flag = "KanXueCTF2019JustForhappy";
char arr = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
char str = "";
int i = 0, v4 = 0;
while (i < strlen(flag)){
while (v4 < strlen(arr))
{
if (flag == arr) {
if (v4 + 48 >= 48 && v4 + 48 <= 57)
{
str = v4 + 48;
}
if (v4 + 87 >= 97 && v4 + 87 <= 122)
{
str = v4 + 87;
}
if (v4 + 29 >= 65 && v4 + 29 <= 90)
{
str = v4 + 29;
}
i++;
v4=0;
break;
}
++v4;
}
}
cout << str << endl;
system("pause");
}
```
5.运行后得到:j0rXI4bTeustBiIGHeCF70DDM,,加上flag{}就是flag{j0rXI4bTeustBiIGHeCF70DDM},输入答案验证一下。
![https://z3.ax1x.com/2021/08/21/fxpOtU.png](https://z3.ax1x.com/2021/08/21/fxpOtU.png)
### 0x4总结
1.通过错误提示定位关键字符串,找到关键跳,关键跳前面的一般就是算法了。
2.分析的时候不要大意,字符串可能不只经过一次变换,对字符串变化流程加以分析。
3.编写逆向算法时,如果看不懂原算法,那就利用程序自身算法,脑袋里一定要反过来思考,才能写出逆向程序。
4.这道1分题花了我6个多小时,还是基础不牢固啊。
### 0x5参考资料
1.无
**PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!** 支持你这种坚持学习的精神!不管干任何事情都不能缺这种持之以恒的精神!给你点赞!!!! 真的从小菜鸟变成大佬了。我从菜鸟变成了菜鸡{:301_999:} 网络教育本科是不是函授?不用到学校坐到教室上课的那种? https://www.aliyundrive.com/s/ifzfsgrHHQn 加油,又兴趣分析下这个吗? 持之以恒,才能无懈可击 学习了不错 谢谢分享 佩服楼主的坚持。给您点赞 {:1_893:} 待道友渡劫成功时,再考虑修不修真