前一篇写了 inline hook和ssdt两种方式,但是相关的的某些东西没讲清楚。

windows rookit防护-Kernel Hook 1.5

前一篇写了 inline hook和ssdt两种方式,但是相关的的某些东西没讲清楚。

TL,DR:介绍驱动加载的方法,PatchGuard简介,如何在测试中关闭/开启KVAS保护(Shadow SSDT中的KiSystemCall64Shadow相关),然后测试ssdt/shadow ssdt hook方法,最后内容是Shadow SSDT枚举。

Ring3 加载一个sys文件

首先有一个hello world的驱动,尝试使用命令行加载他(使用管理员权限)

sc create <使用的名称> type= kernel start= demand binPath="/??/<驱动文件路径,这里应该用的是描述符格式>"

sc start <使用的名称>
sc stop <使用的名称>
sc delete <使用的名称>

image-20260407135338908

sc是Service Control的简称,本质是操作 Windows 的 SCM(服务控制管理器),这里的加载驱动本质上是创建一个 kernel类型的服务,然后启动。

服务控制管理器(SCM)在系统启动时启动。 它是远程过程调用 (RPC) 服务器,以便服务配置和服务控制程序可以作远程计算机上的服务。

服务函数为 SCM 执行的以下任务提供接口:

  • 维护已安装服务的数据库。
  • 在系统启动时或按需启动服务和驱动程序服务。
  • 枚举已安装的服务和驱动程序服务。
  • 维护运行服务和驱动程序服务的状态信息。
  • 将控制请求传输到正在运行的服务。
  • 锁定和解锁服务数据库。

https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-manager

那么sc是如何完成这一过程的呢?对应的代码在附录的《简单的ring3驱动加载器》

image-20260407140946416

Ring0 加载一个sys文件

正常做法就是先注册表HKLM\SYSTEM\CurrentControlSet\Services\<service name>

然后调用ZwLoadDriver API

UNICODE_STRING regPath;
RtlInitUnicodeString(
&regPath,
L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MyDriver2"
);

NTSTATUS status = ZwLoadDriver(&regPath);

**那么使用手动加载呢?**这里需要PE 文件格式的基础,可以看:PE文件格式解析

这里修改下,就可以的得到内核手动映射,不过这个暂时过不了__security_cookie的检测,所以得自己设置AddressofEntry的值或者改写cookie,代码在附录的《手动映射SYS》,注意修改

  • main中的文件路径
  • ExecuteMappedDriver中的entry偏移
  • FixImportTablewhile循环中的IAT个数大小

PatchGuard

具体的分析可能会后续重新写一篇文章,如果实在想提前了解可以参考

https://blog.tetrane.com/downloads/Tetrane_PatchGuard_Analysis_RS4_v1.01.pdf

基本上所有的入门介绍都或多或少参考了这个PDF

本质上,它是内核的重要组成部分,与内核的其他部分一样在 Ring 0 中运行。它并非某种对 Ring 0 代码拥有更大权限的 Ring -1 机制(至少本文讲的范围内没有)。它的主要目的是检查关键的内核结构和代码,确保它们没有被篡改。如果检测到任何未经授权的修改,它会触发蓝屏死机,并附带错误代码CRITICAL_STRUCTURE_CORRUPTION和错误检查代码0x109,从而避免任何错误或混淆。它明确地表明 Ring 0 中某些不应该被修改的内容已被修改。

并且由于PG的异步机制,因此无法确定它何时会检查关键结构和代码。

主要检测的内容有

  • IDT(中断描述符表)和 GDT(全局描述符表)
    • GDT:是IA-32和x86-64架构特有的二进制数据结构。它包含向CPU提供有关内存段的条目。
    • IDT:是一种专用于 IA-32 和 x86-64 架构的二进制数据结构。它是保护模式和长模式下与实模式中断向量表 (IVT) 对应的机制,用于告知 CPU 中断服务例程 (ISR) 的位置(每个中断向量对应一个 ISR)。
  • MSR(模型特定寄存器):CPU 寄存器,用于控制高级行为,例如功能、限制和执行流程管理。
  • SSDT(系统服务描述符表):一个包含指向实现系统调用(例如NtCreateFile,、NtOpenProcess等)的内核函数的指针的表。
  • 内核栈
  • 内核结构
  • 全局变量
  • KPP引擎

