学破解第111天,《攻防世界reverse练习区ReverseMe-120》分析
# 学破解第111天,《攻防世界reverse练习区ReverseMe-120》分析前言:
  一直对黑客充满了好奇,觉得黑客神秘,强大,无所不能,来论坛两年多了,天天看各位大佬发帖,自己只能做一个伸手党。也看了官方的入门视频教程,奈何自己基础太差,看不懂。自我反思之下,决定从今天(2019年6月17日)开始定下心来,从简单的基础教程开始学习,希望能从照抄照搬,到能独立分析,能独立破解。
不知不觉学习了好几个月,发现自己离了教程什么都不会,不懂算法,不懂编程。随着破解学习的深入,楼主这个半吊子迷失了自我,日渐沉迷水贴装X,不能自拔。
==========申明:从第71天楼主开始水贴装X,帖子不再具有连续性,仅供参考,后续帖子为楼主YY专用贴!!!==========
立帖为证!--------记录学习的点点滴滴
## 0x1下载程序
  1.首先查壳,显示Microsoft Visual C++ v.12 - 2013 ( E8 ),无壳。
  2.运行程序看看,随便输入信息,然后提示错误!
## 0x2调试分析
  1.丢进吾爱的OD跑起来,搜索字符串,看到提示信息
```
00401190/$55 push ebp
00401191|.8BEC mov ebp,esp
00401193|.81EC CC000000 sub esp,0xCC
00401199|.68 C04E4100 push ReverseM.00414EC0 ;please input your flah:
0040119E|.E8 24010000 call ReverseM.004012C7
004011A3|.6A 63 push 0x63
004011A5|.8D85 35FFFFFF lea eax,dword ptr ss:
004011AB|.C685 34FFFFFF>mov byte ptr ss:,0x0
004011B2|.6A 00 push 0x0
004011B4|.50 push eax
004011B5|.E8 E6460000 call ReverseM.004058A0
004011BA|.8D85 34FFFFFF lea eax,
004011C0|.50 push eax
004011C1|.68 D84E4100 push ReverseM.00414ED8 ;%s
```
  2.很好,定位到关键函数00401190了,去IDA中F5一下
```
int sub_401190()
{
unsigned int v0; // edx@1
unsigned int v1; // ecx@1
__m128i v2; // xmm1@3
unsigned int v3; // esi@3
const __m128i *v4; // eax@3
__m128i v5; // xmm0@4
int v6; // eax@7
char v8; // @1
char v9; // @1
char v10; // @1
char v11; // @1
unsigned int v12; // @1
sub_4012C7("please input your flah:", v8);
v8 = 0;
memset(&v9, 0, 0x63u);
sub_401376("%s", (unsigned int)&v8);
v10 = 0;
memset(&v11, 0, 0x63u);
sub_401000(&v8, strlen(&v8));
v0 = v12;
v1 = 0;
if ( v12 )
{
if ( v12 >= 0x10 )
{
v2 = _mm_load_si128((const __m128i *)&xmmword_414F20);
v3 = v12 - (v12 & 0xF);
v4 = (const __m128i *)&v10;
do
{
v5 = _mm_loadu_si128(v4);
v1 += 16;
++v4;
_mm_storeu_si128((__m128i *)&v4[-1], _mm_xor_si128(v5, v2));
}
while ( v1 < v3 );
}
for ( ; v1 < v0; ++v1 )
*(&v10 + v1) ^= 0x25u;
}
v6 = strcmp(&v10, "you_know_how_to_remove_junk_code");
if ( v6 )
v6 = -(v6 < 0) | 1;
if ( v6 )
sub_4012C7("wrong\n", v8);
else
sub_4012C7("correct\n", v8);
sub_401416("pause");
return 0;
}
```
  3.看看跳向失败的代码
```
if ( v6 )
sub_4012C7("wrong\n", v8);
else
sub_4012C7("correct\n", v8);
sub_401416("pause");
return 0;
```
也就是说v6为0,才会成功,在上面是
```
v6 = strcmp(&v10, "you_know_how_to_remove_junk_code");
if ( v6 )
v6 = -(v6 < 0) | 1;
```
也就说v10等于you_know_how_to_remove_junk_code,v6就会等于0,然后下面两行明显是垃圾代码,而v10定义为0。
  4.那么就只剩下下面这些代码了
