缓冲区溢出攻击

 

  作为CSapp和网络安全都做了的实验。

  首先我们得到的文件只有这几个:

 

   不像二进制炸弹那个实验,还有c语言代码,知道gdb调试的时候break哪里。那怎么办捏?

  除了gdb之外,我们还有另外一个强大的反汇编调试工具:objdump。在可执行文件bufbomb的目录下执行:objdump -d bufbomb > bufbomb.s,即可获得可执行文件的反汇编代码了。

  

 

   从实验指导书上:

 

   我们可以看出来,缓冲区溢出攻击针对的就是get_buf()这个函数,由于这个函数对于输入字符串没有长度检查,因此造成了缓冲区溢出攻击。

  我们在bufbomb.s的反汇编代码当中查找getbuf这个函数,成功地查找到了。

  

   结合我们之前的c语言传递指针不传递值的原则,我们来分析一下这个汇编代码的结构:

  我们再来看一看上面的写了堆栈地址的那张图片。最下面的是Argument Area。显然我们之前的经验已经告诉了我们,一个函数的堆栈地址空间,最上面的是ebp,也就是ebp的保存值。下面的中间一段就是为这个函数的局部变量所开辟的空间。最下面的Argument Area。我们知道C语言的参数有实参和形参的区别。Argument就是实参,Parameter就是形参。

  那么现在再来分析中间的局部的参数。getbuf()函数首先就是char buf[12],根据char占据一个byte的大小。再根据:

  

   那么就很显然了。从%ebp到%ebp-0xc的这12bytes的空间,都是为字符串的存储准备的。

  这里也给我之前的c语言传递指针不传递值做出了进一步的解释:那就是Argument区域传递字符串指针,但是函数调用过程中的字符串是会存储在堆栈的中间的区域当中的

  好了,我们对于这个函数的堆栈空间就很清楚了。

  然后我们就尝试开始GDB调试环节。然而遇到一点小问题:在设置断点为0x08048fe9,然后r运行的时候提示:

  

   好吧发现还没有设置cookie。注意到最开始的三个文件之一的 makecookie了吗?执行./makecookie your_Id_Number,就是你的学号,然后就生成了cookie了。

  好吧我发现我上面的想法是错误的。权限不够就是权限不够,针对makecookie以及bufbomb这两个文件,均执行chmod 777 makecookie这样的操作。然后在bufbomb的gdb调试当中,就会告诉你需要输入bufbomb -t your_Id_Number。这样才能正确执行。

  下面进入正式的调试环节:

   为什么我们上面需要把断点设置为0x08048fe9呢?是因为我们想知道缓冲区的实际首部地址。我们知道char buf[12]存储在%ebp到%ebp-0xc的这12bytes的空间,但是%ebp-0xc的实际值是多大?

  前面已经知道了查看gdb调试查看寄存器的方法。一个是这里的方法,运行到这里之后,知道此时eax的值就等于%ebp-0xc的实际值,一个i r就搞定,另外一种方法就是p/x $ebp-0xc,很直观。

  那么我们知道缓冲区的首地址是:

  

   所以,如果输入的字符串的长度超过buf的长度的话,那么就会造成缓冲区溢出的问题,精心构造过的输入串可以覆盖当前函数的返回值,进入到我们想要进入的函数当中去。

Level 0

  首先是第一个任务,根据实验指导书的内容,我们的任务就是在getbuf函数返回的时候,跳转到getbuf当中的一个叫做smoke的函数,使得这个原本不会在getbuf当中被执行的函数被执行。

  显然就是需要构造输入串。

  我们先在反汇编代码中找到这个smoke函数。

  

  从这张图片上我们可以看出来,smoke函数的入口是0x08048e20。

  首先输入的字符串显然需要覆盖大小为0xc的缓冲区,然后我们再来看一看之前的堆栈结构,显然,在缓冲区之前还有4字节的%ebp的ebp值保存区域,然后在这个区域的再上面才是返回值所存储的区域。

  所以我们构造一个长度为20bytes的字符串。前面的16bytes可以为任意值,然后最后的4bytes就是构造的返回值的地址。注意内存的大端编制以及小端编制方式,以及前面的smoke函数的入口地址是0x08048e20。

  所以我们可以构造如下的字符串:

  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 8e 04 08

  但是这个实验的实际测试过程有一点点复杂。

 

  首先需要创建一个文件,exploit.txt,然后将上面的这个字符串给放到exploit.txt当中去。然后执行

  

  这样就生成了exploit-raw.txt这个新文件。然后./bufbomb -t your_Id_Number < exploit-raw.txt 

  就可以开始愉快地测试了。结果为:

  

   level0顺利通过

