CSAPP实验四----缓冲区溢出实验bufbomb

来源:互联网 发布:mysql in or 编辑:程序博客网 时间:2024/06/05 06:58

    在网上关于这个的实验有几个版本,这个版本只有三关,都比较基础,为了不让大家混淆,对本实验做一下说明:

文件说明

    1. bufbomb : 主程序,有四个选项,最常用的是 -t ,后面加自己的姓名等,运行时会根据加入的参数生成cookie,第二关和第三关都会用到。

    2.  sendstring :  翻译程序,实验要求将字符转化成ascii码输入,两位输入,即想输入0也要输入00,1对应01,以此类推。可以通过sendstring < exploit.txt > exploit_raw..txt 将翻译结果输入到 exploit_raw.txt 文件中。一般如果输入的文件中有地址时都尽量不要打开输出文件,里面会有一些无法显示的字符,尽量用管道或者重定位来进行输入。gdb下可以提通过重定位将 exploit_raw.txt 文件中的内容作为输入,例如 :run -t pangjingyu < exploit_raw.txt

    3. makecookie: 根据输入的字符串输出唯一的cookie,作为标识,bufbomb中含有此程序。以  -t  选项运行bufbomb时要求输入标识,会自动生成并显示对应的cookie。


关卡说明

    1  getbuf()返回时,不返回到test(),而是直接返回到指定的smoke()函数。

    2  getbuf()返回时,不返回到test(),而是直接返回到指定的fizz()函数,而且要求给fizz()函数传入一个黑客cookie值作为参数

    3  getbuf()返回时,不返回到test(),而是直接返回到指定的bang()函数,并且在返回到band()之前,先修改全局变量global_value为你的黑客cookie值。

关卡1

     思路

       目的是当getbuf()函数返回时不是到test(),而是smoke()。所以要在返回时更改getbuf()ebp+4的位置的返回地址。而该处地址可以通过给其输入一个超长的字数串来更改。

   解决步骤

     1. 首先观察getbuf()和gets(),save_char()函数。第一关只涉及这三个函数,整理其调用顺序是:getbuf() -> gets()-> save_char()。

      2. 观察getbuf()函数:

                                                            

                                                                               图1  getbuf()函数

                            

                                                                     图2  gdb设置断点并输入参数运行

   通过单步执行可以得到如下栈数据:

                         

                                                                                 图3  观察栈参数

                                                          

                                                                           图4  返回地址和栈参数

根据图3和4可以的到在进入Gets()前 栈的参数设置如图:

                                         

                                                                      图5    getbuf()函数栈示意图

       如图,0xffffb11c处存放的0x08048db2正是test()中调用getbuf()的命令接下来的地址。此外,由于只需要改动getbuf()栈,所以没有必要一开始就去深究Gets()和save_char()函数,只要用gdb先了解它们对getbuf()栈的影响。此时还不知道字符串的存储地址,但却可以知道字符串的保存必然会影响到0xffffb0f0 ---- 0xffffb118的地址段内容。所以先手动直接输入一个0x28/2长度(20个字符)的字符串来测试存放的规律:


                               

                                                                          图6  getbuf()函数调用前后命令处设置断点

                            

                                                                           Gets()执行前后getbuf()栈的变化

        可以看到只有0xffffb100 --- 0xffffb117的内存发生变化,而且内容刚好是测试字符串 01234567890123456789的ASCII码。测试其他其他字符串也是对应的ASCII码。所以Gets()和save_char()的最终效果就是将输入的字符串存在0xffffb100处起始的内存中。所以要修改0xffffb11c只要输入一个长度为20+12长度的字符串,且最后四个为函数smoke()的地址。由于getbuf()栈会被破坏,所以对于smoke()函数的栈准备语句可以直接跳过,且samke()中有exit()函数,无需返回到任何地方,所以字符串其他部分不用考虑,可以随意填充。smoke()函数如图:

                                         

                                                                                     图8   smoke()函数

        如图,根据以上分析,只要将0xffffb11c处的地址改为0x08048eb6即可。由于是小端机器,所以最后四个字符存储的方式应该为b68e0408。剩余的由0填充即可。根据给的材料,直接将想要存入的内容写在exploit.txt中,通过sendstring处理后输入到bufbomb中,所以将要处理的字符串应该如下:

                               00000000000000000000000000000000000000000000000000000000b68e0408

        首先在exploit.txt中写入如上字符串,通过sendstring写入exploit_raw.txt中:

                  

                                                                                   图9    处理答案字符串生成输入用文件

        然后进入gdb并设置如图6的断点,通过重定位设置exploit_raw.txt为输入的字符串:

                 

                                                                               图10    输入字符串文件并运行

                        

                                                                                图11   Gets()前后内存对比


         如图11,0xffffb11c处地址成功改为0x08048eb6。继续运行:

                                                

                                                                           图12  成功跳转并结束

