小白670 发表于 2018-5-5 22:32

CTF-逆向教程2菜鸟级

本帖最后由 小白670 于 2018-5-6 14:48 编辑

1. Bugku游戏过关 (找到关键函数进入正确跳转,异或得flag)
运行程序,看到界面跟平常的不太一样,是让我们玩个游戏,大概意思就是要让灯都关上,就是让中间的开关闭合,不想玩直接开始逆向

用ida搜索字符串



发现flag就是程序内的固定字符进行处理然后输出,现在可以直接dump下数据然后写脚本跑出flag,也可以修改程序逻辑,让它直接跳转到正确分支输出flag。但是不管怎么说,这样做都没有达到考察逆向能力的目的,也是这题的缺点,在正规比赛中,是不会出现这种题目的,一般都是最后的输出跟你的输入有关,这样就不能直接修改程序弹出flag,而是要分析程序的校验算法了,这才是真正考察能力的地方。




其实想要修改难度也很简单,你输入的数字要通过这个游戏,最后flag就是你输入的数字,这就倒逼你去分析校验函数,写脚本逆向算法,这也是逆向题的常规做法,找到关键函数,分析对我们输入的检验算法,逆向算法然后跑出正确的输入,而这个正确输入就是我们要的flag!!!这和crackme很类似,就是为了成功注册写个注册机,虽然我看很多人在做160个crackme都是爆破,而不是去耐心的分析算法。之所以说这么多,也是因为我入门的时候做题就是这样,以为修改程序逻辑就算做出来了。

接下来,我们来分析一下它的检验算法,通过搜素字符串很容易找到关键函数(而且这个程序字符串还特别多)程序用了两个循环,一个循环不断接受输入,然后进行处理,最后判断是否正确,正确则输出flag,否则接着循环




   接着让我们进去关键函数看一下,里面涉及三个数组,


if ( a1 )

{

    if ( a1 == 7 )

    {

      v2 = byte_532E28 == 0;

      byte_532E28 = v2;

      v2 = byte_532E27 == 0;

      byte_532E27 = v2;

      v2 = byte_532E28 == 0;

      result = 1;

      byte_532E28 = byte_532E28 == 0;

    }

    else

    {

      v2 = byte_532E28 == 0;

      byte_532E28 = v2;

      v2 = byte_532E27 == 0;

      byte_532E27 = v2;

      v2 = byte_532E29 == 0;

      result = v2;

      byte_532E29 = v2;

    }

}

else

{

    byte_532E28 = byte_532E28 == 0;

    v2 = byte_532E29 == 0;

    byte_532E29 = byte_532E29 == 0;

    v2 = byte_532E28 == 0;

    result = 1;

    byte_532E28 = v2;

}

return result;


三个数组是连着的,最后只需要byte_532E28数组全为1即可,我上面画了简陋的示意图,大概意思就是这三个数组指向的数据有重复。分析可知这个函数就是说打开一盏灯,可能就会关上另外一盏灯,根据它们之间的规律,就可以得出正确的开灯顺序,如果题目设置成正确的开灯顺序就是flag的话,这题会更有意思点。


检验算法分析到这里,这题我懒得算正确的输入了,既然可以爆破那我就省点力气了,用OD搜索字符串,找到输出flag的函数,找到跳转,转到call处



往上找到第一个判断的地方,下断点,然后运行时修改ZF标志位,就可以顺利到达call flag的地方




最后flag就自己出来了

   





2. BugKu love(base64加密,明文比较)Ida打开,shift+12查看字符串,找到提示字符串,找到关键函数,F5分析





首先让我们输入字符串,接着对字符串处理,返回值拷贝给一个新的字符串Dest,然后比较字符串,这里的str2以明文e3nifIH9b_C@n@dH存放在程序中。 先看下sub_41127()这个函数,可以看到最后是通过判断eax的值来跳转的,strncmp的返回值是存放在eax中的,如果sub_41127()改变了eax的值,则说明在sub_41127()函数中还有另外的玄机




这里分析一下,在函数里面有调用一个函数,add esp,8说明是cdecl调用,并且参数有2个,所以上面有两个push是压入参数,剩下的push和pop对应一下,发现eax的值是原来push的值,也就是说这个函数没有改变eax的值,所以这个函数没啥作用