关于KVAS保护

KVAS全称是Kernel Virtual Address Shadow,它的出现与MeltDown(CVE-2017-5754)和TotalMeltDown(CVE-2018-1038)有关。

我的描述不一定准确,大致上来说这两个漏洞利用了CPU的乱序执行技术,即CPU在执行时不一定会按照流程执行。当我们访问一个不能被用户模式访问的内存页时,CPU会执行该语句然后将其缓存到内存中,等到发现不能访问后返回错误,但是该数据依旧存在于缓存当中。利用这种思路就可以完全读取内核中的数据,实现权限提升等。

微软为了缓解该漏洞,从用户页表中隔离出内核页表,让用户态访问到的内核页表也是经过映射的,并且会将用户页表自动标记为NX,让我们的shellcode无法执行

笔者在这里遇到了很有趣的一点就是在最新的AMD Ryzen9 8945HX

DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;

DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);

无法让结果是**KiSystemCall64Shadow**(Shadow SSDT依旧是存在的!),反倒是在Intel i5 9300H的CPU上成功实现了。

所以这里只讲在受影响的Intel CPU上如何实现的

Windows内核缓解机制使用了Kva Shadow内存,尝试将其关闭

在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management

创建两个DWORD值:FeatureSettingsOverride FeatureSettingsOverrideMask,设置如下后重启

image-20260411103229487

设置值为3,然后重启

image-20240118140220468

**这两个值存在并且值正确的时候,关闭KVAS,**使用的就是KiSystemCall64,找到的是SSDT

image-20260410205731607

image-20260410205814991

删除这两个,开启KVAS的时候就为KiSystemCall64Shadow,需要处理的是Shadow SSDT

image-20260411205104804

image-20260411204856925

image-20260411204910559

Full SSDT Hook

上一篇文章提到主要的问题是在64位系统上根据 ssdt 的算法,不能直接将新的函数的地址直接转为数组的下表(寻址范围太小

image-20260411142643283

  1. 找到临近的空位内存并申请

    我手动调试找到了一块合适的位置进行测试,这一过程可以被自动化

    我选择了在ntoskrl.exe.text最后一部分,根据RVA找到位置,而且调试后发现距离ssdt->table仅有0x04ff189

    image-20260411142739249

    image-20260411143034601

  2. 在新申请的临近位置内编写trampoline,跳转到新的函数

    image-20260411144059555

    最后的效果:

image-20260411144205405

实现代码在附录[《完整SSDT Hook》](##完整SSDT Hook)部分

枚举Shadow SSDT

由于前文关于KVAS保护中所提到的内容,这里要分两种情况进行探讨了

  1. KiSystemCall64到Shadow SSDT(我使用AMD CPU)
  2. KiSystemCall64Shadow到Shadow SSDT(我使用Intel CPU)

从KiSystemCall64开始

回顾前文

然后继续向下查到KiSystemServiceRepeat,主要是为了拿到KeServiceDescriptorTable或者KeServiceDescriptorTableShadow的值,在这里就是

35 05 9F 00AE B6 8E 00

image-20260314213520421

我们只需要改特征值找到KeServiceDescriptorTableShadow

for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x1D) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}

image-20260411150221090

dps nt!KeServiceDescriptorTableShadow

image-20260411150438477

对比一下SSDT

image-20260411150458647

多了更多的win32k的函数,并且这里是看不了win32k!W32pServiceTable

image-20260411150712682

附加到使用了win32k的GUI用户态进程就可以看到了,这里attach winlogon.exe

image-20260411152028247

这个是文件管理器explore.exe看到的

image-20260411152411478

到了这里大多数网上的教程就讲的不清楚了,所以我也是慢慢调试出来的

突破点是尝试看下win32k!NtUserQueryWindow

image-20260411160950412

地址居然在win32k!W32pServiceTable之前(比win32k!W32pServiceTable小),而且注意到win32k!W32pServiceTable中的值均为ff开头,不免的让人想起这是做减法

image-20260411161229410

win32k!NtUserQueryWindowtable0x70c3c,尝试使用table的第一个数做减法