```
if ( v12 )
{
if ( v12 >= 0x10 )
{
v2 = _mm_load_si128((const __m128i *)&xmmword_414F20);
v3 = v12 - (v12 & 0xF);
v4 = (const __m128i *)&v10;
do
{
v5 = _mm_loadu_si128(v4);
v1 += 16;
++v4;
_mm_storeu_si128((__m128i *)&v4[-1], _mm_xor_si128(v5, v2));
}
while ( v1 < v3 );
}
for ( ; v1 < v0; ++v1 )
*(&v10 + v1) ^= 0x25u;
}
```
改变v10的里面内容的地方在for循环里面,不断将自身异或0x25,得到加密后的字符串,上面那段算法我还不能一下子看出来,接下来换成OD继续调试。
  5.随便输入01234567890进行调试
```
004011C1|.68 D84E4100 push ReverseM.00414ED8 ;%s
004011C6|.E8 AB010000 call ReverseM.00401376 ;输入01234567890
004011CB|.6A 63 push 0x63
004011CD|.8D45 99 lea eax,dword ptr ss:
004011D0|.C645 98 00 mov byte ptr ss:,0x0
004011D4|.6A 00 push 0x0
004011D6|.50 push eax
004011D7|.E8 C4460000 call ReverseM.004058A0
004011DC|.8D8D 34FFFFFF lea ecx, ;取出输入的字符串存入ecx
004011E2|.83C4 24 add esp,0x24 ;平衡堆栈
004011E5|.8D51 01 lea edx,dword ptr ds: ;存入到edx
004011E8|>8A01 /mov al,byte ptr ds: ;计算字符串长度
004011EA|.41 |inc ecx ;ReverseM.00414EDC
004011EB|.84C0 |test al,al
004011ED|.^ 75 F9 \jnz short ReverseM.004011E8
004011EF|.2BCA sub ecx,edx ;将字符串长度0xB存入ecx
004011F1|.8D85 34FFFFFF lea eax, ;将字符串存入eax
004011F7|.51 push ecx ;ReverseM.00414EDC
004011F8|.50 push eax
004011F9|.8D55 FC lea edx,
004011FC|.8D4D 98 lea ecx,
004011FF|.E8 FCFDFFFF call ReverseM.00401000 ;这个call就是加密函数
00401204|.8B55 FC mov edx, ;edx的值为6
00401207|.83C4 08 add esp,0x8 ;平衡堆栈
0040120A|.33C9 xor ecx,ecx ;清空ecx
0040120C|.85D2 test edx,edx ;判断edx是否为0
0040120E|.74 4A je short ReverseM.0040125A
00401210|.83FA 10 cmp edx,0x10 ;判断edx是不是小于10
00401213|.72 33 jb short ReverseM.00401248 ;这里手动改下C标志,让跳转不实现,方便我下一步分析
00401215|.66:0F6F0D 204>movq mm1,qword ptr ds: ;%%%%%%%%%%%%%%%%H
0040121D|.8BC2 mov eax,edx ;将edx的值6给了ecx
0040121F|.56 push esi
00401220|.83E0 0F and eax,0xF ;将edx与00001111进行与运算
00401223|.8BF2 mov esi,edx
00401225|.2BF0 sub esi,eax ;edx,ecx都为6,这样减esi肯定为0
00401227|.8D45 98 lea eax,
0040122A|.8D9B 00000000 lea ebx,dword ptr ds:
00401230|>f30f6f00 /movdqu xmm0,dqword ptr ds:
00401234|.83C1 10 |add ecx,0x10
00401237|.8D40 10 |lea eax,dword ptr ds:
0040123A|.66:0FEFC1 |pxor mm0,mm1
0040123E|.f30f7f40 f0 |movdqu dqword ptr ds:,xmm0
00401243|.3BCE |cmp ecx,esi
00401245|.^ 72 E9 \jb short ReverseM.00401230
00401247|.5E pop esi
00401248|>3BCA cmp ecx,edx ;这里ecx是0x10也就是16,与6相比,大于等于跳转
0040124A|.73 0E jnb short ReverseM.0040125A
0040124C|.8D6424 00 lea esp,dword ptr ss:
00401250|>80740D 98 25/xor byte ptr ss:,0x25
00401255|.41 |inc ecx ;ReverseM.00414EDC
00401256|.3BCA |cmp ecx,edx
00401258|.^ 72 F6 \jb short ReverseM.00401250
0040125A|>B9 DC4E4100 mov ecx,ReverseM.00414EDC ;you_know_how_to_remove_junk_code
0040125F|.8D45 98 lea eax, ;典型的strcmp函数
00401262|>8A10 /mov dl,byte ptr ds:
00401264|.3A11 |cmp dl,byte ptr ds:
00401266|.75 1A |jnz short ReverseM.00401282
00401268|.84D2 |test dl,dl
0040126A|.74 12 |je short ReverseM.0040127E
0040126C|.8A50 01 |mov dl,byte ptr ds:
0040126F|.3A51 01 |cmp dl,byte ptr ds:
00401272|.75 0E |jnz short ReverseM.00401282
00401274|.83C0 02 |add eax,0x2
00401277|.83C1 02 |add ecx,0x2
0040127A|.84D2 |test dl,dl
0040127C|.^ 75 E4 \jnz short ReverseM.00401262
0040127E|>33C0 xor eax,eax
00401280|.EB 05 jmp short ReverseM.00401287
```
通过以上分析和IDA的最照,除了加密call以外,基本都了解了。
  6.接下来就要去里面瞧一瞧我们输入的字符串到底怎么被处理的。
