zoukankan      html  css  js  c++  java
  • 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,输入进程,通过判断,向下执行。

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

  • 相关阅读:
    Hibernate的查询方式汇总
    JdbcTemplate详解
    spring读取数据库的配置信息(url、username、password)时的<bean>PropertyPlaceholderConfigurer的用法
    spring aop方式配置事务中的三个概念 pointcut advice advisor
    spring mvc静态资源请求和<mvc:annotation-driven>
    spring aop实现原理
    Spring 配置 事务的几种方式
    转!!常用的4种动态网页技术—CGI、ASP、JSP、PHP
    转! java 中“==” 与“ .equals ”比较
    mysql 批处理文件--- 创建 用户 以及 导入数据
  • 原文地址:https://www.cnblogs.com/Virus-Faker/p/12492225.html
Copyright © 2011-2022 走看看