bugku-逆向-13、Take the maze(VC++32位逆向、IDC脚本使用)

首先下载运行ConsoleApplication1.exe:
在这里插入图片描述

是需要输入一个字符串或者数字之类的
之后PEid查壳:
在这里插入图片描述

32位的控制台程序,没有壳。

1、IDA静态分析

再用IDA打开静态分析,找到main函数,按F5反编译:
在这里插入图片描述

main_0函数带注释的源代码:

__int64 main_0()
{
  int v0; // edx
  __int64 v1; // ST00_8
  signed int i; // [esp+D0h] [ebp-48h]
  char v4[16]; // [esp+DCh] [ebp-3Ch]
  char v5; // [esp+ECh] [ebp-2Ch]

  sub_45E9C1("welcome to zsctf!\n");
  sub_45E9C1("show me your key:");
  sub_45D846("%s", v4);
  if ( sub_45B1C7() )
    j__exit(0);
  if ( j__strlen(v4) == 24 )                    // 字符串的长度是24
  {
    v5 ^= 1u;
    sub_45C748(v5);                             // 对v5字符串做变换
    for ( i = 0; i < 24; ++i )
    {
      if ( (v4[i] < 48 || v4[i] > 57) && (v4[i] < 97 || v4[i] > 102) )// 要求每个字符大小在48~57、97~102之间,也就是“0~9、a~f”之间
      {
        sub_45BF7D();                           // 输出“error key”
        goto LABEL_16;
      }
    }
    if ( sub_45E593((int)v4) )                  // 判断字符串
    {
      sub_45E9C1("done!!!The flag is your input\n");
      sub_45D9C7(4);
      sub_45E1B5(a1);                           // 生成一张图片flag.png
    }
    else
    {
      sub_45BF7D();
    }
  }
  else
  {
    sub_45BF7D();
  }
LABEL_16:
  HIDWORD(v1) = v0;
  LODWORD(v1) = 0;
  return v1;
}

在这里插入图片描述

2、主函数分析

程序首先是限定了字符串长度为24,之后调用sub_45C748(v5)函数对v5字符串做变换:
在这里插入图片描述

其中主要是sub_45DCD3((int)v4):
在这里插入图片描述

根据_int8 a1大小为0~255,做不同的变换,case分支太多了没办法逐个函数直接逆向分析,所以我们用OD去动态调试一下,看经过sub_45C748(v5)函数,v5字符串发生了什么变化。

3、OD动态分析

打开OD运行,输入字符串“000000000000000000000000”:
在这里插入图片描述

找到sub_45C748函数,在函数附近下两个断点:
在这里插入图片描述

单步运行,发现字符串在进入sub_45C748函数之前,对字符串的第17个字符进行了异或操作,让字符串变成了“000000000000000010000000”:
在这里插入图片描述

运行完之后的字符串:
在这里插入图片描述

我们跟进这个函数,找到让字符串变化的关键函数:
在这里插入图片描述

进入这个函数,发现运行过之后字符串变成了“0123456789:;<=>?!!"#$%&’”
在这里插入图片描述

再换个输入“111111111111111111111111”,查看字符串变化:
在这里插入图片描述

字符串变为“1032547698;:=<?> #"%$’&”
再换个输入“12345678901234567890abcd”,查看字符串变化:
在这里插入图片描述

字符串变为“1317131?19;9?9;9&)+#uwus”
其实就是进行了先对第17位字符进行异或1:char[16]^0x1,再进行按位与的操作:char[i]=char[i]^i

4、关键sub_463480函数分析

再回到程序:
在这里插入图片描述

要求每个字符大小在48-57、97-102之间,也就是“0-9、a-f”之间,之后通过sub_45E593((int)v5)这个函数判断字符串是否正确,正确就输出"done!!!The flag is your input\n"和一张图片flag.png,否则输出"key error\n"结束程序。
我们接下来分析sub_45E593()函数,查看sub_45E593():
在这里插入图片描述

调用了sub_463480(a1)函数:
在这里插入图片描述

所以我们主要就是分析sub_463480(a1)函数
sub_463480()函数带注释的源代码:

