Re-CSAPP(二进制炸弹)解析

文件来源 :http://files.cnblogs.com/remlostime/bomb.zip

一.解析工具

IDA Pro(64位)

Ubuntu

gdb调试工具

二.分析基本流程

首先用IDA打开,找到main函数,通过main函数的解析,可知大体流程为:

①初始化炸弹

②读取参数后进行判断是否为正确参数,否则引爆炸弹

③一共需要6次判断,即输入6次参数,每一次的判断都有特定的判断方式

  

三.具体分析

①phase_1

首先来进行第一次的判断,运行该二进制文件后,出现以下提示

可以先输入任意字符测试一下,例如

炸弹引爆,程序结束,下面开始正式分析。

经过IDA分析,可以得到第一步思路,在phase_1这个函数上下断,因为执行完该函数,就出现了第一次判断结果

输入任意字符后,发现在phase_1处断下,disass观察该处反汇编

 标红区域为重点分析区域

首先,该函数push了两个参数,第一个是位于0x8049678地址的字符,第二个[ebp+0x8],学过C语言汇编的明白,这是第一个参数所在的地址

接着该函数又call一个函数,即<strings_not_equal>,可以判断这是一个比较函数,如果这两个字符串相等,则eax置零,成功实现跳转,跳到下一个判断函数,否则调用<explode_bomb>,引爆炸弹,程序结束。

于是,关键任务就是找出,0x8049678该处地址所存储的内容是什么,这一点,用IDA进行解决

IDA已经给出答案,即0x8049678处所存的内容为The future will be better tomorrow.

输入程序中进行验证,得到正确结果,进入下一关

 ===================================================================

②phase_2

进入第二个判断函数,随意输入字符测试

 与上述同理,在phase_2函数处下断,进行gdb调试,获得反汇编代码

 红色标记处为关键代码段,下面进行分析

首先将参数push到栈中,然后调用<read_six_numbers>,从字义上来看,就是读取6个数字,读取完成之后,将1与[ebp-0x20]进行比较,如果相等,则进行下面进一步的判断,如果不相等,则触发爆炸函数,程序结束。

于是,可以得到结论,即第一个数字为1。

继续往下分析,首先将ebx赋值为2,然后将ebx的值赋给eax,下面关键点来了

lea          esi,[ebp-0x20]        //将参数的起始地址赋给esi        
imul        eax,dword ptr [esi+ebx*4-0x8]   //[esi+ebx*4-0x8]相当于[esi+(ebx*4-0x8)],即这里代表值,而不是实际参数中的数字
cmp        eax,dword ptr [esi+ebx*4-0x4]  //将参数中的第x个数字与eax相比较
je           0x8048b79 <phase_2+60>      //相等则向下跳转    
call         <explode_bomb>
inc          ebx                                    //ebx+1
cmp        ebx,0x7                             //若不相等
jne          <phase_2+42(imul)>         //跳回imul所在的代码继续执行,即是一个循环
        

通过一个循环,对所输入参数中的数字进行了判断,因为第一个数字已经确定为1,所以下面比较的是从第二个数字开始(这是容易出错的地方)

通过这段代码,即可逆向分析出结果   1 2 6 24 120 720

 ===================================================================

③phase_3

接着往下分析,首先输入任意字符,发现炸弹爆炸,查看ida得到下一个判断函数为phase_3,于是在gdb中对该函数下断,输入参数后中断,下面分析反汇编

x08048b86 <+0>:    push   ebp
   0x08048b87 <+1>:    mov    ebp,esp
   0x08048b89 <+3>:    sub    esp,0x18
