4.4.2 向进程中植入代码
为了完成在栈区植入代码并执行,我们在上节的密码验证程序的基础上稍加修改,使用如下的实验代码。
#include <stdio.h> #include <windows.h> #define PASSWORD "1234567" int verify_password (char *password) { int authenticated; char buffer[44]; authenticated=strcmp(password,PASSWORD); strcpy(buffer,password);//over flowed here! return authenticated; } main() { int valid_flag=0; char password[1024]; FILE * fp; LoadLibrary("user32.dll");//prepare for messagebox if(!(fp=fopen("password.txt","rw+"))) { exit(0); } fscanf(fp,"%s",password); valid_flag = verify_password(password); if(valid_flag) { printf("incorrect password!\n"); } else { printf("Congratulation! You have passed the verification!\n"); } fclose(fp); } |
这段代码在4.3节溢出代码的基础上修改了3处。
(1)增加了头文件windows.h,以便程序能够顺利调用LoadLibrary函数去装载user32.dll。
(2)verify_password函数的局部变量buffer由8字节增加到44字节,这样做是为了有足够的空间来“承载”我们植入的代码。
(3)main函数中增加了LoadLibrary("user32.dll")用于初始化装载user32.dll,以便在植入代码中调用MessageBox。
实验环境如表4-4-1所示。
表4-4-1 实验环境
|
|
推荐使用的环境 |
备 注 |
|
操作系统 |
Windows XP sp2 |
其他Win32操作系统也可进行本实验 |
|
编译器 |
Visual C++ 6.0 |
如使用其他编译器,需重新调试 |
|
编译选项 |
默认编译选项 |
VS2003和VS2005中的GS编译选项会使栈溢出实验失败 |
|
build版本 |
debug版本 |
如使用release版本,则需要重新调试 |
说明:即便完全采用所推荐的实验环境,函数返回地址、MessageBoxA函数的入口地址等也需要重新确定,因为这些地址可能依赖于操作系统的补丁版本等。这些地址的确定方法在实验指导中均给出了详细的说明。
用VC6.0将上述代码编译(默认编译选项,编译成debug版本),得到有栈溢出的可执行文件。在同目录下创建password.txt文件用于程序调试。
我们准备在password.txt文件中植入二进制的机器码,在password.txt攻击成功时,密码验证程序应该执行植入的代码,并在桌面上弹出一个消息框显示“failwest”字样。
让我们在动手之前回顾一下我们需要完成的几项工作。
(1)分析并调试漏洞程序,获得淹没返回地址的偏移。
(2)获得buffer的起始地址,并将其写入password.txt的相应偏移处,用来冲刷返回地址。
(3)向password.txt中写入可执行的机器代码,用来调用API弹出一个消息框。
本节验证程序里verify_password中的缓冲区为44个字节,按照前边实验中对栈结构的分析,我们不难得出栈帧中的状态如图4.4.2所示。
如果在password.txt中写入恰好44个字符,那么第45个隐藏的截断符null将冲掉authenticated低字节中的1,从而突破密码验证的限制。我们不妨就用44个字节作为输入来进行动态调试。
出于字节对齐、容易辨认的目的,我们把“4321”作为一个输入单元。
buffer[44]共需要11个这样的单元。
第12个输入单元将authenticated覆盖;第13个输入单元将前栈帧EBP值覆盖;第14个输入单元将返回地址覆盖。
分析过后,我们需要进行调试验证分析的正确性。首先,在password.txt中写入11组“4321”,共44个字符,如图4.4.3所示
|
| 图4.4.3 制作溢出文件 |
如我们所料,authenticated被冲刷后,程序将进入验证通过的分支,如图4.4.4所示。
|
| 图4.4.4 验证栈的布局 |
用OllyDbg加载这个生成的PE文件进行动态调试,字符串拷贝函数过后的栈状态如图4.4.5所示。
|
| 图4.4.5 调试栈的布局 |
此时的栈区内存如表4-4-2所示。
表4-4-2 栈帧数据
|
局部变量名 |
内 存 地 址 |
偏移3处的值 |
偏移2处的值 |
偏移1处的值 |
偏移0处的值 |
|
buffer[0~3] |
0x0012FAF0 |
0x31 (‘1’) |
0x32 (‘2’) |
0x33 (‘3’) |
0x34 (‘4’) |
|
…… |
(9个双字) |
0x31 (‘1’) |
0x32 (‘2’) |
0x33 (‘3’) |
0x34 (‘4’) |
|
buffer[40~43] |
0x0012FB18 |
0x31 (‘1’) |
0x32 (‘2’) |
0x33 (‘3’) |
0x34 (‘4’) |
|
authenticated(被覆盖前) |
0x0012FB1C |
0x00 |
0x00 |
0x00 |
0x31 (‘1’) |
|
authenticated(被覆盖后) |
0x0012FB1C |
0x00 |
0x00 |
0x00 |
0x00 (NULL) |
|
前栈帧EBP |
0x0012FB20 |
0x00 |
0x12 |
0xFF |
0x80 |
|
返回地址 |
0x0012FB24 |
0x00 |
0x40 |
0x11 |
0x18 |
动态调试的结果证明了前边分析的正确性。从这次调试中,我们可以得到以下信息。
(1)buffer数组的起始地址为0x0012FAF0。
(2)password.txt文件中第53~56个字符的ASCII码值将写入栈帧中的返回地址,成为函数返回后执行的指令地址。
也就是说,将buffer的起始地址0x0012FAF0写入password.txt文件中的第53~56个字节,在verify_password函数返回时会跳到我们输入的字串开始取指执行。
我们下面还需要给password.txt中植入机器代码。
让程序弹出一个消息框只需要调用Windows的API函数MessageBox。MSDN对这个函数的解释如下。
int MessageBox( HWND hWnd, // handle to owner window LPCTSTR lpText, // text in message box LPCTSTR lpCaption, // message box title UINT uType // message box style ); |
hWnd [in] 消息框所属窗口的句柄,如果为NULL,消息框则不属于任何窗口。
lpTex [in] 字符串指针,所指字符串会在消息框中显示。
lpCaption [in] 字符串指针,所指字符串将成为消息框的标题。
uType [in] 消息框的风格(单按钮、多按钮等),NULL代表默认风格。
我们将写出调用这个API的汇编代码,然后翻译成机器代码,用十六进制编辑工具填入password.txt文件。
题外话:熟悉MFC的程序员一定知道,其实系统中并不存在真正的MessagBox函数,对MessageBox这类API的调用最终都将由系统按照参数中字符串的类型选择“A”类函数(ASCII)或者“W”类函数(UNICODE)调用。因此,我们在汇编语言中调用的函数应该是MessageBoxA。多说一句,其实MessageBoxA的实现只是在设置了几个不常用参数后直接调用MessageBoxExA。探究API的细节超出了本书所讨论的范围,有兴趣的读者可以参阅其他书籍。
用汇编语言调用MessageboxA需要3个步骤。
(1)装载动态链接库user32.dll。MessageBoxA是动态链接库user32.dll的导出函数。虽然大多数有图形化操作界面的程序都已经装载了这个库,但是我们用来实验的consol版并没有默认加载它。
(2)在汇编语言中调用这个函数需要获得这个函数的入口地址。
(3)在调用前需要向栈中按从右向左的顺序压入MessageBoxA的4个参数。
为了让植入的机器代码更加简洁明了,我们在实验准备中构造漏洞程序的时候已经人工加载了user32.dll这个库,所以第一步操作不用在汇编语言中考虑。
MessageBoxA的入口参数可以通过user32.dll在系统中加载的基址和MessageBoxA在库中的偏移相加得到。具体的我们可以使用VC6.0自带的小工具“Dependency Walker”获得这些信息。您可以在VC6.0安装目录下的Tools下找到它,如图4.4.6所示。
|
| 图4.4.6 使用Depends |
运行Depends后,随便拖拽一个有图形界面的PE文件进去,就可以看到它所使用的库文件了。在左栏中找到并选中user32.dll后,右栏中会列出这个库文件的所有导出函数及偏移地址;下栏中则列出了PE文件用到的所有的库的基地址。
1 2 下一页