发布时间:2025-12-09 14:04:00 浏览次数:12
1.IAT_HOOK
IAT是程序中存储导入函数地址的数据结构,如果HOOK了导入函数地址。就可以在函数调用的时候,将函数流程HOOK到我们指定的流程。但是我个人觉得这种方式最好要结合DLL注入的方式,如果单纯的使用HOOK,那么就需要将需要执行的操作的shellcode写入目标进程,如果操作复杂,可能需要的shellcode量特别大,所以我们需要借助DLL注入,这样就将我们需要执行的代码写入进程内部,在HOOK的Detour函数只需要实现LoadLibrary的操作。
IATHOOK的基本原理就是通过修改程序IAT数据结构,将原始调用API函数地址Target函数地址修改为Detour函数地址。所以IAT_HOOK需要实现以下几个步骤:
1)、构造Detour函数
2)、获取Target函数地址
3)、通过PE获取Target函数所在的IAT的地址
4)、保存原始的IAT地址和IAT地址所存储的内容
5)、修改IAT地址中的数据
6)、如果需要调用原来API函数,可以直接使用保存的API地址,可以就保证了HOOK的有效性
2.EAT_HOOK
使用EAT_HOOK需要注意一下两点:第一:EAT存储的是函数地址的偏移,所以在HOOK EAT的时候需要加上基地址,在写入EAT的时候,Detour地址需要减去BaseAddress。第二,EAT不对隐式链接起作用,只对显示链接起作用,也就是说对于那种GetProcAddress的那种调用起作用。
EAT_HOOK的原理和IAT_HOOK类似,都是通过修改函数地址数据从而HOOK。EAT_HOOK,也需要进行以下步骤:
1)、获取Target函数在HookModule上的RVA
2)、获取导出函数数组首地址
3)、遍历查找Target函数RVA
4)、切记在修改函数地址之前,需要保存EAT地址和原函数地址
5)、将Detour函数地址写入EAT
下面用代码来实现x64下的IAT HOOK和EAT HOOK
#include <stdio.h>#include <Windows.h>#include <Psapi.h>//#include "pe.h"#pragma comment(lib,"user32.lib")#pragma comment(lib,"psapi.lib")typedef int (__fastcall *MSGBOXA)(HWND hwnd, char *text, char *title, UINT type);typedef bool (__stdcall *TERMINATEPROCESS)(HANDLE hProcess, UINT uExitCode);ULONG64 OriMsgBoxA;ULONG64 OriTerminateProcess;int __fastcall iatProxyMessageBoxA(HWND hwnd, char *text, char *title, UINT type){MSGBOXA orifun=(MSGBOXA)OriMsgBoxA;printf("[iatProxyMessageBoxA - %s][%s]\n",title,text);return 0;orifun(hwnd,text,title,type);}int __fastcall eatProxyMessageBoxA(HWND hwnd, char *text, char *title, UINT type){MSGBOXA orifun=(MSGBOXA)OriMsgBoxA;printf("[eatProxyMessageBoxA - %s][%s]\n",title,text);return 0;orifun(hwnd,text,title,type);}bool __fastcall eatProxyTerminateProcess(HANDLE hProcess, UINT uExitCode){TERMINATEPROCESS orifun=(TERMINATEPROCESS)OriTerminateProcess;printf("eatProxyTerminateProcess\n");return orifun(hProcess,uExitCode);}bool __fastcall iatProxyTerminateProcess(HANDLE hProcess, UINT uExitCode){TERMINATEPROCESS orifun=(TERMINATEPROCESS)OriTerminateProcess;printf("iatProxyTerminateProcess\n");return orifun(hProcess,uExitCode);}VOID EAT_HOOK_TEST64(char *ModName, char *FunName, ULONG64 ProxyFunAddr){HANDLE hMod;PVOID BaseAddress = NULL;IMAGE_DOS_HEADER * dosheader;IMAGE_OPTIONAL_HEADER64 * opthdr;PIMAGE_EXPORT_DIRECTORY exports;USHORT index=0 ; ULONG addr, i;PUCHAR pFuncName = NULL;PULONG pAddressOfFunctions;PULONG pAddressOfNames;PUSHORT pAddressOfNameOrdinals;BaseAddress= GetModuleHandleA(ModName);MODULEINFO mi={0};GetModuleInformation(GetCurrentProcess(),(HMODULE)BaseAddress,&mi,sizeof(MODULEINFO));DWORD ass;VirtualProtect(BaseAddress,mi.SizeOfImage,PAGE_EXECUTE_READWRITE,&ass);hMod = BaseAddress;dosheader = (IMAGE_DOS_HEADER *)hMod;opthdr =(IMAGE_OPTIONAL_HEADER64 *) ((BYTE*)hMod+dosheader->e_lfanew+24);//24=4+sizeof(IMAGE_FILE_HEADER)exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)dosheader+ opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);pAddressOfFunctions=(ULONG*)((BYTE*)hMod+exports->AddressOfFunctions); pAddressOfNames=(ULONG*)((BYTE*)hMod+exports->AddressOfNames); pAddressOfNameOrdinals=(USHORT*)((BYTE*)hMod+exports->AddressOfNameOrdinals); for (i = 0; i < exports->NumberOfNames; i++) {index=pAddressOfNameOrdinals[i];addr=pAddressOfFunctions[index];pFuncName = (PUCHAR)( (BYTE*)hMod + pAddressOfNames[i]);addr = pAddressOfFunctions[index];if(!strcmp((const char*)pFuncName,FunName)){pAddressOfFunctions[index]=(ULONG)((ULONG64)ProxyFunAddr-(ULONG64)hMod);printf("eat fix!!!\n");;}} }BOOL IAT_HOOK_TEST64(char *DllName, HMODULE hMod, ULONG64 g_orgProc, ULONG64 g_newProc) { IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hMod; IMAGE_OPTIONAL_HEADER64* pOptHeader = (IMAGE_OPTIONAL_HEADER64 *)((BYTE*)hMod + pDosHeader->e_lfanew + 24); //24=4+sizeof(IMAGE_FILE_HEADER) IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hMod + pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); // 在导入表中查找user32.dll模块。因为MessageBoxA函数从user32.dll模块导出 while(pImportDesc->FirstThunk) { char* pszDllName = (char*)((BYTE*)hMod + pImportDesc->Name); if(lstrcmpiA(pszDllName, DllName) == 0) { break; } pImportDesc++; } if(pImportDesc->FirstThunk) { // 一个IMAGE_THUNK_DATA就是一个双字,它指定了一个导入函数 // 调入地址表其实是IMAGE_THUNK_DATA结构的数组,也就是DWORD数组 IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)((BYTE*)hMod + pImportDesc->FirstThunk); while(pThunk->u1.Function) { // lpAddr指向的内存保存了函数的地址 ULONG64* lpAddr = (ULONG64*)&(pThunk->u1.Function); if(*lpAddr == g_orgProc) { DWORD dwOldProtect; VirtualProtect(lpAddr, sizeof(ULONG64), PAGE_EXECUTE_READWRITE, &dwOldProtect); *lpAddr=(ULONG64)g_newProc;printf("iat fix!!!\n"); return TRUE; } pThunk++; } } return FALSE; }int main(){OriMsgBoxA=(ULONG64)MessageBoxA;IAT_HOOK_TEST64("user32.dll",GetModuleHandleA(0),(ULONG64)MessageBoxA,(ULONG64)iatProxyMessageBoxA);EAT_HOOK_TEST64("user32.dll","MessageBoxA",(ULONG64)eatProxyMessageBoxA);OriTerminateProcess=(ULONG64)TerminateProcess;IAT_HOOK_TEST64("kernel32.dll",GetModuleHandleA(0),(ULONG64)TerminateProcess,(ULONG64)iatProxyTerminateProcess);EAT_HOOK_TEST64("kernel32.dll","TerminateProcess",(ULONG64)eatProxyTerminateProcess);printf("Press any key to test.\n");getchar();//test MessageBoxAMessageBoxA(0,"Direct call MessageBoxA","test",0);MSGBOXA msgboxA=(MSGBOXA)GetProcAddress(LoadLibraryA("user32.dll"),"MessageBoxA");msgboxA(0,"Call MessageBoxA_Ptr from GetProcAddress","test",0);//test TerminateProcess[输入无效句柄测试一下即可]TerminateProcess((HANDLE)1234,0);TERMINATEPROCESS tp=(TERMINATEPROCESS)GetProcAddress(GetModuleHandleA("kernel32.dll"),"TerminateProcess");tp((HANDLE)1234,0);getchar();return 0;}3.VirtualFunctionHook
C++虚函数存在的意义是为了方便使用多态性。在实现虚函数Hook的时候需要注意如下问题:1.在构建DetourFun函数的时候,一定要构造DetourClass,因为在调用虚函数的时候使用了Thiscall的函数调用约定,如果直接调用detourfun函数应该使用的标准调用约定,两者不统一,会出错。2.当使用Trampolinefun回调的时候,需要重新实例化一个TrampolineClass。
实现代码可以参考链接:https://blog.csdn.net/ab7936573/article/details/65967178
4.inline hook
下面以x64内核为例,hook PsLookupProcessByProcessId函数,使得不能打开计算器。代码如下:
#include <ntddk.h>#include "LDE64x64.h"KIRQL WPOFF(){KIRQL irql = KeRaiseIrqlToDpcLevel();UINT64 cr0 = __readcr0();cr0 &= 0xfffffffffffeffff;__writecr0(cr0);_disable();return irql;}VOID WPON(KIRQL irql){UINT64 cr0 = __readcr0();cr0 |= 10000;_enable();__writecr0(cr0);KeLowerIrql(irql);}typedef NTSTATUS(__fastcall *PSLOOKUPPROCESSBYPROCESSID)(HANDLE ProcessId, PEPROCESS *Process);ULONG64 my_eprocess_id = 0;//待保护进程的eprocessULONG pslp_patch_size = 0;//PsLookupProcessByProcessId被修改了N字节PUCHAR pslp_head_n_byte = NULL;//PsLookupProcessByProcessId的前N字节数组PVOID originalPsLookupProcessByProcessId = NULL;//PsLookupProcessByProcessId的原函数PVOID GetFunctionAddress(PCWSTR FunctionName){UNICODE_STRING unicodeStringName;RtlInitUnicodeString(&unicodeStringName, FunctionName);return MmGetSystemRoutineAddress(&unicodeStringName);}NTSTATUS Proxy_PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process){NTSTATUS st;st = ((PSLOOKUPPROCESSBYPROCESSID)originalPsLookupProcessByProcessId)(ProcessId, Process);if (NT_SUCCESS(st)){PUCHAR pImage = (PUCHAR)((ULONGLONG)*Process + IMAGEFILENAME);//KdPrint(("process ulong %I64x\n", (ULONGLONG)Process));//KdPrint(("process 指针 %I64x\n", *Process));//KdPrint(("当前路径为 %s\n", pImage));if (0 == strcmp(pImage, "calc.exe")){*Process = 0;st = STATUS_ACCESS_DENIED;}}return st;}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;}/*4位inline hook64位系统没有了上面这样的方便之处,因此必须有一种新的策略。64位的跳转,可用两种方法,下面两个方法都是绝对跳转指令,第一个影响rax寄存器,可能需要先保存原来的rax的值:1,48 b8 ef cd ab 89 67 45 23 01 mov rax, 0x0123456789abcdefff e0 jmp rax2,0xff25 [0x00000000]0xef cd ab 89 67 45 23 01这里用第二种方法,将一个old_func_address的前x个字节修改为跳转到我们的new_func_address,步骤:1,反汇编old_func_address处的指令,累加其长度,依次反汇编下去,直到长度大于12,例如为15;2,复制这15个字节的指令,将old_func_address的前12个字节修改为:0xff25 0x00000000 new_func_address(8个字节);3,跳转完成之后将其前15个字节还原。这个方法要求函数长度大于15个字节,所以有一个方法用于适用于小于15字节长度的函数:通过一个0xe9 tmp_address跳转到我们申请的空间(该空间地址与old_func_address的间隔在4G范围内,通过VirtualAlloc函数达成),在tmp_address处再long jmp(0xff25 …)。*/// 解释一下跳转的代码。我之前使用的跳转流程是:// MOV RAX, 绝对地址// JMP RAX// 后来感觉修改 RAX 不太好(显然 RAX 是易失性寄存器),于是换了方式:// JMP QWORD PTR[本条指令结束后的地址]// 以上指令的机器码是: FF 25 00 00 00 00。//// 因为跨 4G 跳转指令是 14 字节,而我们// 修改了 PsLookupProcessByProcessId 的头 15 字节(正好三条指令),前 6 字节// 是指令,后 9 字节并不是指令,而是数据(前 8 字节是绝对地址)和填充码(最// 后 1 字节没有意义)。// https://blog.csdn.net/Lactoferrin/article/details/7216207// 传入:待HOOK函数地址,代理函数地址,接收原始函数地址的指针,接收补丁长度的指针;返回:原来头N字节的数据PVOID HookKernelApi(IN PVOID ApiAddress, IN PVOID Proxy_ApiAddress, OUT PVOID *Original_ApiAddress, OUT ULONG *PatchSize){KIRQL irql;UINT64 tmpv;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 datahead_n_byte = kmalloc(*PatchSize);irql = WPOFFx64();memcpy(head_n_byte, ApiAddress, *PatchSize); // 将初始的*PatchSize字节的机器码 保存到 自己申请的内存空间中KdPrint(("head_n_byte is %p\n", head_n_byte));KdPrint(("PatchSize si %d\n", *PatchSize));WPONx64(irql);//step 2: Create ori functionori_func = kmalloc(*PatchSize + 14);// 申请一大段内存 保存 原始机器码+跳转机器码RtlFillMemory(ori_func, *PatchSize + 14, 0x90);tmpv = (ULONG64)ApiAddress + *PatchSize;// 跳转到没被打补丁的那个字节memcpy(jmp_code_orifunc + 6, &tmpv, 8);KdPrint(("head_n_byte is %p\n", jmp_code_orifunc));memcpy((PUCHAR)ori_func, head_n_byte, *PatchSize); // 前面试初始的机器码memcpy((PUCHAR)ori_func + *PatchSize, jmp_code_orifunc, 14); // 后面是一个jmp指令,jmp到原函数PatchSize之后的位置继续执行*Original_ApiAddress = ori_func;//step 3: fill jmp codetmpv = (UINT64)Proxy_ApiAddress;memcpy(jmp_code + 6, &tmpv, 8);//step 4: Fill NOP and hookirql = WPOFFx64();RtlFillMemory(ApiAddress, *PatchSize, 0x90);memcpy(ApiAddress, jmp_code, 14);WPONx64(irql);//return ori codereturn head_n_byte;}/*总结一下过程在原函数开头构建两个jmp,第一个jmp到第二个jmp地址上,第二个jmp到我写的函数,在我写的函数中调到我分配的地址中执行,执行的代码先把原函数开头的15个字节执行,再jmp到原函数地址+15的位置执行,原函数返回后,继续执行我的代码*/VOID InlineHook(){LDE_init();PVOID psLookupProcessAdress = GetFunctionAddress(L"PsLookupProcessByProcessId");pslp_head_n_byte = HookKernelApi(psLookupProcessAdress,(PVOID)Proxy_PsLookupProcessByProcessId,&originalPsLookupProcessByProcessId,&pslp_patch_size);}//传入:被HOOK函数地址,原始数据,补丁长度VOID UnhookKernelApi(IN PVOID ApiAddress, IN PVOID OriCode, IN ULONG PatchSize){KIRQL irql;irql = WPOFFx64();memcpy(ApiAddress, OriCode, PatchSize);WPONx64(irql);}VOID UnhookPsLookupProcessByProcessId(){PVOID psLookupProcessAdress = GetFunctionAddress(L"PsLookupProcessByProcessId");UnhookKernelApi(psLookupProcessAdress, pslp_head_n_byte, pslp_patch_size);}头文件 LDE64x64.h 百度搜一下,有很多,我就不帖了
5.VEH_HOOK
VEH技术的主要原理是利用异常处理改变程序指令流程。通过主动抛出异常,使程序触发异常,控制权交给异常处理例程的这一系列操作来实现HOOK。
这里简单提一下VEH,向量异常处理,基于VEH链表而不是栈,这样的话其作用范围是进程全局,而不是线程。且优先级也高于SEH,这也是VEH_HOOK的优势所在。
VEH_HOOK通过异常机制实现HOOK,必不可少需要构造异常处理函数,同时也需要人为的构造异常,同时为了实现永久化机制,保证执行原操作需要实现TrampolineFun函数。所以总结VEH_HOOK步骤如下:
1)、构造TrampolineFun
2)、构造异常处理函数,即Detour函数
3)、人为构造异常。
用软件断点实现如下
#include <Windows.h>LPVOID Checkaddr = NULL;BYTE oldbyte = 0;DWORD WINAPI ExceptionHandle(EXCEPTION_POINTERS* ExceptionInfo){if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT){if (ExceptionInfo->ExceptionRecord->ExceptionAddress == LPVOID((BYTE*)Checkaddr)){ExceptionInfo->ExceptionRecord->ExceptionFlags |= 0x100; // TF置为1// 先恢复*(BYTE*)Checkaddr = oldbyte;MessageBoxW(NULL, L"hook里的", L"hook里的", NULL);return EXCEPTION_CONTINUE_EXECUTION;}}else if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP){if (ExceptionInfo->ExceptionRecord->ExceptionAddress == LPVOID((BYTE*)Checkaddr + 1)){// 重新挂上,重新挂上失败*(BYTE*)Checkaddr = 0xcc;return EXCEPTION_CONTINUE_EXECUTION;}}return EXCEPTION_CONTINUE_SEARCH;}void veh_hook(){HINSTANCE hInst = LoadLibrary(L"User32.DLL");Checkaddr = (LPVOID)GetProcAddress(hInst, "MessageBoxW");AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandle);// 写入int3 这里是用的软件断点oldbyte = *(BYTE*)Checkaddr;DWORD oldProtect;VirtualProtect(Checkaddr, 2, PAGE_EXECUTE_READWRITE, &oldProtect);*(BYTE*)Checkaddr = 0xcc;MessageBoxW(NULL, L"hook外的1", L"hook外的1", NULL);MessageBoxW(NULL, L"hook外的2", L"hook外的2", NULL);}用硬件断点实现如下
#include <windows.h>#include <tlhelp32.h>DWORD ThreadID;HANDLE hThread;PVOID ExceptionHandle = NULL;PVOID T_OrgProc[4];PVOID T_NewProc[4];class Dr7_Hook{public:Dr7_Hook();~Dr7_Hook();HANDLE Dr7_Hook::Start_Thread();BOOL Initialize();DWORD HOOK(PVOID OrgProc, PVOID NewProc);BOOL UnHOOK(PVOID NewProc);//void Start(HANDLE hThread);void Start();void Stop();private:};//Hookvoid Dr7_Hook::Start(){CONTEXT Context;Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;;GetThreadContext(GetCurrentThread(), &Context);Context.Dr0 = (DWORD)T_OrgProc[0];Context.Dr1 = (DWORD)T_OrgProc[1];Context.Dr2 = (DWORD)T_OrgProc[2];Context.Dr3 = (DWORD)T_OrgProc[3];Context.Dr7 = 0x405;SetThreadContext(GetCurrentThread(), &Context);}//Hook指定线程void Start(DWORD dwThreadId){CONTEXT Context;HANDLE hThread;Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;hThread = OpenThread(THREAD_ALL_ACCESS, NULL, dwThreadId);GetThreadContext(hThread, &Context);Context.Dr0 = (DWORD)T_OrgProc[0];Context.Dr1 = (DWORD)T_OrgProc[1];Context.Dr2 = (DWORD)T_OrgProc[2];Context.Dr3 = (DWORD)T_OrgProc[3];Context.Dr7 = NULL;if (Context.Dr0){Context.Dr7 = Context.Dr7 | 3;}if (Context.Dr1){Context.Dr7 = Context.Dr7 | 12;}if (Context.Dr2){Context.Dr7 = Context.Dr7 | 48;}if (Context.Dr3){Context.Dr7 = Context.Dr7 | 192;}Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;//Context.Dr7 = 0x405;SetThreadContext(hThread, &Context);CloseHandle(hThread);}void Dr7_Hook::Stop(){CONTEXT Context;Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;;GetThreadContext(GetCurrentThread(), &Context);Context.Dr0 = NULL;Context.Dr1 = NULL;Context.Dr2 = NULL;Context.Dr3 = NULL;Context.Dr7 = NULL;SetThreadContext(GetCurrentThread(), &Context);}//多线程Hookbool Initialize_Thread(){HANDLE hThreadSnap = NULL;//HANDLE hThread;DWORD dwMypid;dwMypid = GetCurrentProcessId();THREADENTRY32 te32 = { 0 };hThreadSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (hThreadSnap == INVALID_HANDLE_VALUE)return (FALSE);te32.dwSize = sizeof(THREADENTRY32);if (Thread32First(hThreadSnap, &te32)){do{if (te32.th32OwnerProcessID == dwMypid){if (ThreadID != te32.th32ThreadID){SuspendThread(hThread);//线程挂起Start(te32.th32ThreadID);ResumeThread(hThread);//线程恢复}}} while (Thread32Next(hThreadSnap, &te32));}else{return FALSE;CloseHandle(hThreadSnap);}CloseHandle(hThreadSnap);return TRUE;}HANDLE Dr7_Hook::Start_Thread(){hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Initialize_Thread, NULL, NULL, &ThreadID);return hThread;}DWORD NTAPI ExceptionHandler(EXCEPTION_POINTERS * ExceptionInfo){for (size_t i = 0; i < 4; i++){if (ExceptionInfo->ExceptionRecord->ExceptionAddress == T_OrgProc[i]){ExceptionInfo->ContextRecord->Rip = (DWORD)T_NewProc[i];return EXCEPTION_CONTINUE_EXECUTION;}}return EXCEPTION_CONTINUE_SEARCH;}BOOL Dr7_Hook::Initialize(){BOOL Jud;ExceptionHandle = AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)ExceptionHandler);for (size_t i = 0; i < 4; i++){T_OrgProc[i] = NULL;T_NewProc[i] = NULL;}Jud = (BOOL)ExceptionHandle;//CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, this, 0, NULL);return Jud;}DWORD Dr7_Hook::HOOK(PVOID OrgProc, PVOID NewProc){for (size_t i = 0; i < 4; i++){if (!T_OrgProc[i]){T_OrgProc[i] = OrgProc;T_NewProc[i] = NewProc;return i;}}return 0;}BOOL Dr7_Hook::UnHOOK(PVOID NewProc){if (NewProc == NULL){Stop();return (BOOL)RemoveVectoredExceptionHandler(ExceptionHandle);}else{for (size_t i = 0; i < 4; i++){if (T_NewProc[i] == NewProc){T_OrgProc[i] = 0;T_NewProc[i] = 0;Start();return TRUE;}}}return FALSE;}Dr7_Hook::Dr7_Hook(){if (ExceptionHandle == NULL){Initialize();}}Dr7_Hook::~Dr7_Hook(){UnHOOK(NULL);CloseHandle(HANDLE(ThreadID));}6. SSDT_HOOK
SSDT中文全称为系统服务描述符表,其作用是作为R3和R0层的通道,将用户态API函数和内核函数联系起来。用简单的API函数举例子,我们调用了CreateFile,其会调用ZwCreateFile,然后调用NtCreateFile,经过参数和模式的检查,然后调用系统服务分发函数KiSystemService进入内核。在R0中通过传入的系统服务号(函数索引)得到系统服务的地址,然后调用该系统服务即可。
所以,根据上述,我们可以知道SSDT其实是一个存储系统服务的数组。SSDT_HOOK其实就是在内核层的AddressHook。只不过他修改是系统服务描述符表数据。
因为SSDT的索引号和系统服务内核地址是一一对应的,所以不需要向普通的AddressHook一一对比函数地址。所以让我们来屡一下执行SSDT的操作。我们有目的向原因开始。如果我们需要执行SSDT_HOOK的话,首先需要修改为与SSDT中的系统服务地址,但又由于系统服务地址是和服务索引是保持对应关系的,所以我们还需要获取索引号。
根据上面的分析,我们知道首先需要获取服务索引号。但是服务索引号和函数地址对应的,在X86系统中,相对于导出函数偏移量1的地址往后读四个字节就是SSDT服务索引号。但是对于X64位的系统,却是函数地址偏移为4的地址读取四个字节。所以需要得到服务索引号,就需要得到导出函数地址。
我们现在总结一下得到服务索引的步骤:
Step1:将Ntdll.dll载入内存
Step2:获取导出函数地址
Step3:计算函数索引
下面以x64内核为例,进行ssdt hook
#include <ntddk.h>typedef struct _SYSTEM_SERVICE_TABLE {PVOID ServiceTableBase;PVOID ServiceCounterTableBase;ULONGLONG NumberOfServices;PVOID ParamTableBase;} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;typedef struct _SERVICE_DESCRIPTOR_TABLE {SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe (native api)SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user)SYSTEM_SERVICE_TABLE Table3; // not usedSYSTEM_SERVICE_TABLE Table4; // not used}SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;//NtTerminateProcesstypedef NTSTATUS(__fastcall *NTTERMINATEPROCESS)(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus);NTKERNELAPI UCHAR * PsGetProcessImageFileName(PEPROCESS Process);//SSDT表基址PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;NTTERMINATEPROCESS NtTerminateProcess = NULL;ULONG OldTpVal;UCHAR OldKeBugCheckData[15];// 关闭写保护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);}// BOOL GetKeServiceDescriptorTable64(){PUCHAR SatrtSearchAddress = (PUCHAR)__readmsr(0xC0000082);UCHAR b1 = 0, b2 = 0, b3 = 0;ULONG templong = 0;ULONGLONG addr = 0;for (PUCHAR i = SatrtSearchAddress; i < SatrtSearchAddress + 500; ++i){if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2)){// //4c8d15if (*i == 0x4c && *(i + 1) == 0x8d && *(i + 2) == 0x15){memcpy(&templong, i + 3, 4);KeServiceDescriptorTable = (ULONGLONG)templong + (ULONGLONG)i + 7;return TRUE;}}}return FALSE;}// 获取SSDT函数地址ULONGLONG GetSSDTFuncCurAddr(ULONG id){LONG dwtemp = 0;PULONG ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;dwtemp = ServiceTableBase[id];dwtemp = dwtemp >> 4;return (ULONGLONG)ServiceTableBase + (LONGLONG)dwtemp;}//自己的NtTerminateProcessNTSTATUS __fastcall Fuck_NtTerminateProcess(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus){PEPROCESS Process;NTSTATUS st = ObReferenceObjectByHandle(ProcessHandle, 0, *PsProcessType, KernelMode, &Process, NULL);DbgPrint("Fake_NtTerminateProcess called!");if (NT_SUCCESS(st)){if (!_stricmp(PsGetProcessImageFileName(Process), "calc.exe"))return STATUS_ACCESS_DENIED;elsereturn NtTerminateProcess(ProcessHandle, ExitStatus);}elsereturn STATUS_ACCESS_DENIED;}/*相关解释:1.为什么要二次跳转?WIN64内核里的每个驱动都不在同一个4GB里,4字节的整数只能表示4GB的范围,所以不管怎么修改这个4字节都不会跳到你的代理函数,因为你的驱动不可能跟NTOSKRNL在同一个4GB里面。2.参数的处理:函数地址的低四位存放了函数参数个数减4的数字,如果参数为5,那么低四位的数字为1,如果参数个数小于等于4个,低四位的数位0,可以再WINDBG里面查看到。*///InlineHook_KeBugCheckExVOID FuckKeBugCheckEx(){KIRQL irql;ULONGLONG myfun;// 保存原KeBugCheck前15个字节memcpy(OldKeBugCheckData, KeBugCheckEx, 15);// 48b8a024100480f8ffff mov rax,offset MyDriver1!Fuck_NtTerminateProcess (fffff880`041024a0)// ffe0 jmp raxUCHAR jmp_code[] = "\x48\xB8\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xE0";myfun = (ULONGLONG)Fuck_NtTerminateProcess;//替换成自己的函数地址memcpy(jmp_code + 2, &myfun, 8);irql = WPOFFx64();memset(KeBugCheckEx, 0x90, 15);memcpy(KeBugCheckEx, jmp_code, 12);WPONx64(irql);}ULONG GetOffsetAddress(ULONGLONG FuncAddr){ULONG dwtmp = 0;PULONG ServiceTableBase = NULL;ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase);return dwtmp << 4;}// 开启ssdthookVOID ssdthook(){KIRQL irql;ULONGLONG dwtmp = 0;PULONG ServiceTableBase = NULL;if (!GetKeServiceDescriptorTable64()){return;}NtTerminateProcess = (NTTERMINATEPROCESS)GetSSDTFuncCurAddr(41);//set kebugcheckexFuckKeBugCheckEx();//show new addressServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;OldTpVal = ServiceTableBase[41];//record old offset valueirql = WPOFFx64();ServiceTableBase[41] = GetOffsetAddress((ULONGLONG)KeBugCheckEx);WPONx64(irql);DbgPrint("KeBugCheckEx: %llx", (ULONGLONG)KeBugCheckEx);DbgPrint("New_NtTerminateProcess: %llx", GetSSDTFuncCurAddr(41));}// 关闭ssdthookVOID UnhookSSDT(){KIRQL irql;PULONG ServiceTableBase = NULL;ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;//set valueirql = WPOFFx64();ServiceTableBase[41] = GetOffsetAddress((ULONGLONG)NtTerminateProcess);//OldTpVal;//直接填写这个旧值也行memcpy(KeBugCheckEx, OldKeBugCheckData, 15);WPONx64(irql);//没必要恢复KeBugCheckEx的内容了,反正执行到KeBugCheckEx时已经完蛋了。DbgPrint("NtTerminateProcess: %llx", GetSSDTFuncCurAddr(41));}7. IRP_HOOK
IRP全称是IO请求包,发送到设备驱动程序的大多数请求都打包在IRP中。操作系统组件或驱动程序通过调用IoCallDriver将IRP发送给驱动程序。
大概的执行流程是这样的:IO管理器创建一个IRP来代表一个IO操作,并且将该IRP传递给正确的驱动程序,当此IO操作完成时再处理该请求包。相对的,驱动程序(上层的虚拟设备驱动或者底层的真实设备驱动)接收一个IRP,执行该IRP指定的操作,然后将IRP传回给IO管理器,告诉它,该操作已经完成,或者应该传给另一个驱动以进行进一步处理。
IO管理器可以使用一下三个函数创建IRP。但此时,IRP堆栈还没有被初始化,难以进行拦截。然后使用你可以调用IoGetNextIrpStackLocation函数获得该IRP第一个堆栈单元的指针。然后初始化这个堆栈单元。当初始化完成之后,就可以调用IoCallDriver函数把IRP发送到设备驱动程序了。这就可以在中途进行拦截啦。
根据上述流程,执行IrpHook可以在三个地址进行,第一:在Irp初始化之后,第二:在发往派遣例程过程中,第三,直接修改需要拦截驱动对象派遣例程函数表。
通过查看 IofCallDriver函数发现,在函数开头存在一个jmp指令。ff2500c85480其中ff25是jmp的机器码,后面的机器码是跳转的绝对地址。可以使用InlineHook直接修改跳转地址即可
void HookpIofCallDriver(){ KIRQL oldIrql; ULONG addr = (ULONG)IofCallDriver; //保存原始的IofCallDriver函数地址 __asm { mov eax, addr mov esi, [eax + 2] mov eax, [esi] mov old_piofcalldriver, eax } //引发硬件优先IRQL oldIrql = KeRaiseIrqlToDpcLevel(); __asm { mov eax, cr0 mov oData, eax and eax, 0xffffffff mov cr0, eax mov eax, addr; IofCallDriver mov esi, [eax + 2] mov dword ptr[esi], offset NewpIofCallDriver; 写入新的数据 mov eax, oData;恢复cr0的数据 mov cr0, eax } KeLowerIrql(oldIrql); return;}给一个更详细的链接:https://bbs.pediy.com/thread-60022.htm
8.Object HOOK
NTSTATUS Hook(){ NTSTATUS Status; HANDLE hFile; UNICODE_STRING Name; OBJECT_ATTRIBUTES Attr; IO_STATUS_BLOCK ioStaBlock; PVOID pObject = NULL; RtlInitUnicodeString(&Name, L"\\Device\\HarddiskVolume1\.txt"); InitializeObjectAttributes(&Attr, &Name, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 0, NULL); Status = ZwOpenFile(&hFile, GENERIC_ALL, &Attr, &ioStaBlock, 0, FILE_NON_DIRECTORY_FILE); if (!NT_SUCCESS(Status)) { KdPrint(("File is Null\n")); return Status; } //获取访问对象的句柄 Status = ObReferenceObjectByHandle(hFile, GENERIC_ALL, NULL, KernelMode, &pObject, NULL); if (!NT_SUCCESS(Status)) { KdPrint(("Object is Null\n")); return Status; } KdPrint(("pobject is %08X\n", pObject)); addrs = OBJECT_TO_OBJECT_HEADER(pObject);//获取对象头 //POBJECT_TYPE pType = addrs->Type;//获取对象类型结构 object-10h KdPrint(("pType is %08X\n", pType)); //保存原始地址 //POBJECT_TYPE->OBJECT_TYPE_INITIALIZER.ParseProcedure OldParseProcedure = pType->TypeInfo.ParseProcedure;//获取服务函数原始地址OBJECT_TYPE+9C位置为打开 KdPrint(("OldParseProcedure addrs is %08X\n", OldParseProcedure)); KdPrint(("addrs is %08X\n", addrs)); //MDL去掉内存保护 __asm { cli; mov eax, cr0; and eax, not 10000h; mov cr0, eax; } //hook pType->TypeInfo.ParseProcedure = NewParseProcedure; __asm { mov eax, cr0; or eax, 10000h; mov cr0, eax; sti; } Status = ZwClose(hFile); return Status;}