image-20260411161354291

总结出公式

RealFuncAddr = table - (0xFFFFFFFF-(DWORD)W32pServiceTable[index])>>4

测试代码可见附录[《Shadow SSDT 枚举》](##Shadow SSDT 枚举)

  • DriverEntry中的HANDLE pid = (HANDLE)600;需要修改,这里我用的是winlogon.exe的pid
  • 关于结构体SYSTEM_SERVICE_TABLE的定义可以精简
  • Hook:手法和SSDT的一致

image-20260411165217065

image-20260411165133268

从KiSystemCall64Shadow开始

这个时候要完成两件事情了,KiSystemCall64Shadow找到SSDTShadowSSDT

跟踪KiSystemCall64Shadow找到KiSystemServiceUser

image-20260411210154482

image-20260411210206777

KiSystemServiceUser向下就会找到老熟人

image-20260411210116285

然后用老方法即可,无非就是多几段字节序列搜索

引用

https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-manager

https://r0keb.github.io/posts/PatchGuard-Internals/

https://standa-note.blogspot.com/2015/10/some-tips-to-analyze-patchguard.html

https://blog.tetrane.com/downloads/Tetrane_PatchGuard_Analysis_RS4_v1.01.pdf

https://joe1sn.eu.org/2024/01/25/win-hevd-exp-stackoverflow-III/

附录

简单的ring3驱动加载器

#include <windows.h>
#include <iostream>
#include <string>

bool CreateDriver(const std::string& name, const std::string& path) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (!hSCM) {
std::cerr << "OpenSCManager failed: " << GetLastError() << std::endl;
return false;
}
std::string ntPath = "\\??\\" + path;
SC_HANDLE hService = CreateServiceA(
hSCM,
name.c_str(),
name.c_str(),
SERVICE_START | DELETE | SERVICE_STOP,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
ntPath.c_str(),
NULL, NULL, NULL, NULL, NULL
);

if (!hService) {
DWORD err = GetLastError();
if (err == ERROR_SERVICE_EXISTS) {
std::cout << "Service already exists." << std::endl;
}
else {
std::cerr << "CreateService failed: " << err << std::endl;
CloseServiceHandle(hSCM);
return false;
}
}
else {
std::cout << "Service created successfully." << std::endl;
CloseServiceHandle(hService);
}

CloseServiceHandle(hSCM);
return true;
}

bool StartDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), SERVICE_START);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

if (!StartService(hService, 0, NULL)) {
DWORD err = GetLastError();
if (err == ERROR_SERVICE_ALREADY_RUNNING) {
std::cout << "Already running." << std::endl;
}
else {
std::cerr << "StartService failed: " << err << std::endl;
}
}
else {
std::cout << "Driver started." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

bool StopDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), SERVICE_STOP);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

SERVICE_STATUS status;
if (!ControlService(hService, SERVICE_CONTROL_STOP, &status)) {
std::cerr << "Stop failed: " << GetLastError() << std::endl;
}
else {
std::cout << "Driver stopped." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

bool DeleteDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), DELETE);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

if (!DeleteService(hService)) {
std::cerr << "Delete failed: " << GetLastError() << std::endl;
}
else {
std::cout << "Service deleted." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

int main(int argc, char* argv[]) {
if (argc < 3) {
std::cout << "Usage:\n"
<< " create <name> <path>\n"
<< " start <name>\n"
<< " stop <name>\n"
<< " delete <name>\n";
return 0;
}

std::string action = argv[1];
std::string name = argv[2];

if (action == "create") {
if (argc < 4) {
std::cerr << "Missing driver path.\n";
return 1;
}
std::string path = argv[3];
CreateDriver(name, path);
}
else if (action == "start") {
StartDriver(name);
}
else if (action == "stop") {
StopDriver(name);
}
else if (action == "delete") {
DeleteDriver(name);
}
else {
std::cerr << "Unknown action.\n";
}

return 0;
}

手动映射SYS

#include <ntifs.h>
#include <ntimage.h>

typedef NTSTATUS(*DriverEntry_t)(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
);

typedef struct _SYSTEM_MODULE_ENTRY {
PVOID Reserved[2];
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_ENTRY Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

// ZwQuerySystemInformation 原型
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemModuleInformation = 11
} SYSTEM_INFORMATION_CLASS;

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

//typedef struct _SYSTEM_MODULE_ENTRY {
// PVOID Reserved[2];
// PVOID ImageBase;
// ULONG ImageSize;
// ULONG Flags;
// USHORT Index;
// USHORT Unknown;
// USHORT LoadCount;
// USHORT ModuleNameOffset;
// CHAR FullPathName[256];
//} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

//typedef struct _SYSTEM_MODULE_INFORMATION {
// ULONG NumberOfModules;
// SYSTEM_MODULE_ENTRY Modules[1];
//} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

PVOID GetKernelBase(PCSTR ModuleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation, NULL, 0, &size
);

if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

buffer = ExAllocatePool2(
POOL_FLAG_NON_PAGED, size, 'bKmM'
);

if (!buffer)
return NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation, buffer, size, &size
);

