详谈内核三步走Inline Hook实现

前置知识:汇编 驱动 windbg 函数参数调用

关键词:堆栈平衡  inline hook 

详谈内核三步走InlineHook实现

/  wofeiwo

(一)Inline hook原理

Inline hook通俗的说就是对函数执行流程进行修改,达到控制函数过滤操作的目的。理论上我们可以在函数任何地方把原来指令替换成我们的跳转指令,也确实有些人在inline

的时候做的很深,来躲避inline的检测,前提是必须对函数的流程和指令非常熟悉,且这种深层次的inlline不具有通用性,稳定性也是问题。本文讨论的是具有通用性的两类inline的实现。

Inline hook原理:解析函数开头的几条指令,把他们Copy到数组保存起来,然后用一个调用我们的函数的几条指令来替换,如果要执行原函数,则在我们函数处理完毕,再执行我们保存起来的开头几条指令,然后调回我们取指令之后的地址执行。用下图来解释:

原函数:

 开头指令A

指令B

Inline 后:

JMP  MyFunction

指令B

 

 


MyFunction

处理函数

JMP  ResumeFunction

 

 

 

 

ResumeFunction

开头指令A

JMP 回去

 

 

 

整个Inline hook的过程就大体这样,中间牵扯到对函数的检查,地址的获取就直接调用函数即可。

本文所要讨论的两类Inlinehook都是基于上面原理。

 

说明三点:

1、堆栈平衡是重中之重,参数压也需要格外注意

2R0模式下内存是不允许写的,需要去除写保护,设置CR0寄存器

3、提高中断级别到DPC,禁止线程切换

                    

(二)inline hook应用

Inline hook可分为两类:

1inline导出函数,选择ObReferenceObjectByHandle做例子。

2inline未导出函数,选择KiInsertQueueApc做例子。

导出函数前几个字节可以利用windbg自己查看是什么内容,而未导出函数就需要自己解析指令确定需要hook几个字节,其间还有很多问题需要注意。当大家真正的弄懂了我这篇文章,回头再看inline hook就会觉得inline也不过如此。

下面通过2个例子来讲inline hook的使用(这部分知识网上也有很多,但都很零散不系统,本文部分思路及代码的确参考了网上资源,有抄袭之嫌,希望读者谅解。我一直强调“授人以鱼不如授人以渔”,代码并不重要,关键是思想。)

1inline hookObReferenceObjectByHandle保护进程

ObReferenceObjectByHandle属于ntoskrnl.exe导出函数,在内核中调用频繁。

NtCreateProcess创建进程需要调用ObReferenceObjectByHandleNtTerminateProcess需要调用ObReferenceObjectByHandle,基于这我们就可以利用Hook来保护进程同时屏蔽进程的创建。

效果:已经运行的记事本任务管理器无法结束

流程:

HookObReferenceObjectByHandle------DetourMyObReferenceObjectByHandle----------UnHookObReferenceObjectByHandle

核心代码分析如下:

//=======================================inline HOOKObReferenceObjectByHandle===========================

 

//ObReferenceObjectByHandlentoskrnl.exe导出函数,采用HOOK前五个字节的方式

 

//字节型数据 unsigned char

ULONG  CR0VALUE;

BYTE  OriginalBytes[5]={0};            //保存原始函数前五个字节          

BYTE JmpAddress[5]={0xE9,0,0,0,0};      //跳转到HOOK函数的地址

 

extern POBJECT_TYPE *PsProcessType;

 

NTKERNELAPI NTSTATUS ObReferenceObjectByHandle(

                                                                              

                                                                              IN HANDLE  Handle,

                                                                              IN ACCESS_MASK  DesiredAccess,

                                                                              IN POBJECT_TYPE  ObjectType OPTIONAL,

                                                                              IN KPROCESSOR_MODE  AccessMode,

                                                                              OUT PVOID  *Object,

                                                                              OUT POBJECT_HANDLE_INFORMATION  HandleInformation OPTIONAL

                                                                              

                                                                              );

 

//HOOK函数

 

NTSTATUS DetourMyObReferenceObjectByHandle(

                                                                       

                                                                       IN HANDLE  Handle,                          

                                                                       IN ACCESS_MASK DesiredAccess

                                                                       IN POBJECT_TYPE  ObjectType OPTIONAL,

                                                                       IN KPROCESSOR_MODE  AccessMode,

                                                                       OUT PVOID  *Object,

                                                                       OUT POBJECT_HANDLE_INFORMATION  HandleInformation OPTIONAL);

 

