win 64 ring0 inline hook

如下內容參考黑客防線2012合訂本316頁數組

 

1.首先要注意的問題函數

inline hook 不能截斷指令. 也就是說修改目標函數的指令實現跳轉到本身的函數裏面時, 不能截斷掉目標函數的指令.測試

由於在本身的函數裏面還要調用原來的函數,可是原來的函數若是被截斷那就沒辦法正常執行代碼編碼

 

2.反彙編引擎. spa

用來動態解析內存中指令, 這裏就是用來獲取所需字節數的最少修改指令數所佔的大小. 也就是防止出現指令截斷.代理

使用LDE64 (網上就有).指針

uchar szShellCode[12800]={...};
typedef int (*LDE_DISASM)(void *p, int dw);
LDE_DISASM LDE;

void LDE_init()
{
    LDE=ExAllocatePool(NonPagedPool,12800);
    memcpy(LDE,szShellCode,12800);
}

使用:code

ULONG GetPatchSize(PUCHAR Address)
{
    ULONG LenCount = 0, Len = 0;
    while (LenCount <= 14)    //至少須要14字節
    {
        Len = LDE(Address, 64);
        Address = Address + Len;
        LenCount = LenCount + Len;
    }
    return LenCount;
}

關於inline hook思路:blog

首先聲明全局變量來存儲 A 代碼.  A代碼就是原始的前n字節.進程

聲明全局變量存儲B代碼.  是A代碼+jmp C  . 這個C是原始函數+sizeof(A)

A代碼用來unhook的 , B代碼用來從本身的函數跳回去原始函數繼續執行.

因此先獲取跳轉到本身函數的機器碼. D

這個機器碼是經過如下實現的:

  

    這裏跨4G跳轉使用的方式是jmp qword ptr [rip], 對應的機器碼是ff2500000000 6個字節
    當執行到這條指令時,假如這條指令地址是這樣
    0000000000000000   jmp qword ptr [rip]
    那麼又假以下一條指令這樣:
    0000000000000006   cccccccccccccccc  (這裏的彙編碼爲int3 int3 int3...int3 共8個)
    那麼指令執行完jmp指令後,將跑到地址爲cccccccccccccccc處的代碼執行,而不是執行int3 指令.
    簡單來講就是把rip當成普通寄存器使用了. 所以至少須要14字節的數據來跳轉任意內存處.
    爲何是至少14字節呢? 由於指令不能截斷,當hook時不能直接只改掉前14字節,這樣可能會由於
    某條指令橫跨第14字節,這樣hook就會將這條指令截斷. 所以須要經過反彙編引擎對字節碼進行
    解析,直到解析的指令內容>=14字節.將這些指令內容大小做爲修改的大小,多餘的填充nop.

而後將前n字節保存到A.

設置好B

再將D寫入到原始函數前n字節.  這樣就實現inline hook.

在本身的函數裏面能夠經過調用B來執行正確的原始函數. 

unhook就很簡單, 將A寫回原始函數前n字節便可. 測試結果:

 

 

最後附上大佬的代碼:(有本身寫的一些註釋)

 

#include <ntddk.h>

#define kmalloc(_s) ExAllocatePoolWithTag(NonPagedPool, _s, 'SYSQ')
#define kfree(_p) ExFreePool(_p)

typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process);
ULONG64 my_eprocess = 0;            //待保護進程的eprocess
ULONG pslp_patch_size = 0;        //PsLookupProcessByProcessId被修改了N字節
PUCHAR pslp_head_n_byte = NULL;    //PsLookupProcessByProcessId的前N字節數組
PVOID ori_pslp = NULL;            //PsLookupProcessByProcessId的原函數


KIRQL WPOFFx64()
{
    KIRQL irql = KeRaiseIrqlToDpcLevel();
    UINT64 cr0 = __readcr0();
    cr0 &= 0xfffffffffffeffff;
    __writecr0(cr0);
    _disable();
    return irql;
}

void WPONx64(KIRQL irql)
{
    UINT64 cr0 = __readcr0();
    cr0 |= 0x10000;
    _enable();
    __writecr0(cr0);
    KeLowerIrql(irql);
}

//傳入:被HOOK函數地址,原始數據,補丁長度
VOID UnhookKernelApi(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize)
{
    KIRQL irql;
    irql = WPOFFx64();
    memcpy(ApiAddress, OriCode, PatchSize);
    WPONx64(irql);
}