关卡2

    思路

       第二关是跳到fizz()并准备好参数。跳转根据第一关的答案直接改掉最后四个字节为608e0408即可。准备参数则要看fizz()函数会在哪个地方提取该参数。由于将其他位置用0代替,所以当getbuf()函数最后执行ret时ebp的值将会为0,此时就需要从fizz()函数的入口开始执行,那么fizz()的ebp的值应该为getbuf()的最终的esp的值 0xffffb11c

     解决步骤

    首先判断推断的正确性,设置图6的断点,将第一关的答案的最后四个字节改为 608e0408,通过gdb查看ebp,esp,eip的值的变化:

                             

                                                                       图13     leave/ret前后寄存器值的变化情况1

         

                                                                       图14  leave/ret前后寄存器值的变化情况1

        如上图,成功进入了fizz函数,观察fizz的参数准备过程:

                                                                       
                                                                           图14    fizz()参数准备和判断过程
        

        由mov 0x8(%ebp), %eax以及cmp指令可以看出 $ebp+8 的地址存放的就是我们应该为fizz准备的参数,而0x804a1d4存放的则是程序运行时根据名字得到的cookie。根据图13得到的ebp,esp等数据可以知道该地址应该为0xffffb120 – 4 + 8 = 0xffffb124。而第一关的答案修改到0xffffb11c,所以要再加8字节,且最后4个字节为cookie,即2ac090b5的小端排列:b590c02a。其余四个字节随意填充。据此得到的答案为:

               00000000000000000000000000000000000000000000000000000000608e040800000000b590c02a

        将答案输入到exploit.txt并用 sendstring处理后存放到exploit_raw.txt中,用gdb进行测试:
                                                
                                                                       图15   成功通过关卡2
        如图,解决了第二个关卡。

关卡3

    思路

       由于目标文件在运行过程中无法判断一个地址属于.text还是其他地址,所以可以通过在字符串中输入一部分汇编指令的操作码并控制跳转指令来运行。可以观察bang()函数找到全局变量global_value的地址,然后做出相应的汇编指令的操作码。
    

    步骤

       首先观察bang准备和判断参数的部分:
                                                                                        
                                                                                                                             图16   bang()准备和比较参数

        可以看出0x804a1c4和0x804a1d4为判断的对象,而由关卡2中fizz判断参数的地址也有0x804a1d4可以知道该地址存放的是cookie标准值,那么0x804a1c4就是需要改变的地址。用来修改此处的汇编代码如下图所示:

                                                                                                                             
                                                                                                                                             图17  计划插入的代码

        新建一个test.c文件如下:

                                                                                                                                  

                                                                                                                                                     图18  test.c

        用-m32  -S选项生成汇编文件test.s,在如下所示的leave指令上方插入图17中的代码:

                                                          

                                                                                                                            图19   编译生成汇编文件


                                                                                                                          

                                                                                                                             图20   插入计划代码

        继续编译链接test.s文件生成test,并用objdump反汇编出test1.s文件:

                                          

                                                                           图21  编译生成可执行文件并反汇编


        打开test1.s找到如下部分即为需要的指令代码:

                                               

                                                                                                          图22   计划代码的指令码

        jmp指令不能直接用,而是需要计算。根据图21得到的指令的长度( 5+5+5 ),再加上输入的字符串存储的地址(0xffffb100),可以推断出当运行到jmp指令(未执行jmp)时,eip的值为0xffffb10a,而该指令为PC相对寻址,所以e9后的数字应该为:0x8048e10– ( 0xffffb10a + 5 ) , 即0x0804dd01。所以字符串的代码部分为 b8b590c02a a3c4a10408 e901dd0408, 而对应答案1和2中跳转的最后四个字节则改为 00b1ffff,即字符串的首地址。 长度与关卡1的答案长度相同,因为不需要像关卡2去为函数准备参数

              a1d4a10408a3c4a10408e901dd04080000000000000000000000000000b1ffff


        接下来用sendstring处理exploit.txt并输出到exploit_raw.txt,进入gdb:

                    

                                                                                                                         图23  处理待测答案

        继续通过gdb进行答案验证:

                  

                                                                                                 图24  重定位输入流输入待验证答案

                               

                                                       图25   单步执行到getbuf的最后一条指令并查看内存情况


        如图,执行getbuf()最后的ret指令前查看是否成功把代码首地址0xffffb100写入0xffffb11c,如图表示写入成功。继续执行:


                                                       

                                                                                     图26  插入代码的执行


         如图,成功跳转到存放指令字符串的位置,并成功执行了想要插入的代码。

                                         
                                                                                                 图27  成功跳转到bang()函数

                              
                                                                                        图28  成功通过第三关

        第三关也可以输入如下的代码:

                                                                                                               
                                                                                             图29  第三关第二解

        对应的答案串为:

                                                   a1d4a10408a3c4a10408b8168e0408a320b1ffffc30000000000000000b1ffff


        由于在getbuf()执行ret前esp为0xffffb11c = ebp,执行ret后栈顶出栈,esp+4,没有ebp变动,且在插入代码中没有入栈出栈的操作,所以插入代码的ret的出栈数据 ( 执行该命令后的eip的值 ) 应该存储在0xffffb120处。因为bang()比较的是两个绝对地址的内容,所以bang()函数的esp/ebp准备部分的指令可以不执行,直接跳到0x8048e16即可。虽然不用计算jmp的相对寻址,但增加了将bang()首地址0x8048e16入栈的两条指令。

        其他

           当直接运行bufbomb的第三关时有可能遇到在gdb中运行成功而直接运行会产生段错误的情况,是因为linux下的栈随机化是默认开机时开启的,每一次运行可执行程序时都会分配不同的内存。切换到root模式,输入命令:
echo 0 > /proc/sys/kernel/randomize_va_space
即可。每次开机时会重新开启栈随机化。
    此外可以通过建立如下C文件:
#include<stdio.h> unsigned int find_start(void) {  __asm__("movl %esp,%eax"); } int main() {  printf("0x%x\n",find_start()); }
生成可执行文件后重复运行几次,如果输出的地址均相同则证明未开启栈随机化。


1 0
原创粉丝点击