=> 0x08048b8c <+6>:    lea    eax,[ebp-0x8]
   0x08048b8f <+9>:    push   eax
   0x08048b90 <+10>:    lea    eax,[ebp-0x4]
   0x08048b93 <+13>:    push   eax
   0x08048b94 <+14>:    push   0x8049968
   0x08048b99 <+19>:    push   DWORD PTR [ebp+0x8]      //传入参数
   0x08048b9c <+22>:    call   0x8048878 <sscanf@plt>   //将输入的字符串转为数据  int类型
   0x08048ba1 <+27>:    add    esp,0x10
   0x08048ba4 <+30>:    cmp    eax,0x1                  //如果输入的参数小于2个,跳转到爆炸函数
   0x08048ba7 <+33>:    jg     0x8048bae <phase_3+40>
   0x08048ba9 <+35>:    call   0x80493ec <explode_bomb>
   0x08048bae <+40>:    cmp    DWORD PTR [ebp-0x4],0x7  
   0x08048bb2 <+44>:    ja     0x8048c19 <phase_3+147>  //如果大于7,跳转到爆炸函数
   0x08048bb4 <+46>:    mov    eax,DWORD PTR [ebp-0x4]
   0x08048bb7 <+49>:    jmp    DWORD PTR [eax*4+0x80496cc]   //通过连续比较跳转和具有switch结构特征的跳转表,判断出下面是一个是switch结构
   0x08048bbe <+56>:    mov    eax,0x0
   0x08048bc3 <+61>:    jmp    0x8048c12 <phase_3+140>      //switch(ret)通过返回值判断跳转地址
   0x08048bc5 <+63>:    mov    eax,0x0
   0x08048bca <+68>:    jmp    0x8048c0d <phase_3+135>
   0x08048bcc <+70>:    mov    eax,0x0
   0x08048bd1 <+75>:    jmp    0x8048c08 <phase_3+130>
   0x08048bd3 <+77>:    mov    eax,0x0
   0x08048bd8 <+82>:    jmp    0x8048c03 <phase_3+125>
   0x08048bda <+84>:    mov    eax,0x0
   0x08048bdf <+89>:    jmp    0x8048bfe <phase_3+120>
   0x08048be1 <+91>:    mov    eax,0x0
   0x08048be6 <+96>:    jmp    0x8048bf9 <phase_3+115>
   0x08048be8 <+98>:    mov    eax,0x359
   0x08048bed <+103>:    jmp    0x8048bf4 <phase_3+110>
   0x08048bef <+105>:    mov    eax,0x0
   0x08048bf4 <+110>:    sub    eax,0x1df                     //
   0x08048bf9 <+115>:    add    eax,0x2bd                    // 
   0x08048bfe <+120>:    sub    eax,0x2db                    //
   0x08048c03 <+125>:    add    eax,0xf2                     //    初始化参数
   0x08048c08 <+130>:    sub    eax,0x86                     //
   0x08048c0d <+135>:    add    eax,0x86                     //
   0x08048c12 <+140>:    sub    eax,0x19b                    //
   0x08048c17 <+145>:    jmp    0x8048c23 <phase_3+157>
   0x08048c19 <+147>:    call   0x80493ec <explode_bomb>
   0x08048c1e <+152>:    mov    eax,0x0
   0x08048c23 <+157>:    cmp    DWORD PTR [ebp-0x4],0x5
   0x08048c27 <+161>:    jg     0x8048c2e <phase_3+168>
   0x08048c29 <+163>:    cmp    eax,DWORD PTR [ebp-0x8]
   0x08048c2c <+166>:    je     0x8048c33 <phase_3+173>
   0x08048c2e <+168>:    call   0x80493ec <explode_bomb>
   0x08048c33 <+173>:    leave  
   0x08048c34 <+174>:    ret    

sscanf()函数是c语言中将字符转为数据的函数,通过查看0x8049968地址中的值可以得出,传入函数的是两个int类型的参数

然后phase_3函数会判断输入的参数是否大于一个,否则就跳转到爆炸函数结束进程,接下来会判断输入的第一个数据是否为小于7的int型数据,如果是,则跳转到switch结构执行,如果不是,则跳转到爆炸函数。

到了switch结构可以发现,第一个数据可以当作一个retn值,即作为switch跳转表的索引,通过这个值,来跳转到相应的地址接着执行。

这里拿retn = 5进行举例,当返回值为5时,会跳转到0x08048c12,因为前面eax已经被初始化为0,所以sub之后变为-411,接着判断第一个数据是否为5,第二个数据是否为-411,如果这两个数都符合要求,就通过判断函数。

当然,这里也可以用其他值,可以进行分析尝试。

 ===================================================================

④phase_4

老样子,输入任意字符检测,通过gdb在phase_4中下断,得到反汇编进行进一步分析

   0x08048c71 <+0>:    push   ebp
   0x08048c72 <+1>:    mov    ebp,esp
   0x08048c74 <+3>:    sub    esp,0x1c
