大家好,又见面了,我是你们的朋友全栈君。
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; //待保护进程的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中文全称为系统服务描述符表,其作用是作为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;
//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中。操作系统组件或驱动程序通过调用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;
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141346.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...