```
00401000/$55 push ebp
00401001|.8BEC mov ebp,esp
00401003|.83EC 0C sub esp,0xC ;分配3个局部变量
00401006|.53 push ebx
00401007|.56 push esi
00401008|.8B75 0C mov esi, ;arg2保存的是字符串到长度0xB,也就是11
0040100B|.33DB xor ebx,ebx ;清空ebx,后面是花指令
0040100D|.894D F4 mov ,ecx
00401010|.33C0 xor eax,eax
00401012|.33C9 xor ecx,ecx
00401014|.8955 F8 mov ,edx
00401017|.894D FC mov ,ecx
0040101A|.57 push edi ;上面这些是花指令???
0040101B|.85F6 test esi,esi ;esi为0就跳转
0040101D|.0F84 3F010000 je ReverseM.00401162
00401023|.8B7D 08 mov edi, ;arg1使我们输入到字符串
00401026|>33D2 /xor edx,edx ;清空edx
00401028|.3BC6 |cmp eax,esi ;比较eax和esi的值
0040102A|.73 12 |jnb short ReverseM.0040103E ;不小于0跳转
0040102C|.8D6424 00 |lea esp,dword ptr ss: ;取出esp寄存器中的值给esp
00401030|>803C38 20 |/cmp byte ptr ds:,0x20 ;判断edx+edi等不等于0x20,也就是32
00401034|.75 06 ||jnz short ReverseM.0040103C
00401036|.40 ||inc eax
00401037|.42 ||inc edx
00401038|.3BC6 ||cmp eax,esi
0040103A|.^ 72 F4 |\jb short ReverseM.00401030
0040103C|>3BC6 |cmp eax,esi
0040103E|>74 72 |je short ReverseM.004010B2
00401040|.8BCE |mov ecx,esi ;ecx获得字符串长度
00401042|.2BC8 |sub ecx,eax ;ecx减去eax
00401044|.83F9 02 |cmp ecx,0x2 ;eax和2比较
00401047|.72 0D |jb short ReverseM.00401056 ;小于就跳走
00401049|.803C38 0D |cmp byte ptr ds:,0xD ;不相等就跳走
0040104D|.75 07 |jnz short ReverseM.00401056
0040104F|.807C38 01 0A|cmp byte ptr ds:,0xA
00401054|.74 50 |je short ReverseM.004010A6
00401056|>8A0C38 |mov cl,byte ptr ds: ;再将字符串第一个字符给ecx的低8位
00401059|.80F9 0A |cmp cl,0xA
0040105C|.74 48 |je short ReverseM.004010A6
0040105E|.85D2 |test edx,edx ;edx是0,不跳走
00401060|.0F85 05010000 |jnz ReverseM.0040116B
00401066|.80F9 3D |cmp cl,0x3D ;0x3d是=
00401069|.75 0A |jnz short ReverseM.00401075 ;不相等跳走
0040106B|.43 |inc ebx
0040106C|.83FB 02 |cmp ebx,0x2
0040106F|.0F87 F6000000 |ja ReverseM.0040116B
00401075|>80F9 7F |cmp cl,0x7F ;0x7F是127
00401078|.0F87 ED000000 |ja ReverseM.0040116B ;大于就跳走
0040107E|.0FB6C9 |movzx ecx,cl ;将cl给ecx,字符0
00401081|.8A89 404E4100 |mov cl,byte ptr ds: ;将0x34赋值给cl
00401087|.80F9 7F |cmp cl,0x7F
0040108A|.0F84 DB000000 |je ReverseM.0040116B ;同样的比较
00401090|.80F9 40 |cmp cl,0x40
00401093|.73 08 |jnb short ReverseM.0040109D ;不小于0跳转
00401095|.85DB |test ebx,ebx ;ebx之前被清空了,这还是0
00401097|.0F85 CE000000 |jnz ReverseM.0040116B
0040109D|>8B4D FC |mov ecx,
004010A0|.41 |inc ecx ;ecx+1
004010A1|.894D FC |mov ,ecx ;再将ecx给local1,也就是说ecx每次加1
004010A4|.EB 03 |jmp short ReverseM.004010A9
004010A6|>8B4D FC |mov ecx,
004010A9|>40 |inc eax ;eax每次也是加1
004010AA|.3BC6 |cmp eax,esi ;eax此时为1,还是小于0
004010AC|.^ 0F82 74FFFFFF \jb ReverseM.00401026 ;这里小于0跳转
004010B2|>85C9 test ecx,ecx
004010B4|.0F84 A8000000 je ReverseM.00401162 ;相等,这里不会跳走
004010BA|.8B75 F4 mov esi,
004010BD|.8D0C49 lea ecx,dword ptr ds:
004010C0|.8D0C4D 070000>lea ecx,dword ptr ds: ;这是在干嘛,ecx的值为0x49
004010C7|.C1E9 03 shr ecx,0x3 ;右移3位,值为9
004010CA|.2BCB sub ecx,ebx
004010CC|.85F6 test esi,esi
004010CE|.0F84 A3000000 je ReverseM.00401177
004010D4|.8B55 F8 mov edx,
004010D7|.390A cmp dword ptr ds:,ecx
004010D9|.0F82 98000000 jb ReverseM.00401177 ;不跳走
004010DF|.33C9 xor ecx,ecx
004010E1|.C745 FC 03000>mov ,0x3 ;将0x3给回去
004010E8|.33DB xor ebx,ebx
004010EA|.894D 0C mov ,ecx ;ecx清0
004010ED|.85C0 test eax,eax
004010EF|.74 69 je short ReverseM.0040115A
004010F1|>8A0F /mov cl,byte ptr ds: ;下面这一段就是在for循环处理输入的字符串了
004010F3|.80F9 0D |cmp cl,0xD
004010F6|.74 5E |je short ReverseM.00401156
004010F8|.80F9 0A |cmp cl,0xA
004010FB|.74 59 |je short ReverseM.00401156
004010FD|.80F9 20 |cmp cl,0x20
00401100|.74 54 |je short ReverseM.00401156
00401102|.0FB6C9 |movzx ecx,cl
00401105|.8A91 404E4100 |mov dl,byte ptr ds: ;读取4给dl
0040110B|.33C9 |xor ecx,ecx ;清空ecx
0040110D|.80FA 40 |cmp dl,0x40
00401110|.0F94C1 |sete cl ;设置为false
00401113|.C1E3 06 |shl ebx,0x6 ;将ebx左移6位
00401116|.294D FC |sub ,ecx
00401119|.0FB6CA |movzx ecx,dl ;将dl的值4给ecx
0040111C|.8B55 0C |mov edx, ;arg2此时是0
0040111F|.83E1 3F |and ecx,0x3F ;0x3F是?
00401122|.42 |inc edx ;edx+1
00401123|.0BD9 |or ebx,ecx ;ebx和ecx或运算
00401125|.8955 0C |mov ,edx
00401128|.83FA 04 |cmp edx,0x4
0040112B|.75 29 |jnz short ReverseM.00401156 ;跳走
0040112D|.8B55 FC |mov edx,
00401130|.33C9 |xor ecx,ecx
00401132|.894D 0C |mov ,ecx
00401135|.85D2 |test edx,edx
00401137|.74 08 |je short ReverseM.00401141
00401139|.8BCB |mov ecx,ebx
0040113B|.C1E9 10 |shr ecx,0x10
0040113E|.880E |mov byte ptr ds:,cl
00401140|.46 |inc esi
00401141|>83FA 01 |cmp edx,0x1
00401144|.76 08 |jbe short ReverseM.0040114E
00401146|.8BCB |mov ecx,ebx
00401148|.C1E9 08 |shr ecx,0x8
0040114B|.880E |mov byte ptr ds:,cl
0040114D|.46 |inc esi
0040114E|>83FA 02 |cmp edx,0x2
00401151|.76 03 |jbe short ReverseM.00401156
00401153|.881E |mov byte ptr ds:,bl
00401155|.46 |inc esi
00401156|>47 |inc edi ;edi的值+1
00401157|.48 |dec eax
00401158|.^ 75 97 \jnz short ReverseM.004010F1 ;eax的值减1
0040115A|>8B45 F8 mov eax,
0040115D|.2B75 F4 sub esi,
00401160|.8930 mov dword ptr ds:,esi
00401162|>5F pop edi
00401163|.5E pop esi
00401164|.33C0 xor eax,eax
00401166|.5B pop ebx
00401167|.8BE5 mov esp,ebp
00401169|.5D pop ebp
0040116A|.C3 retn
```
垃圾代码太多,分析的是不是有点晕头转向。
  7.再来IDA,看看401000函数
