学破解第129天,《攻防世界reverse练习区key》学习
## 学破解第129天,《攻防世界reverse练习区key》学习前言:
  从小学到大专(计算机网络技术专业),玩过去的,所以学习成绩惨不忍睹,什么证书也没考,直到找不到工作才后悔,不知道怎么办才好。
  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.去攻防世界找到这个key题目,下载文件,PEID查壳,Microsoft Visual C++程序
  2.运行程序看一看,直接黑窗口一闪而过,会自动退出,好像看不出什么?
### 0x2动静结合调试分析
  1.既然会退出,那就找原因,丢到OD里面下一个exitprocess断点,停在了:
```
0031F7FC 7079F4ED/CALL 到 ExitProcess 来自 ucrtbase.7079F4E7
0031F800 FFFFFFFF\ExitCode = 0xFFFFFFFF
0031F804 00000000
0031F808 7079F474返回到 ucrtbase.7079F474 来自 ucrtbase.7079F4B4
```
不知道怎么回溯到用户代码,可能经过多层调用,换成exit断点试试
```
00C3123E|. /74 25 je short key.00C31265
00C31240|. |8B0D C850C300 mov ecx,dword ptr ds:[<&MSVCP140.std::ce>;msvcp140.std::cerr
00C31246|. |BA E452C300 mov edx,key.00C352E4 ;?W?h?a?t h?a?p?p?e?n?
00C3124B|. |68 502CC300 push key.00C32C50
00C31250|. |E8 AB170000 call key.00C32A00
00C31255|. |8BC8 mov ecx,eax ;msvcp140.std::cerr
00C31257|. |FF15 9850C300 call dword ptr ds:[<&MSVCP140.std::basic>;msvcp140.std::basic_ostream<wchar_t,std::char_traits<wchar_t> >::operator<<
00C3125D|. |6A FF push -0x1 ; /status = FFFFFFFF (-1.)
00C3125F|. |FF15 6851C300 call dword ptr ds:[<&api-ms-win-crt-runt>; \exit
00C31265|> \8D55 AC lea edx,
```
往上翻,记下段首00C31100
  2.看退出函数的返回后的用户代码段首是00C31100 ,用IDA F5反编译一下401100这个函数,看,主要用户代码都出来了,省时省力。
```
int sub_401100()
{
signed int v0; // esi@1
signed int v1; // esi@3
unsigned int v2; // edi@3
void **v3; // ebx@3
void **v4; // eax@4
int v5; // ecx@7
int v6; // ST04_4@7
int v7; // ST08_4@7
int v8; // ST0C_4@7
int v9; // eax@8
char *v10; // esi@10
int v11; // ecx@16
void **v12; // eax@18
int v13; // eax@20
int v14; // ecx@20
int v15; // eax@21
int v16; // eax@21
int v17; // eax@21
int v18; // eax@21
int v19; // eax@21
int v20; // eax@21
int v21; // eax@21
const char *v22; // edx@21
int v23; // eax@23
int result; // eax@27
int (__cdecl *v25)(int); // @20
int Dst; // @7
char v27; // @7
char v28; // @10
int v29; // @16
char v30; // @16
int v31; // @16
int v32; // @16
char v33; // @16
FILE *File; // @10
char v35; // @23
void *v36; // @1
int v37; // @1
unsigned int v38; // @1
void *v39; // @1
int v40; // @1
unsigned int v41; // @1
void *Memory; // @1
int v43; // @1
unsigned int v44; // @1
__int128 v45; // @1
__int16 v46; // @1
char v47; // @1
int v48; // @1
v41 = 15;
v40 = 0;
LOBYTE(v39) = 0;
v48 = 0;
v38 = 15;
v37 = 0;
LOBYTE(v36) = 0;
LOBYTE(v48) = 1;
v0 = 0;
v43 = 1684630885;
LOWORD(v44) = 97;
*(_OWORD *)Memory = xmmword_40528C;
v46 = 11836;
v47 = 0;
v45 = xmmword_4052A4;
do
{
sub_4021E0(1u, (*((_BYTE *)Memory + v0) ^ *((_BYTE *)&v45 + v0)) + 22);
++v0;
}
while ( v0 < 18 );
v1 = 0;
v44 = 15;
v43 = 0;
LOBYTE(Memory) = 0;
LOBYTE(v48) = 2;
v2 = v38;
v3 = (void **)v36;
do
{
v4 = &v36;
if ( v2 >= 0x10 )
v4 = v3;
sub_4021E0(1u, *((_BYTE *)v4 + v1++) + 9);
}
while ( v1 < 18 );
memset(&Dst, 0, 0xB8u);
sub_401620(v5, v6, v7, v8);
LOBYTE(v48) = 3;
if ( v27[*(_DWORD *)(Dst + 4)] & 6 )
{
v9 = sub_402A00(std::cerr, "?W?h?a?t h?a?p?p?e?n?");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v9, sub_402C50);
exit(-1);
}
sub_402E90(&Dst, &v39);
v10 = &v28;
if ( File )
{
if ( !(unsigned __int8)sub_4022F0(&v28) )
v10 = 0;
if ( fclose(File) )
v10 = 0;
}
else
{
v10 = 0;
}
v33 = 0;
v30 = 0;
std::basic_streambuf<char,std::char_traits<char>>::_Init(&v28);
v31 = dword_408590;
File = 0;
v32 = dword_408594;
v29 = 0;
if ( !v10 )
std::basic_ios<char,std::char_traits<char>>::setstate((char *)&Dst + *(_DWORD *)(Dst + 4), 2, 0);
v12 = Memory;
if ( v44 >= 0x10 )
v12 = (void **)Memory;
v13 = sub_4020C0(v11, v40, v12, v43);
v14 = std::cout;
v25 = sub_402C50;
if ( v13 )
{
v22 = "=W=r=o=n=g=K=e=y=";
}
else
{
v15 = sub_402A00(std::cout, "|------------------------------|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v15, sub_402C50);
v16 = sub_402A00(std::cout, "|==============================|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v16, sub_402C50);
v17 = sub_402A00(std::cout, "|==============================|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v17, sub_402C50);
v18 = sub_402A00(std::cout, "|==============================|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v18, sub_402C50);
v19 = sub_402A00(std::cout, "\\/\\/\\/\\/\\==============|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v19, sub_402C50);
v20 = sub_402A00(std::cout, " \\/\\/\\/\\/\\=============|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v20, sub_402C50);
v21 = sub_402A00(std::cout, " |-------------|");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v21, sub_402C50);
std::basic_ostream<char,std::char_traits<char>>::operator<<(std::cout, sub_402C50);
v14 = std::cout;
v22 = "Congrats You got it!";
v25 = sub_402C50;
}
v23 = sub_402A00(v14, v22);
std::basic_ostream<char,std::char_traits<char>>::operator<<(v23, v25);
sub_401570(&v35);
std::basic_ios<char,std::char_traits<char>>::~basic_ios<char,std::char_traits<char>>(&v35);
if ( v44 >= 0x10 )
sub_402630(Memory, v44 + 1);
if ( v2 >= 0x10 )
sub_402630(v3, v2 + 1);
result = v41;
if ( v41 >= 0x10 )
result = sub_402630(v39, v41 + 1);
return result;
}
```
  3.代码是有了,但是还是没办法看出为什么退出了,搜一下可疑字符串,看到了:
