Hook原理

Editor發表於2018-06-21

Hook原理


對於會Hook的人來說,Hook其實也就那麼回事.對於沒有Hook過的人來說,會感覺Hook很高大上(其實也沒毛病).

那麼今天我們就來探討一些Hook的原理是什麼.

我認為任何Hook都可以分為以下三步(簡稱WFH):

需要Hook的是什麼,在哪裡(後面簡稱Where).

尋找到Hook的地方.(後面簡稱Find)

進行Hook.(後面簡稱Hook)

當然了同一個型別的Hook所在的地方一般是一樣的.但尋找到Hook的地方,和進行Hook卻會有許多不同的方法.我們要抓住的是不變的地方.

根據這3點我們舉例來驗證一下.


Hook API

第一個我儘量說得詳細一些.

舉例子:Hook API:OpenProcess 讓win10 64位的工作管理員關閉不了任何程式.

1. Where.

Hook API:OpenProcess. 在kernelbase.dll裡面.

Hook原理

2.Find.

方式1:

通過LoadLibrary載入kernelbase.dll模組基地址.

通過GetProcAddress獲取OpenProcess的地址.

方式2:程式設計直接引用OpenProcess的地址,因為在Windows下3大模組user32.dll,kernelbase.dll,ntdll.dll的載入基地址在每個應用程式中都是一樣的.

方式3:通過尋找目標的IAT找到OpenProcess

3.Hook.

方式1:通過注入dll到目標程式進行,可以替換kernelbase.dll裡面的OpenProcess的前面5個位元組為jmp跳轉到我們自己的地址,也可以修改目標程式的IAT.

方式2:通過WriteProcessMemory寫入程式碼,修改目標程式的OpenProcess跳轉到我們的程式碼.

程式碼例項:F1+H1(Find的第二種方式,Hook的第一種方式,後面不再說明):

新建一個dll檔案:

Hook原理


在dll檔案裡面寫如下程式碼


如果你的win10是64位的就編譯64位的,32位就編譯32位的


//dllmain.cpp : 定義 DLL 應用程式的入口點。

DWORD oldProtect;

BYTE  JmpBtye[5];

BYTE  OldByte[5];

void*OpenProcessaddr;

boolH1_OpenProcess();

void UnHook();

BOOLAPIENTRY DllMain( HMODULE hModule,

DWORD  ul_reason_for_call,

LPVOID lpReserved

)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

H1_OpenProcess();

break;

case DLL_PROCESS_DETACH:

UnHook();

break;

}

returnTRUE;

}

HANDLE MyOpenProcess(

DWORD dwDesiredAccess,

BOOL bInheritHandle,

DWORD dwProcessId)

{

dwDesiredAccess &=~PROCESS_TERMINATE;//去掉關閉程式的許可權

UnHook();//恢復Hook 任何調整到原來的地方執行.

HANDLE h=OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);

H1_OpenProcess();

returnh;

}

void*F1_OpenProcess()

{

//尋找到OpenProcess的地址

void*addr=0;

//載入kernel32.dll

HMODULE hModule=LoadLibraryA("kernelbase.dll");

//獲取OpenProcess的地址

addr=(void*)GetProcAddress(hModule,"OpenProcess");

returnaddr;

}

void*F2_OpenProcess()

{

return(void*)OpenProcess;

}

boolH1_OpenProcess()

{

//1.開始尋找地址

void*addr=F1_OpenProcess();

OpenProcessaddr=addr;

//判斷是否尋找成功

if(addr==0)

{

MessageBoxA(NULL,"尋找地址失敗",NULL,0);

returnfalse;

}

//2.進行Hook

/*

一般程式碼段是不可寫的,我們需要把其改為可讀可寫.

*/

VirtualProtect((void*)addr,5, PAGE_EXECUTE_READWRITE,&oldProtect);

//修改前面的5個位元組為jmp 跳轉到我們的程式碼.

//內聯Hook 跳轉偏移計算方式:跳轉偏移=目標地址-指令地址-5

//jmp 的OpCode 為:0xE9

JmpBtye[0]=0xE9;

*(DWORD*)&JmpBtye[1]=(DWORD)((longlong)MyOpenProcess-(longlong)addr-5);

//儲存原先位元組

memcpy(OldByte, (void*)addr,5);

//替換原先位元組

memcpy((void*)addr, JmpBtye,5);

}

void UnHook()