```
signed int __usercall sub_401000@<eax>(unsigned int *a1@<edx>, _BYTE *a2@<ecx>, char *a3, unsigned int a4)
{
int v4; // ebx@1
unsigned int v5; // eax@1
int v6; // ecx@1
char *v7; // edi@2
int v8; // edx@3
bool v9; // zf@3
unsigned __int8 v10; // cl@11
char v11; // cl@16
_BYTE *v12; // esi@23
unsigned int v13; // ecx@23
unsigned int v14; // ebx@25
unsigned __int8 v15; // cl@26
char v16; // dl@29
_BYTE *v18; // @1
unsigned int *v19; // @1
int v20; // @1
unsigned int v21; // @25
int i; // @25
v4 = 0;
v18 = a2;
v5 = 0;
v6 = 0;
v19 = a1;
v20 = 0;
if ( !a4 )
return 0;
v7 = a3;
do
{
v8 = 0;
v9 = v5 == a4;
if ( v5 < a4 )
{
do
{
if ( a3 != 32 )
break;
++v5;
++v8;
}
while ( v5 < a4 );
v9 = v5 == a4;
}
if ( v9 )
break;
if ( a4 - v5 >= 2 && a3 == 13 && a3 == 10 || (v10 = a3, v10 == 10) )
{
v6 = v20;
}
else
{
if ( v8 )
return -44;
if ( v10 == 61 && (unsigned int)++v4 > 2 )
return -44;
if ( v10 > 0x7Fu )
return -44;
v11 = byte_414E40;
if ( v11 == 127 || (unsigned __int8)v11 < 0x40u && v4 )
return -44;
v6 = v20++ + 1;
}
++v5;
}
while ( v5 < a4 );
if ( !v6 )
return 0;
v12 = v18;
v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4;
if ( v18 && *v19 >= v13 )
{
v21 = 3;
v14 = 0;
for ( i = 0; v5; --v5 )
{
v15 = *v7;
if ( *v7 != 13 && v15 != 10 && v15 != 32 )
{
v16 = byte_414E40;
v21 -= v16 == 64;
v14 = v16 & 0x3F | (v14 << 6);
if ( ++i == 4 )
{
i = 0;
if ( v21 )
*v12++ = v14 >> 16;
if ( v21 > 1 )
*v12++ = BYTE1(v14);
if ( v21 > 2 )
*v12++ = v14;
}
}
++v7;
}
*v19 = v12 - v18;
return 0;
}
*v19 = v13;
return -42;
}
```
传递了四个参数,结合前面OD的分析可知a3是我们输入的字符串,a4是字符串的长度,a2是0012FF78-0x4,a1是0012FF78-0x68,v18 = a2;v19 = a1;v7 = a3。
  8.再来看这些值用在哪里,做了什么。