//

 

//hook流程HookObReferenceObjectByHandle---DetourMyObReferenceObjectByHandle---UnHookObReferenceObjectByHandle

 

void  HookObReferenceObjectByHandle()

 

{

      

       //赋值前面定义的数组

       KIRQL Irql;

       KdPrint(("[ObReferenceObjectByHandle] :0x%x",ObReferenceObjectByHandle)); //地址验证

       //保存函数前五个字节内容

       RtlCopyMemory(OriginalBytes,(BYTE *)ObReferenceObjectByHandle,5);

       //保存新函数五个字节之后偏移

       *(ULONG *)(JmpAddress+1)=(ULONG)DetourMyObReferenceObjectByHandle-((ULONG)ObReferenceObjectByHandle+5);

       //开始inline hook

       //关闭内存写保护

       _asm

             

       {

              push eax

                    

                     mov eax, cr0

                     mov CR0VALUE, eax

                     and eax, 0fffeffffh 

                     mov cr0, eax

                     pop eax

       }

      

       //提升IRQL中断级

       Irql=KeRaiseIrqlToDpcLevel();

       //函数开头五个字节写JMP

       RtlCopyMemory((BYTE *)ObReferenceObjectByHandle,JmpAddress,5);

       //恢复Irql

       KeLowerIrql(Irql);

       //开启内存写保护

      

       __asm

             

       {      

             

              push eax

                    

                     mov eax, CR0VALUE

                    

                     mov cr0, eax

                    

                     pop eax

                    

       }

      

}

 

 

 

_declspec (naked) NTSTATUSOriginalObReferenceObjectByHandle(IN HANDLE Handle,

                                                                                                         

                                                                                                         IN ACCESS_MASK DesiredAccess,

                                                                                                         

                                                                                                         IN POBJECT_TYPE ObjectType OPTIONAL,

                                                                                                         

                                                                                                         IN KPROCESSOR_MODE AccessMode,

                                                                                                        

                                                                                                         OUT PVOID *Object,

                                                                                                         

                                                                                                         OUT POBJECT_HANDLE_INFORMATION HandleInformation  OPTIONAL)

                                                                                                         

{

      

       _asm

             

       {  

             

              movedi,edi

                     push ebp

                     mov ebp,esp

                     mov eax,ObReferenceObjectByHandle

                     add eax,5

                     jmp eax               

                    

       }

      

}

 

 

NTSTATUS DetourMyObReferenceObjectByHandle(

                                                                       

                                                                       IN HANDLE  Handle,

                                                                       

                                                                       IN ACCESS_MASK  DesiredAccess,

                                                                       

                                                                       IN POBJECT_TYPE  ObjectType OPTIONAL,

                                                              

                                                                       IN KPROCESSOR_MODE  AccessMode,

                                                                       

                                                                       OUT PVOID  *Object,

                                                                       

                                                                       OUT POBJECT_HANDLE_INFORMATION  HandleInformation OPTIONAL)

                                                                       

{

      

       NTSTATUS status;

      

       //调用原函数

      

       status=OriginalObReferenceObjectByHandle(Handle,DesiredAccess,ObjectType,AccessMode,Object,HandleInformation);

      

       if((status==STATUS_SUCCESS)&&(DesiredAccess==1))

             

       {  

             

              if(ObjectType== *PsProcessType)

                    

              {

                    

                     if( _stricmp((char *)((ULONG)(*Object)+0x174),"notepad.exe")==0)

                           

                     {  

                           

                           ObDereferenceObject(*Object);

                           

                           return STATUS_INVALID_HANDLE;

                           

                     }

                    

              }

             

       }

      

       return status;

      

}

 

 

 

void UnHookObReferenceObjectByHandle()

 

{

      

       //把五个字节再写回到原函数

      

       KIRQL Irql;

      

    //关闭写保护

      

       _asm

             

       {

             

              push eax

                    

                     mov eax, cr0

                    

                     mov CR0VALUE, eax

                    

                     and eax, 0fffeffffh 

                    

                     mov cr0, eax

                    

                     pop eax

                    

       }

      

    //提升IRQLDpc

      

    Irql=KeRaiseIrqlToDpcLevel();

      

       RtlCopyMemory((BYTE *)ObReferenceObjectByHandle,OriginalBytes,5);

      

       KeLowerIrql(Irql);

      

    //开启写保护

      

       __asm

             

       {      

             

                 pusheax

                     mov eax, CR0VALUE

                     mov cr0, eax

                    

                     pop eax

                    

       }

}

 

