针对Windows注入和Hook的

1. UserMode 远程线程注入代码

将DLL名称注入到目标进程的内存中,随后通过CreateRemoteThread创建远程线程,执行函数LoadLibrary调用相关DLL

[代码示例]:

// UserMode远程线程注入代码
// hProcess 目标进程句柄, DllName 注入的DLL路径
BOOL HookCodeClass::RemoteThreadInjectDll(HANDLE hProcess, PVOID DllName)
{
	DWORD Pid = NULL;
	DWORD TrueLen = NULL;
	HANDLE localhProcess = hProcess;
	PVOID localDllName = DllName;

	// 在远程进程中申请空间
	LPVOID DllSpace = VirtualAllocEx(localhProcess/*申请内存所在的进程句柄*/,
		NULL/*保留页面的内存地址, 一般用NULL自动分配*/, 4096/*分配内存的大小*/,
		MEM_COMMIT/*为特定的页面区域分配内存中或磁盘中的页面文件中的物理存储*/,
		PAGE_EXECUTE_READWRITE/*内存模块可读可写可执行*/);
	if (NULL == DllSpace)
	{
		MessageBoxA(0, "申请内存空间失败", "提示", MB_OK);
		return FALSE;
	}

	// 注入线程函数参数
	BOOL writeFlag = WriteProcessMemory(localhProcess/*由OpenProcess返回的进程句柄*/, 
		DllSpace/*要写的内存首地址,在写入之前,此函数将先检查目标地址是否可用,并能容纳待写入的数据。*/,
		localDllName/*要写入的数据首地址*/, 
		MAX_PATH/*要写入的字节数,非零值代表成功。*/,
		&TrueLen/*实际写入的长度*/);
	if (!writeFlag)
	{
		// 写入失败
		MessageBox(0, "注入DLL名称失败", "提示", MB_OK);
		return FALSE;
	}

	// 远程线程创建,设置线程回调函数的地址为 LoadLibraryW
	HANDLE longthread =
		CreateRemoteThread(localhProcess/*句柄*/, NULL/*安全属性*/, 0/*栈大小*/,
		(LPTHREAD_START_ROUTINE)LoadLibraryW/*线程处理函数, 此函数必须存在目标进程中*/,
			DllSpace/*参数*/, NULL/*默认创建后的状态*/, NULL/*线程ID*/);
	if (NULL == longthread)
	{
		MessageBox(0, "远程线程创建失败", "提示", MB_OK);
		return FALSE;
	}

	// 获取线程函数退出码, 即 LoadLibraryW 的返回值, DLL的首地址
	DWORD localExitCode = NULL;
	GetExitCodeThread(longthread, &localExitCode);
	HMODULE hMode = (HMODULE)localExitCode;

	// 释放为DLL名称申请的内存空间
	if (NULL == VirtualFreeEx(hProcess/*目标进程的句柄。该句柄必须拥有 PROCESS_VM_OPERATION 权限*/,
		DllSpace/*指向要释放的虚拟内存空间首地址的指针*/,
		4096/*虚拟内存空间的字节数*/,
		MEM_DECOMMIT/*内存空间不可用,内存页还将存在*/))
	{
		MessageBox(0, "释放为DLL名字申请的内存空间失败", "提示", MB_OK);
		return FALSE;
	}

	return TRUE;
}

2. UserMode APC注入代码

使用线程APC回调机制,利用方式和远程注入差不多