接着分析处理字符串的函数,对密码学有所了解的人可以看出这是base64加密算法,这就很简单了,对e3nifIH9b_C@n@dH先循环递增减去i,然后进行base64解密就得到最后结果了
if ( my_str && len )

{

    v7 = len / 3;

    if ( (signed int)(len / 3) % 3 )

      ++v7;

    v7 *= 4;

    *a3 = v7;

    malloc(v7 + 1);

    Dst = (void *)sub_411127();

    if ( Dst )

    {

      j_memset(Dst, 0, v7 + 1);

      v9 = my_str;

      v7 = len;

      i = 0;

      v5 = 0;

      while ( v7 > 0 )

      {

      byte_41A144 = 0;

      byte_41A144 = 0;

      byte_41A144 = 0;

      for ( i = 0; i < 3 && v7 >= 1; ++i )

      {

          byte_41A144 = *v9;

          --v7;

          ++v9;

      }

      if ( !i )

          break;

      v4 = i;

      if ( i == 1 )

      {

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[(signed int)(unsigned __int8)byte_41A144 >> 2];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[((byte_41A144 & 0xF0) >> 4) | 16 * (byte_41A144 & 3)];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn;

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn;

      }

      else if ( v4 == 2 )

      {

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[(signed int)(unsigned __int8)byte_41A144 >> 2];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[((byte_41A144 & 0xF0) >> 4) | 16 * (byte_41A144 & 3)];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[((byte_41A144 & 0xC0) >> 6) | 4 * (byte_41A144 & 0xF)];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn;

      }

      else if ( v4 == 3 )

      {

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[(signed int)(unsigned __int8)byte_41A144 >> 2];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[((byte_41A144 & 0xF0) >> 4) | 16 * (byte_41A144 & 3)];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn[((byte_41A144 & 0xC0) >> 6) | 4 * (byte_41A144 & 0xF)];

          *((_BYTE *)Dst + v5++) = aAbcdefghijklmn & 0x3F];

      }

      }

      *((_BYTE *)Dst + v5) = 0;

    }

}


如果不熟悉base64加密函数的话,现逆算法的话还是有点麻烦和费时的,这需要一定的识别算法能力,因为大多数加密算法都是用现成的或者对现有的进行改进利用 要逆base64算法。


首先先了解一下base64加密的原理转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲器中剩下的bit用0补足。然后,每次取出6(因为26=64)个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。 当原数据长度不是3的整数倍时, 如果最后剩下一个输入数据,在编码结果后加2个“=”;如果最后剩下两个输入数据,编码结果后加1个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证数据还原的正确性。 这里还有一篇博文有实例介绍:https://blog.csdn.net/u013412497/article/details/51552335   

最后我就写了一个简单的脚本:
import base64



str2="e3nifIH9b_C@n@dH"

flag=""

for i in range(len(str2)):

    flag+=chr(ord(str2)-i)

print base64.b64decode(flag)


Flag是{i_l0ve_you}






3. 南邮ctf WxyVM1(要用脚本处理大量数据)


用ida分析,发现关键函数都在main函数中,输入字符串后先调用一个函数,再判断输入的字符串长度是否为24,接着和一个数组比较值,如果相等的话,v4的值就为1,程序puts正确


乍一看不怎么难,首先看下sub_4005B6这个函数,如果没有对输入字符串进行处理的话,那么这题就结束了,哈哈哈哈哈怎么可能这么简单呢,这好歹也是我选的肯定要比入门级难




进去后发现是一个很长的循环,为了好辨识,我把刚才的输入命名为my_str,可以看到这就是根据byte_6010C0数组的值来选择switch的不同分支,然后选择性的和byte_6010C0数组的值进行运算。其实就算法来说并不复杂,但是在处理数据的复杂度上可以说是比一般题目要难。在这个时候,就需要用到我们idc脚本了,ida集成支持idc和python脚本,这题用idc脚本的话很快就能得到flag





简单介绍下idc脚本,idc和c语言很像,在idc中,变量命名叫auto,比如说定义i,在idc中写auto i;

ida有很多内带的函数,我们这题要用到的一个函数叫PatchByte
ida自带的说明如下:
// Change value of a program byte
// If debugger was active then the debugged process memory will be patched too
//      ea    - linear address
//      value - new value of the byte
// Returns: 1 if successful, 0 if notsuccess PatchByte       (long ea,long value);   
// change a byte


这个函数的作用就是改变程序里一个byte的值
Idc脚本里必须声明main函数
static main()
{
//代码
}
Idc的介绍就到这里,下面是我们的脚本:


static main()

{

auto i;

for ( i = 14997; i >= 0; i = i - 3 )

{

    auto v0 = Byte(0x6010C0+i);

    auto v3 = Byte(0x6010C0+(i + 2));

    auto result = v0;

    if(v0==1){

      result =Byte(0x6010C0+i + 1);

      PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)-v3);

            }

   if(v0==2){

         result =Byte(0x6010C0+i + 1);

       PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)+v3);

            }

    if(v0==3){

      result =Byte(0x6010C0+i + 1);

      PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)^v3);

            }

   if(v0==4){

      result =Byte(0x6010C0+i + 1);

      PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)/v3);

            }

   if(v0==5){

         result =Byte(0x6010C0+i + 1);

PatchByte(0x601060 + result*4,Byte(0x601060 + result*4)^Byte(0x601060+v3*4));

            }

    else

      continue;



}



for(i=0;i<24;i++)

Message("%c",Byte(0x601060+i*4));//最后输出修改后既逆向得到的flag




}




整个idc脚本结构和分析的函数一样,所以我没写注释了,地址在ida可以看到,在ida中载入脚本,就能一键出flag




消息的输出是在ida下方的窗口,可以看到flag已经出来了 这题主要是介绍idc脚本的使用,并未涉及到更高深的用法,但是也可以认识到idc对提高逆向效率的实用性



4.实验吧 分道扬镳 (分析可知是在走迷宫,移动顺序既flag)

这个题目挺有意思的,除了验证函数不同,还加了一个混淆的错误验证算法。
同样可以搜索字符串找到关键函数,首先判断长度是否等于22,然后在一个循环里,一位一位判断输入,其中输入只能为107 ,106 ,104,108,转化为ascii码为k,j,h,l,接下来的判断需要先分析v3,v5和v6的值





程序中并没有特意给v3,v5,v6赋值,所以需要分析变量存储的实际情况,看紫色框是在栈帧中的偏移量,在栈中可以看到,v5在v4后面,v6在v5后面,所以给v4赋值字符串时,v5和v6处也覆盖了字符串file:///C:\Users\ASUS\AppData\Local\Temp\ksohtml\wpsF834.tmp.jpg file:///C:\Users\ASUS\AppData\Local\Temp\ksohtml\wpsF835.tmp.jpg   





知道这些变量的值之后,分析下面四个不同的分支,发现了以下规律
104 - h -v3--
106 - j -v3+=8
108 - l -v3++
107 - k -v3-=8
其中v3都不能等于42,既*,而等于35既#就成功了 再看v4字符串,只有一个#,这看上去不就像是走迷宫么,h,l表示左右,k,j表示上下


********* *    ** * ** ** * ** ** * #* ** **** **      *********
结合前面的分析,v5的位置就是起始位置,把字符串复制到文档中,每8个一行,得到如下迷宫,起始位置和终点位置找到以后,再根据刚才分析出的方向键,即可得出正确的走法file:///C:\Users\ASUS\AppData\Local\Temp\ksohtml\wpsF836.tmp.jpg



jjjjjlllllkkkkkhhhjjjl


当然了,其实它还有另一条线,真正运行时不走那条线,而是先判断迷宫这条线而且也只是简单的异或,没有迷宫好玩,关键是那个函数算出来的flag还是假的哈哈哈。 file:///C:\Users\ASUS\AppData\Local\Temp\ksohtml\wpsF847.tmp.jpg






未完待续······

zttxjjh 发表于 2018-5-5 23:11

菜鸟前来学习!谢谢!

amazingADC 发表于 2018-5-5 23:27

学习了,感谢楼主

wulianghao 发表于 2018-5-5 23:54

太厉害了膜拜大佬啊

xlhr123 发表于 2018-5-6 00:09

感谢楼主分享,支持一下!

wulianghao 发表于 2018-5-6 00:40

感谢封箱!!!! 大大

qqqwww0078 发表于 2018-5-6 02:31

学习一下 学习lz

moke666 发表于 2018-5-6 08:55

感谢楼主,学习了,{:1_921:}

0531chuxin 发表于 2018-5-6 09:54

嗯嗯.....挺不错的

kanxue2018 发表于 2018-5-6 10:38

支持一下!
页: [1] 2 3 4
查看完整版本: CTF-逆向教程2菜鸟级