一个文件路径比较可疑,点开看看,有一个API函数在尝试打开这个文件。
```
01052583|.6A 40 push 0x40
01052585|.6A 01 push 0x1
01052587|.68 B8520501 push key.010552B8 ;C:\Users\CSAW2016\haha\flag_dir\flag.txt
0105258C|.FF15 BC500501 call dword ptr ds:[<&MSVCP140.std::_Fiopen>] ;msvcp140.std::_Fiopen
```
  4.那我就去对应目录创建一个对应的文件,内容随便写一个52pojie试试,打开cmd窗口,输入:
mkdir C:\Users\CSAW2016\haha\flag_dir\
echo 52pojie>C:\Users\CSAW2016\haha\flag_dir\flag.txt
然后可以看到跳过了0004126E 这个退出函数,然后再看IDA中的代码,v13为1,就提示错误的key,可以看到最近的地方是000420C0影响了v13的值。
  5.OD下断点跑过来一看:
```
00041317|.FF75 D4 push ;0x12
0004131A|.0F4345 C4 cmovnb eax,
0004131E|.50 push eax ;idg_cni~bjbfi|gsxb
0004131F|.FF75 BC push ;0x7
00041322|.51 push ecx ;52pojie
00041323|.8D4D AC lea ecx,
00041326|.E8 950D0000 call key.000420C0
```
咦,为什么IDA里面会有5个参数看一下thiscall函数调用约定,从右往左传递,第一个参数是this指针。
  6.又是一段很长的代码,我们记住自己的目的就好,返回值必须为0。