// UserModeAPC注入代码
// hProcess 目标进程句柄, DllName 注入的DLL路径
BOOL HookCodeClass::ApcInjectDll(HANDLE hProcess, PVOID DllName)
{
	ULONG ProcessId = 0;
	HANDLE hThread = NULL;
	ULONG ThreadIdsLen = 0;
	PVOID pBaseAddr = NULL;
	ULONG DllNameLen = strlen((char *)DllName) + 1;
	ULONG dwRet = 0;
	PVOID pLoadLibraryA = NULL;

	// 获取进程 PID
	ProcessId = GetProcessId(hProcess);
	if (0 >= ProcessId)
	{
		printf("ProcessId Error: %d\n", ProcessId);
		return FALSE;
	}

	// 根据 PID 获取获取所有相应的线程 TID
	if (!GetAllThreadIdByProcessId(ProcessId, ThreadIds, &ThreadIdsLen))
	{
		printf("GetAllThreadIdByProcessId Error: %x\n", GetLastError());
		return FALSE;
	}

	// 在注入的进程中申请空间
	pBaseAddr =
		VirtualAllocEx(hProcess, NULL, DllNameLen, MEM_COMMIT | MEM_RESERVE,
			PAGE_EXECUTE_READWRITE);
	if (pBaseAddr == NULL)
	{
		printf("VirtualAllocEx Error: %x\n", GetLastError());
		return FALSE;
	}

	// 向申请的空间中写入 Dll 路径数据
	WriteProcessMemory(hProcess, pBaseAddr, DllName, DllNameLen, &dwRet);
	if (DllNameLen != dwRet)
	{
		printf("WriteProcessMemory Error: %x\n", GetLastError());
		return FALSE;
	}

	// 获取 LoadLibraryA 地址
	pLoadLibraryA =
		GetProcAddress(GetModuleHandleA((char *)"kernel32.dll"), "LoadLibraryA");
	if (pLoadLibraryA == NULL)
	{
		printf("GetProcAddress Error: %x\n", GetLastError());
		return FALSE;
	}

	// 遍历线程, 插入APC
	for (ULONG i = 0; i < ThreadIdsLen; i++)
	{
		// 打开线程
		hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, ThreadIds[i]);
		if (NULL != hThread)
		{
			// 插入 APC, LoadLibraryA 作为回调函数地址, Dll 路径数据作为参数
			if (!QueueUserAPC((PAPCFUNC)pLoadLibraryA, hThread, (ULONG_PTR)pBaseAddr))
			{
				printf("QueueUserAPC Error: %x\n", GetLastError());
			}
			// 关闭线程句柄
			CloseHandle(hThread);
			hThread = NULL;
			printf("插入 APC 成功!\n");
		}
	}

	return TRUE;
}

3. IAT Hook

这里的 IAT Hook 配合远程现场注入,随后在目标程序中运行 DLL 代码

任何PE文件在调用其他模块时,都会存储一张以上的导入表,其中包括使用模块的函数名和地址(要求函数为名称导出),通过调换指定函数地址达到自己的目的
IAT表的局限性:只能修改IAT表中存在的函数,若是自定义函数、动态导入函数指针调用的将无法修改
BOOL WINAPI MyNtQueryInformationProcess(_In_ HANDLE hProcess, _In_ UINT uExitCode, OUT PVOID ProcessInformation, IN ULONG ProcessInformationLength, OUT PULONG ReturnLength OPTIONAL)
{
    //function();
    return 0;
}

VOID MyHookFun()
{
    // 获取当前进程指定函数地址
    DWORD funBase = (DWORD)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtQueryInformationProcess");
    //DWORD funBasezW = (DWORD)GetProcAddress(LoadLibrary(L"ntdll.dll"), "ZwSetInformationThread");
    
    // 获取本模块基址
    DWORD ImageBase = (DWORD)GetModuleHandle(0);
    
    // 获取DOS头
    IMAGE_DOS_HEADER * dosHeader = (IMAGE_DOS_HEADER *)ImageBase;
    IMAGE_NT_HEADERS * ntHeader = 0;
    ntHeader = (PIMAGE_NT_HEADERS)(ImageBase + dosHeader->e_lfanew);
    IMAGE_IMPORT_DESCRIPTOR * importTable = (IMAGE_IMPORT_DESCRIPTOR *)(ntHeader->OptionalHeader.DataDirectory[1].VirtualAddress + ImageBase);
    IMAGE_THUNK_DATA32 * iat = 0;
    DWORD page = 0;
    for (; importTable->Name; importTable++)
    {
        // 如果找到指定DLL
        if (strcmp((char *)(ImageBase + importTable->Name), "ntdll.dll") == 0)
        {
            break;
        }
    }

    if (importTable->Name != 0)
    {
        iat = (IMAGE_THUNK_DATA32 *)(importTable->FirstThunk + ImageBase);
        // 如果没有找到指定的函数iat++
        while (iat->u1.Function != funBase)
        {
        iat++;
        }
        // 如果找到
        if (iat->u1.Function)
        {
            // 修改指定函数地址为我的函数地址
            VirtualProtect(&iat->u1.Function, 4, PAGE_EXECUTE_READWRITE, &page);
            iat->u1.Function = (DWORD)MyNtQueryInformationProcess;
            VirtualProtect(&iat->u1.Function, 4, page, &page);
        }
    }

    return;
}

4. 系统消息钩子

消息钩子是Windows操作系统提供的机制

相关函数如下:

// 设置钩子
HHOOK SetWindowsHookExA(
    int idHook, // 钩子类型
    HOOKPROC lpfn, // 钩子函数
    HINSTANCE hmod, // DLL的句柄,包含lpfn参数指向的钩子过程。
    DWORD dwThreadId // 欲钩住的线程(为0则不指定,全局)
);

