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\\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;
}

 

 

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

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

(0)
上一篇 2022年5月26日 上午7:40
下一篇 2022年5月26日 上午8:00


相关推荐

  • goland激活码最新3月[在线序列号]「建议收藏」

    goland激活码最新3月[在线序列号],https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月20日
    126
  • WordPress个人博客美化

    WordPress个人博客美化WordPress 个人博客美化个人博客 https www xiaohuangyr top 有兴趣可以访问一下以下截图是电脑端访问的效果 和手机差很多一 基本概念 1 起因双十一的时候 因为阿里云折扣力度很大 就购买了三年的 ECS 云服务器 又在阿里云购买了一个域名 配置如下 CPU amp 内存 1 核 2GiB 操作系统 Ubuntu20 0464 位实例规格 ecs n4 small 实例规格族 共享计算型随后在云服务器上安装了宝塔面板 然后利用 WordPress 部署搭

    2025年11月3日
    7
  • substring方法截取字符串以及其他方式

    substring方法截取字符串以及其他方式方法一,指定字符,截取字符串,返回字符串数组:Stringstr=“abcd,123,123abc,fij23”;String[]strs=str.split(“,”);方法二,指定索引号,截取字符串:将字符串从索引号为5开始截取,一直到字符串末尾。(索引值从0开始):Stringstr=“asdfghjkl”;str.substring(5);从索引号2开始到索引好4结束(并且不包含索引4截取在内,也就是说实际截取的是2和3号字符):Stringsb=“asdfghj

    2022年5月23日
    164
  • 进制转换python实验五_python进制转换:十进制转二进制的用法「建议收藏」

    进制转换python实验五_python进制转换:十进制转二进制的用法「建议收藏」我们在学习python时候肯定会碰到关于进制转换,其实这是非常简单的,这个就像小学学习数学乘法口诀意义,只要记住转换口诀即可轻松应用,一起来看下具体的操作内容吧~一、python进制转换dec(十进制)—>bin(二进制)dec(十进制)—>oct(八进制)dec(十进制)—>hex(十六进制)二、十进制我们所熟知的十进制,其实是从0开始,数到9之后,就跳到10,…

    2022年5月19日
    50
  • leetcode 颜色分类_LEETCODE

    leetcode 颜色分类_LEETCODE给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。示例 1:输入:nums = [2,0,2,1,1,0]输出:[0,0,1,1,2,2]示例 2:输入:nums = [2,0,1]输出:[0,1,2]示例 3:输入:nums = [0]输出:[0]示例 4:输入:nums = [1]输出:[1] 提示:n == num

    2022年8月9日
    13
  • SSDT详解

    SSDT详解本文对于 SSDT 的原理和作用进行细致入微的解释和说明 并且配以大量实例 相信您定会有所收获 什么是 SSDT 什么是 SSDT 自然 这个是我必须回答的问题 不过在此之前 请你打开命令行 cmd exe 窗口 并输入 dir 并回车 好了 列出了当前目录下的所有文件和子目录 那么 以程序员的视角来看 整个过程应该是这样的 由用户输入 dir 命令 cmd exe 获取用户输入

    2026年3月17日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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