有点像个人日记,其中也遇到了问题……
……
正文开始:
缓冲区溢出(Buffer Overflow),呃,对于程序员来说,总是不愉快的,而且它往往成为许多漏洞的根源。不过对于Hacker们,可能是个很高兴的发现。
十分常见的一种缓冲区溢出情况就是……通过适当的溢出,覆盖其他区域的数据,从而打乱程序的正常流程。
堆栈溢出,是一种比较容易实现的。
此篇文章就以C++为工具,来实践几个堆栈溢出的例子。
先简单回想下,堆栈内部一般存放什么数据——
临时变量,函数调用的参数,一些寄存器信息。
呵呵,寄存器信息?!对的,这个是个很好的注入点。
函数调用的时候,先是参数按照一定顺序压入堆栈,然后保存指令寄存器地址(EIP)。
函数调用……其实是打乱了程序的顺序执行,因为它使程序从这个位置跳到另一个位置。
那么,当函数返回的时候,它需要恢复原有的流程顺序,那么,EIP就是保存了调用前的下条指令地址。
这么说……我们可以把这个堆栈中原先的EIP值改掉,那么程序不就按照我们的意图执行了?
指针运算,为这一切提供了可能。
当然,我们也不是随意乱改堆栈中EIP的值。如果改后的地址,不是存放指令,或者不是这个段内的数据,那么就会出现类似以下错误(SPF:段保护错误):
我们看如下的代码……
//测试环境:Windows XP + SP2 Intel T2050 Centrino Duo //编译环境:Visual C++ 8.0 (无CLR) #include "stdafx.h" void func(){ printf("Never called implicitly./n"); }
void inject(){ //用来测试溢出的函数 }
int _tmain(int argc, _TCHAR* argv[]) { inject(); printf("Normal END./n"); } |
当然上面的代码,只是先给个框架,还没给出最最关键的代码。
先简单解释下,有些可能令人感到生疏的字眼……
头文件stdafx.h 不太熟悉?
没关系……你可以认为就是包含了stdio.h的所有内容(外加一个tchar.h头文件)。
_tmain,和main一样都是程序入口函数,不过它跟字符编码(Unicode还是MBCS)有关。
现在,相信聪明的你已经明白此次代码的目的了……
就是不通过显式,去调用func函数。
我们目前要做手脚的就是inject函数。
我们为了修改堆栈内EIP的值,我们需要有一个指向堆栈数据的指针。
呵呵,这里是C++,不是汇编,不能随便读取某些寄存器(ESP、EBP)的值。不过我们可以建立一个临时变量,然后获得它的地址……
呃,在此之前,我们需要大致想下堆栈内存布局:
|―――――――――――――-|
|―― main中的临时变量 ――|
|―― 参数(如果有的话) ――|
|―― 保存的EIP ――|
|―― 保存的EBP ――|
|―― inject的临时变量 ――|
|―――――――――――――-|
那么,我们只要在inject中定义一个临时变量,然后取出它的地址,地址向上加就可以得到EIP所在的地址。
因为偶的电脑(大多数都是)属于IA32(Intel Architecture 32-bit)系列,每个寄存器都是32位。
那么,在C++下使用int(32位,其实更准确是unsigned long)是再适合不过了,这样来,指针运算就可以方便多了。
int sth=10; //变量可以随便取名,取值 int* p=&sth; |
嘿嘿,接下来,就是让指针p成为“溢出的凶器”。
……按照刚才所算的……
p[0]就是sth,p[1]就是保存的EBP,p[2]……就是它了,EIP!
要把p[2]改为函数func的入口地址。
那么……
最后一句,就是p[2]=(int)func。
说明:如果不进行强制转型,会出现编译错误,提示: error C2440: “=”: 无法从“void (__cdecl *)(void)”转换为“int” |
运行下,试试吧……
期待,输出Never called implicitly,而不是Normal END。
……
结果……
输出了Normal END,
有点失望,还出现了运行错误:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
它明显是在进行堆栈修正的时候,找不到曾经放在栈里的正确EBP指针……它需要这个指针来恢复栈顶(ESP寄存器值)。
也就是说,我们修改的p[2],其实是保存的EBP!
这么说……改为p[3]试试,即如下的代码:
void inject() { int sth=10; int* p=&sth; p[3]=(int)func; } |
这下……顺利点了……!
输出了Never called implicitly,但是……出现了一个非法操作:
果然……
事情变的不顺利啦……
不过没关系,坚信这些问题都能解决……
问题,目前可以归结为两个:
1, 为什么p[3]才是保存的EIP?! 2, 即使修改了EIP值,但是为什么程序会出现非法操作? |
暂时把问题先提出到这里吧,下一篇文章再随便写写自己的解决途径。