缓冲区溢出攻击
作为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