驱动加载后,结束记事本程序如下:

    (图一)

 

详细分析:

1ObReferenceObjectByHandle分析

NTSTATUS 

  ObReferenceObjectByHandle(

    HandleIN ACCESS_MASK  DesiredAccess,

    IN KPROCESSOR_MODE  AccessMode,

    *,

    );

函数原型如上,由句柄获取对象指针,函数返回值:

STATUS_SUCCESS                       调用成功

STATUS_OBJECT_TYPE_MISMATCH       

STATUS_ACCESS_DENIED                权限不够

STATUS_INVALID_HANDLE                无效句柄        

 

调用NtTerminateProcess需要调用ObReferenceObjectByHandle,因此我们通过对函数返回值进程修改来达到保护进程。但是NtCreateProcess(最终调用的PspCreateProcess)同样调用这个函数,如果不加区分的话,创建进程同样被禁止了,那么如何区分到底是谁在调用呢。参考WRK,我发现可以通过第二个参数DesiredAccess来判别,创建进程和结束进程第二个参数明显不同,PROCESS_CREATE_PROCESSPROCESS_TERMINATE问题就解决了。

PspCreateProcess位于 WRK-v1.2\base\ntos\ps\create.c

调用ObReferenceObjectByHandle代码:

Status = ObReferenceObjectByHandle(ParentProcess,
                                            PROCESS_CREATE_PROCESS,
                                           PsProcessType,
                                           PreviousMode,
                                           &Parent,
                                           NULL);

NtTerminateProcess位于 WRK-v1.2\base\ntos\ps\psdelete.c

调用ObReferenceObjectByHandle代码:

st =ObReferenceObjectByHandle (ProcessHandle,
                                    PROCESS_TERMINATE,
                                   PsProcessType,
                                   KeGetPreviousModeByThread(&Self->Tcb),
                                   &Process,
                                   NULL);

DesiredAccess参数说明:

#definePROCESS_TERMINATE         (0x0001) //winnt
#define PROCESS_CREATE_THREAD     (0x0002) // winnt
#define PROCESS_SET_SESSIONID     (0x0004) // winnt
#define PROCESS_VM_OPERATION      (0x0008) // winnt
#define PROCESS_VM_READ          (0x0010) // winnt
#define PROCESS_VM_WRITE         (0x0020) // winnt
// begin_ntddk begin_wdm begin_ntifs
#define PROCESS_DUP_HANDLE        (0x0040)// winnt
// end_ntddk end_wdm end_ntifs
#define PROCESS_CREATE_PROCESS    (0x0080) // winnt
#define PROCESS_SET_QUOTA        (0x0100) // winnt
#define PROCESS_SET_INFORMATION   (0x0200) // winnt
#define PROCESS_QUERY_INFORMATION (0x0400) // winnt
#define PROCESS_SET_PORT         (0x0800)
#define PROCESS_SUSPEND_RESUME    (0x0800) // winnt

 

2、函数调用说明

C语言中我们调用一个函数就直接写函数名就可以,但是实际是进行了下面的操作:

把函数参数压入堆栈,压入函数返回地址,调用函数,为新函数开辟堆栈空间申请局部变量,

恢复堆栈保持堆栈平衡

_stdcall调用方式)汇编代码就是:

Push 参数4

Push 参数3

Push 参数2

Push 参数1

Call 函数 call指令同时完成2个操作,一是把返回地址压入堆栈,二跳转到调用函数入口地址

 

Push  ebp

Movebp,esp

Sub esp, XX ;开辟帧空间

……

Add  esp ,XX

Pop ebp

Retn         ;恢复堆栈平衡

堆栈详细情况:

ESP

局部变量

 

 

 

 

 

EBP

返回地址

参数1

参数2

参数3

参数4

堆栈是由高地址到低地址。

参数就通过EBP来去,四字节对齐的

 

参数4----------------------EBP+0x14

参数3----------------------EBP+0x10