if (!NT_SUCCESS(status))
{
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION modules =
(PSYSTEM_MODULE_INFORMATION)buffer;

for (ULONG i = 0; i < modules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY mod = &modules->Modules[i];

PCSTR fullPath = mod->FullPathName;
PCSTR fileName = fullPath + mod->ModuleNameOffset;

if (_stricmp(fileName, ModuleName) == 0)
{
base = mod->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelModuleBaseByName(PCSTR moduleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

// 先获取长度
status = ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

// 分配非分页池
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'tag1');
if (!buffer)
return NULL;

status = ZwQuerySystemInformation(SystemModuleInformation, buffer, size, &size);
if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION sysModules = (PSYSTEM_MODULE_INFORMATION)buffer;
for (ULONG i = 0; i < sysModules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY entry = &sysModules->Modules[i];
PCSTR name = entry->FullPathName + entry->ModuleNameOffset; // 获取文件名
if (_stricmp(name, moduleName) == 0)
{
base = entry->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelExport(PVOID moduleBase, PCSTR functionName)
{
if (!moduleBase || !functionName)
return NULL;

PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

PIMAGE_NT_HEADERS64 ntHeaders = (PIMAGE_NT_HEADERS64)((PUCHAR)moduleBase + dosHeader->e_lfanew);
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE)
return NULL;

IMAGE_DATA_DIRECTORY exportDirData = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!exportDirData.Size)
return NULL;

PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)moduleBase + exportDirData.VirtualAddress);

ULONG* nameRvas = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfNames);
USHORT* ordinals = (USHORT*)((PUCHAR)moduleBase + exportDir->AddressOfNameOrdinals);
ULONG* functions = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfFunctions);

for (ULONG i = 0; i < exportDir->NumberOfNames; i++)
{
PCSTR name = (PCSTR)((PUCHAR)moduleBase + nameRvas[i]);
if (_stricmp(name, functionName) == 0)
{
USHORT ordinal = ordinals[i];
ULONG funcRva = functions[ordinal];
return (PVOID)((PUCHAR)moduleBase + funcRva);
}
}

return NULL; // 未找到
}



BOOLEAN FixRelocation(PVOID newBase, PIMAGE_NT_HEADERS64 nt)
{
ULONGLONG delta = (ULONGLONG)newBase - nt->OptionalHeader.ImageBase;
if (delta == 0) return TRUE;

IMAGE_DATA_DIRECTORY relocDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

if (!relocDir.Size) return TRUE;

PIMAGE_BASE_RELOCATION reloc =
(PIMAGE_BASE_RELOCATION)((PUCHAR)newBase + relocDir.VirtualAddress);

PUCHAR end = (PUCHAR)reloc + relocDir.Size;

while ((PUCHAR)reloc < end && reloc->SizeOfBlock)
{
if (reloc->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION))
break;

UINT32 count =
(reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);

PUSHORT list =
(PUSHORT)((PUCHAR)reloc + sizeof(IMAGE_BASE_RELOCATION));

for (UINT32 i = 0; i < count; i++)
{
USHORT type = list[i] >> 12;
USHORT offset = list[i] & 0xFFF;

if (type == IMAGE_REL_BASED_ABSOLUTE)
continue;

if (type == IMAGE_REL_BASED_DIR64)
{
PULONGLONG addr =
(PULONGLONG)((PUCHAR)newBase + reloc->VirtualAddress + offset);

*addr += delta;
}
}

reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)reloc + reloc->SizeOfBlock);
}

