缓冲区溢出
1. 背景
当一个程序被执行时,系统维护一个执行堆栈来存储关于程序中活动函数的信息。执行堆栈中存储的一个重要信息是函数终止时要执行的下一条指令的地址。
- 在许多C/C++代码中,我们通过写入数组的末尾,可能破坏执行堆栈。这被称为smash the stack。
- 当函数终止时,控制流将跳转到内存中的随机地址。
- 但是,黑客可以通过使用精心编制的数据集来控制程序流,将数据写入数组的末尾。
2. 预备知识
2.1 程序结构

- 文本区域 只读,包含代码、说明等
- 数据区域 包含初始化和未初始化的数据,静态变量也存放在这里
- 堆栈区域 后进先出
2.2 堆栈区域的功能
- 动态分配函数中使用的局部变量。
- 将参数传递给函数。
- 函数返回值。
堆栈指针(SP)指向堆栈的顶部。
堆栈的底部位于固定地址。
堆栈由逻辑堆栈帧组成,这些帧在调用函数时被推送,在返回时被弹出。
帧指针(FP)指向帧内的固定位置。
A logical stack frame contains the arguments to the function, the local variables declared in the function, and the data necessary to recover the logical stack frame of the previous function, including the value of the instruction pointer at the time of the function call.
当调用一个函数的时候,一个逻辑栈帧(logical stack frame)被构造,函数退出时,这个逻辑栈帧就释放。一个逻辑栈帧包含指向函数的指针,包括函数里的局部变量,和可以恢复到上一个栈帧的数据等。
我们除了SP之外还有一个指向当前栈帧固定位置的指针,会使得很多操作变得方便,所以引入了FP(frame pointer)。
当一个函数被调用的时候,它要做的第一件事情是:储存前一个FP。因为这样一来,可以在函数退出的时候将FP的值恢复。
2.3 一个例子
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
当调用function的时候,参数入栈顺序为3,2,1。
指令call将指令指针(IP)入栈。我们将栈中的IP看作一个return address。


FP: Frame Pointer that points to the current stack frame
SP: Stack Pointer that points to the top of the stack
通过从SP中减去局部变量的大小来为它们分配空间。
内存以字大小的倍数寻址。
在我们的例子中,字的大小是4个字节。
为5字节缓冲区分配8字节(2个字)。
为10字节缓冲区分配12字节(3个字)。
SP减去20。

3. 缓冲区溢出
void function(char *str) {
char buffer[16];
strcpy(buffer,str);
}
void main() {
char large_string[256] = {0};
int i;
for(i = 0; i < 255; i++)
large_string[i] = 'A';
function(large_string);
}
上面这个例子使用strcpy()而不是strncpy()复制字符串而不进行绑定检查。
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
ret = buffer1 + 12;
(*ret) += 8;
}
ret = buffer1 + 12
the pointer *ret is now pointing to the return address of the function (ret in the diagram).
(*ret) += 8
the return address of the function is increased by 8.
Skip the assignment statement to the printf call.
The program prints 0 instead of 1.

浙公网安备 33010602011771号