8种HOOK技术[通俗易懂]

8种HOOK技术[通俗易懂]1.IAT_HOOKIAT是程序中存储导入函数地址的数据结构,如果HOOK了导入函数地址。就可以在函数调用的时候,将函数流程HOOK到我们指定的流程。但是我个人觉得这种方式最好要结合DLL注入的方式,如果单纯的使用HOOK,那么就需要将需要执行的操作的shellcode写入目标进程,如果操作复杂,可能需要的shellcode量特别大,所以我们需要借助DLL注入,这样就将我们需要执行的代码写入…

大家好,又见面了,我是你们的朋友全栈君。

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;			//待保护进程的eprocess
ULONG 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 hook
64位系统没有了上面这样的方便之处,因此必须有一种新的策略。
64位的跳转,可用两种方法,下面两个方法都是绝对跳转指令,第一个影响rax寄存器,可能需要先保存原来的rax的值:
1,
48 b8 ef cd ab 89 67 45 23 01   mov rax, 0x0123456789abcdef
ff e0                           jmp rax

2,
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:
};
//Hook
void 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);
}
//多线程Hook
bool 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;

//NtTerminateProcess
typedef 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;
}

//自己的NtTerminateProcess
NTSTATUS __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_KeBugCheckEx
VOID 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;
}

// 开启ssdthook
VOID 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));
}

// 关闭ssdthook
VOID 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\.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;
}

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141346.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • nginx https配置后无法访问,可能防火墙在捣鬼

    nginx https配置后无法访问,可能防火墙在捣鬼

    2021年10月13日
  • P2P技术原理及应用[通俗易懂]

    P2P技术原理及应用[通俗易懂] P2P技术原理及应用    作者:金海廖小飞 摘要:对等网络(P2P)有3种主要的组织结构:分布式哈希表(DHT)结构、树形结构、网状结构。P2P技术已经延伸到几乎所有的网络应用领域,如分布式科学计算、文件共享、流媒体直播与点播、语音通信及在线游戏支撑平台等方面。现在人们已经开始将重心转入到覆盖层网络的节点延时聚集研究、覆盖网之间(Inter-Overlay)优化研究、P2P支撑平…

  • snmptrap配置_snmp服务端ip和端口

    snmptrap配置_snmp服务端ip和端口一、trap的用途TRAP是提供从代理进程到管理站的异步报告机制。为了使管理站能够及时而又有效地对被管理设备进行监控,同时又不过分增加网络的通信负载,必须使用陷入(TRAP)制导的轮讯过程。代理进程负责在必要时向管理站报告异常事件,得到异常事件的报告后,管理站可以查询有关的代理,以便得到更具体的信息,对事件的原因做进一步的分析二、trap的工作流程1、agent端: A

  • nginx的负载均衡算法有哪些_负载均衡策略

    nginx的负载均衡算法有哪些_负载均衡策略1、轮询(默认)特点:每个请求按时间顺序逐一分配到不同的后端服务器处理。适用业务场景:后端服务器硬件性能配置完全一致,业务无特殊要求时适用。upstreamtomcats{ server192.168.1.173:8080; server192.168.1.175:8080;}2、加权轮询特点:指定轮询几率,weight值(权重)和访问比例成正比,用户请求按权重比例分配。适用业务场景:后端服务器硬件性能处理能力不平均的情形。upstreamtomcats{ server

  • 使用一个运放滤三次谐波 二阶有源带通滤波器的电路设计及波形效果

    使用一个运放滤三次谐波 二阶有源带通滤波器的电路设计及波形效果本文主要讲无限增益多路反馈有源带通滤波器的实现,工程实作,非理论知识,关于其他方法简略提,不做细究

  • Tess4j maven demo[通俗易懂]

    Tess4j maven demo[通俗易懂]tess4j实现文字识别Demo,下面为内容实现源码,内容仅为一个demo,demo下载地址:tess4jDemopublicclassTess4JTest{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(newLoggHelper().toString());staticfinaldo…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号