1、简介

         INLINE HOOK原理:

        Inline Hook通过硬编码的方式向内核API的内存空间(通常是开始的一段字节,且一般在第一个call之前,这么做是为了防止堆栈混乱)写入跳转语句,这样,该API只要被调用,程序就会跳转到我们的函数中来,我们在自己写的函数里需要完成3个任务:
         1)重新调整当前堆栈。程序流程在刚刚跳转的时候,内核API并没有执行完,而我们的函数需要根据其结果来进行信息过滤,所以我们需要保证内核API能在顺利执行完毕后返回到我们的函数中来,这就要求对当前堆栈做一个调整。
         2)执行遗失的指令。我们向内核API地址空间些如跳转指令(jmp xxxxxxxx)时,势必要覆盖原先的一些汇编指令,所以我们一定要保证这些被覆盖的指令能够顺利执行(否则,你的及其就要BSOD了,呵呵,Blue Screen Of Death)。关于这部分指令的执行,一般是将其放在我们的函数中,让我们的函数“帮助”内核API执行完被覆盖的指令,然后再跳回内核API中被覆盖内后后的地址继续执行剩余内容。跳回去的时候,一定要算好是跳回到什么地址,是内核API起始地址后的第几个字节。
         3)信息过滤。这个就不用多说了,内核API顺利执行并返回到我们的函数中,我们自然要根据其结果做一些信息过滤,这部分内容因被hook的API以及Hook目的的不同而不同。

         Inline hook的工作流程:
         1)验证内核API的版本(特征码匹配)。
         2)撰写自己的函数,要完成以上三项任务。
         2)获取自己函数的地址,覆盖内核API内存,供跳转。

 

         简而言之,inlinehook的原理就是,修改函数,使其跳转到我们指定的地方。

         常见的有改函数入口,也有改函数尾,函数中间的
         比如,通常函数开头的汇编代码都是这样:

      mov edi,edi;

      push esp;

      mov ebp,esp;

     而我们便可以通过修改这里进行HOOK。

 

2、示例(摘自看雪)

示例1:

#include <ntifs.h>
#include <windef.h>

ULONG g_KiInsertQueueApc;
ULONG g_uCr0;

BYTE g_HookCode[5] = { 0xe9, 0, 0, 0, 0 }; //JMP NEAR
BYTE g_OrigCode[5] = { 0 }; // 原函数的前字节内容
BYTE jmp_orig_code[7] = { 0xEA, 0, 0, 0, 0, 0x08, 0x00 }; //JMP FAR

BOOL g_bHooked = FALSE;

VOID
fake_KiInsertQueueApc (
                       PKAPC Apc,
                       KPRIORITY Increment
                       );

VOID
Proxy_KiInsertQueueApc (
                        PKAPC Apc,
                        KPRIORITY Increment
                        );

void WPOFF()
{
    ULONG uAttr;
    _asm
    {
        push eax;
        mov eax, cr0;
        mov uAttr, eax;
        and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
        mov cr0, eax;
        pop eax;
        cli
    };

    g_uCr0 = uAttr; //保存原有的 CRO 屬性

}

VOID WPON()
{

    _asm
    {
        sti
            push eax;
        mov eax, g_uCr0; //恢復原有 CR0 屬性
        mov cr0, eax;
        pop eax;
    };

}

