我假设读者已经非常熟悉detours,阅读此文只是为了增强对detours的理解以及为了实现x hook。有关detours原理部分不再多讲。
X86 Kernel Hook
早些年,我把detours1.5移植到x86核心层,工作的不错,我一直用它来hook系统一些内部函数,有时候也用来hook IoCreateFile这类导出函数。让detours1.5在核心工作稳定并不是一件困难的事情。可能有些c/c++的麻烦,但是很快就可以解决。唯一需要注意的地方是detours1.5用VirtualProtect来让内存READ_WRITE_EXECUTE,在核心层有2种方法,第一种是群众所喜闻乐见的清除cr0,第二种是在核心层通过调用native api做VirtualProtect的事情。
detours的方法对比import/export方法有一些很明显的好处,其最大的好处是可以用来hook内部函数。而且由于hook的方法是直接修改函数体,所以不管调用者怎么玩花样,都很难绕过hook。
detours的缺点主要如下:
1,detours x86无法hook小于5字节的函数
2,detours x86需要一个完备的反汇编器和解释器,实际上detours代码中并不包含这个,因此,如果需要写一个函数阻止他人hook,可以这么写:
proc near
xor eax,eax
jeax 1
int 3
... // do something
proc end
注意到这里的这个jmp,因为eax肯定为0,所以该int3不会被调用,而被detours过的代码则很可能走到int3上去了,为了让detours的代码不走到int3,detours必须能够解析出前面3行代码的意思,并且修正jeax 1为jeax 1+(trampoline-function)。用类似的技术,也可以欺骗detours。
3,detours x86无法处理如下函数:
proc near
flag: ... // 函数前5个字节
.... //do something
jmp flag
.... // do something
proc end
该函数执行体中有一个jmp,跳到前5个字节。可是被detours过之后,该函数的前5个字节被修改了,而且改成了jmp trampoline。为了能够让detours可以处理此操作,必须反汇编解析整个函数体,用2种所描述的方法修改jmp flag。
综上述,detours思路很好,但是存在缺陷,要搞定这些缺陷,需要完整反汇编器。
X Kernel Hook
最近有一个需求要在x下实现类似的hook模块,我找到了detours2.1,给MS发了email,MS的答复是,包含bit的detours2.1,需要10000 USD。
于是我就删掉了MS的email,开始自己动手来做这个事情了。我大致说一下原理和需要注意的地方。
对于detours来说,受上面所述特性影响的是,trampoline通常位于heap memory/nonpaged pool,new_function位于我们自己所写代码的dll/driver中,old_function位于我们所需要hook的那个模块中。这里面存在一个基本矛盾是,new_function通常和old_function分别处于2个不同dll或者.sys中,系统很可能把他们加载到了距离很远的空间中,也即abs(new_function-old_function)>4G。这样一来,就无法使用e9 xxxxxxxx,而必须使用ff15 [xxxxxxxx]了,而且xxxxxxxx是一个32的偏移,所以[xxxxxxxx]还不能位于我们的dll/sys中。
trampoline可以预先分配一个100字节的buffer,初始化全部填充为nop,在进行7的时候,可以从trampoline的底部,也即100-14的位置开始填入ff,15,00,00,00,00, _bit_old_function+14(15,16...)。
以上算法的缺点和x86 detours的缺点一样,第一条为无法hook函数体小于14字节的函数。
14个字节相当大,有时候这个缺陷不可忍受,为此,介绍一种更为肮脏的手段。
以上就是x下的detours过程。
有一个x下需要注意的问题,vc8不支持x下的_asm关键字,所以
_asm{
cli
mov eax,cr0
and eax,not 1000h
mov cr0,eax }不能再用
取而代之的是
_disable();
uint cr0=__readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
当然还可以继续用native api,不过以上方法简洁而且为广大群众所喜闻乐见。有关于_disable等函数,请查阅新版msdn。
至于IA,我对此一无所知。
顺便说几点:
1,EMT的cpu上可以run winos,但是,不知为何,vmware无法在EMT的cpu上install/run winos。而amd cpu上即便安装的是win32 os,也可以在其上的vmware里install/run winos。
2,softice已经停止开发,而且不支持x,只有virtual模式才支持。鉴于其已经停止开发,建议大家都使用windbg。
3,idapro 5.0反汇编x的代码,错误百出,一团乱麻,基本上需要先U再C。
因为14字节的太大,以至于始终觉得不爽。后来想到了一个解决方案。
假设原函数是old_func,新函数是new_func,那么分配trampoline的时候,用某些技术方法,限定分配出的内存和old_func在同一个4G。可以通过VirtualAlloc实现,具体方法可以是多次改变第一个参数,调用VirtualAlloc,直到返回值不为NULL为止。
这样一来,detours的逻辑改变为:
1,首先把old_func的前5个字节拷贝到trampoline+14,然后修改为jmp offset,也即e9 trampoline-5-old
2,trampoline的前6字节为ff15 [0],接下来的8个字节为new_func_address
3,trampoline+14+5之后的5个字节为jmp (trampoline+14+5+5 - (old_func_addr+5))
这样调用old的时候,会首先执行jmp offset到trampoline,trampoline又jmp到了new_func,new_func调用old的时候,会直接跳到trampoline+14处,执行原来的前5个字节,然后再jmp会原函数体。
如此,一切都完美了 :)