{

//恢復原先位元組

memcpy((void*)OpenProcessaddr, OldByte,5);

//恢復屬性

DWORD p;

VirtualProtect((void*)OpenProcessaddr,5, oldProtect, &p);

}

把dll注入工作管理員,因為注入不是我們主題,所以這裡我只是簡單的貼出程式碼,直接拿來用就可以

```

#include <windows.h>


//獲取程式控制程式碼

HANDLE GetThePidOfTargetProcess(HWND hwnd)

{

DWORD pid;

GetWindowThreadProcessId(hwnd, &pid);

HANDLE hProcee=::OpenProcess(PROCESS_ALL_ACCESS | PROCESS_CREATE_THREAD,0, pid);

returnhProcee;

}

//提升許可權

void Up()

{

HANDLE hToken;

LUID luid;

TOKEN_PRIVILEGES tp;

OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);

tp.PrivilegeCount=1;

tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;

tp.Privileges[0].Luid=luid;

AdjustTokenPrivileges(hToken,0, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);

}

//程式注入

BOOLDoInjection(char*DllPath, HANDLE hProcess)

{

DWORD BufSize=strlen(DllPath)+1;

LPVOID AllocAddr=VirtualAllocEx(hProcess, NULL, BufSize, MEM_COMMIT, PAGE_READWRITE);

WriteProcessMemory(hProcess, AllocAddr, DllPath, BufSize, NULL);

PTHREAD_START_ROUTINE pfnStartAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA");

HANDLE hRemoteThread;

hRemoteThread=CreateRemoteThread(hProcess, NULL,0, pfnStartAddr, AllocAddr,0, NULL);

if(hRemoteThread)

{

MessageBox(NULL, TEXT("注入成功"), TEXT("提示"), MB_OK);

returntrue;

}

else

{

MessageBox(NULL, TEXT("注入失敗"), TEXT("提示"), MB_OK);

returnfalse;

}

}

intmain()

{

//這裡填寫視窗標題

HWND hwnd=FindWindowExA(NULL, NULL, NULL,"工作管理員");

Up();

HANDLE hP=GetThePidOfTargetProcess(hwnd);

//開始注入

//這裡填寫Dll路徑

DoInjection("E:\\studio\\VS2017\\F2H1.MessageBox\\x64\\Release\\F2H1.MessageBox.dll", hP);

}

```

注入之後看效果

Hook原理

Hook原理)

Hook原理

其實還有很多方式,剩下的方式你就可以自己慢慢嘗試了.

SSDT Hook.

剛才說了使用者層的Hook,接下來我們再說一下核心層的Hook,其實還是3歩曲.WFH

實現相似的功能,讓所有程式關閉不了自己的程式.

1.Where

Windows 作業系統共有4個系統服務描述符.其中只用了兩個,第一個是SSDT,第二個是ShadowSSDT

系統描述符結構如下:


typedef struct _KSYSTEM_SERVICE_TABLE

{

ULONG*ServiceTableBase;       //服務表基址 第一個表示SSDT 緊接著下一個ShadowSSDT

ULONG*ServiceCounterTableBase;//計數表基址

ULONG NumberOfServices;        //表中項的個數

UCHAR*ParamTableBase;         //參數列基址

}KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;

SSDT Hook:NtOpenProcess,在ntkrnlpa.exe核心模組中的系統服務描述符表中的SSDT表中的第190號.

使用PCHunter32檢視
Hook原理


2.Find


方式1:在Win7 32下,系統服務描述符表直接匯出符號KeServiceDescriptorTable,可以直接獲取其地址,然後通過其第一個ServiceTableBase就是SSDT的地址,接著找到第190號函式.

方式2:可以通過PsGetCurrentThread 獲取ETHREAD結構,該結構的第一個欄位KTHREAD有一個欄位ServiceTable儲存著系統描述符表的地址KeServiceDescriptorTable.通過其第一個ServiceTableBase就是SSDT的地址,接著找到第190號函式.


0: kd> u PsGetCurrentThread

nt!PsGetCurrentThread:

840473f164a124010000   mov     eax,dword ptr fs:[00000124h]  ;ETHREAD

840473f7c3              ret

Hook原理


3.Hook


方式1:替換找到的地方,換成我們自己的函式

方式2:獲取找到的地方的函式指標,改變其程式碼跳轉到自己的程式碼(其實就是inline Hook).

例子:F2H1

新建一個驅動程式:

Hook原理


2.程式碼如下:


#include <ntifs.h>

#pragma pack(1)

typedef struct _KSYSTEM_SERVICE_TABLE

{

ULONG*ServiceTableBase;       //服務表基址 第一個表示SSDT 緊接著下一個是ShadowSSDT

ULONG*ServiceCounterTableBase;//計數表基址

ULONG NumberOfServices;        //表中項的個數

UCHAR*ParamTableBase;         //參數列基址

}KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;

#pragma pack()

void*OldNtProcess=0;

//匯入系統描述符表

extern"C"NTSYSAPI KSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

typedef NTSTATUS(NTAPI*NTOPENPROCESS)(PHANDLE  ProcessHandle,

ACCESS_MASK  DesiredAccess,

POBJECT_ATTRIBUTES  ObjectAttributes,

PCLIENT_ID  ClientId);

NTOPENPROCESS g_NtOpenProcess=NULL;

NTSTATUS NTAPI MyOpenProcess(

PHANDLE  ProcessHandle,

ACCESS_MASK  DesiredAccess,

POBJECT_ATTRIBUTES  ObjectAttributes,

PCLIENT_ID  ClientId

)

{

if(ClientId->UniqueProcess==(HANDLE)916)//指定保護的程式ID

{

returnSTATUS_ABANDONED;

}

returng_NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

}

void OffProtect()

{

__asm {//關閉記憶體保護

push eax;

mov eax, cr0;

andeax, ~0x10000;//關閉CR0.WP位,關閉頁保護

mov cr0, eax;

pop eax;

}

}

void OnProtect()

{

__asm {//恢復記憶體保護

push eax;

mov eax, cr0;

oreax,0x10000;//開啟CR0.WP位,開啟頁保護

mov cr0, eax;

pop eax;

}

}

void*F1_NtOpenProcess()

{

return(void*)&KeServiceDescriptorTable.ServiceTableBase[190];

}

void*F2_NtOpenProcess()

{

PETHREAD eThread=PsGetCurrentThread();

PKSYSTEM_SERVICE_TABLE kServiceTable=(PKSYSTEM_SERVICE_TABLE)(*(ULONG*)((ULONG)eThread+0xbc));

return(void*)&kServiceTable->ServiceTableBase[190];

}

boolH1_NtOpenProcess()

{

OldNtProcess=F2_NtOpenProcess();//Find

g_NtOpenProcess=(NTOPENPROCESS)(*(ULONG*)OldNtProcess);//儲存就地址

OffProtect();//由於SSDT表是隻讀的所以需要關閉頁寫入保護

(*(ULONG*)OldNtProcess)=(ULONG)MyOpenProcess;//寫入自己的函式地址

OnProtect();//恢復

returntrue;

}

void UnHook()

{

OffProtect();//由於SSDT表是隻讀的所以需要關閉頁寫入保護

(*(ULONG*)OldNtProcess)=(ULONG)g_NtOpenProcess;//恢復函式

OnProtect();//恢復

}

void Unload(PDRIVER_OBJECT pDri)

{

UNREFERENCED_PARAMETER(pDri);

UnHook();

}

extern"C"NTSTATUS DriverEntry(PDRIVER_OBJECT pDri, PUNICODE_STRING pRegStr)

{

UNREFERENCED_PARAMETER(pRegStr);

pDri->DriverUnload=Unload;

H1_NtOpenProcess();

returnSTATUS_SUCCESS;

}


載入驅動程式(自己寫的一個小工具,也可以網上下載)


Hook原理)


4.檢視效果


Hook原理

Hook原理

Hook原理


SYSENTRY Hook


這裡我再說一些Hook,也是3歩曲WFH.但是我不再提供具體實現.


我們知道以前windows系統是通過int2e中斷進入系統核心的,但是現在是通過cpu提供的一個功能sysentry進入系統的(32位是sysentry,64位是syscall).這是一個CPU指令,如果對該指令不知道的話,可以檢視我另外一篇文章:


1.Where


SYSENTRY Hook:190號功能號,功能號儲存在eax中.

SYSENTRY指令進入系統核心的地址儲存在MSR暫存器裡面的**IA32_SYSENTER_EIP**(0x176)號暫存器.


2.Find

通過指令rdmsr讀取**IA32_SYSENTER_EIP**MSR暫存器.其中ecx儲存的是讀取msr的序號,也就是0x176號,返回的結果儲存在edx:eax(64位,edx儲存高32位,eax儲存低32位).因為是32位系統,所以只需要eax的值即可.