// 为钩子链中的下一个子程序设置钩子
LRESULT CallNextHookEx(
    HHOOK hhk, // 钩子句柄
    int nCode, // 钩子事件代码
    WPARAM wParam,
    LPARAM lParam
);

// 卸载钩子
BOOL UnhookWindowsHookEx(
    HHOOK hhk // 钩子句柄
); 

5. KernelMode APC注入代码

逆向 NtQueueApcThread   可以发现,其是调用了 KeInitializeApc  初始化 KAPC 数据结构体, 随后调用 KeInsertQueueApc 挂入目标线程的 APC 队列

 

这里我们用更底层的 KiInsertQueueApc 插入 APC 强制暂停进程运行(其实就是懒不想自己构建 KAPC)

这在之前我们需要了解, 通过逆向 KeSuspendThread 会发现这个函数调用了KiInsertQueueApc, 而插入的 APC 是 KTHREAD->SuspendApc, 所以代码实现如下:

#include <ntifs.h>

typedef VOID (FASTCALL *typedef_KiInsertQueueApc)(
	IN PKAPC Apc,
	IN KPRIORITY Increment
);
typedef_KiInsertQueueApc KiInsertQueueApc;

VOID KermelModeSuspendThread(IN PETHREAD Thread)
{
	// 获取 SuspendApc
	PKAPC pApc;
	pApc = (PKAPC)((ULONG)Thread + 0x16C);

	if (MmIsAddressValid(pApc) && (pApc->Thread == (PKTHREAD)Thread))
	{
		KiInsertQueueApc(pApc, 0);
	}
}

VOID GetKiInsertQueueApc()
{
	// 获取 KeInsertQueueApc 函数
	ULONG lKiInsertQueueApcAddress = 0;
	ULONG lKeInsertQueueApcAddress = 0;
	UNICODE_STRING lFunctionName;
	RtlInitUnicodeString(&lFunctionName, L"KeInsertQueueApc");
	lKeInsertQueueApcAddress = MmGetSystemRoutineAddress(&lFunctionName);

	// 获取 KiInsertQueueApc 函数, (WinXipSp3 上的偏移是 0x319E)
	lKiInsertQueueApcAddress = lKeInsertQueueApcAddress + 0x319E;
	KiInsertQueueApc = (typedef_KiInsertQueueApc)lKiInsertQueueApcAddress;

	KdPrint(("KiInsertQueueApc: [0x%X]\n", KiInsertQueueApc));
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
	KdPrint(("驱动卸载\r\n"));
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING Reg)
{
	KdPrint(("驱动加载\r\n"));	
	DriverObject->DriverUnload = DriverUnload;
	NTSTATUS status = STATUS_SUCCESS;
	
	GetKiInsertQueueApc();
	KermelModeSuspendThread((PETHREAD)0x813ed748); // 目标线程 PETHREAD

	return status;
}

运行驱动后成功暂停目标程序, 而且 PcHunter 无法结束程序

6. 64位inlinehook

https://bbs.pediy.com/thread-257490.htm

7. 对内核SSDT的Hook

X86 和 X64 的 Hook 方式还是有许多不一样的

7.1 X86 Hook SSDT 实现进程保护

#include <Ntifs.h>
#include <WinDef.h>
// 32 位的 SSDT HOOK
// 未声明函数
// 获取 EPROCESS 结构中的进程名称
PCHAR PsGetProcessImageFileName(PEPROCESS Process);
// 定义函数指针
typedef NTSTATUS (*pNtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS  ExitStatus);
pNtTerminateProcess OldNtTerminateProcess;
// 定义系统服务表结构体
typedef struct _SERVICETABLE
{
       PULONG ServiceTableAddr; // 函数地址表的地址 (4字节成员)
       PULONG Count; // 当前系统服务表被调用的次数
       ULONG ServiceLimit; // 系统服务表中函数的个数
       PUCHAR ArgMentTable; // 系统参数表地址 (1字节成员)
}SERVICETABLE, *PSERVICETABLE;
#ifdef _X86_
       // 32 位下 SSDT 是导出的
       extern PSERVICETABLE KeServiceDescriptorTable; // 获取 SSDT 表的地址
