當前位置:
首頁 > 知識 > Hook原理

Hook原理

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裡面。

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的第一種方式,後面不再說明):

(1)新建一個dll文件:

(2)在dll文件裡面寫如下代碼:

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

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

DWORD oldProtect;

BYTE JmpBtye[5];

BYTE OldByte[5];

void* OpenProcessaddr;

boolH1_OpenProcess();

voidUnHook();

BOOL APIENTRYDllMain( HMODULE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

)

{

switch(ul_reason_for_call)

{

caseDLL_PROCESS_ATTACH:

H1_OpenProcess();

break;

caseDLL_PROCESS_DETACH:

UnHook();

break;

}

returnTRUE;

}

HANDLEMyOpenProcess(

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

//載入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 ==)

{

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

returnfalse;

}

//2.進行Hook

/*

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

*/

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

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

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

//jmp 的OpCode 為:0xE9

JmpBtye[] =0xE9;

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

//保存原先位元組

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

//替換原先位元組

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

}

voidUnHook()

{

//恢復原先位元組

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

//恢復屬性

DWORD p;

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

}

把dll注入任務管理器,因為注入不是我們主題,所以這裡我只是簡單的貼出代碼,直接拿來用就可以。

```

#include

//獲取進程句柄

HANDLEGetThePidOfTargetProcess(HWND hwnd)

{

DWORD pid;

GetWindowThreadProcessId(hwnd, &pid);

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

returnhProcee;

}

//提升許可權

voidUp()

{

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[].Attributes = SE_PRIVILEGE_ENABLED;

tp.Privileges[].Luid = luid;

AdjustTokenPrivileges(hToken,, &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,, pfnStartAddr, AllocAddr,,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);

}

```

注入之後看效果

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

SSDT Hook

剛才說了用戶層的Hook,接下來我們再說一下內核層的Hook,其實還是3歩曲。WFH

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

1.Where

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

系統描述符結構如下:

typedefstruct_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查看

2. Find

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

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

: kd> u PsGetCurrentThread

nt!PsGetCurrentThread:

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

840473f7c3ret

3.Hook

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

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

例子:F2H1

新建一個驅動程序:

2.代碼如下:

#include

#pragmapack(1)

typedefstruct_KSYSTEM_SERVICE_TABLE

{

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

ULONG *ServiceCounterTableBase;// 計數表基址

ULONG NumberOfServices;// 表中項的個數

UCHAR *ParamTableBase;// 參數表基址

}KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

#pragmapack()

void*OldNtProcess =;

// 導入系統描述符表

extern"C"NTSYSAPI KSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

typedefNTSTATUS(NTAPI *NTOPENPROCESS)(PHANDLE ProcessHandle,

ACCESS_MASK DesiredAccess,

POBJECT_ATTRIBUTES ObjectAttributes,

PCLIENT_ID ClientId);

NTOPENPROCESS g_NtOpenProcess =NULL;

NTSTATUS NTAPIMyOpenProcess(

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

}

voidOffProtect()

{

__asm {//關閉內存保護

push eax;

mov eax, cr0;

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

mov cr0, eax;

pop eax;

}

}

voidOnProtect()

{

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

}

voidUnHook()

{

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

(*(ULONG *)OldNtProcess) = (ULONG)g_NtOpenProcess;//恢複函數

OnProtect();//恢復

}

voidUnload(PDRIVER_OBJECT pDri)

{

UNREFERENCED_PARAMETER(pDri);

UnHook();

}

extern"C"NTSTATUSDriverEntry(PDRIVER_OBJECT pDri, PUNICODE_STRING pRegStr)

{

UNREFERENCED_PARAMETER(pRegStr);

pDri->DriverUnload = Unload;

H1_NtOpenProcess();

returnSTATUS_SUCCESS;

}

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

4.查看效果

SYSENTRY Hook

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

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

1.Where

SYSENTRYHook:190號功能號,功能號保存在eax中.

SYSENTRY指令進入系統內核的地址保存在MSR寄存器裡面的**IA32_SYSENTER_EIP** (x176)號寄存器.

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 : Pos,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 : Ptr64void

+0x038OpenProcedure : Ptr64long//打開 回調函數

+0x040CloseProcedure : Ptr64void//關閉 回到函數

+0x048DeleteProcedure : Ptr64void

+0x050ParseProcedure : Ptr64long

+0x058SecurityProcedure : Ptr64long

+0x060QueryNameProcedure : Ptr64long//查詢名稱 回調函數

+0x068OkayToCloseProcedure : Ptr64unsignedchar

3.Hook

根據找到的位置替換裡面回調函數指針為我們自己寫的函數即可,比如替換OpenProcedure。

IDT Hook


1.Where

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

2.Find

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

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

3.Hook

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


總結:

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

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

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

本文轉載自【看雪社區】


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員之家 的精彩文章:

當我和程序員說:你的Bug,我可以幫忙解決時……
Python開發者必知的13個Python GUI庫

TAG:程序員之家 |