NTSTATUS Proxy_PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process)
{
    NTSTATUS st;
    st = ((PSLOOKUPPROCESSBYPROCESSID)ori_pslp)(ProcessId, Process);
    if (NT_SUCCESS(st))
    {
        if (*Process == (PEPROCESS)my_eprocess)
        {
            *Process = 0;
            st = STATUS_ACCESS_DENIED;
        }
    }
    return st;
}

void *GetFunctionAddr(PCWSTR FunctionName)
{
    UNICODE_STRING UniCodeFunctionName;
    RtlInitUnicodeString(&UniCodeFunctionName, FunctionName);
    return MmGetSystemRoutineAddress(&UniCodeFunctionName);
}
/*
    這裏跨4G跳轉使用的方式是jmp qword ptr [rip], 對應的機器碼是ff2500000000 6個字節
    當執行到這條指令時,假如這條指令地址是這樣
    0000000000000000   jmp qword ptr [rip]
    那麼又假以下一條指令這樣:
    0000000000000006   cccccccccccccccc  (這裏的彙編碼爲int3 int3 int3...int3 共8個)
    那麼指令執行完jmp指令後,將跑到地址爲cccccccccccccccc處的代碼執行,而不是執行int3 指令.
    簡單來講就是把rip當成普通寄存器使用了. 所以至少須要14字節的數據來跳轉任意內存處.
    爲何是至少14字節呢? 由於指令不能截斷,當hook時不能直接只改掉前14字節,這樣可能會致使
    某條指令橫跨第14字節,若是這樣hook就會將這條指令截斷. 所以須要經過反彙編引擎對字節碼進行
    解析,直到解析的指令內容>=14字節.將這些指令內容大小做爲修改的大小,多餘的填充nop.

*/

ULONG GetPatchSize(PUCHAR Address)
{
    ULONG LenCount = 0, Len = 0;
    while (LenCount <= 14)    //至少須要14字節
    {
        Len = LDE(Address, 64);
        Address = Address + Len;
        LenCount = LenCount + Len;
    }
    return LenCount;
}


//傳入:待HOOK函數地址,代理函數地址,接收跳回原始函數代碼內容的地址的指針,接收補丁長度的指針;返回:原來頭N字節的數據
PVOID HookKernelApi(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize)
{
    //這裏面有2個是動態分配的,須要在程序卸載時釋放掉,是head_n_byte,ori_func
    //它們被賦值到返回值和參數Original_ApiAddress了
    KIRQL irql;
    UINT64 tmpv;
    //一個是保存被hook函數前n字節碼,一個是保存代理函數調用原始函數用的代碼,不能直接調用原始函數,由於已經被改了.
    PVOID head_n_byte, ori_func;

    UCHAR jmp_code[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
    UCHAR jmp_code_orifunc[] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
    //How many bytes shoule be patch
    *PatchSize = GetPatchSize((PUCHAR)ApiAddress);
    //step 1: Read current data
    head_n_byte = kmalloc(*PatchSize);
    irql = WPOFFx64();
    memcpy(head_n_byte, ApiAddress, *PatchSize);
    WPONx64(irql);
    //step 2: Create ori function
    ori_func = kmalloc(*PatchSize + 14);    //原始機器碼+跳起色器碼
    RtlFillMemory(ori_func, *PatchSize + 14, 0x90);
    tmpv = (ULONG64)ApiAddress + *PatchSize;    //跳轉到沒被打補丁的那個字節
    DbgPrint("ApiAddress is %p\n", ApiAddress);
    DbgBreakPoint();
    memcpy(jmp_code_orifunc + 6, &tmpv, 8);

    memcpy((PUCHAR)ori_func, head_n_byte, *PatchSize);
    memcpy((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14);
    *Original_ApiAddress = ori_func;
    //step 3: fill jmp code
    tmpv = (UINT64)Proxy_ApiAddress;
    memcpy(jmp_code + 6, &tmpv, 8);
    //step 4: Fill NOP and hook
    irql = WPOFFx64();
    RtlFillMemory(ApiAddress, *PatchSize, 0x90);
    memcpy(ApiAddress, jmp_code, 14);
    WPONx64(irql);
    //return ori code
    
    return head_n_byte;
}








VOID HookPsLookupProcessByProcessId()
{
    //pslp_head_n_byte和ori_pslp 最後須要釋放掉
    pslp_head_n_byte = HookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"),
        (PVOID)Proxy_PsLookupProcessByProcessId,
        &ori_pslp,
        &pslp_patch_size);
}

VOID UnhookPsLookupProcessByProcessId()
{
    UnhookKernelApi(GetFunctionAddr(L"PsLookupProcessByProcessId"),
        pslp_head_n_byte,
        pslp_patch_size);
}
相關文章
相關標籤/搜索