参数2----------------------EBP+0xc

参数1---------------------EBP+0x8

局部变量则通过Ebp-XX来获取

 

因此inline的时候要时刻考虑堆栈平衡,破坏了堆栈平衡就会导致函数崩溃。

我通常inline hook的思路就是三步走:

HOOK函数-----DetourMy处理函数----------UnHook函数

处理函数中对返回结果或者中间数据进行修改处理,然后调用原始函数。由于在我们处理的时候原始函数已经被hook了,所以我自己构造了一个原始函数,但是由于参数在我们hook前已经压人堆栈了,所以这里我们不用重新开辟帧,因此声名函数类型为_declspec(naked)

。有人就会问那么你调用处理函数的时候,参数不是重复压了,这里请注意,我们是通过JMP方式跳转到我们处理函数入口地址的,而不是Call的形式,所以并没有执行上面所说的函数调用过程,参数仍然是原始函数的。也就是说在真个inline hook过程中我们不能破坏原始帧的EBP

 

关于函数调用很栈帧的相关联系可能比较难理解,我也在尽肯能的用通俗的话来解释清楚,有什么不理解的地方或者个人见解欢迎大家跟我交流。

 

2inline hookKiInsertQueueApc对抗插APC杀进程

KiInsertQueueAPc内核未导出函数,我下面提供的代码可以作为未导出函数inline的通用模板来使用,大家根据自己需要进行修改,基于inlineObReferenceObjectByHandle已经把原理分析了,这部分我就不详加分析,仍然采用的但不走,Hook函数---DetourMy函数---UnHook函数

直接看核心代码:

//===================inline hook KiInsertQueueApc====================

//KiInsertQueueApc内核未导出函数,可以从导出函数KeInsertQueueApc定位

//修改KiInsertQueueApc开头5字节

//处理函数思路:apc-->kthread---apc_state--eprocess--进程名字

//HookKiInsertQueueApc---DetourMyKiInsertQueueApc---UnHookKiInsertQueueApc

ULONG CR0VALUE;

ULONG g_KiInsertQueueApc;

          

BYTE JmpAddress[5]={0xE9,0,0,0,0};      //跳转到HOOK函数的地址

BYTE  OriginalBytes[5]={0};            //保存原始函数前五个字

 

VOID FASTCALL DetourMyKiInsertQueueApc(IN PKAPCApc,IN KPRIORITY Increment);

 

VOID WPOFF()

{

       _asm

             

       {

             

              push eax

                    

                     mov eax, cr0

                    

                     mov CR0VALUE, eax

                    

                     and eax, 0fffeffffh 

                    

                     mov cr0, eax

                    

                     pop eax

                     cli

                    

       };

      

}

 

VOID WPON()

{

    __asm

             

       {      

              sti

              push eax

                    

                     mov eax, CR0VALUE

                    

                     mov cr0, eax

                    

                     pop eax

                    

       };

}

//1、获取KiInsertQueueApc地址

ULONG GetFunctionAddr( IN PCWSTRFunctionName)    //PCWSTR常量指针,指向16UNICODE

{

       UNICODE_STRINGUniCodeFunctionName;

       RtlInitUnicodeString( &UniCodeFunctionName,FunctionName );

       return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );  

}

 

ULONG GetKiInsertQueueApcAddr()

{

       ULONG sp_code1=0x28,sp_code2=0xe8,sp_code3=0xd88a; //特征码,sp_code3windbg显示错误,应该为d88a

       ULONG address=0;

       PUCHAR addr;

       PUCHAR p;

       addr=(PUCHAR)GetFunctionAddr(L"KeInsertQueueApc");

       for(p=addr;p<p+PAGE_SIZE;p++)

       {

              if((*(p-1)==sp_code1)&&(*p==sp_code2)&&(*(PUSHORT)(p+5)==sp_code3))

              {

                     address=*(PULONG)(p+1)+(ULONG)(p+5);

                     break;

              }

       }

       KdPrint(("[KeInsertQueueApc]addr %x\n",(ULONG)addr));

    KdPrint(("[KiInsertQueueApc] address %x\n",address));

    return address;

}

 

VOID HookKiInsertQueueApc()