BOOL __cdecl sub_463480(int a1)
{
  int v1; // ST18_4
  int v2; // ST18_4
  BOOL result; // eax
  int v4; // ST18_4
  int v5; // [esp+D8h] [ebp-38h]
  int v6; // [esp+E4h] [ebp-2Ch]
  int v7; // [esp+F0h] [ebp-20h]
  int v8; // [esp+FCh] [ebp-14h]
  int v9; // [esp+108h] [ebp-8h]

  v9 = 0;
  v6 = 0;
  v5 = 0;
  while ( 2 )
  {
    v1 = v9++;
    if ( v1 >= 12 )                             // v1 = 0~12,12的时候返回
      return v5 == 311;                         // 返回的时候v5 = 311则返回true,得到flag;否则返回false
    if ( sub_45B1C7() )                         // 确定调用进程是否由用户模式的调试器调试
      j__exit(0);                               // 被动态调试就返回
    v2 = *(char *)(v6++ + a1);                  // a1是字符串的首地址,v2相当于取字符串中的0、2、4……位置上的字符字符
    switch ( v2 )                               // v2大小是48~52,也就是数字0~4,否则退出
    {
      case 48:
        v8 = 0;                                 // v8,大小0~4,下面byte_541168数组的下标,决定调用哪个函数
        goto LABEL_12;
      case 49:
        v8 = 1;
        goto LABEL_12;
      case 50:
        v8 = 2;
        goto LABEL_12;
      case 51:
        v8 = 3;
        goto LABEL_12;
      case 52:
        v8 = 4;
LABEL_12:
        v4 = *(char *)(v6++ + a1);              // a1是字符串的首地址,v2相当于取字符串中的1、3、5……位置上的字符字符
        switch ( v4 )                           // v4的大小是53~57、97~102,也就是数字5~9、a~f,否则退出
        {
          case 53:
            v7 = 5;                             // v7,大小5~15,下面byte_541168数组的下标,决定函数的第二个参数
            goto LABEL_25;
          case 54:
            v7 = 6;
            goto LABEL_25;
          case 55:
            v7 = 7;
            goto LABEL_25;
          case 56:
            v7 = 8;
            goto LABEL_25;
          case 57:
            v7 = 9;
            goto LABEL_25;
          case 97:
            v7 = 10;
            goto LABEL_25;
          case 98:
            v7 = 11;
            goto LABEL_25;
          case 99:
            v7 = 12;
            goto LABEL_25;
          case 100:
            v7 = 13;
            goto LABEL_25;
          case 101:
            v7 = 14;
            goto LABEL_25;
          case 102:
            v7 = 15;
LABEL_25:
            switch ( byte_541168[v8] )          // 决定调用哪个函数,byte_541168[]数组就是'delru0123456789'
            {                                   // v5是最终返回的结果311,byte_541168[v7] - 48控制循环次数
              case 100:                         // d,增加循环次数个26
                sub_45CC4D((int)&v5, byte_541168[v7] - 48);
                continue;
              case 108:                         // l,减少循环次数个1
                sub_45D0A3((int)&v5, byte_541168[v7] - 48);
                continue;
              case 114:                         // r,增加循环次数个1
                sub_45CB0D((int)&v5, byte_541168[v7] - 48);
                continue;
              case 117:                         // u,减少循环次数个26
                sub_45D0E9((int)&v5, byte_541168[v7] - 48);
                continue;
              default:
                result = 0;
                break;
            }
            break;
          default:
            result = 0;
            break;
        }
        break;
      default:
        result = 0;                             // 返回0,表示错误
        break;
    }
    return result;
  }
}

1、程序首先通过v9和v1来控制循环12次;
2、v1等于12时返回,返回的时候判断结果v5,等于311则返回true,得到flag;否则返回false;
3、sub_45B1C7()调用IsDebuggerPresent()函数确定调用进程是否由用户模式的调试器调试,被动态调试就返回;
4、循环12次就是把24个字符分成了12组,每两个字符一组,通过v2获得获得第一个字符,v2大小在4852之内则将字符转化为数字04给v8,否则退出;v4获得第二个字符,v4大小在5357、97102之内则将字符转化为数字5~15给v7,否则退出;
5、根据switch ( byte_541168[v8] ) 决定调用下面四个中的一个函数:
在这里插入图片描述

(1)byte_541168数组分析

这四个函数的代码逻辑相似,只是具体功能不同,两个参数是(int)&v5和byte_541168[v7] - 48,v5是最终返回的结果311,byte_541168[v7] - 48控制循环次数:
查看byte_541168数组:
在这里插入图片描述

64就是’d’,其实它和下面的’elru0123456789’是一起的,这里只是IDA对它们的识别格式不一样,64h被识别成了单字节,我们把
它们转化一下,先全部选中右键转化为未定义:
在这里插入图片描述

然后在00541168这一行右键就可以看到,IDA已经重新识别到了字符数组‘delru0123456789’:
在这里插入图片描述

而在下面的其他行比如0054116B,IDA识别到的字符数组就是后面的‘ru0123456789’:
在这里插入图片描述

在00541168这一行右键,或者按快捷键‘A’解释光标的地址为字符数组的首地址,转化为字符数组‘delru0123456789’,0:
在这里插入图片描述

最后的那个0是C、C++字符数组中最后表示结束位置的’\0’。
我们再回到代码,就很好理解了:v8大小04,表示下面byte_541168数组的04个数“delru”的下标;v7大小515,表示下面byte_541168数组的第515个数“0123456789”,0的下标。

(2)迷宫游戏的方向和形状

6、(1)byte_541168[v8]的值为d(100)则调用第1个函数sub_45CC4D:
在这里插入图片描述
在这里插入图片描述

目的是让v5增加循环次数个26;
(2)byte_541168[v8]的值为l(108)则调用第2个函数sub_45D0A3:
在这里插入图片描述
在这里插入图片描述

目的是让v5减少循环次数个1;
(3)byte_541168[v8]的值为r(114)则调用第3个函数sub_45CB0D:
在这里插入图片描述
在这里插入图片描述

