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 MessageBoxA MessageBoxA(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 data head_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 function ori_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 code tmpv = (UINT64)Proxy_ApiAddress; memcpy(jmp_code + 6, &tmpv, 8); //step 4: Fill NOP and hook irql = WPOFFx64(); RtlFillMemory(ApiAddress, *PatchSize, 0x90); memcpy(ApiAddress, jmp_code, 14); WPONx64(irql); //return ori code return 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 used SYSTEM_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)) { // //4c8d15 if (*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; else return NtTerminateProcess(ProcessHandle, ExitStatus); } else return 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 rax UCHAR 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 kebugcheckex FuckKeBugCheckEx(); //show new address ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase; OldTpVal = ServiceTableBase[41]; //record old offset value irql = 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 value irql = 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发送到设备驱动程序了。这就可以在中途进行拦截啦。
- IoBuildAsynchronousFsdRequest 创建异步IRP
- IoBuildSynchronousFsdRequest 创建同步IRP
- IoBuildDeviceIoControlRequest 创建一个同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL请求。
根据上述流程,执行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\\1.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;}
---------------------
作者:liuhaidon1992
来源:CSDN
原文:https://blog.csdn.net/liuhaidon1992/article/details/103874348?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param
版权声明:本文为作者原创文章,转载请附上博文链接!
内容解析By:CSDN,CNBLOG博客文章一键转载插件