8种HOOK技术

1.IAT_HOOK

 IAT是程序中存储导入函数地址的数据结构,如果HOOK了导入函数地址。就可以在函数调用的时候,将函数流程HOOK到我们指定的流程。但是我个人觉得这种方式最好要结合DLL注入的方式,如果单纯的使用HOOK,那么就需要将需要执行的操作的shellcode写入目标进程,如果操作复杂,可能需要的shellcode量特别大,所以我们需要借助DLL注入,这样就将我们需要执行的代码写入进程内部,在HOOKDetour函数只需要实现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类似,都是通过修改函数地址数据从而HOOKEAT_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中文全称为系统服务描述符表,其作用是作为R3R0层的通道,将用户态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中。操作系统组件或驱动程序通过调用IoCallDriverIRP发送给驱动程序。

大概的执行流程是这样的: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其中ff25jmp的机器码,后面的机器码是跳转的绝对地址。可以使用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博客文章一键转载插件

猜你喜欢

转载自blog.csdn.net/weixin_41875267/article/details/108467151