目的是让v5增加循环次数个1;
(4)byte_541168[v8]的值为u(117)则调用第4个函数sub_45D0E9:
在这里插入图片描述
在这里插入图片描述

目的是让v5减少循环次数个26;
(5)byte_541168[v8]的值为e(101)则退出;
到这,我们大概明白题目的意思了,结合题目的名称Take the maze(走迷宫),迷宫大概是下面这样:
在这里插入图片描述

我们要从迷宫的左上角v5=0走到右下角311,方法就是我们输入的字符串,24个字符分成12组,每组两个字符,第一个字符负责方向就是往上(减26)、往下(加26)、往左(减1)、往右(加1),第二个字符负责走几步(循环次数),碰到最外面四周的墙壁就返回,由四个函数中的这一行代码处理:

if ( i / 26 > 10 )return result; // i/26的最大值是11,走到了最下边,返回当前行数
if ( i %26 < 1 )return result; // i%26的最小值是0,走到了最左边,返回当前行数
if ( i % 26 > 24 )return result; // i%26最大值是25,走到了最右边,返回当前行数
if ( i / 26 < 1 )return result; // i/26的最小值是0,走到了最上边,返回当前行数

7、最重要的就是下面这四个判断分别在下四个函数里:
if ( dword_540548[i] ^ dword_540068[i] ) return result;//非0即真,循环的次数i要确保这两个数组中的相同位置的值异或为0也就是相等,出现不相等即异或不为0就返回

if ( dword_5404DC[i] ^ dword_53FFFC[i] )return result;
if ( dword_5404E4[i] ^ dword_540004[i] )return result;
if ( dword_540478[i] ^ dword_53FF98[i] )return result;

相当于每个位置i的能走的方向,只要这个方向上两个数组中的相同位置的值相等。所以对于i大小范围:0~311中,每个i让这四个判断中的某个判断不成立则表示这个i位置上,可以往某个方向走;成立则直接返回,表示不能往这个方向走。
所以我们接下来就是要得到每个位置i能走的方向的迷宫地图,可以把这8个数组都复制下来,对i:0~311计算每个位置上异或的结果;也可以直接在IDA里写idc或者Python的脚本,直接利用现有的8个数组,计算得到地图。

5、脚本寻找路径

把8个数组都复制下来的过程太多了,这里就用的是写idc脚本的方式了。
文件->脚本命令,打开IDC和Python的命令行:
在这里插入图片描述

idc脚本源代码:

auto i;
for(i = 0;i <= 311; ++i){
    if(i % 26 == 0)
       Message("\n");
    if(Dword(0x540548 + i * 4) ^ Dword(0x540068 + i * 4))
       Message("-");
    else
        Message("d");
      
    if(Dword(0x5404DC + i * 4) ^ Dword(0x53FFFC + i * 4))
       Message("-");
    else
        Message("l");        
       
    if(Dword(0x5404E4 + i * 4) ^ Dword(0x540004 + i * 4))
        Message("-");
    else
        Message("r");
    
    if(Dword(0x540478 + i * 4) ^ Dword(0x53FF98 + i * 4))
        Message("-");
    else
        Message("u");
   
    Message("  ");

 }

return 0;
运行结果:
在这里插入图片描述

根据地图,我们只能转12次方向,得到的路径:

方向和步数是d,1,r,1,d,3,r,1,r,6,d,3,d,2,r,9,d,1,r,4;
其中方向dlru对应的是v8的0234,对应转化后的字符串v4的0234;
步数0-9对应的是v7的5-15,对应转化后的字符串v4的5-f;
所以进入迷宫前的字符串是:0,6,3,6,0,8,3,6,3,b,0,8,0,7,3,e,0,6,3,9;
我们再把06360836063b0839073e0639进行按位异或的操作就行了,按位异或的操作是可以直接逆向的,我们可以把06360836063b0839073e0639输入程序得到按位异或后的字符串或者用代码完成对第17位字符的异或和按位异或的操作。

6、路径转换得到flag

把06360836063b0839073e0639输入程序:
在这里插入图片描述

得到对第17位字符的异或和按位异或后的字符串:“07154=518?9i<5=6!&!v$#%.”
或者自己用代码完成对第17位字符的异或和按位异或的操作:
得到flag的源代码:

#include<stdio.h>
int main(){
    char str[24] = {'0','6','3','6','0','8','3','6','0','6','3','b','0','8','3','9','0','7','3','e','0','6','3','9'};
    int i;
    str[16] ^= 1;
    for( i=0 ; i<24 ; i++){
        str[i] ^= i;
        ///07154=518?9i<5=6 &!v$#%.
        printf("%c",str[i]);
    }
    return 0;
}

运行结果:
在这里插入图片描述

我们把“07154=518?9i<5=6!&!v$#%.”输入ConsoleApplication1.exe:
在这里插入图片描述

也得到了一张flag.png图片:
在这里插入图片描述

打开是一张二维码,用手机扫描二维码:
在这里插入图片描述

Flag还要加上作者的名字Docupa,所以最终的flag是:zsctf{07154=518?9i<5=6!&!v$#%.Docupa}。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值