Level 1

  (其核心实质就是,修改跳转地址,继续使用之前的堆栈结构)

  

  这个实验和level0差不多,需要进入一个叫做fizz的函数,不同的是,在这个叫做fizz的函数中,有一个参数,我们需要传递一个参数,而且这个参数需要等于cookie。

  同理,我们先在反汇编代码当中找到fizz函数。

  

  这里就涉及到一个我不太明白的问题:函数调用的时候,以及ret的时候,堆栈的指针具体的变化情况?

  因为之前我所面对的都是一个个二进制炸弹,都是一个个已经进入了的函数,所以我不太了解像这样的缓冲区攻击当中,当调用到另一个函数的时候,堆栈指针是如何变化的?

  为什么我会突然问这个问题呢?因为我在这个实验中,fizz函数的入口地址是0x08048dc0,然后我的cookie是0x45c0c60c。然后按照上面的那张堆栈图,我就觉得答案应该是:

  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 8d 04 08 0c c6 c0 45

  然而并不是这样的,真正的答案是:

  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 8d 04 08 00 00 00 00  0c c6 c0 45

  我不太明白为什么中间又多了四个00。所以下面是对这个问题的探究:

  这个问题应该还分为两种,那就是正常返回的函数(继续执行return address处的指令),以及中途调用其他函数(如递归调用),堆栈指针如何变化?

  因此,显而易见地,我们需要对于ret以及call这两个关键字的机理做一点探究。

  首先分析一下call命令。call命令好像还分为段内转移以及段间转移。这里就按照最简单的来看。

    

   这是显然的,首先把当前指令地址入栈(位置在传参的下面,作为被调用函数的返回地址),%esp减去4,然后跳转到目标指令地址,开始执行新的函数。

  然后分析一下ret命令。ret是一个函数完整地执行完,然后回到了最上面的返回地址当中去。(这也是我们这里要关注的重点)

  在ret之前,我们需要关注到,ret之前总是有一条leave指令一起出现,怎么回事捏?

  原来leave 指令等价于一下这两条指令:

    mov  %ebp ,%esp

    pop  %ebp

  很精妙的一条指令,首先是让%esp回到了程序最处的时候,指向保存%ebp原始值的位置,然后一次pop指令,让%ebp恢复原始值,然后就%esp+=4,指向了返回地址的位置。

  之后继续执行ret指令。

  ret就只相当于一条指令:

    pop eip

  即将程序地址恢复到返回值,然后再%esp+=4。

  相信现在对于%esp如何变化已经非常清楚了。

 

 

  回到问题不难发现,在fizz函数之前,get_buff函数输入之后,%esp会指向返回地址上面一个地址。

 

  看懂了,很简单,进入fizz之后还要push %ebp,那么ebp就会保存在原来的返回地址的位置,ebp也会指向这里。而且分析fizz代码可以看出来,val保存在ebp加8的位置。所以显然要这样构造。

Level 2

  进入选做阶段。

  显然这个任务的目标是修改bang函数的global_value为我们的cookie0x45c0c60c

  

  查看一下bang函数的反汇编代码。

  显然两个printf调用输出的是两个原函数中的字符串。我们发现前一个printf有movl $0x8049a0b,(%esp),后一个printf有movl $0x8049870,$(%esp)。猜测应该就是字符串的起始地址。

  

  果然如此。

  那么%eax保存的应该就是global_value。

  显然,global_value保存的地址是0x804a1dc。

  等等!!!global_value是全局变量,所以我们需要修改的是全局变量,而不是目标跳转函数当中的局部变量

  那么问题就有一点麻烦了。

  首先我们需要自己写一个程序,首先修改全部变量global_value,然后跳转到bang函数处执行即可

  所以首先编写汇编代码,注意汇编代码以.s结尾

  

  

  汇编代码当中,第一行修改global_value,第二行将返回地址(bang函数入口地址)入栈因为ret指令只相当于pop eip

  之后再获取bang.o文件。

  再获取机器码

  

  我们要想办法让这段代码执行,怎么办捏?

  有办法,那就是利用缓冲区。我们知道,指令地址和数据地址是共享内存空间的。一般来说是互不干扰的,指令按顺序,从指令低字节到指令高字节一条条地被取出来到eip执行。但是我们可以利用溢出,将getbuf的返回地址跳转到buf的起始地址,那么之后eip就一条条地读取栈中的数据,从栈顶到栈底(惊讶地发现也是地址增大的顺序)。

  所以这里就是利用数据充当地址。

  不难构造攻击串为:c7 05 dc a1 04 08 0c ..... 04 08 c3加上缓冲区入口地址,也就是esp的地址减去0xc

  那么需要获取getbuf函数当中的缓冲区首地址

  

  

  缓冲区起始地址就是这个。

  构造名为bang.txt的攻击字符串为:

  

  注意很巧,其实已经溢出到了ebp了,但是此时的ebp已经不是ebp了,而是作为指令被读取。

  测试的时候出现了:

  

 

  尝试:

 

   

 

 

   

  发现还是不行

  发现是没有关闭地址随机化

  关闭/开启Linux地址随机化机制_break_cat的博客-CSDN博客

  

  

  可以了。

level 3

  

  

 

   

  显然我们想要修改getbuf()原本的为1的返回值为我们的cookie,使得val等于cookie。

  而且这里的getbuf执行完之后需要回到test()当中来。

 

  所以构造思路如下

  首先和上一个程序一样,我们构造一个可执行程序存储在缓冲区当中,缓冲区的返回值为缓冲区的首地址。

  然后我们分析可执行程序。

  显然需要修改eax的值为cookie0x45c0c60c,然后修改返回地址为call getbuf之后的0x804901e

  不难写出汇编代码:

  movl $0x45c0c60c, %eax

  push $0x804901e

  ret

  保存为test.s

  

  

  

  但是注意!!!设置返回地址的时候,势必会覆盖ebp。

  而因为我们之后想要正常回到test函数中。而我们这里ret的时候,执行leave指令,因此ebp就恢复不到test函数当中的ebp

  那么怎么办呢?

 

  可以看得到test函数当中的ebp:

  

 

   攻击字符串为

  b8 0c c6 c0 45 68 1e 90 04 08 c3 00 28 ba ff ff fc b9 ff ff

  

 

posted @ 2021-11-13 22:24  TheDa  阅读(606)  评论(0编辑  收藏  举报