高级返回库函数Exploit代码实现(上)
这篇文章大致可以分成两部分。前一部分,描述了高级返回库函数技术。一些现有的观点,或是与其类似的,已经被其他人公开发表的一些观点。然而,这些重要的技术信息资源是零散的。通常,不同平台的实现中,伴随的那些源代码都不是很有教育作用,或者根本没有作用。因而,我决定集合一些有用的资源和我自己的一些想法,写进这篇文章中,它应当利于帮助人们方便的参考。从这些内容公布在众多的安全列表中,应该判断出,这些信息决不是现有的普通公共认识。
第二部分专注于对PaX保护下的系统,通过不同途径实现堆栈缓冲溢出。现在的PaX性能被改进增强了许多,通过堆栈随机地址处理和函数库地址映射的方法增加安全性,并以一种审慎重视的姿态挑战exploit编码者。最初的方法是通过直接调用动态链接标志来决定程序的流程被呈现出来。这种方法非常普遍,而成功的实现所需求的条件也相当容易。
由于PaX保护下Intel平台稍有不同,而一般的示范源代码是为linux i386 glibc系统设计的。PaX被大多数人认为不是很稳定;然而,现有的技术(为linux i386 glibc描述的)能够轻易的在其他系统/体系上实现,能够用于逃避不可执行的安全设计,包括一些在硬件级别保护的实现。
假定读者具备exploit技术的基础知识,在更进一步的学习前,已经对文章[1][2]的理解吸收,[1][2]包含的对ELF描述的实际操作。
2 传统返回函数库技术
传统的返回函数库技术在文章[2]很好的描述了,所以在这里只是简单的摘要。该技术用于逃避堆栈不可执行的保护,是非常普遍的方法。与在堆栈中定位返回代码的地址不同,有缺陷的函数被返回到被动态库占用的内存区域。通过下面的堆栈构造溢出堆栈的缓冲达到目的,如下所示:
< 堆栈增长的方向
内存地址增长的方向 >
| buffer fillup(*)| function_in_lib | dummy_int32 | arg_1 | arg_2 | ...
int32 会被有缺陷的函数的返回地址保存覆盖
(*) buffer fillup 会被保存的$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[栈指针]增长"的方法
这种方法攻击使用过"fomitframepointer"这种编译选项(该编译参数通常不保存帧指针在函数寄存器中,避免指令保存,建立,恢复帧指针)进行编译的文件而设计的,这种编译条件下,典型的函数结尾象这样:
第二部分专注于对PaX保护下的系统,通过不同途径实现堆栈缓冲溢出。现在的PaX性能被改进增强了许多,通过堆栈随机地址处理和函数库地址映射的方法增加安全性,并以一种审慎重视的姿态挑战exploit编码者。最初的方法是通过直接调用动态链接标志来决定程序的流程被呈现出来。这种方法非常普遍,而成功的实现所需求的条件也相当容易。
由于PaX保护下Intel平台稍有不同,而一般的示范源代码是为linux i386 glibc系统设计的。PaX被大多数人认为不是很稳定;然而,现有的技术(为linux i386 glibc描述的)能够轻易的在其他系统/体系上实现,能够用于逃避不可执行的安全设计,包括一些在硬件级别保护的实现。
假定读者具备exploit技术的基础知识,在更进一步的学习前,已经对文章[1][2]的理解吸收,[1][2]包含的对ELF描述的实际操作。
2 传统返回函数库技术
传统的返回函数库技术在文章[2]很好的描述了,所以在这里只是简单的摘要。该技术用于逃避堆栈不可执行的保护,是非常普遍的方法。与在堆栈中定位返回代码的地址不同,有缺陷的函数被返回到被动态库占用的内存区域。通过下面的堆栈构造溢出堆栈的缓冲达到目的,如下所示:
< 堆栈增长的方向
内存地址增长的方向 >
| buffer fillup(*)| function_in_lib | dummy_int32 | arg_1 | arg_2 | ...
int32 会被有缺陷的函数的返回地址保存覆盖
(*) buffer fillup 会被保存的$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[栈指针]增长"的方法
这种方法攻击使用过"fomitframepointer"这种编译选项(该编译参数通常不保存帧指针在函数寄存器中,避免指令保存,建立,恢复帧指针)进行编译的文件而设计的,这种编译条件下,典型的函数结尾象这样:
顶(0)
踩(0)
上一篇:学会使用Linux性能分析工具
- 最新评论