很多搞IT都听说过缓冲区溢出,但是怎么利用缓冲区溢出的Bug来运行你自己的代码呢?这里我只介绍怎么利用静态缓冲区溢出来运行黑客程序。因为动态的,我还不会用。:)
第一节 堆栈的形式
在程序运行的时候,每次程序调用一个函数的时候,程序就要在内存里面分配一个空间来保存这个函数要使用到的本地变量,还有参数啊什么什么的。下面用一个程序片断来演示一下堆栈的形式
void TestCaller()
{
TestMethod(1);
int i = 0;
}
void TestMethod(int p)
{
int l1 = 0;
char l2 = '3';
}
上面的程序很简单,就是定义了两个函数,函数TestCaller调用了函数TestMethod,而TestMethod定义了两个局部变量。在计算机的内存里面堆栈的形式如下(大概的模型啊,不是很精确的):
|----------------------|
| .... | <------ TestCaller的堆栈
|----------------------|
| 1 | <------ TestCaller传给TestMethod的参数p的值
|----------------------|
| TestCaller的ebp的值 | <------ TestCaller的堆栈的起始位置
|----------------------|
| 函数返回地址 | <------- TestMethod调用完成以后的返回地址,就是TestCaller函数中,调用
|----------------------| TestMethod语句的下一行语句,本例中是int i = 0;
| 0 | <------- TestMethod的第一个本地变量 l1的值
|----------------------|
| '3' | <------- TestMethod的第二个变量l2的值
|----------------------|
第二节 一个写的很差的程序
我们来看一个写的很差的程序:
void BadFunc(char *input)
{
if ( input == NULL ) return; // 嗯,有错误验证
char local[20];
strcpy(local, input); // 将input指向的内存里面的数据拷贝到local中去
}
很多人都知道为什么这个程序写的很差,是啊,加入input指向的字符串的长度不只20个呢,那会怎么样?
我们先在调试器上试一下,BadFunc("12345678901234567890AAAAAAAAAAAAAAAAAAA"),运行一下程序,恩,VS弹出一个错误对话框(对不起,我这里没有C++ Builder,所有的程序都是在VS 2005调试通过的,VS 6.0和VS 2003可能稍微修改一些输入字符串的长度)。对话框的内容是:
Unhandled exception at 0x41414141 in bufferoverflow.exe: 0xC0000005: Access violation reading location 0x41414141.
注:字符'A'的ASCII玛就是41。在调试器跟踪一下,你就会发现这个错误对话框刚好是在BadFunc执行完成以后出现的。错误信息是Access violation reading location,说明你的程序在执行完BadFunc之后,CPU试图从0x41414141这个位置读取一些数据。打开内存窗口(VS 2005:Debug -- Windows -- Memory -- Memory 1),在Location那个文本框输入ebp<回车>。单击右键,选择4字节显示,你就会发现内存窗口的左上角(MSN空间不能插图,真的是很郁闷)有一溜的0x41414141。右边的ASCII显示也是一长串的AAAA.在内存窗口在往后面走一下,你就会发现刚才BadFunc的参数12345678901234567890AAAAAAAAAAAAAAAAAAA就在ebp的附近。记得我在开始说过的,ebp过去一些,就是你的函数返回地址。VS弹出的错误对话框说明了函数返回地址已经被你的垃圾数据(多出来的AAAAAAAAA)覆盖了,当BadFunc返回的时候,CPU照常读取保存在ebp后面的函数返回地址,然后试图跳到返回地址所指向的位置继续执行。当BadFunc返回的时候,因为返回地址已经是0x41414141(AAAA)了,CPU试图读取内存地址为0x41414141的指令,继续执行程序,但是0x41414141是无效的内存地址(里面保存了什么东西只有天知道),于是一个Access violation(AV)就扔出来了。
这样我们就有机会改变程序原来的执行路径了,只要我们把BadFunc的返回地址改成我们想要的地址,而这个地址刚好有我们事先设计好的指令就可以了。现在来一个比较简单的:
#include
#include
void foo(const char
*input)
{
char
buf[10];
strcpy(buf, input);
}
void bar(void
)
{
printf("Augh! I've been hacked!/n"
);
}
int main(int argc, char
*argv[])
{
printf("Address of main = %p/n"
, main);
printf("Address of foo = %p/n"
, foo);
printf("Address of bar = %p/n", bar); // 0x00411131
foo("1234567890123456/xa0/x12/x41");
return 0;
}
上面的例子里面,你要将最后的/xa0/x12/x41转换成你自己机器上bar函数的地址。最后你会看到,虽然在main函数中没有调用bar函数,但是bar函数还是被调用了。
上面的例子只是调用程序本身已有的函数,你还是没有办法调用你自己编写的函数。例如,你还不能把你写的dll导入到你要黑的程序里面去,所以你要让BadFunc执行你自己的函数,你还是有一些事情要做的。呵呵,下回再说