6.1.5 方法三、SEH获得kernel基址

“海纳百川,有容乃大!”老师说道,“我们再看看其他动态获得函数地址的方法吧!大家可以了解各种方法的优劣,在不同的时候选用不同的方法。”

“嗯,好的!”

“还有个比较有名的方法,是通过SEH异常处理链来获得Kernel32.dll的基址,再采用引出表的方法,获取函数的地址。”

“哦?怎么通过SEH获得Kernel32.dll的地址呢?”大家感兴趣的问道。

“呵呵!前面大家已经接触过了默认异常处理函数——UnhandledExceptionFilter,还记得它吗?”

“当然记得,在堆溢出利用时,我们一般都是通过覆盖这个默认异常处理函数指针来获得控制权的。”

“对,这里我要说的是,UnhandledExceptionFilter指针是在异常链的最后,它的上一个值是指向下一个处理点的地址。因为后面没有异常处理点了,所以应该是0xFFFFFFFF。如图6-8。”

根据这个原理,我们可以搜索异常链,得到UnhandledExceptionFilter的地址,方法和代码如下:”

mov esi,fs:0
lodsd
GetExeceptionFilter:
cmp [eax],0xffffffff
je GetedExeceptionFilter //如果到达最后一个节点
mov eax,[eax] //否则往后遍历,一直到最后一个节点
jmp GetExeceptionFilter
GetedExeceptionFilter:
mov eax, [eax+4]

“测试一下,最后一句执行时,得到我们的UnhandledExceptionFilter地址为0x77E7BB86。如图6-9中的EAX值。”

  

“嗯,不错不错!这下我们知道了UnhandledExceptionFilter函数的地址了,但和Kernerl32的基地址有什么关系吗?”古风问道。

“不要急嘛!听我分析一下,UnhandledExceptionFilter函数是Kernel32.dll里面的函数,那函数的地址必然是在Kernel32的地址空间内。我们就从UnhandledExceptionFilter函数的地址往上找,找到开头的地方,自然就是Kerner32的基地址了。”

“哇!对啊!”

“Kerner32的开始标志是MZ和PE,而且因为系统分配某个空间时,总要从一个分配粒度的边界开始,在Windows下,这个粒度是64KB。所以我们搜索时,可以按照64kb递减往低地址搜索,当到了MZ和PE标志时,就找到了Kernel32的基地址。实现代码如下:”

FindMZ:
and eax,0xffff0000 //64k对齐特征加快查找速度
cmp word ptr [eax],'ZM' //根据PE可执行文件特征查找KERNEL32.DLL的基址
jne MoveUp
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP' //根据PE可执行文件特征查找KERNEL32.DLL的基址
je Found //如果符合MZ及PE头部特征,则认为已经找到,并通过Eax返回给调用者
MoveUp:
dec eax //准备指向下一个界起始地址
jmp FindMZ
Found:
nop

“之前,eax中存的是UnhandledExceptionFilter函数的指针。我们把前面两段合起来,得到FindKernelBySEH.cpp(光盘有收录),运行测试一下。”

“在VC中用__asm关键字嵌入汇编,按F10单步调试,到最后一句时,结果如图6-10。得到Kernel32的基址为0x77E40000,和前面得到的值是一样的。”

  

“这个方法也很巧妙也!”大家赞叹不已,感叹怎么会有如此天才的人。

“程序就是艺术,不能用语言表达,只能凭心去感受。”老师说道。

“在病毒中,还有种方法也是类似的。原理是:可执行程序都由Kernel32.dll中的CreateProcess函数来调用,所以病毒先得到堆栈中保存的返回地址,也就是Kernel32.dll地址空间的一个地址;然后再使用刚才的方法向上搜索,找到kernel32的基址。代码如下:”

Mov dword ptr eax, [esp+0x1C] //保存的返回地址,在kernel32中
FindMZ:
and eax,0xffff0000 //64k对齐特征加快查找速度
cmp word ptr [eax],'ZM' //根据PE可执行文件特征查找KERNEL32.DLL的基址
jne MoveUp
mov ecx,[eax+0x3c]
add ecx,eax
cmp word ptr [ecx],'EP' //根据PE可执行文件特征查找KERNEL32.DLL的基址
je Found //如果符合MZ及PE头部特征,则认为已经找到,并通过Eax返回给调用者
MoveUp:
dec eax //准备指向下一个界起始地址
jmp FindMZ
Found:
nop

“哦,涉及到病毒了,越来越有意思了。”

“呵呵,本是同根生嘛!”

“获得kernel32的基址后,我们再用引出表获得Get的地址,再获得其他函数的地址,是吗?”同学们问道。

“对!但要注意的是,这个方法是通过搜索异常链表,然后对齐搜索得到kerner32的基址。如果溢出利用方式是通过覆盖SEH,然后CALL EBX或pop pop ret来改变程序流程,那么SEH链已经被我们覆盖,这种方法就会出错。”

“哦!那怎么办?”

“此时,就只能用PEB来获得kerner32的基址了。”老师有点遗憾的说。“然后再获得函数的地址。”

老师说道,“除了先获取GetProcAddress函数地址,再通过GetProcAddress函数获取其他函数的地址外。还有一种改进的查找方法,就是直接通过引出表把所有函数的地址都找出来。”