学破解第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工具查壳:
4.根据上述信息,知道这是一个32位无壳的MFC程序,可以使用OD和IDA来调试。
0x2寻找爆破点
1.对于window程序不像控制台程序,IDA找到main函数,一层层看就完了,这找到winmain函数,也不知道怎么找到逻辑,还是和我没有window编程基础有关。
2.还是先用OD找到爆破点,定位关键函数再去看代码对比。
2.那么我们OD载入程序,F9运行起来,随便输入123456,点击验证,弹出错误提示,再去401000处搜索一下字符串:
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,[local.1]
0040185A |. C64415 DC 00 mov byte ptr ss:[ebp+edx-0x24],0x0
0040185F |. 8B45 BC mov eax,[local.17] ; cm.004035C0
00401862 |. 50 push eax ; /s2 = 00000001 ???
00401863 |. 8D4D DC lea ecx,[local.9] ; |
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 [local.17],cm.004035C0 ; KanXueCTF2019JustForhappy
00401811 |. C785 50FFFFFF>mov [local.44],cm.00403580 ; abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ
有两个可疑字符串,继续看:
0040181B |> /8B45 FC /mov eax,[local.1] ; eax=0
0040181E |. |8B4D 08 |mov ecx,[arg.1] ; ecx=0018F700
00401821 |. |833C81 3E |cmp dword ptr ds:[ecx+eax*4],0x3E ; 堆栈 ds:[0018F700]=00000001
00401825 |. |7D 30 |jge short cm.00401857 ; 大于等于62则跳转
00401827 |. |8B55 FC |mov edx,[local.1] ; edx=0
0040182A |. |8B45 08 |mov eax,[arg.1] ; eax=0018F700
0040182D |. |833C90 00 |cmp dword ptr ds:[eax+edx*4],0x0 ; 堆栈 ds:[0018F700]=00000001
00401831 |. |7C 24 |jl short cm.00401857 ; 小于0则跳转
00401833 |. |8B4D FC |mov ecx,[local.1] ; ecx=0
00401836 |. |8B55 08 |mov edx,[arg.1] ; edx=0018F700
00401839 |. |8B048A |mov eax,dword ptr ds:[edx+ecx*4] ; eax=1
0040183C |. |8B4D FC |mov ecx,[local.1] ; ecx=0
0040183F |. |8B95 50FFFFFF |mov edx,[local.44] ; edx="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
00401845 |. |8A0402 |mov al,byte ptr ds:[edx+eax] ; al = b
00401848 |. |88440D DC |mov byte ptr ss:[ebp+ecx-0x24],al ; 存入一块内存
0040184C |. |8B4D FC |mov ecx,[local.1] ; ecx=0
0040184F |. |83C1 01 |add ecx,0x1 ; ecx+1
00401852 |. |894D FC |mov [local.1],ecx ; 变量1=ecx
00401855 |.^\EB C4 \jmp short cm.0040181B ; 开始下一轮循环
这是一个循环结构,每次我输入的处理一个我输入的变量存入某一块内存地址ebp+ecx-0x24,[ecx+eax*4]这个在学习数据结构时我们知道它大概率是数组,而且是4字节对齐的,从一个数组中取出数据al=edx+eax就是赋值的过程,a1=edx[i],i是我输入的值,并且我输入的值每一位取值必须在[0,62)这个区间。
6.F4跳过循环,可以看到我输入的123456,变成了bcdefg:
00401862 |. 50 push eax ; /s2 = "KanXueCTF2019JustForhappy"
00401863 |. 8D4D DC lea ecx,[local.9] ; |
00401866 |. 51 push ecx ; |s1 = "bcdefg"
00401867 |. E8 84070000 call <jmp.&MSVCRT.strcmp> ; \strcmp
再回过头来看看这两行的可疑的字符串:
KanXueCTF2019JustForhappy
abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ
第一个没得说,一会和我输入的字符串处理后作比较,第二个字符串我们看看,这个字符串所在的起始地址为:0x403580,再看前面赋值语句:
0040183F |. 8B95 50FFFFFF |mov edx,[local.44] ; edx="abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ"
00401845 |. 8A0402 |mov al,byte ptr ds:[edx+eax] ; al = b
edx的值就是0x403580,是不是一下子很清楚了,a1=arr[1]=b,a2=arr[2]=c,a1=arr[3]=c,以此类推通过替换的方式得到bcdefg。
0x3代码分析
1.将程序用32位IDA打开,找到004017B0这个函数,打开看一看代码涨啥样:
int __cdecl sub_4017F0(int a1)
{
int result; // eax
char Str1[28]; // [esp+D8h] [ebp-24h] BYREF
int v3; // [esp+F4h] [ebp-8h]
int v4; // [esp+F8h] [ebp-4h]
v4 = 0;
v3 = 0;
while ( *(int *)(a1 + 4 * v4) < 62 && *(int *)(a1 + 4 * v4) >= 0 )
{
Str1[v4] = aAbcdefghiabcde[*(_DWORD *)(a1 + 4 * v4)];
++v4;
}
Str1[v4] = 0;
if ( !strcmp(Str1, "KanXueCTF2019JustForhappy") )
result = sub_401770();
else
result = sub_4017B0();
return result;
}
aAbcdefghiabcde这个数组存的就是“abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ”,上述代码反过来就是遍历这个数组中的每一个元素和KanXueCTF2019JustForhappy"相等就保存下来,逆向的代码为:
char arr[63] = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
while (v4 < strlen(arr))
{
if (flag[i] == arr[v4])
//取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; // [esp-4h] [ebp-C4h]
int v5[26]; // [esp+4Ch] [ebp-74h] BYREF
int i; // [esp+B4h] [ebp-Ch]
char *Str; // [esp+B8h] [ebp-8h]
CWnd *v8; // [esp+BCh] [ebp-4h]
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]; ++i )
{
if ( Str[i] > 57 || Str[i] < 48 )
{
if ( Str[i] > 122 || Str[i] < 97 )
{
if ( Str[i] > 90 || Str[i] < 65 )
sub_4017B0();
else
v5[i] = Str[i] - 29;
}
else
{
v5[i] = Str[i] - 87;
}
}
else
{
v5[i] = Str[i] - 48;
}
}
return sub_4017F0(v5);
}
找到了转换的函数,这种层层嵌套读起来真是要人命,同样逆向过来,大于变成小于等于,小于变成大于等于,-变成+,得到:
if (v4 + 48 >= 48 && v4 + 48 <= 57)
{
str[i] = v4 + 48;
}
if (v4 + 87 >= 97 && v4 + 87 <= 122)
{
str[i] = v4 + 87;
}
if (v4 + 29 >= 65 && v4 + 29 <= 90)
{
str[i] = v4 + 29;
}
4.最终整理后的代码如下:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
char flag[26] = "KanXueCTF2019JustForhappy";
char arr[63] = "abcdefghiABCDEFGHIJKLMNjklmn0123456789opqrstuvwxyzOPQRSTUVWXYZ";
char str[26] = "";
int i = 0, v4 = 0;
while (i < strlen(flag)){
while (v4 < strlen(arr))
{
if (flag[i] == arr[v4]) {
if (v4 + 48 >= 48 && v4 + 48 <= 57)
{
str[i] = v4 + 48;
}
if (v4 + 87 >= 97 && v4 + 87 <= 122)
{
str[i] = v4 + 87;
}
if (v4 + 29 >= 65 && v4 + 29 <= 90)
{
str[i] = v4 + 29;
}
i++;
v4=0;
break;
}
++v4;
}
}
cout << str << endl;
system("pause");
}
5.运行后得到:j0rXI4bTeustBiIGHeCF70DDM,,加上flag{}就是flag{j0rXI4bTeustBiIGHeCF70DDM},输入答案验证一下。
0x4总结
1.通过错误提示定位关键字符串,找到关键跳,关键跳前面的一般就是算法了。
2.分析的时候不要大意,字符串可能不只经过一次变换,对字符串变化流程加以分析。
3.编写逆向算法时,如果看不懂原算法,那就利用程序自身算法,脑袋里一定要反过来思考,才能写出逆向程序。
4.这道1分题花了我6个多小时,还是基础不牢固啊。
0x5参考资料
1.无
PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!