return TRUE;
}

BOOLEAN FixImportTable(PVOID imageBase, PIMAGE_NT_HEADERS64 nt)
{
IMAGE_DATA_DIRECTORY importDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

if (!importDir.Size)
return TRUE;

PIMAGE_IMPORT_DESCRIPTOR import =
(PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)imageBase + importDir.VirtualAddress);
size_t i = 0;
while (i < 2)
{
++i;

PCSTR moduleName = (PCSTR)((PUCHAR)imageBase + import->Name);

PVOID moduleBase = GetKernelBase(moduleName);
//PVOID moduleBase = GetKernelModuleBaseByName(moduleName);

if (!moduleBase)
return FALSE;

PIMAGE_THUNK_DATA64 thunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);

PIMAGE_THUNK_DATA64 origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);

//PIMAGE_THUNK_DATA64 origThunk;

if (import->OriginalFirstThunk)
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);
}
else
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);
}

while (origThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME importName =
(PIMAGE_IMPORT_BY_NAME)((PUCHAR)imageBase + origThunk->u1.AddressOfData);

PVOID fn = GetKernelExport(moduleBase, importName->Name);

if (!fn)
return FALSE;

thunk->u1.Function = (ULONGLONG)fn;

origThunk++;
thunk++;
}
import++;
}
return TRUE;
}


NTSTATUS ExecuteMappedDriver(PDRIVER_OBJECT DriverObject, PVOID imageBase)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)imageBase + dos->e_lfanew);

ULONG entryRVA = nt->OptionalHeader.AddressOfEntryPoint;

//DriverEntry_t entry = (DriverEntry_t)((PUCHAR)imageBase + 0x3208);
DriverEntry_t entry = (DriverEntry_t)((PUCHAR)imageBase + 0x2C);

UNICODE_STRING fakeReg;
RtlInitUnicodeString(&fakeReg, L"\\Registry\\Machine\\System\\Fake");

return entry(DriverObject, &fakeReg); // DriverObject = NULL
}

NTSTATUS MapImage(PDRIVER_OBJECT DriverObject, PVOID buffer)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)buffer + dos->e_lfanew);

SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;

PVOID imageBase = ExAllocatePool(NonPagedPool, imageSize);
if (!imageBase)
return STATUS_INSUFFICIENT_RESOURCES;

RtlZeroMemory(imageBase, imageSize);

// 拷贝 headers
RtlCopyMemory(imageBase, buffer, nt->OptionalHeader.SizeOfHeaders);

// 拷贝 section
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
PVOID dest = (PUCHAR)imageBase + section[i].VirtualAddress;
PVOID src = (PUCHAR)buffer + section[i].PointerToRawData;

RtlCopyMemory(dest, src, section[i].SizeOfRawData);
}
// - 重定位修复
FixRelocation(imageBase, nt);

// - IAT 导入解析
FixImportTable(imageBase, nt);

return ExecuteMappedDriver(DriverObject, imageBase);
}

NTSTATUS ReadFileToBuffer(
PCWSTR filePath,
PVOID* outBuffer,
PULONG outSize
)
{
NTSTATUS status;
HANDLE fileHandle;
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatus;
UNICODE_STRING uFilePath;

*outBuffer = NULL;
*outSize = 0;

RtlInitUnicodeString(&uFilePath, filePath);

InitializeObjectAttributes(
&objAttr,
&uFilePath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL
);

// 打开文件
status = ZwCreateFile(
&fileHandle, GENERIC_READ, &objAttr, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
);

if (!NT_SUCCESS(status)) {
DbgPrint("ZwCreateFile failed: 0x%X\n", status);
return status;
}

// 获取文件大小
FILE_STANDARD_INFORMATION fileInfo;
status = ZwQueryInformationFile(
fileHandle,
&ioStatus,
&fileInfo,
sizeof(fileInfo),
FileStandardInformation
);

if (!NT_SUCCESS(status)) {
ZwClose(fileHandle);
return status;
}

ULONG fileSize = (ULONG)fileInfo.EndOfFile.QuadPart;

// 分配内存
PVOID buffer = ExAllocatePool(NonPagedPool, fileSize);
if (!buffer) {
ZwClose(fileHandle);
return STATUS_INSUFFICIENT_RESOURCES;
}

// 读取文件
status = ZwReadFile(
fileHandle, NULL, NULL, NULL, &ioStatus, buffer, fileSize, NULL, NULL
);

if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
ZwClose(fileHandle);
return status;
}

