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;
bool?H1_OpenProcess();
void?UnHook();
BOOL APIENTRY?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;
? ?}
? ?return?TRUE;
}
HANDLE?MyOpenProcess(
? ?DWORD dwDesiredAccess,
? ?BOOL ?bInheritHandle,
? ?DWORD dwProcessId)
{
? ?dwDesiredAccess &= ~PROCESS_TERMINATE;//去掉關閉程序的權限
? ?UnHook();//恢復Hook 任何調整到原來的地方執行.
? ?HANDLE h = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
? ?H1_OpenProcess();
? ?return?h;
}
void?*?F1_OpenProcess()
{
? ?//尋找到OpenProcess的地址
? ?void?* addr =?0;
? ?//加載kernel32.dll
? ?HMODULE hModule = LoadLibraryA("kernelbase.dll");
? ?//獲取OpenProcess的地址
? ?addr=(void?*)GetProcAddress(hModule,?"OpenProcess");
? ?return?addr;
}
void?*?F2_OpenProcess()
{
? ?return?(void?*)OpenProcess;
}
bool?H1_OpenProcess()
{
? ?//1.開始尋找地址
? ?void?* addr = F1_OpenProcess();
? ?OpenProcessaddr = addr;
? ?//判斷是否尋找成功
? ?if?(addr ==?0)
? ?{
? ? ? ?MessageBoxA(NULL,"尋找地址失敗",NULL,0);
? ? ? ?return?false;
? ?}
? ?//2.進行Hook
? ?/*
? ?一般代碼段是不可寫的,我們需要把其改為可讀可寫.
? ?*/
? ?VirtualProtect((void?*)addr,?5, PAGE_EXECUTE_READWRITE,&oldProtect);
? ?//修改前面的5個字節為jmp 跳轉到我們的代碼.
? ?//內聯Hook 跳轉偏移計算方式:跳轉偏移=目標地址-指令地址-5
? ?//jmp 的OpCode 為:0xE9
? ?JmpBtye[0] =?0xE9;
? ?*(DWORD *)&JmpBtye[1] = (DWORD)((long?long)MyOpenProcess - (long?long)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);
? ?return?hProcee;
}
//提升權限
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);
}
//進程注入
BOOL?DoInjection(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);
? ? ? ?return?true;
? ?}
? ?else
? ?{
? ? ? ?MessageBox(NULL, TEXT("注入失敗"), TEXT("提示"), MB_OK);
? ? ? ?return?false;
? ?}
}
int?main()
{
? ?//這里填寫窗口標題
? ?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。
?
系統描述符結構如下:
?
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查看
?
2. Find
?
方式1:在Win7 32下,系統服務描述符表直接導出符號KeServiceDescriptorTable,可以直接獲取其地址,然后通過其第一個ServiceTableBase?就是SSDT的地址,接著找到第190號函數。
?
方式2:可以通過?PsGetCurrentThread?獲取ETHREAD結構,該結構的第一個字段KTHREAD有一個字段ServiceTable保存著系統描述符表的地址KeServiceDescriptorTable。通過其第一個?ServiceTableBase?就是SSDT的地址,接著找到第190號函數。
?
0: kd> u PsGetCurrentThread
nt!PsGetCurrentThread:
840473f1?64a124010000 ? ?mov ? ? eax,dword ptr?fs:[00000124h] ?;ETHREAD
840473f7?c3?? ? ? ? ? ? ?ret
?
?
3.Hook
?
方式1:替換找到的地方,換成我們自己的函數
?
方式2:獲取找到的地方的函數指針,改變其代碼跳轉到自己的代碼(其實就是inline Hook)
?
例子:F2H1
?
新建一個驅動程序:
?
?
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
? ?{
? ? ? ?return?STATUS_ABANDONED;
? ?}
? ?return?g_NtOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
}
void?OffProtect()
{
? ?__asm {?//關閉內存保護
? ? ? ?push eax;
? ? ? ?mov eax, cr0;
? ? ? ?and?eax, ~0x10000;//關閉CR0.WP位,關閉頁保護
? ? ? ?mov cr0, eax;
? ? ? ?pop eax;
? ?}
}
void?OnProtect()
{
? ?__asm {?//恢復內存保護
? ? ? ?push eax;
? ? ? ?mov eax, cr0;
? ? ? ?or?eax,?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];
}
bool?H1_NtOpenProcess()
{
? ?OldNtProcess = F2_NtOpenProcess();//Find
? ?g_NtOpenProcess = (NTOPENPROCESS)(*(ULONG *)OldNtProcess);//保存就地址
? ?OffProtect();//由于SSDT表是只讀的所以需要關閉頁寫入保護
? ?(*(ULONG *)OldNtProcess) = (ULONG)MyOpenProcess;//寫入自己的函數地址
? ?OnProtect();//恢復
? ?return?true;
}
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();
? ?return?STATUS_SUCCESS;
}
?
加載驅動程序(自己寫的一個小工具,也可以網上下載)
?
?
4.查看效果
?
?
?
?
?
SYSENTRY Hook
?
?
這里我再說一些Hook,也是3歩曲WFH。但是我不再提供具體實現。
?
我們知道以前windows系統是通過int?2e中斷進入系統內核的,但是現在是通過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
? +0x000?TypeList ? ? ? ? ? ? ? ? ? ? : _LIST_ENTRY
? +0x010?Name ? ? ? ? ? ? ? ? ? ? ? ? : _UNICODE_STRING
? +0x020?DefaultObject ? ? ? ? ? ? ? ?: Ptr64 Void
? +0x028?Index ? ? ? ? ? ? ? ? ? ? ? ?: UChar
? +0x02c?TotalNumberOfObjects ? ? ? ? : Uint4B
? +0x030?TotalNumberOfHandles ? ? ? ? : Uint4B
? +0x034?HighWaterNumberOfObjects ? ? : Uint4B
? +0x038?HighWaterNumberOfHandles ? ? : Uint4B
? +0x040?TypeInfo ? ? ? ? ? ? ? ? ? ? : _OBJECT_TYPE_INITIALIZER ?//1.這個
? +0x0b0?TypeLock ? ? ? ? ? ? ? ? ? ? : _EX_PUSH_LOCK
? +0x0b8?Key ? ? ? ? ? ? ? ? ? ? ? ? ?: Uint4B
? +0x0c0?CallbackList ? ? ? ? ? ? ? ? : _LIST_ENTRY
ntdll!_OBJECT_TYPE_INITIALIZER
? +0x000?Length ? ? ? ? ? ? ? ? ? ? ? : Uint2B
? +0x002?ObjectTypeFlags ? ? ? ? ? ? ?: UChar
? +0x002?CaseInsensitive ? ? ? ? ? ? ?: Pos?0,?1?Bit
? +0x002?UnnamedObjectsOnly ? ? ? ? : Pos?1,?1?Bit
? +0x002?UseDefaultObject ? ? ? ? ? ? : Pos?2,?1?Bit
? +0x002?SecurityRequired ? ? ? ? ? ? : Pos?3,?1?Bit
? +0x002?MaintainHandleCount ? ? ? ? : Pos?4,?1?Bit
? +0x002?MaintainTypeList ? ? ? ? ? ? : Pos?5,?1?Bit
? +0x002?SupportsObjectCallbacks ? ? : Pos?6,?1?Bit
? +0x004?ObjectTypeCode ? ? ? ? ? ? ? : Uint4B
? +0x008?InvalidAttributes ? ? ? ? ? ?: Uint4B
? +0x00c?GenericMapping ? ? ? ? ? ? ? : _GENERIC_MAPPING
? +0x01c?ValidAccessMask ? ? ? ? ? ? ?: Uint4B
? +0x020?RetainAccess ? ? ? ? ? ? ? ? : Uint4B
? +0x024?PoolType ? ? ? ? ? ? ? ? ? ? : _POOL_TYPE
? +0x028?DefaultPagedPoolCharge ? ? : Uint4B
? +0x02c?DefaultNonPagedPoolCharge : Uint4B
? +0x030?DumpProcedure ? ? ? ? ? ? ? ?: Ptr64 ? ??void
? +0x038?OpenProcedure ? ? ? ? ? ? ? ?: Ptr64 ? ??long//打開 回調函數
? +0x040?CloseProcedure ? ? ? ? ? ? ? : Ptr64 ? ??void//關閉 回到函數
? +0x048?DeleteProcedure ? ? ? ? ? ? ?: Ptr64 ? ??void
? +0x050?ParseProcedure ? ? ? ? ? ? ? : Ptr64 ? ??long
? +0x058?SecurityProcedure ? ? ? ? : Ptr64 ? ??long
? +0x060?QueryNameProcedure ? ? ? ? : Ptr64 ? ??long?//查詢名稱 回調函數
? +0x068?OkayToCloseProcedure ? ? ? ? : 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。
轉載于:https://www.cnblogs.com/davidwang456/articles/9212053.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
- 上一篇: Python/WSGI 应用快速入门--
- 下一篇: 8道Java经典面试题