```
v13 = ((unsigned int)(6 * v6 + 7) >> 3) - v4;
if ( v18 && *v19 >= v13 )
{
v21 = 3;
v14 = 0;
for ( i = 0; v5; --v5 )//v5是字符串的长度,一直-1操作
{
v15 = *v7;//注意了v7这个字符串的第一个字符给了v15
if ( *v7 != 13 && v15 != 10 && v15 != 32 )//这是在干啥?
{
v16 = byte_414E40;//一串类似密码表的东西,以0x7F开头,字符不可见>?456789:;<=@',0
v21 -= v16 == 64;//v16是密码表中第v15+1个字符,64是@符号
v14 = v16 & 0x3F | (v14 << 6);取v16后六位,v14的前两位给v14
if ( ++i == 4 )//判断i等不等于4,同时i++,每次取4个字符,然后依次赋值给v12的三个字符
{
i = 0;//每循环4次,i的值清零
if ( v21 )//如果v21 = 1
*v12++ = v14 >> 16;//将v14右移16位赋值给*v12,同时v12指针右移一位
if ( v21 > 1 )//如果v21 > 1
*v12++ = BYTE1(v14);//直接将V14给*v12,同时v12指针右移一位
if ( v21 > 2 )//如果v21 > 2
*v12++ = v14;直接将v14的值给*v12,同时v12指针右移一位
}
}
++v7;//字符串指针右移一位
}
*v19 = v12 - v18;
return 0;
}
```
  9.这怎么特别的像base64解密的过程呢?