ZwClose(fileHandle);

*outBuffer = buffer;
*outSize = fileSize;

DbgPrint("Read file success, size: %lu\n", fileSize);

return STATUS_SUCCESS;
}

VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Stopping -> %wZ\n", &DriverObject->DriverName);
}

NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint("Driver Running -> %wZ\n", &DriverObject->DriverName);
DriverObject->DriverUnload = DriverUnload;
PVOID buffer;
ULONG size;

NTSTATUS status = ReadFileToBuffer(
L"\\??\\C:\\Users\\WDKRemoteUser\\Desktop\\test\\helloworld.sys",
//C:\Users\WDKRemoteUser\Desktop\test
&buffer,
&size
);

if (!NT_SUCCESS(status)) {
DbgPrint("Can't read file\n");
return status;
}

MapImage(DriverObject, buffer);
status = STATUS_SUCCESS;
return status;
}

完整SSDT Hook

#include "helper.h"
#include <ntifs.h>

typedef NTSTATUS(*NTOPENFILE)(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);

typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID tableBase;
PVOID serviceCountBase;
ULONG64 numberOfServices;
PVOID unkown;
}SYSTEM_SERVICE_TABLE, * PSYSTEM_SERVICE_TABLE;

NTOPENFILE OriginalNtOpenFile = NULL;
ULONG NtOpenFileIndex = 51;
PSYSTEM_SERVICE_TABLE g_Table = NULL;
ULONG64 g_Trampoline = 0x0;

NTSTATUS HookNtOpenFile(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
)
{
if (ObjectAttributes && ObjectAttributes->ObjectName)
{
DbgPrint("HookNtOpenFile: %wZ\n", ObjectAttributes->ObjectName);
}

return OriginalNtOpenFile(
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
ShareAccess,
OpenOptions
);
}

LONG64 GetFuncAddr(size_t index, PSYSTEM_SERVICE_TABLE ssdt) {
if (index > ssdt->numberOfServices)
return 0;
LONG offset = ((PLONG)(ssdt->tableBase))[index] >> 4;
return (DWORDLONG)(ssdt->tableBase) + offset;
}

VOID DisableWP()
{
ULONG64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
}

VOID EnableWP()
{
ULONG64 cr0 = __readcr0();
cr0 |= 0x10000;
__writecr0(cr0);
_enable();
}

VOID HookSSDT(PSYSTEM_SERVICE_TABLE ssdt)
{
PULONG table = ssdt->tableBase;
ULONG entry = table[NtOpenFileIndex];
ULONG64 base = (ULONG64)table;
ULONG64 original = base + (entry >> 4);
OriginalNtOpenFile = (NTOPENFILE)original;
ULONG param = entry & 0xF;
if (g_Trampoline == 0) {
return;
}
ULONG64 hookAddr = (ULONG64)g_Trampoline; //TODO: 修改为trampoline
ULONG newEntry = (ULONG)(((hookAddr - base) << 4) | param);
DisableWP();
table[NtOpenFileIndex] = newEntry;
EnableWP();
}
VOID UnhookSSDT(PSYSTEM_SERVICE_TABLE ssdt)
{

PULONG table = ssdt->tableBase;
ULONG entry = table[NtOpenFileIndex];
ULONG param = entry & 0xF;
ULONG64 base = (ULONG64)table;
ULONG64 original = (ULONG64)OriginalNtOpenFile;
ULONG newEntry = (ULONG)(((original - base) << 4) | param);
DisableWP();
table[NtOpenFileIndex] = newEntry;
EnableWP();
}

