
3.1 数据位置
有大量的漏洞利用代码可以用来覆写函数指针或对象指针,其中包括缓冲区溢出。
缓冲区溢出通常是由边界不足的循环引起的。通常,有以下这些循环类型:
由上界限制的循环:循环重复执行N次,其中N小于或等于p的边界,指针指定一系列对象,例如,从p到p+N-1。
由下界限制的循环:循环重复执行N次,其中N小于或等于p的边界,指针指定一系列对象,例如,从p到p-N+1。
由数组的末元素地址(又名Hi)限制的循环:循环递增一个间接的指针,直到它等于Hi。
由数组的首元素地址(又名Lo)限制的循环:循环递减一个间接的指针,直到它等于Lo。
由空终止符限制的循环:循环递增一个间接的指针,直到它的目标为空。
要使这些循环类型的缓冲区溢出能被用于覆写函数指针或对象指针,下列条件必须同时成立。
1.缓冲区与目标函数指针或者对象指针必须分配在同一个段内。
2.对于由上界限制的循环、由Hi限制的循环,或由空终止符限制的循环,缓冲区必须位于比目标函数指针或者对象指针更低的内存地址处。对于由下界限制的循环或由Lo限制的循环,缓冲区必须位于比目标函数指针或者对象指针更高的内存地址处。
3.该缓冲区必须是界限不充分的,因此容易被缓冲区溢出利用。
要确定缓冲区和目标函数或者对象指针是否位于同一段内,首先必须了解不同的变量类型是如何在不同的内存段中分配的。
UNIX可执行文件包含data段和BSS [1]段。data段包含了所有已初始化的全局变量和常数。BSS段包含了所有未初始化的全局变量。将已初始化和未初始化变量分开是为了让汇编器不将未初始化的变量内容(BSS段)写入目标文件中。
例3.1展示了一个变量的声明及其存储位置的关系。代码中的注释说明了每一个变量的存储空间分别分配在哪里。
例3.1 数据声明和进程内存组织
01 static int GLOBAL_INIT = 1; /* 数据段, 全局 */ 02 static int global_uninit; /* BSS 段, 全局 */ 03 04 int main(int argc, char **argv) { /* 栈, 局部 */ 05 int local_init = 1; /* 栈, 局部 */ 06 int local_uninit; /* 栈, 局部 */ 07 static int local_static_init = 1; /* 数据段, 局部 */ 08 static int local_static_uninit; /* BSS 段, 局部 */ 09 /* buff_ptr 的存储空间是栈, 局部 */ 10 /* 分配的内存是堆, 局部 */ 11 int *buff_ptr = (int *)malloc(32); 12 }
虽然UNIX和Windows的内存组织有很多不同之处,但例3.1的示例程序中展示的变量在这两种操作系统中遵循同样的分配模式。