小菜鸟一枚 发表于 2023-6-1 19:03

学破解第205天,《攻防世界 Reverse练习区happyctf》学习

前言:

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

**立帖为证!--------记录学习的点点滴滴**

## 0x1 思路收集
  1.下载后通过IDA反编译看看main函数:

```
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
int v4; //
char *v5; //
char v6; // BYREF
char v7; //
char *v8; //
char *v9; //
char *v10; //
char v11; // BYREF
char v12; // BYREF
int v13; //

sub_402930(v12);
v13 = 0;
sub_401530(&unk_4DDAF8, "please input flag");
sub_4039B0(sub_402310);
sub_401500(&unk_4DDA80, v12);
if ( sub_405DE0(v12) == 24 )
{
    sub_402A20(v11);
    LOBYTE(v13) = 1;
    sub_402570(v11);
    v10 = v12;
    v9 = (char *)sub_405270(v12);
    v8 = (char *)sub_4052B0(v12);
    while ( v9 != v8 )
    {
      v7 = *v9;
      sub_403B70(v7);
      ++v9;
    }
    qmemcpy(v6, "rxusoCqxw{yqK`{KZqag{r`i", 24);
    sub_402590(v6);
    v5 = (char *)sub_405290(v11);
    v4 = sub_4052E0(v11);
    while ( v5 != (char *)v4 )
    {
      if ( !(unsigned __int8)sub_403BB0(*v5) )
      {
      sub_401530(&unk_4DDAF8, "error");
      sub_4039B0(sub_402310);
      LOBYTE(v13) = 0;
      sub_4034E0(v11);
      v13 = -1;
      sub_403450(v12);
      return 0;
      }
      ++v5;
    }
    sub_401530(&unk_4DDAF8, "good job");
    sub_4039B0(sub_402310);
    LOBYTE(v13) = 0;
    sub_4034E0(v11);
    v13 = -1;
    sub_403450(v12);
    result = 0;
}
else
{
    sub_401530(&unk_4DDAF8, "not enought");
    sub_4039B0(sub_402310);
    v13 = -1;
    sub_403450(v12);
    result = 0;
}
return result;
}
```

  2.通过观察看到第一个外层判断,像24这种可以猜测是长度,根据经验可知一般在验证密码时,会先判断非空,防止空指针异常报错,其次判断长度是否正确(这样可以避免对错误的密码进行大量的运算,提高运行速度),最后才是处理输入的密码,通过一系列运算与真实的密码进行比较。

```
      if ( sub_405DE0(v12) == 24 )
```                                                                                                   

  3.第二次个判断在这里,可以看到有“error”字符串,说明这里也是一个关键,这个if如果成立,就代表密码错了。

```
      if ( !(unsigned __int8)sub_403BB0(*v5) )
```

  4.通过上述分析找到了关键判断的位置,那么就下来就要看这两个if比较的参数来自哪里?代表什么?接下来就需要结合动态调试进行判断了。

## 0x2 调试分析
  1.在ida中找到第一个if对应的汇编代码和地址,直接OD中打断点运行过去,刚刚我们猜测这是长度,试试输入24个a看看。

```
00406824   .83F8 18       cmp eax,0x18
00406827   .74 42         je short happyCTF.0040686B
00406829   .68 10234000   push happyCTF.std::endl<char,std::char_t>
0040682E   .68 D0F34A00   push happyCTF.004AF3D0                   ;not enought
```

  2.可以看到此时寄存器eax的值就是0x18,也就是24,那么通过第一个if可以知道密码长度了。

![https://s1.ax1x.com/2023/05/31/p9xJyJP.png](https://s1.ax1x.com/2023/05/31/p9xJyJP.png)

  3.第二个if是与v5和v4相关,那么我们单步走,记录值的变化,这里这段对应ida里面第一个while循环代码段,通过单步可以知道捣鼓半天就是逐个取我们输入的字符,进004068C2这个call里面干了一些奇怪的事。

```
004068A1   > /8B45 C0       mov eax,dword ptr ss:
004068A4   . |83C0 01       add eax,0x1
004068A7   . |8945 C0       mov dword ptr ss:,eax
004068AA   > |8B45 C0       mov eax,dword ptr ss:
004068AD   . |3B45 BC       cmp eax,dword ptr ss:
004068B0   . |74 17         je short happyCTF.004068C9
004068B2   . |8B45 C0       mov eax,dword ptr ss:
004068B5   . |8A08          mov cl,byte ptr ds:
004068B7   . |884D BB       mov byte ptr ss:,cl
004068BA   . |0FB645 BB   movzx eax,byte ptr ss:
004068BE   . |50            push eax
004068BF   . |8D4D C8       lea ecx,dword ptr ss:
004068C2   . |E8 A9D2FFFF   call happyCTF.<lambda_1b3a4e77a09e1a7ed4>
004068C7   .^\EB D8         jmp short happyCTF.004068A1

对应IDA反汇编出来的这一块:
      while ( v9 != v8 )
    {
      v7 = *v9;
      sub_403B70(v7);
      ++v9;
    }
```

  4.接下来看一下那个函数,a2就是我们输入的字符,可以看到做了一个异或0x14的操作,然后里面又调用了一些函数,看了半天看不懂,那就还是用OD进这个call看一下,。

```
int __thiscall sub_403B70(void *this, char a2)
{
char v3; // BYREF
void *v4; //

v4 = this;
v3 = a2 ^ 0x14;
sub_406170(v3);
return ++dword_4DD8F8;
}

00403B7C|.0FB645 08   movzx eax,byte ptr ss:            ;取我输入的字符a
00403B80|.83F0 14       xor eax,0x14                               ;异或0x14
00403B83|.8845 BB       mov byte ptr ss:,al            ;将异或后的值存起来
```

  5.那就结果论,打上断点重复走,可以看到不管哪个位置的a都是异或0x14得到u,可以猜测主要逻辑就是异或0x14,其他的可能是无关函数,继续往下,到第二个if那里,可以看到外面大层就是while循环,然后这里取加密后的字符u调用一个可疑函数,调用完之后就是判断跳转了,正好对应第二个if那里。

```
00406971   .8D4D 9C       lea ecx,dword ptr ss:            ;加密后的第一个字符u
00406974   .E8 37D2FFFF   call happyCTF.<lambda_7686c8adb828765130ce>
00406979   .0FB6C8      movzx ecx,al
0040697C   .85C9          test ecx,ecx                               ;根据ecx是否为0进行跳转,值来自于al寄存器
0040697E   .75 4B         jnz short happyCTF.004069CB
00406980   .68 10234000   push happyCTF.std::endl<char,std::char_tra>
00406985   .68 DCF34A00   push happyCTF.004AF3DC                     ;error
```

  6.进来00406974这个call可以判断出实际上整个程序就是拿我输入的字符串异或0x14,然后和固定字符串“rxusoCqxw{yqK\`{KZqag{r\`i”进行比较,相等就是正确的flag。

```
00403BC7|.0FB60411      movzx eax,byte ptr ds:            ;r
00403BCB|.0FB64D 08   movzx ecx,byte ptr ss:            ;u
00403BCF|.3BC1          cmp eax,ecx                              ;判断是否相等
00403BD1|.74 04         je short happyCTF.00403BD7
00403BD3|.32C0          xor al,al
00403BD5|.EB 0F         jmp short happyCTF.00403BE6                ;如果不相等执行这个jmp,eax为1
00403BD7|>A1 FCD84D00   mov eax,dword ptr ds:[indextocalealue_size>
00403BDC|.83C0 01       add eax,0x1
00403BDF|.A3 FCD84D00   mov dword ptr ds:[indextocalealue_sizeopen>
00403BE4|.B0 01         mov al,0x1
```

## 0x3 flag计算
  1.经过前面的分析我们已经理清了整个程序的逻辑,接下来复制关键部分,直接进行解密,整理后的c代码如下。

```
#include <stdio.h>
#include <stdlib.h>

int main()
{
    char pass = "rxusoCqxw{yqK`{KZqag{r`i";
    char flag;

    for (int i = 0; i < 24; i++)
    {
      flag=pass^0x14;
      printf("%c",flag);
    }
   
    system("pause");
    return 1;
}
```

  2.运行得到flag:

![https://s1.ax1x.com/2023/05/31/p9xdlZD.png](https://s1.ax1x.com/2023/05/31/p9xdlZD.png)

  3.验证flag,正确。

![https://s1.ax1x.com/2023/05/31/p9xdJJA.png](https://s1.ax1x.com/2023/05/31/p9xdJJA.png)

## 0x4 总结
  1.一步步反向推理,排除干扰函数,得到处理flag比较运算的函数。

  2.看了别人的wp才知道这个题提供了pdb文件的,可以ida打开程序的时候加载,这样很容易区分,比如标注std的就是库函数,很长很陌生的函数就是自定义的,可以更快排除干扰函数。

冥界3大法王 发表于 2023-6-1 22:22

下面是二进制忍者的:我认为看起来更科学和舒服。{:301_1006:}
0042a9f0      int32_t var_4_5 = 0xffffffff
0042a9f2      int32_t var_8 = 0x455f4a
0042a9fd      int32_t* fsbase
0042a9fd      int32_t var_c = *fsbase
0042aa09      int32_t __saved_ebx
0042aa09      int32_t var_30 = data_468f50 ^ &__saved_ebx
0042aa0e      *fsbase = &var_c
0042aa14      int32_t var_28 = 0
0042aa1c      int32_t eax_3 = data_46abec
0042aa23      if (eax_3 != 0)
0042aa2e          void var_24
0042aa2e          sub_406290(&var_24, eax_3)
0042aa3c          int32_t var_4 = 0
0042aa44          void var_14
0042aa44          sub_406290(&var_14, "MB-3186-2945")
0042aa4d          int32_t ebx_1 = 1
0042aa57          var_4.b = 1
0042aa5c          int32_t var_28_1 = 1
0042aa60          int32_t eax_4 = sub_406560(&var_24, &var_14)
0042aa67          void var_1c
0042aa67          int32_t eax_5
0042aa67          if (eax_4 == 0)
0042aa72            sub_406290(&var_1c, "MB31862945")
0042aa7c            ebx_1 = 3
0042aa85            int32_t var_4_1 = 2
0042aa8d            int32_t var_28_2 = 3
0042aa91            eax_5 = sub_406560(&var_24, &var_1c)
0042aa98          if (eax_4 != 0 || (eax_4 == 0 && eax_5 != 0))
0042aa9a            eax_5 = 1
0042aaa8          *(data_46abec + 0x28) = eax_5
0042aaab          int32_t var_4_2 = 1
0042aab3          if ((ebx_1.b & 2) != 0)
0042aab5            ebx_1 = ebx_1 & 0xfffffffd
0042aabc            int32_t var_28_3 = ebx_1
0042aac0            sub_4064c0(&var_1c)
0042aac8          int32_t var_4_3 = 0
0042aad0          if ((ebx_1.b & 1) != 0)
0042aad6            sub_4064c0(&var_14)
0042aadf          int32_t var_4_4 = 0xffffffff
0042aae7          eax_3 = sub_4064c0(&var_24)
0042aaf0      *fsbase = var_c
0042aafc      return eax_3

冥界3大法王 发表于 2023-6-1 22:15

@小菜鸟一枚
IDA的代码很多看不明白,比如下面的,能不能按行给我解释下:

int sub_42A9F0()
{
int result; // eax
char v1; // bl
int v2; // eax
char v3; // BYREF
char v4; // BYREF
char v5; // BYREF
int v6; //

result = dwInitParam;
if ( dwInitParam )
{
    sub_406290(dwInitParam);
    v6 = 1;
    sub_406290("MB-3186-2945");
    v1 = 1;
    if ( sub_406560(v5) || (sub_406290("MB31862945"), v1 = 3, v6 = 2, (v2 = sub_406560(v4)) != 0) ) 这句也整不明白
      v2 = 1;
    *(_DWORD *)(dwInitParam + 40) = v2;
    v6 = 1;
    if ( (v1 & 2) != 0 )
    {
      v1 &= ~2u;                           &是啥? 指针?
      sub_4064C0(v4);
    }
    v6 = 0;
    if ( (v1 & 1) != 0 )糊涂
      sub_4064C0(v5);
    v6 = -1;
    return sub_4064C0(v3);
}
return result;
}

||是啥? 或么?
if 然后后面()是成立? 后面没有then看着真别扭

街角凝眸 发表于 2023-6-1 19:12

能开发什么样的程序了

计算机学习者 发表于 2023-6-1 19:19

菜鸟哥,你的全部记录及学习资源:https://www.52pojie.cn/thread-1791705-1-1.html链接失效了

sunzhw 发表于 2023-6-1 19:36

感谢分享,一起学习进步

冥界3大法王 发表于 2023-6-1 20:16

@小菜鸟一枚
菜鸟姥爷满血复活了。。。{:301_971:}

wananlove 发表于 2023-6-1 20:35

感谢分享,学到了

moruye 发表于 2023-6-1 21:55

jasonwei 发表于 2023-6-1 22:45

感谢感谢,顺便学习学习
页: [1] 2 3 4
查看完整版本: 学破解第205天,《攻防世界 Reverse练习区happyctf》学习