PUCHAR BuildTrampoline(ULONG64 funcAddr)
{
static UCHAR trampo[12] = {
0x48, 0xB8, // mov rax, imm64
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // addr placeholder
0xFF, 0xE0 // jmp rax
};

*(PULONG64)&trampo[2] = funcAddr;

return trampo;
}

VOID WriteTpCode(PSYSTEM_SERVICE_TABLE ssdt, PUCHAR jmpCode)
{
UNREFERENCED_PARAMETER(jmpCode);
PUCHAR tpAddr = (PUCHAR)ssdt->tableBase + 0x04ff189;
DbgPrint("[ssdt hook] trampoline code at %p\n", tpAddr);

DisableWP();
RtlCopyMemory(tpAddr, jmpCode, 12);
EnableWP();
g_Trampoline = (ULONG64)tpAddr;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
UnhookSSDT(g_Table);
DbgPrint("Driver Unloaded\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);

DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;
PVOID kernelbase = GetKernelBase("ntoskrnl.exe");
DbgPrint("[ssdt hook]ntoskrnl base: %p\n", kernelbase);


DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);
PUCHAR tempptr = (PUCHAR)(dmsr);
LONG offset = 0;
PSYSTEM_SERVICE_TABLE table = NULL;
for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x15) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}
if (offset == 0) {
DbgPrint("Not found KeServiceDescriptorTable\n");
return STATUS_NOT_FOUND;
}
g_Table = table;
DbgPrint("KeServiceDescriptorTable at: %p\n tableBase: %p\n MyHook at: %p\n", table, table->tableBase, HookNtOpenFile);
DbgPrint("NtOpenFile at: %p\n", GetFuncAddr(NtOpenFileIndex, table));
PUCHAR tpCode = BuildTrampoline((ULONG64)HookNtOpenFile);
DbgPrint("Trampoline code at: %p\n", tpCode);
WriteTpCode(table, tpCode);

HookSSDT(table);
DbgPrint("[hooked] NtOpenFile at: %p\n", GetFuncAddr(NtOpenFileIndex, table));


return STATUS_SUCCESS;
}

Shadow SSDT 枚举

#include <ntifs.h>
#include <ntddk.h>

extern PEPROCESS NTAPI PsGetNextProcess(_In_opt_ PEPROCESS Process);
extern UCHAR* NTAPI PsGetProcessImageFileName(_In_ PEPROCESS Process);

typedef NTSTATUS(*NTOPENFILE)(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);

typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID tableBase;
PVOID serviceCountBase;
ULONG64 numberOfServices;
PVOID unkown1;
PVOID shadowTableBase;
PVOID shaodwserviceCountBase;
ULONG64 numberOfShadowServices;
PVOID unkown2;
}SYSTEM_SERVICE_TABLE, * PSYSTEM_SERVICE_TABLE;
PSYSTEM_SERVICE_TABLE g_Table = 0x0;


LONG64 GetFuncAddr(size_t index, PSYSTEM_SERVICE_TABLE ssdt) {
if (index > ssdt->numberOfShadowServices)
return 0;
LONG offset = (0xFFFFFFFF - ((PLONG)(ssdt->shadowTableBase))[index]) >> 4;
return (DWORDLONG)(ssdt->shadowTableBase) - offset;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Unloaded\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);

DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;


DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);
PUCHAR tempptr = (PUCHAR)(dmsr);
LONG offset = 0;
PSYSTEM_SERVICE_TABLE table = NULL;
for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x1D) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}
if (offset == 0) {
DbgPrint("Not found KeServiceDescriptorTableShadow\n");
return STATUS_NOT_FOUND;
}
g_Table = table;
DbgPrint("KeServiceDescriptorTableShadow at: %p\n tableBase: %p\n", table, table->shadowTableBase);


HANDLE pid = (HANDLE)600;
PEPROCESS Process = NULL;

if (NT_SUCCESS(PsLookupProcessByProcessId(pid, &Process)))
{
KAPC_STATE state;

KeStackAttachProcess(Process, &state);

for (size_t i = 0; i < table->numberOfShadowServices; i++)
{
DbgPrint("No.%d func at: %p\n", i, GetFuncAddr(i, table));
}

KeUnstackDetachProcess(&state);

ObDereferenceObject(Process);
}
return STATUS_SUCCESS;
}