```
signed int __thiscall sub_4020C0(int this, int a2, unsigned int a3, int a4, unsigned int a5)
{
unsigned int v5; // edi@1
unsigned int v6; // edx@5
int v7; // esi@8
unsigned int v8; // edx@8
bool v9; // cf@12
unsigned __int8 v10; // al@14
unsigned __int8 v11; // al@16
unsigned __int8 v12; // al@18
signed int result; // eax@19
v5 = a3;
if ( *(_DWORD *)(this + 16) < a3 )
v5 = *(_DWORD *)(this + 16);
if ( *(_DWORD *)(this + 20) >= 0x10u )
this = *(_DWORD *)this;
v6 = a5;
if ( v5 < a5 )
v6 = v5;
if ( v6 )
{
v7 = a4;
v9 = v6 < 4;
v8 = v6 - 4;
if ( v9 )
{
LABEL_11:
if ( v8 == -4 )
goto LABEL_20;
}
else
{
while ( *(_DWORD *)this == *(_DWORD *)v7 )
{
this += 4;
v7 += 4;
v9 = v8 < 4;
v8 -= 4;
if ( v9 )
goto LABEL_11;
}
}
v9 = *(_BYTE *)this < *(_BYTE *)v7;
if ( *(_BYTE *)this != *(_BYTE *)v7
|| v8 != -3
&& ((v10 = *(_BYTE *)(this + 1), v9 = v10 < *(_BYTE *)(v7 + 1), v10 != *(_BYTE *)(v7 + 1))
|| v8 != -2
&& ((v11 = *(_BYTE *)(this + 2), v9 = v11 < *(_BYTE *)(v7 + 2), v11 != *(_BYTE *)(v7 + 2))
|| v8 != -1 && (v12 = *(_BYTE *)(this + 3), v9 = v12 < *(_BYTE *)(v7 + 3), v12 != *(_BYTE *)(v7 + 3)))) )
{
result = -v9 | 1;
goto LABEL_21;
}
LABEL_20:
result = 0;
LABEL_21:
if ( result )
return result;
}
if ( v5 >= a5 )
result = v5 != a5;
else
result = -1;
return result;
}
```
从result 0反向推导,然后定位到LABEL_11:,能看到v8不能等于-4,继续往下看result = -v9 | 1;上面计算中v9必须为0,再继续往下看result = v5 != a5;,可以知道这里v5必须等于a5,这个过程参数很多,有点头皮发麻,动态调试走一下,看看能不能看出点信息。
  7.可以看到是拿52pojie和idg_cni~bjbfi|gsxb相比较,这里比较完,都相等,然后走je跳到xor eax,eax就是正确的流程。
```
00A52104|. /74 34 je short key.00A5213A
00A52106|> |8A01 mov al,byte ptr ds: ;ecx寄存器:52pojie
00A52108|. |3A06 cmp al,byte ptr ds: ;eax寄存器:idg_cni~bjbfi|gsxb
00A5210A|. |75 27 jnz short key.00A52133
00A5210C|. |83FA FD cmp edx,-0x3
00A5210F|. |74 29 je short key.00A5213A
00A52111|. |8A41 01 mov al,byte ptr ds:
00A52114|. |3A46 01 cmp al,byte ptr ds:
00A52117|. |75 1A jnz short key.00A52133
00A52119|. |83FA FE cmp edx,-0x2
00A5211C|. |74 1C je short key.00A5213A
00A5211E|. |8A41 02 mov al,byte ptr ds:
00A52121|. |3A46 02 cmp al,byte ptr ds:
00A52124|. |75 0D jnz short key.00A52133
00A52126|. |83FA FF cmp edx,-0x1
00A52129|. |74 0F je short key.00A5213A
00A5212B|. |8A41 03 mov al,byte ptr ds:
00A5212E|. |3A46 03 cmp al,byte ptr ds:
00A52131|. |74 07 je short key.00A5213A
00A52133|> |1BC0 sbb eax,eax
00A52135|. |83C8 01 or eax,0x1
00A52138|. |EB 02 jmp short key.00A5213C
00A5213A|> \33C0 xor eax,eax
```
  8.那我把文件的内容替换成idg_cni~bjbfi|gsxb,然后运行程序发现提示成功了,提交flag,正确:
### 0x3总结
  1.这道题看上去很难,想要返回值0,好似要经过多番运算,最后OD一调试,实际上就是把两个传进来的字符串作比较。
  2.刚开始下断点想着exitprocess,没回溯过去,习惯了点OD上面的API断点,后来想到exit就断下,回溯过去了。
  3.有时候看不出反编译的那一段代码作用时,用OD跑起来看看,会有意外发现。
  4.OD和IDA都用起来,便于定位和分析问题。
### 0x4参考资料
加密解密第四版-函数的调用约定
  **PS:善于总结,善于发现,找到分析问题的思路和解决问题的办法。虽然我现在还是零基础的小菜鸟一枚,也许学习逆向逆天改命我会失败,但也有着成功的可能,只要还有希望,就决不放弃!** snowhee 发表于 2022-2-7 17:21
师傅我想问一下,result 0反向推导,定位到LABEL_11,然后判断v8等于-4才能跳转LABEL_20,如果v8不等于-4, ...
我们的目的是return 0,只有lable 20才会给将返回值置0,然后向上定位到lable 11,那么v8就必须等于-4,这样就就不会执行lable 21前面那个if,不然,result值必然非0,if一判断true,返回result了。我们必须让他跳转到lable 21,但是不能执行前两行,所以必须先让result为0,跳过第一个if,然后执行这个if ( v5 >= a5 ),这里v5必须等于a5,然后就return 0了. 总有癫嘿想坑朕 发表于 2020-10-4 10:04
看不懂,但是感觉很厉害,支持你一下,活到老学到老,我快40了,刚开始学python,共勉一个。
共勉一个。同样的人,同样的在做,都是一开始! 感谢楼主分享 好像有点看懂,找了半天找出了判断条件和判断结果,然后修改判断结果来达到想要的结果是吧 支持原创。破解是个体力活,学习破解是脑力活 贵在坚持,看来我也得好好学习 巧遇学长,我是华科计院的大一新生,在准备ctf的招新{:301_987:}
以后应该也会学这些吧。 这个游戏还是不错的 前排膜拜大神 谢谢分享,学习了 up加油。