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:程序員之家 |