让我们来回顾下整个过程,缓冲区被溢出了,数据被复制到缓冲区外面并且覆盖掉了那个“金丝雀”值(译者注: windows 上面也有类似的机制,不过在windows上这个值叫做安全cookies )同时也覆盖掉了函数的返回地址。但是,悲剧的是在函数就要返回到那个被改写的地址继续执行的时候,函数检查了下那个金丝雀值是不是被改写了。于是函数没有返回而是执行另外一个函数安全的让进程退出了。现在坏消息来了,对于一个攻击者并没有一个很好的方式来绕过这个检测。你可能会想到暴力猜解那个金丝雀值。但是这个值每次都不同,除非你非常的幸运被你猜到了 (译者注:概率:1/2^32),而且这样做也是费时而且容易被发现的。但是还有好消息,那就是在很多的情况下这个并不能阻止溢出攻击。举例来说,栈里面的金丝雀值只是保护SIP不被非法的改写,但是它不能阻止函数的局部变量被改写。这就很容易导致下一步的溢出,这会在下面的文章里演示。上面讲的保护机制有效的阻止我们老的攻击方式的攻击,但是马上这种保护机制就会失效。
4.2 NX:不可执行内存
你可能注意到我们不仅仅去掉了-fno-stack-protector这个标识,同时也去掉了-zexecstack标识,(也就是允许执行栈中的代码)现代的操作系统是不允许这种情况发生的,系统把需要写入数据的内存标识为可行,把保存指令的内存标识为可执行,但是不会有一块内存被同时标识为可写和可执行的。因此我们既不能在可执行的内存区域写入我们的shellcode 也不能在可写入的地方执行我们的shellcode (译者注:哈哈 系统的保护错误很变态吧 本来内存就只要可读 或者 可写属性 后来加入的 可执行 属性大大增强了系统的安全性)。我们需要另外的一种方式来让欺骗程序执行我们的代码,答案就是ROP(Return-Oriented Programming),这个技巧就是使用程序中已经有的代码片段,也就是位于可执行文件的.text节里面代码,使用一种方式将这些代码片段链到一起使他们看来就像我们以前的shellcode。关于此,我不会深入的讲解,但是我会在文件的结尾给大家一个例子。还是让我先展示下如果程序如果执行堆栈里的代码会发送的情况(肯定是执行失败了)。
-----------------------------------
$ cat nx.c
int main(int argc, char **argv) {
char shellcode[] =
"\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff"
"\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48"
"\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21";
void (*func)() = (void *)shellcode;
func();
}
$ gcc nx.c -o nx -zexecstack
$ ./nx
Hax!$
$ gcc nx.c -o nx
$ ./nx
Segmentation fault (core dumped)
-----------------------------------
我们把我们要执行的代码放到了堆栈上的一个数组里,然后让一个函数指针指向这个数组,然后执行这个函数。当我们编译的时候和之前一样带上 –zexecstack,我们的shellcode 就会执行,但是如果不带上这个选项,栈空间就会被标识为不可执行的,程序也就会随着一个段错误而执行失败。
4.3 ASLR:地址空间随机化
我们为了演示那个经典的溢出攻击,做的最后一件事就是关掉 ASLR,通过在root下执行echo "0" > /proc/sys/kernel/randomize_va_space 。ASLR可以确保每次程序被加载的时候,他自己和他所加载的库文件都会被映射到虚拟地址空的不同地址处。这就意味着我们不能使用我们自己在gdb里面调试时的地址了。因为这个程序在运行的时候这个地址有可能变成另外一个。要注意当你调试一个程序的时候 gdb 会关掉ASLR。但是我们可以在调试的时候打开这个选项,以便我们可以更真实的看到程序执行时发送的一切,具体看下面的演示
(输出的过长字符串在右边截断了,左边显示的地址信息才是最重要的):
-----------------------------------
$ gdb -q ./oldskool
Reading symbols from /home/me/.hax/vuln/oldskool...done.
(gdb) set disable-randomization off
(gdb) break main
Breakpoint 1 at 0x4005df: file oldskool.c, line 11.
(gdb) run
Starting program: /home/me/.hax/vuln/oldskool
Breakpoint 1, main (argc=1, argv=0x7fffe22fe188) at oldskool.c:11
11 go(argv[1]);
(gdb) i proc map
process 6988
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/me/.hax/vuln
0x600000 0x601000 0x1000 0x0 /home/me/.hax/vuln
0x601000 0x602000 0x1000 0x1000 /home/me/.hax/vuln
0x7f0e120ef000 0x7f0e122a4000 0x1b5000 0x0 /lib/x86_64-linux-
0x7f0e122a4000 0x7f0e124a3000 0x1ff000 0x1b5000 /lib/x86_64-linux-
0x7f0e124a3000 0x7f0e124a7000 0x4000 0x1b4000 /lib/x86_64-linux-
0x7f0e124a7000 0x7f0e124a9000 0x2000 0x1b8000 /lib/x86_64-linux-
0x7f0e124a9000 0x7f0e124ae000 0x5000 0x0
0x7f0e124ae000 0x7f0e124d0000 0x22000 0x0 /lib/x86_64-linux-
0x7f0e126ae000 0x7f0e126b1000 0x3000 0x0
0x7f0e126ce000 0x7f0e126d0000 0x2000 0x0
0x7f0e126d0000 0x7f0e126d1000 0x1000 0x22000 /lib/x86_64-linux-
0x7f0e126d1000 0x7f0e126d3000 0x2000 0x23000 /lib/x86_64-linux-
0x7fffe22df000 0x7fffe2300000 0x21000 0x0 [stack]
0x7fffe23c2000 0x7fffe23c3000 0x1000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/me/.hax/vuln/oldskool
Breakpoint 1, main (argc=1, argv=0x7fff7e16cfd8) at oldskool.c:11
11 go(argv[1]);
(gdb) i proc map
process 6991
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x401000 0x1000 0x0 /home/me/.hax/vuln
0x600000 0x601000 0x1000 0x0 /home/me/.hax/vuln
0x601000 0x602000 0x1000 0x1000 /home/me/.hax/vuln
0x7fdbb2753000 0x7fdbb2908000 0x1b5000 0x0 /lib/x86_64-linux-
0x7fdbb2908000 0x7fdbb2b07000 0x1ff000 0x1b5000 /lib/x86_64-linux-
0x7fdbb2b07000 0x7fdbb2b0b000 0x4000 0x1b4000 /lib/x86_64-linux-
0x7fdbb2b0b000 0x7fdbb2b0d000 0x2000 0x1b8000 /lib/x86_64-linux-
0x7fdbb2b0d000 0x7fdbb2b12000 0x5000 0x0
0x7fdbb2b12000 0x7fdbb2b34000 0x22000 0x0 /lib/x86_64-linux-
0x7fdbb2d12000 0x7fdbb2d15000 0x3000 0x0
0x7fdbb2d32000 0x7fdbb2d34000 0x2000 0x0
0x7fdbb2d34000 0x7fdbb2d35000 0x1000 0x22000 /lib/x86_64-linux-
0x7fdbb2d35000 0x7fdbb2d37000 0x2000 0x23000 /lib/x86_64-linux-
0x7fff7e14d000 0x7fff7e16e000 0x21000 0x0 [stack]
0x7fff7e1bd000 0x7fff7e1be000 0x1000 0x0 [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
-----------------------------------