4.2 修改邻接变量
4.2.1 修改邻接变量的原理
通过上一节,我们已经知道了函数调用的细节和栈中数据的分布情况。如图4.1.8所示,函数的局部变量在栈中一个挨着一个排列。如果这些局部变量中有数组之类的缓冲区,并且程序中存在数组越界的缺陷,那么越界的数组元素就有可能破坏栈中相邻变量的值,甚至破坏栈帧中所保存的EBP值、返回地址等重要数据。
题外话:大多数情况下,局部变量在栈中的分布是相邻的,但也有可能出于编译优化等需要而有所例外。具体情况我们需要在动态调试中具体对待,这里出于讲述基本原理的目的,可以暂时认为局部变量在栈中是紧挨在一起的。
我们将用一个非常简单的例子来说明破坏栈内局部变量对程序的安全性有什么影响。
#include <stdio.h> #define PASSWORD "1234567" int verify_password (char *password) { int authenticated; char buffer[8]; // add local buff to be overflowed authenticated=strcmp(password,PASSWORD); strcpy(buffer,password); //over flowed here! return authenticated; } main() { int valid_flag=0; char password[1024]; while(1) { printf("please input password: "); scanf("%s",password); valid_flag = verify_password(password); if(valid_flag) { printf("incorrect password!\n\n"); } else { printf("Congratulation! You have passed the verification!\n"); break; } } } |
上述代码是第3章最后一节中Crack实验的验证程序修改而来的。请尤其注意以下两处修改:
(1)verify_password()函数中的局部变量char buffer[8]的声明位置。
(2)字符串比较之后的strcpy(buffer,password)。
这两处修改实际上对程序的密码验证功能并没有额外作用,这里加上它们只是为了人为制造一个栈溢出漏洞。
按照前面对系统栈工作原理的了解,我们不难想象出这段代码执行到 int verify_password (char *password) 时的栈帧状态如图4.2.1所示。
|
| 图4.2.1 栈帧布局 |
题外话:这里只是给出了字符数组的缓冲区与局部变量authenticated在栈中的一种分布形式。出于编译优化等目的,变量在栈中的存储顺序可能会有变化,需要在动态调试时具体问题具体分析。
可以看到,在verify_password 函数的栈帧中,局部变量int authenticated恰好位于缓冲区char buffer[8]的“下方”。
authenticated为int类型,在内存中是一个DWORD,占4个字节。所以,如果能够让buffer数组越界,buffer[8]、buffer[9]、buffer[10]、buffer[11]将写入相邻的变量authenticated中。
观察一下源代码不难发现,authenticated变量的值来源于strcmp函数的返回值,之后会返回给main函数作为密码验证成功与否的标志变量:当authenticated为0时,表示验证成功;反之,验证不成功。
如果我们输入的密码超过了7个字符(注意:字符串截断符NULL将占用一个字节),则越界字符的ASCII码会修改掉authenticated的值。如果这段溢出数据恰好把authenticated改为0,则程序流程将被改变。本节实验要做的就是研究怎样用非法的超长密码去修改buffer的邻接变量authenticated从而绕过密码验证程序这样一件有趣的事情。