3.Hook

通過wrmsr寫入我們自己的地址,地址放在edx:eax(64位,edx儲存高32位,eax儲存低32位).即可完成Hook.


Object Hook


每一個不同的核心物件,都對應著一個不同的型別索引:TypeIndex.通過該索引可以找到該核心物件的型別:OBJECT_TYPE

1.Where

在核心物件的TypeInfo中.

2.Find

通過ObGetObjectType核心函式獲取核心物件型別(OBJECT_TYPE)的OBJECT_TYPE中有一個欄位TypeInfo(型別_OBJECT_TYPE_INITIALIZER),其中儲存著,在建立核心物件,銷燬核心物件的一系列建構函式.


對應結構:


//OBJECT_TYPE-->TypeInfo:_OBJECT_TYPE_INITIALIZER

ntdll!_OBJECT_TYPE

+0x000TypeList                     : _LIST_ENTRY

+0x010Name                         : _UNICODE_STRING

+0x020DefaultObject                : Ptr64 Void

+0x028Index                        : UChar

+0x02cTotalNumberOfObjects         : Uint4B

+0x030TotalNumberOfHandles         : Uint4B

+0x034HighWaterNumberOfObjects     : Uint4B

+0x038HighWaterNumberOfHandles     : Uint4B

+0x040TypeInfo                     : _OBJECT_TYPE_INITIALIZER //1.這個

+0x0b0TypeLock                     : _EX_PUSH_LOCK

+0x0b8Key                          : Uint4B

+0x0c0CallbackList                 : _LIST_ENTRY

ntdll!_OBJECT_TYPE_INITIALIZER

+0x000Length                       : Uint2B

+0x002ObjectTypeFlags              : UChar

+0x002CaseInsensitive              : Pos0,1Bit

+0x002UnnamedObjectsOnly         : Pos1,1Bit

+0x002UseDefaultObject             : Pos2,1Bit

+0x002SecurityRequired             : Pos3,1Bit

+0x002MaintainHandleCount         : Pos4,1Bit

+0x002MaintainTypeList             : Pos5,1Bit

+0x002SupportsObjectCallbacks     : Pos6,1Bit

+0x004ObjectTypeCode               : Uint4B

+0x008InvalidAttributes            : Uint4B

+0x00cGenericMapping               : _GENERIC_MAPPING

+0x01cValidAccessMask              : Uint4B

+0x020RetainAccess                 : Uint4B

+0x024PoolType                     : _POOL_TYPE

+0x028DefaultPagedPoolCharge     : Uint4B

+0x02cDefaultNonPagedPoolCharge : Uint4B

+0x030DumpProcedure                : Ptr64     void

+0x038OpenProcedure                : Ptr64    long//開啟 回撥函式

+0x040CloseProcedure               : Ptr64     void//關閉 回到函式

+0x048DeleteProcedure              : Ptr64     void

+0x050ParseProcedure               : Ptr64    long

+0x058SecurityProcedure         : Ptr64    long

+0x060QueryNameProcedure         : Ptr64    long//查詢名稱 回撥函式

+0x068OkayToCloseProcedure         : Ptr64     unsigned char

3.Hook

根據找到的位置替換裡面回撥函式指標為我們自己寫的函式即可.比如替換OpenProcedure.

IDT Hook

1.Where

在中斷描述符表(IDT)中.

2.Find

idtr暫存器指向中斷描述符表.通過idtr找到.

說明:idtr是一個48位暫存器,其中低16位儲存中斷描述符表長度.高32位是中斷描述符表.的基地址.

3.Hook

通過構造一箇中斷門或者陷阱門,其中中斷門或陷阱門的偏移地址寫自己的地址.然後把中斷門或者陷阱門寫入都相應的IDT表項中.


總結:

從上面我們可以看到,其實Hook都是一樣的,只是對應的地方不同,尋找的方法不同,替換(修改)的方法不同而已.

有的人可能就要反問了,SetWindowsHookEx,就不要知道Hook的地方在哪了,也不需要尋找.確實,這兩歩不需要我們自己做,但並不代表不需要,這只是作業系統為我們做了而已,我們只需要提供一個回撥函式即可.

所以下面我留下一個小測試:就是自己自己實現SetWindowsHookEx.

相關文章