{   

       KIRQL Irql;

       g_KiInsertQueueApc=GetKiInsertQueueApcAddr();

       KdPrint(("[KiInsertQueueApc]KiInsertQueueApc %x\n",g_KiInsertQueueApc));

    // 保存原函数的前字节内容

    RtlCopyMemory (OriginalBytes, (BYTE*)g_KiInsertQueueApc, 5);

       //新函数对原函数的偏移地址

    *( (ULONG*)(JmpAddress + 1) ) = (ULONG)DetourMyKiInsertQueueApc - (ULONG)g_KiInsertQueueApc - 5;

    // 禁止系统写保护,提升IRQLDPC

    WPOFF();

    Irql =KeRaiseIrqlToDpcLevel();

    //inline hook函数

       RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc,JmpAddress, 5 );

    // 恢复写保护,降低IRQL

    KeLowerIrql(Irql);

    WPON();      

}

//原函数

_declspec (naked) VOID FASTCALLOriginalKiInsertQueueApc(IN PKAPCApc,IN KPRIORITY Increment)

{

       _asm

       {

              //前五个字节

              movedi,edi

                     push ebp

                     mov ebp,esp

                    

                     mov eax,g_KiInsertQueueApc

                     add eax,5

                     jmp eax

       }

}

//处理函数

//apc--kthread--apc_state--eprocess

VOID FASTCALL DetourMyKiInsertQueueApc(IN PKAPCApc,IN KPRIORITY Increment)

{

       ULONG thread;

       ULONG process;

       if(MmIsAddressValid((PULONG)((ULONG)Apc+0x008)))   //地址验证 KAPC结构+008--->kthread

              thread=*((PULONG)((ULONG)Apc+0x008));

       else

              return ;

       if(MmIsAddressValid((PULONG)((ULONG)thread+0x044))) //kthread+30-->KAPC_STATE+10-->eprocess

              process=*((PULONG)((ULONG)thread+0x044));

       else

              return ;

    if(MmIsAddressValid((PULONG)((ULONG)process+0x174))) //eprocess+174---->进程名字

       {

              if((_stricmp((char *)((ULONG)process+0x174),"notepad.exe")==0)&&(Increment==2))

              {

                     return ;

 

              }

              else

                     OriginalKiInsertQueueApc(Apc,Increment);

 

       }

       else

              return;

}

 

//卸载函数

VOID UnHookKiInsertQueueApc()

{

       KIRQL Irql;

    WPOFF();

    Irql =KeRaiseIrqlToDpcLevel();

    //inline hook函数

    RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc,OriginalBytes, 5);

    // 恢复写保护,降低IRQL

    KeLowerIrql(Irql);

    WPON();      

}

 考虑到大家水平不一,对一些问题我详细如下:

1、特征码的寻找

利用windbgkernel debug来查找:

uf KeInsertQueueApc

nt!KeInsertQueueApc+0x3b:

804e6d0a 8b450c          mov     eax,dwordptr [ebp+0Ch]

804e6d0d 8b5514          mov     edx,dwordptr [ebp+14h]

804e6d10 894724          mov     dwordptr [edi+24h],eax

804e6d13 8b4510          mov     eax,dwordptr [ebp+10h]

804e6d16 8bcf            mov     ecx,edi

804e6d18 894728         mov    dwordptr [edi+28h],eax

804e6d1b e8523fffff       call   nt!KiInsertQueueApc (804dac72)

804e6d20 8ad8 (错误)  mov     bl,al

特征码就是sp_code1=0x28 sp_code2=0xe8sp_code3=0xd88awindbg显示有误,应该是d88a

这种方法就是通过已导出函数定位未导出函数通常使用的方法,具有通用性。详细见代码。

 

2、取EPRocess的过程

Apc-----kthread-----apc_stateeprocess

dt _KAPC            偏移0x008指向KTHREAD

dt _KTHREAD        偏移0x034指向KAPC_STATE

dt _KAPC_STATE     偏移0x10指向EPROCESS

dt _EPROCESS        偏移0x174指向进程名

 

 

()  总结

很多人觉得inline hook比较难,处理起来很麻烦。但是我相信看完我这篇文章,你一定不会这么认为了,inlinehook其实只要细心,注意细节跟别的hook没什么两样。本人采用的三步走inline hook做到了把inline简单化,同时有保证了堆栈的平衡。

    由于代码采用的硬编码,编译环境是sp3+VMware,请根据自己操作系统自行修改。欢迎读者跟我交流。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值