=> 0x08048c77 <+6>:    lea    eax,[ebp-0x4]  //结构体指针
   0x08048c7a <+9>:    push   eax
   0x08048c7b <+10>:    push   0x804996b
   0x08048c80 <+15>:    push   DWORD PTR [ebp+0x8]
   0x08048c83 <+18>:    call   0x8048878 <sscanf@plt>    
   0x08048c88 <+23>:    add    esp,0x10
   0x08048c8b <+26>:    cmp    eax,0x1     //传入的参数如果不等于1,跳转到爆炸函数,进程结束
   0x08048c8e <+29>:    jne    0x8048c96 <phase_4+37>
   0x08048c90 <+31>:    cmp    DWORD PTR [ebp-0x4],0x0  //判断参数是否大于0
   0x08048c94 <+35>:    jg     0x8048c9b <phase_4+42>   //大于则跳转
   0x08048c96 <+37>:    call   0x80493ec <explode_bomb>
   0x08048c9b <+42>:    push   DWORD PTR [ebp-0x4]      //将传入的参数作为fun4的参数
   0x08048c9e <+45>:    call   0x8048c35 <func4>        //执行fun4()
   0x08048ca3 <+50>:    add    esp,0x4
   0x08048ca6 <+53>:    cmp    eax,0x90                //如果fun4()的返回值为0x90,则通过判断函数,否则结束进程
   0x08048cab <+58>:    je     0x8048cb2 <phase_4+65>
   0x08048cad <+60>:    call   0x80493ec <explode_bomb>
   0x08048cb2 <+65>:    leave  
   0x08048cb3 <+66>:    ret    

通过大体分析可以得知,传入sscanf函数的参数只有1个,如果这个参数大于0,则将这个参数作为fun4()的参数执行fun4(),执行完该函数后,如果返回值为0x90,则成功通过判断函数。

于是,下一步需要知道fun4都做了些什么,在ida中观察得

                               push    ebp
.text:08048C36                 mov     ebp, esp
.text:08048C38                 push    esi
.text:08048C39                 push    ebx
.text:08048C3A                 mov     ebx, [ebp+arg_0]      //将[ebp+8]这个参数传给ebx
.text:08048C3D                 cmp     ebx, 1                //如果参数大于1,继续执行,否则跳出fun4,结束进程
.text:08048C40                 jg      short loc_8048C49
.text:08048C42                 mov     esi, 0
.text:08048C47                 jmp     short loc_8048C67
.text:08048C49 ; ---------------------------------------------------------------------------
.text:08048C49
.text:08048C49 loc_8048C49:                            
.text:08048C49                 mov     esi, 0
.text:08048C4E
.text:08048C4E loc_8048C4E:                            
.text:08048C4E                 sub     esp, 0Ch
.text:08048C51                 lea     eax, [ebx-1]        //ebx-1后,将新ebx的地址给eax
.text:08048C54                 push    eax
.text:08048C55                 call    func4               //将eax作为参数再次调用fun4,可知是一个递归结构
.text:08048C5A                 sub     ebx, 2              
.text:08048C5D                 add     esi, eax
.text:08048C5F                 add     esp, 10h
.text:08048C62                 cmp     ebx, 1
.text:08048C65                 jg      short loc_8048C4E    //若ebx值大于1,跳回循环
.text:08048C67
.text:08048C67 loc_8048C67:                           
.text:08048C67                 lea     eax, [esi+1]
.text:08048C6A                 lea     esp, [ebp-8]
.text:08048C6D                 pop     ebx
.text:08048C6E                 pop     esi
.text:08048C6F                 leave
.text:08048C70                 retn
.text:08048C70 func4           endp

其中含有一个递归结构,最终的目的就是当ebx为1时,该递归结构完毕,继续向下执行,于是,可以根据汇编逆出正向代码

#include "stdio.h"
int fun4(int n)
{
    int ret = 0;
    if (n<=1)
    {
        return 1;
    }
    for (int i = n; i > 1;i-=2)
    {
        int m = i - 1;
        ret += fun4(m);
    }
    return ret + 1;
}
int main ()
{
    for (int i = 2;;i++)
    {
        if (fun4(i) == 144)
        {
            printf("%d", i);
        }
    }
        return 0;
}

得到 n =11,输入进程,通过判断,向下执行。

===================================================================

原文地址:https://www.cnblogs.com/Virus-Faker/p/12492225.html