## 0x3尝试编写算法
  1.思路,我输入的字符串通过base64解密,然后异或0x25等于"you_know_how_to_remove_junk_code"这个字符串,就说明我输入的字符串是正确的flag,那么我可以反其道而行之,输入这个比较的字符串,然后异或25,接着进行base64加密不就可以得到flag了吗?
  2.编写程序,成功得到flag:XEpQek5LSlJ6TUpSelFKeldASEpTQHpPUEtOekZKQUA=,具体base64编码解码实现函数:(https://www.52pojie.cn/thread-1212431-1-1.html)。
```
int main()
{
char str1 = "you_know_how_to_remove_junk_code";
char str2;
for (int i = 0; i < strlen(str1); i++)
{
*(str1 + i) ^= 0x25;
}
encodeBase64(str1, strlen(str1), str2, strlen(str2));
cout << str2 << endl;
system("pause");
return 0;
}
```
  3.IDA F5的效果不太好,差点错过了401000函数,最后还是贴一张成功图,高兴一下。
## 0x4总结
  1.论坛编辑器会自动把识别成斜体,还好我用的是c++有指针可以用*操作。
  2.调试果然不能全靠IDA的F5,压根就没识别到在哪调用的401000函数,结合OD,到了调用这个函数,然后再去IDA分析。
  3.听说IDA也能动态调试,我不会,只能碰到入栈参数和函数调用去OD中调试看,然后再回IDA分析。
  4.不懂汇编,完全不能区分哪些是垃圾指令,只能靠猜。
  5.只有了解算法的实现原理,最好能体验一把算法的实现,结合反汇编代码,才能更好的分析算法。
总结:楼主是个小菜鸟,离了教程啥都不会! 1933420530 发表于 2020-7-5 12:14
膜拜大佬,能不能教我,
https://www.52pojie.cn/thread-1208234-1-1.html来,跟着我从第一天学,或者你自己搜索论坛零基础教程,选择合适自己的入门。:lol
问一下楼主学破解的话 一般多少时间之内才有起色{:301_1003:} 大佬大佬 只要努力肯专一定会成功的加油! 加油加油!!! 厉害厉害,比我强!哈哈 {:1_921:}加油加油 加油,奥里给 坚持,一定胜利✌ 坚持,一定胜利✌
楼主的毅力不错,继续努力吧,一定会胜利的,加油!