#endif
KIRQL irQl;
BOOL Flas = FALSE;
// 修改Cr0寄存器, 去除写保护(内存保护机制)
KIRQL RemovWP()
{
       // (PASSIVE_LEVEL)提升 IRQL 等级为DISPATCH_LEVEL,并返回旧的 IRQL
       // 需要一个高的IRQL才能修改
       irQl = KeRaiseIrqlToDpcLevel();
       ULONG_PTR cr0 = __readcr0(); // 内联函数:读取Cr0寄存器的值, 相当于: mov eax,   cr0;
       // 将第16位(WP位)清0,消除写保护
       cr0 &= ~0x10000; // ~ 按位取反
       _disable(); // 清除中断标记, 相当于 cli 指令,修改 IF标志位
       __writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
       return irQl;
}
// 复原Cr0寄存器
KIRQL UndoWP()
{
       ULONG_PTR cr0 = __readcr0();
       cr0 |= 0x10000; // WP复原为1
       _disable(); // 清除中断标记, 相当于 cli 指令,清空 IF标志位
       __writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
       // 恢复IRQL等级
       KeLowerIrql(irQl);
       return irQl;
}
NTSTATUS HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
       PEPROCESS Process;
       NTSTATUS status;
       // 获取内核对象
       status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_ALL_ACCESS,  *PsProcessType, KernelMode, &Process, NULL);
       if (NT_SUCCESS(status))
       {
              if (strcmp(PsGetProcessImageFileName(Process), "calc.exe") == 0) //  如果为保护进程
              {
                     if (PsGetCurrentProcess() != Process) // 如果不为自己进程
                     {
                           return STATUS_ACCESS_DENIED; // 返回删除成功(拦截)
                     }
              }
       }
       return OldNtTerminateProcess(ProcessHandle, ExitStatus); // 调用原来的函数
}
// Hook 指定函数
BOOL HookSSDTCall(ULONG SsdtIndex)
{
       DbgPrint("进入HookSSDTCall\n");
       NTSTATUS status = STATUS_SUCCESS;
       ULONG index;
       if (Flas == FALSE)
       {
              Flas = TRUE;
              // 保存原来函数地址
              OldNtTerminateProcess =  (pNtTerminateProcess)(KeServiceDescriptorTable->ServiceTableAddr[SsdtIndex]);
              // 关闭写保护
              RemovWP();
              (pNtTerminateProcess)KeServiceDescriptorTable->ServiceTableAddr[SsdtIndex] =  HookNtTerminateProcess;// HOOK
              // 开启写保护
              UndoWP();
       }
       else
       {
              // 关闭写保护
              RemovWP();
              // 保存原来函数地址
              (pNtTerminateProcess)KeServiceDescriptorTable->ServiceTableAddr[SsdtIndex] =  OldNtTerminateProcess;// 修改回去
              // 开启写保护
              UndoWP();
       }
       DbgPrint("离开HookSSDTCall\n");
       return TRUE;
}
VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
       // 修复函数地址
       HookSSDTCall(257);
       DbgPrint("卸载驱动\n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegeditPath)
{
       DbgPrint("启动驱动\n");
       NTSTATUS status = STATUS_SUCCESS;
       DriverObject->DriverUnload = Unload;
       HookSSDTCall(257);
       return status;
}

7.1.2 另一种 X86 Hook SSDT 的方式

就是自己再创建一份 SSDT表, 并且将线程中的服务表地址(KTHREAD.ServiceTable )指向我们的SSDT表

代码实现如下:

// 头文件
#pragma once
#ifndef SSDT
#define SSDT
#include <Ntifs.h>
#include <windef.h>
// 系统服务表结构体
typedef struct _SystemServiceTable
{
       PULONG FunctionsAddrTable;
       ULONG CallCount;
       ULONG FunctionsLimit;
       PUCHAR FunctionsArgsAddrTable;
}SystemServiceTable, *PSystemServiceTable;
// SSDT表结构体
typedef struct _SSDT_TABLE
{
       SystemServiceTable serviceTableNt;
       SystemServiceTable serviceTableWin32k;
       SystemServiceTable Null3;
       SystemServiceTable Null4;
}SSDT_TABLE, *PSSDT_TABLE;
extern PSSDT_TABLE KeServiceDescriptorTable; // 获取 XP导出的内核服务表
PSSDT_TABLE CopyServiceDescriptorTable; // 我们额外拷贝的一份SSDT表
// hook线程句柄
HANDLE hookThread;
// 用于控制 Hook 线程回调
BOOLEAN IsFlags;
#endif
// 驱动代码
#include "SSDT.h"
PCHAR PsGetProcessImageFileName(PEPROCESS Process);
// 获取影子表地址
ULONG GetWin32KTable()
{
       ULONG KeServiceDescriptorTableShadow =  *(PULONG)&KeServiceDescriptorTable  - 0x40;
       return KeServiceDescriptorTableShadow;
}
// 1. 获取指定进程 EPROCESS
// 2. 获取指定进程的线程链表
// 3. 获取线程SSDT地址
// 4. Copy SSDT表
// 5. 将进程下的所有线程指向我们 SSDT
// 6. 判断是否为 UI 线程,因为 UI 线程切换 PsConvertToGuiThread 函数内部 0x3A处 会做一个比较
// 判断当前线程的 ServiceTable 是否等于 KeServiceDescriptorTable , 所以我们需要修改
// 遍历返回目标进程的 EPROCESS
ULONG EnumGetTargetProcess(PCHAR ProcessName)
{
       // KPCR.PrcbData.CurrentThread.ApcState.Process 存储的是当前进程的 EPROCESS  地址
       ULONG CurrentEprocess = 0;
       __asm
       {
              mov eax, dword ptr fs:[0x124]; // 获取 KPCR.PrcbData.CurrentThread
              mov ecx, dword ptr[eax + 0x44]; // 获取  CurrentThread.ApcState.Proces
              mov CurrentEprocess, ecx;
       }
       // _EPROCESS.ActiveProcessLinks 存储的是当前系统活动进程的链表
       PLIST_ENTRY ProcessEntry = (PLIST_ENTRY)(CurrentEprocess + 0x88); //  ActiveProcessLinks
       PLIST_ENTRY CurrentProcessEntry = ProcessEntry;
       while (CurrentProcessEntry->Flink != ProcessEntry)
       {
              // 循环进程链表获取目标进程
              // 获取当前进程 EPROCESS 的首地址
              ULONG NextEprocess = (ULONG)CurrentProcessEntry - 0x88;
              // 获取当前进程的名称 _EPROCESS.ImageFileName
              PCHAR NextProcessName = (PCHAR)(NextEprocess + 0x174);
              if (_strnicmp(NextProcessName, ProcessName, strlen(ProcessName)) ==  0)
              {
                     // 如果为指定进程,返回 EPROCESS 的首地址
                     return NextEprocess;
              }
              // 否则继续循环
              CurrentProcessEntry = CurrentProcessEntry->Flink;
       }
       // 到达这里表示没有找到,指定进程
       return 0;
}
// 获取指定进程的活动线程链表
PLIST_ENTRY GetThreadEntry(ULONG Eprocess)
{
       // _EPROCESS.ThreadListHead 是当前进程的活动线程链表
       return (PLIST_ENTRY)(*(PULONG)(Eprocess + 0x190));
}
// 修改 PsConvertToGuiThread
VOID ModifyPsConvertToGuiThread()
{
       // PsConvertToGuiThread 为未导出函数
       ULONG PsConvertToGuiThread = (ULONG)0x805c3720;
       // 805c3764 740a  je nt!PsConvertToGuiThread + 0x50 (805c3770)
       // 变为 805c3764 EB0a  jmp nt!PsConvertToGuiThread + 0x50 (805c3770)
       *(PBYTE)(PsConvertToGuiThread + 0x44) = (BYTE)0xEB;
}
// 将其进程的线程指向我们的 SSDT
NTSTATUS HookALLThreadServiceTable(PLIST_ENTRY ThreadEntry)
{
       //KdBreakPoint();
       NTSTATUS status = STATUS_SUCCESS;
       DbgPrint("HookALLThreadServiceTable进入\n");
       // 获取进程的线程链表
       PLIST_ENTRY CurrentProcessThreadEntry = ThreadEntry;
       // 遍历线程链表
       PLIST_ENTRY NextProcessThreadEntry = CurrentProcessThreadEntry;
       while (NextProcessThreadEntry->Flink != CurrentProcessThreadEntry)
       {
              // Hook 当前进程中的所有线程
              // 获取当前 ETHREAD 首地址
              ULONG NextEThread = (ULONG)NextProcessThreadEntry - 0x22c;
              if (NextEThread < 0x80000000) goto NextListEntry;
              if (*(PULONG)(NextEThread + 0xE0) ==  (ULONG)CopyServiceDescriptorTable) goto NextListEntry;
              // 修改 _KTHREAD.ServiceTable 为我们的 SSDT
              *(PULONG)(NextEThread + 0xE0) = (ULONG)CopyServiceDescriptorTable;
       NextListEntry:
              NextProcessThreadEntry = NextProcessThreadEntry->Flink;
       }
       DbgPrint("HookALLThreadServiceTable退出\n");
       return status;
}
// Copy 一份 SSDT 表
NTSTATUS InitCopySSDT()
{
       NTSTATUS status = STATUS_SUCCESS;
       // 开辟 SSDT 表空间
       CopyServiceDescriptorTable = ExAllocatePool(NonPagedPool,  sizeof(SSDT_TABLE));
       if (CopyServiceDescriptorTable == NULL)
       {
              DbgPrint("ExAllocatePoolOne Error\n");
              return STATUS_UNSUCCESSFUL;
       }
       memset(CopyServiceDescriptorTable, 0, sizeof(SSDT_TABLE));
       // 开辟一段空间用来存储 函数地址表
       CopyServiceDescriptorTable->serviceTableNt.FunctionsAddrTable =
              ExAllocatePool(NonPagedPool,  KeServiceDescriptorTable->serviceTableNt.FunctionsLimit * 4);
       if (CopyServiceDescriptorTable->serviceTableNt.FunctionsAddrTable == NULL)
       {
              ExFreePool(CopyServiceDescriptorTable);
              DbgPrint("ExAllocatePoolTwo Error\n");
              return STATUS_UNSUCCESSFUL;
       }
       memset(CopyServiceDescriptorTable->serviceTableNt.FunctionsAddrTable, 0,  KeServiceDescriptorTable->serviceTableNt.FunctionsLimit * 4);
       // 复制函数地址表内容
       RtlMoveMemory(CopyServiceDescriptorTable->serviceTableNt.FunctionsAddrTable,  KeServiceDescriptorTable->serviceTableNt.FunctionsAddrTable,
              KeServiceDescriptorTable->serviceTableNt.FunctionsLimit * 4);
       // 复制函数个数、函数参数表地址
       CopyServiceDescriptorTable->serviceTableNt.FunctionsLimit =
              KeServiceDescriptorTable->serviceTableNt.FunctionsLimit;
       CopyServiceDescriptorTable->serviceTableNt.FunctionsArgsAddrTable =
              KeServiceDescriptorTable->serviceTableNt.FunctionsArgsAddrTable;
       // 复制影子表
       // 开辟一段空间用来存储 函数地址表
       PSSDT_TABLE KeServiceDescriptorTableShadow =  (PSSDT_TABLE)(GetWin32KTable()); // 获取Win32K服务表地址
       // 复制函数地址表内容
       CopyServiceDescriptorTable->serviceTableWin32k.FunctionsAddrTable =
              KeServiceDescriptorTableShadow->serviceTableWin32k.FunctionsAddrTable;
       // 复制函数个数、函数参数表地址
       CopyServiceDescriptorTable->serviceTableWin32k.FunctionsLimit =
              KeServiceDescriptorTableShadow->serviceTableWin32k.FunctionsLimit;
       CopyServiceDescriptorTable->serviceTableWin32k.FunctionsArgsAddrTable =
              KeServiceDescriptorTableShadow->serviceTableWin32k.FunctionsArgsAddrTable;
       return status;
}
//卸载驱动的时候,也修复 HOOK 的线程
NTSTATUS FixALLThreadServiceTable(PLIST_ENTRY ThreadEntry)
{
       NTSTATUS status = STATUS_SUCCESS;
       DbgPrint("FixALLThreadServiceTable进入\n");
       // 获取进程的线程链表
       PLIST_ENTRY CurrentProcessThreadEntry = ThreadEntry;
       // 遍历线程链表
       PLIST_ENTRY NextProcessThreadEntry = CurrentProcessThreadEntry;
       PSSDT_TABLE KeServiceDescriptorTableShadow =  (PSSDT_TABLE)(GetWin32KTable());
       while (NextProcessThreadEntry->Flink != CurrentProcessThreadEntry)
       {
              // Hook 当前进程中的所有线程
              // 获取当前 ETHREAD 首地址
              ULONG NextEThread = (ULONG)NextProcessThreadEntry - 0x22c;
              // 修改 _KTHREAD.ServiceTable 一律替换为 SSSDT
              *(PULONG)(NextEThread + 0xE0) =  (ULONG)KeServiceDescriptorTableShadow;
              NextProcessThreadEntry = NextProcessThreadEntry->Flink;
       }
       DbgPrint("FixALLThreadServiceTable退出\n");
       return status;
}
//后续工作, 考虑到线程会有创建和销毁,我们需要不停的修改线程 ServiceTable
VOID ThreadHookRoutine(_In_ PVOID StartContext)
{
       IsFlags = TRUE; //激活标志
       LARGE_INTEGER timeOut = RtlConvertLongToLargeInteger(-30 * 1000 * 1000);
       while (IsFlags)
       {
              DbgPrint("循环Hook中\n");
              KeDelayExecutionThread(KernelMode, FALSE, &timeOut);
              HookALLThreadServiceTable((PLIST_ENTRY)StartContext);
       }
       DbgPrint("循环Hook结束!!!\n");
       // 一旦停止Hook,标志开始复原系统环境
       // 复原指定进程的线程
       FixALLThreadServiceTable((PLIST_ENTRY)StartContext);
       ZwClose(hookThread);
}
VOID Unload(PDRIVER_OBJECT DriverObject)
{
       DbgPrint("卸载驱动\n");
       //IsFlags = FALSE; // 禁用标志, 开始复原
       //// 等待一段时间
       //LARGE_INTEGER timeOut = RtlConvertLongToLargeInteger(-30 * 1000 * 1000);
       //KeDelayExecutionThread(KernelMode, FALSE, &timeOut);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING Regedit)
{
       DbgPrint("加载驱动\n");
       DriverObject->DriverUnload = Unload;
       NTSTATUS status = STATUS_SUCCESS;
       //KdBreakPoint();
       // 寻找目标进程 EPROCESS
       ULONG Process = EnumGetTargetProcess("Dbgview.exe");
       if (!Process)
       {
              return STATUS_UNSUCCESSFUL;
       }
       // 拷贝一份 SSDT 表
       InitCopySSDT();
       // 获取进程线程活动链表
       PLIST_ENTRY ThreadEntry = GetThreadEntry(Process);
       ModifyPsConvertToGuiThread();
       // Hook 进程的线程
       HookALLThreadServiceTable(ThreadEntry);
       // 启动一个线程循环HOOK _KTHREAD.ServiceTable
       PsCreateSystemThread(&hookThread, THREAD_ALL_ACCESS, NULL, NULL, NULL,  ThreadHookRoutine, (PVOID)ThreadEntry);
       DbgPrint("%p\n", CopyServiceDescriptorTable);
       return status;
}

7.2 X64 Hook SSDT 实现进程保护

64位和32位的区别:
都有写保护,但是 64 位下因为微软的特殊处理导致可以HOOK的范围只有512M大小的地址空间, 超出512M地址空间的会被无效处理掉,也就导致普通的HOOK会被无效处理
所以我们需要借助一个跳板,也就是 KeBugCheckEx 函数(其是在512M有效的地址空间中),通过InlineHook让操作系统调用这个 KeBugCheckEx 函数时会跳向我们的函数代码
针对我们的想要Hook的函数都可以用被修改的 KeBugCheckEx 函数地址替代
#include <Ntifs.h>
#include <windef.h>
// 64位的 SSDT 表HOOK
// 定义系统服务表结构体
typedef struct _SERVICETABLE
{
       PULONG ServiceTableAddr; // 函数地址表的地址 (4字节成员)
       PULONG Count; // 当前系统服务表被调用的次数
       ULONG ServiceLimit; // 系统服务表中函数的个数
       PUCHAR ArgMentTable; // 系统参数表地址 (1字节成员)
}SERVICETABLE, *PSERVICETABLE;
// 定义函数指针
typedef NTSTATUS(*pNtTerminateProcess)(HANDLE ProcessHandle, NTSTATUS ExitStatus);
pNtTerminateProcess OldNtTerminateProcess;
// 未声明函数
// 获取 EPROCESS 结构中的进程名称
PCHAR PsGetProcessImageFileName(PEPROCESS Process);
PSERVICETABLE KeServiceDescriptorTable = NULL;
KIRQL irQl;
// 修改Cr0寄存器, 去除写保护(内存保护机制)
KIRQL RemovWP()
{
       DbgPrint("RemovWP\n");
       // (PASSIVE_LEVEL)提升 IRQL 等级为DISPATCH_LEVEL,并返回旧的 IRQL
       // 需要一个高的IRQL才能修改
       irQl = KeRaiseIrqlToDpcLevel();
       ULONG_PTR cr0 = __readcr0(); // 内联函数:读取Cr0寄存器的值, 相当于: mov eax,   cr0;
       // 将第16位(WP位)清0,消除写保护
       cr0 &= ~0x10000; // ~ 按位取反
       _disable(); // 清除中断标记, 相当于 cli 指令,修改 IF标志位
       __writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
       return irQl;
}
// 复原Cr0寄存器
KIRQL UndoWP()
{
       DbgPrint("UndoWP\n");
       ULONG_PTR cr0 = __readcr0();
       cr0 |= 0x10000; // WP复原为1
       _disable(); // 清除中断标记, 相当于 cli 指令,清空 IF标志位
       __writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
       // 恢复IRQL等级
       KeLowerIrql(irQl);
       return irQl;
}
// 我们函数
NTSTATUS HookNtTerminateProcess(HANDLE ProcessHandle, NTSTATUS ExitStatus)
{
       DbgPrint("进入 HookNtTerminateProcess\n");
       PEPROCESS Process;
       NTSTATUS status;
       // 获取内核对象
       status = ObReferenceObjectByHandle(ProcessHandle, PROCESS_ALL_ACCESS,  *PsProcessType, KernelMode, &Process, NULL);
       if (NT_SUCCESS(status))
       {
              if (strcmp(PsGetProcessImageFileName(Process), "calc.exe") == 0) //  如果为保护进程
              {
                     if (PsGetCurrentProcess() != Process) // 如果不为自己进程
                     {
                           return STATUS_ACCESS_DENIED; // 返回删除成功(拦截)
                     }
              }
       }
       return OldNtTerminateProcess(ProcessHandle, ExitStatus); // 调用原来的函数
}
// HOOK KeBugCheckEx 函数, 让其通过调用 KeBugCheckEx 到我们的函数中
BOOL HOOKKeBugCheckEx()
{
       DbgPrint("进入 HOOKKeBugCheckEx\n");
       /*
              mov rax, 0xFFFFFFFF~FFFFFFFF;
              jmp rax;
       */
       char jmp_Code[] = { "\x48\xB8\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF\xE0" };  // 构建       ShellCode
       ULONGLONG pMyHookFun = (ULONGLONG)HookNtTerminateProcess;
       RtlMoveMemory(&jmp_Code[2], &pMyHookFun, sizeof(PVOID)); // 将我们的函数地址写入, 间接Call
       PVOID BugFunAddr = NULL;
       BugFunAddr = (PVOID)KeBugCheckEx;// 将函数地址给 BugFunAddr
       // 关闭写保护
       RemovWP();
       RtlMoveMemory(BugFunAddr, jmp_Code, sizeof(jmp_Code)); // inlineHook, 将我们的代码写入其中
       // 恢复写保护
       UndoWP();
       return TRUE;
}
VOID GetSsdtTableAddr()
{
       DbgPrint("进入 GetSsdtTableAddr\n");
       // 获取SSDT表地址
       LONG offset;
       // 获取 KiSystemCall64 地址
       PVOID pKiSystemCall64 = (PVOID)__readmsr(0xC0000082);
       UCHAR ulCode1, ulCode2, ulCode3;
       for (ULONG index = 0; index < 1024; index++)
       {
              // 获取内存数据
              ulCode1 = *(PUCHAR)((PUCHAR)pKiSystemCall64 + index);
              ulCode2 = *(PUCHAR)((PUCHAR)pKiSystemCall64 + index + 1);
              ulCode3 = *(PUCHAR)((PUCHAR)pKiSystemCall64 + index + 2);
              // 判断特征码
              if (ulCode1 == (UCHAR)0x4c &&
                     ulCode2 == (UCHAR)0x8d &&
                     ulCode3 == (UCHAR)0x15)
              {
                     // 获取 SSDT 表偏移
                     offset = *(PLONG)((PUCHAR)pKiSystemCall64 + index + 3);
                     // 根据偏移计算地址
                     KeServiceDescriptorTable =  (PSERVICETABLE)((PUCHAR)pKiSystemCall64 + index + 7 + offset);
                     break;
              }
       }
}
BOOL HookSSDTCall(ULONG SsdtIndex)
{
       DbgPrint("进入 HookSSDTCall\n");
       PCHAR FunAddr = NULL;
       LONG offset;
       HOOKKeBugCheckEx(); // Hook KeBugCheckEx 函数
       GetSsdtTableAddr(); // 获取 KeServiceDescriptorTable 地址
       RemovWP();
       FunAddr = (PCHAR)KeBugCheckEx;
       offset = (LONG)((PCHAR)FunAddr -  (PCHAR)KeServiceDescriptorTable->ServiceTableAddr); // 计算 KeBugCheckEx 地址 -  ServiceTableAddr
       offset <<= 4; // ( KeBugCheckEx地址 - ServiceTableAddr) << 4
       // 获取原函数地址
       OldNtTerminateProcess =  (pNtTerminateProcess)((PCHAR)KeServiceDescriptorTable->ServiceTableAddr +  ((KeServiceDescriptorTable->ServiceTableAddr[41]) >> 4));
       // 填写 KeBugCheckEx 偏移
       KeServiceDescriptorTable->ServiceTableAddr[41] = offset;
       UndoWP();
       return TRUE;
}
VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
       DbgPrint("卸载驱动\n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING   RegeditPath)
{
       DbgPrint("加载驱动\n");
       //KdBreakPoint();
       NTSTATUS status = STATUS_SUCCESS;
       DriverObject->DriverUnload = Unload;
       HookSSDTCall(41);
       return STATUS_SUCCESS;
}

HOOK 指定函数让其无法关闭计算器进程

 

原创文章,转载请注明: 转载自Windows内核安全驱动编程

本文链接地址: 针对Windows注入和Hook的

发表评论

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