//
// 停止inline hook
//
VOID UnHookKiInsertQueueApc ()
{
    KIRQL  oldIrql;
    WPOFF();
    oldIrql = KeRaiseIrqlToDpcLevel();
    RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_OrigCode, 5 );
    KeLowerIrql(oldIrql);
    WPON();
    g_bHooked = FALSE;
}
//
// 开始inline hook --  KiInsertQueueApc
//
VOID HookKiInsertQueueApc ()
{
    KIRQL  oldIrql;
    if (g_KiInsertQueueApc == 0) {
        DbgPrint("KiInsertQueueApc == NULL\n");
        return;
    }
    //DbgPrint("开始inline hook --  KiInsertQueueApc\n");
    DbgPrint( "KiInsertQueueApc的地址t0x%08x\n", (ULONG)g_KiInsertQueueApc );
    DbgPrint( "fake_KiInsertQueueApc的地址t0x%08x\n", (ULONG)fake_KiInsertQueueApc );
    
    // 保存原函数的前字节内容
    RtlCopyMemory (g_OrigCode, (BYTE*)g_KiInsertQueueApc, 5);

   //jmp指令,此处为短跳,计算相对偏移,同时,jmp xxxxxx这条指令占了5个字节

    *( (ULONG*)(g_HookCode + 1) ) = (ULONG)fake_KiInsertQueueApc - (ULONG)g_KiInsertQueueApc - 5;

    // 禁止系统写保护,提升IRQL到DPC
    WPOFF();
    oldIrql = KeRaiseIrqlToDpcLevel();

    RtlCopyMemory ( (BYTE*)g_KiInsertQueueApc, g_HookCode, 5 );
    *( (ULONG*)(jmp_orig_code + 1) ) = (ULONG) ( (BYTE*)g_KiInsertQueueApc + 5 );

    RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc, g_OrigCode, 5);
    RtlCopyMemory ( (BYTE*)Proxy_KiInsertQueueApc + 5, jmp_orig_code, 7);

    // 恢复写保护,降低IRQL
    KeLowerIrql(oldIrql);
    WPON();
    g_bHooked = TRUE;

}

//
// 跳转到我们的函数里面进行预处理,裸函数,有调用者进行堆栈的平衡
//
__declspec (naked)
VOID
fake_KiInsertQueueApc (
                       PKAPC Apc,
                       KPRIORITY Increment
                       )
{

    // 去掉DbgPrint,不然这个hook会产生递归
    //DbgPrint("inline hook --  KiInsertQueueApc 成功\n");
    __asm
    {
        jmp Proxy_KiInsertQueueApc
    }
}

//
// 代理函数,负责跳转到原函数中继续执行
//
__declspec (naked)
VOID
Proxy_KiInsertQueueApc (
                        PKAPC Apc,
                        KPRIORITY Increment
                        )
{
    __asm {  // 共字节
        _emit 0x90
            _emit 0x90
            _emit 0x90
            _emit 0x90
            _emit 0x90  // 前字节实现原函数的头字节功能
            _emit 0x90  // 这个填充jmp
            _emit 0x90
            _emit 0x90
            _emit 0x90
            _emit 0x90  // 这字节保存原函数+5处的地址
            _emit 0x90  
            _emit 0x90  // 因为是长转移,所以必须是0x0080
    }
}

ULONG GetFunctionAddr( IN PCWSTR FunctionName)
{
    UNICODE_STRING UniCodeFunctionName;
    RtlInitUnicodeString( &UniCodeFunctionName, FunctionName );
    return (ULONG)MmGetSystemRoutineAddress( &UniCodeFunctionName );  
}

//根据特征值,从KeInsertQueueApc搜索中搜索KiInsertQueueApc
ULONG FindKiInsertQueueApcAddress()
{
    char * Addr_KeInsertQueueApc = 0;
    int i = 0;
    char Findcode[] = { 0xE8, 0xcc, 0x29, 0x00, 0x00 };
    ULONG Addr_KiInsertQueueApc = 0;
    Addr_KeInsertQueueApc = (char *) GetFunctionAddr(L"KeInsertQueueApc");
    for(i = 0; i < 100; i ++)
    {
        if( Addr_KeInsertQueueApc[i] == Findcode[0] &&
            Addr_KeInsertQueueApc[i + 1] == Findcode[1] &&
            Addr_KeInsertQueueApc[i + 2] == Findcode[2] &&
            Addr_KeInsertQueueApc[i + 3] == Findcode[3] &&
            Addr_KeInsertQueueApc[i + 4] == Findcode[4]
        )
        {
            Addr_KiInsertQueueApc = (ULONG)&Addr_KeInsertQueueApc[i] + 0x29cc + 5;
            break;
        }
    }
    return Addr_KiInsertQueueApc;
}

VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
    DbgPrint("My Driver Unloaded!");
    UnHookKiInsertQueueApc();
}

NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath )
{
    DbgPrint("My Driver Loaded!");
    theDriverObject->DriverUnload = OnUnload;
    g_KiInsertQueueApc = FindKiInsertQueueApcAddress();
    HookKiInsertQueueApc();
    return STATUS_SUCCESS;
}