前一篇写了 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 <使用的名称>
sc是Service Control的简称,本质是操作 Windows 的 SCM(服务控制管理器),这里的加载驱动本质上是创建一个 kernel类型的服务,然后启动。
服务控制管理器(SCM)在系统启动时启动。 它是远程过程调用 (RPC) 服务器,以便服务配置和服务控制程序可以作远程计算机上的服务。
服务函数为 SCM 执行的以下任务提供接口:
维护已安装服务的数据库。
在系统启动时或按需启动服务和驱动程序服务。
枚举已安装的服务和驱动程序服务。
维护运行服务和驱动程序服务的状态信息。
将控制请求传输到正在运行的服务。
锁定和解锁服务数据库。
https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-manager
那么sc是如何完成这一过程的呢?对应的代码在附录的《简单的ring3驱动加载器》
Ring0 加载一个sys文件
正常做法就是先注册表HKLM\SYSTEM\CurrentControlSet\Services\<service name>
然后调用ZwLoadDriver API
UNICODE_STRING regPath; RtlInitUnicodeString ( ®Path, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MyDriver2" ); NTSTATUS status = ZwLoadDriver (®Path);
**那么使用手动加载呢?**这里需要PE 文件格式的基础,可以看:PE文件格式解析
这里修改下,就可以的得到内核手动映射,不过这个暂时过不了__security_cookie的检测,所以得自己设置AddressofEntry的值或者改写cookie,代码在附录的《手动映射SYS》 ,注意修改
main中的文件路径
ExecuteMappedDriver中的entry偏移
FixImportTable中while循环中的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,设置如下后重启
设置值为3,然后重启
**这两个值存在并且值正确的时候,关闭KVAS,**使用的就是KiSystemCall64,找到的是SSDT
删除这两个,开启KVAS 的时候就为KiSystemCall64Shadow,需要处理的是Shadow SSDT
Full SSDT Hook
上一篇文章提到 主要的问题是在64位系统上根据 ssdt 的算法,不能直接将新的函数的地址直接转为数组的下表(寻址范围太小 )
找到临近的空位内存并申请
我手动调试找到了一块合适的位置进行测试,这一过程可以被自动化
我选择了在ntoskrl.exe的.text最后一部分,根据RVA找到位置,而且调试后发现距离ssdt->table仅有0x04ff189
在新申请的临近位置内编写trampoline,跳转到新的函数
最后的效果:
实现代码在附录[《完整SSDT Hook》](##完整SSDT Hook)部分
枚举Shadow SSDT
由于前文关于KVAS保护 中所提到的内容,这里要分两种情况进行探讨了
从KiSystemCall64到Shadow SSDT(我使用AMD CPU)
从KiSystemCall64Shadow到Shadow SSDT(我使用Intel CPU)
从KiSystemCall64开始
回顾前文
然后继续向下查到KiSystemServiceRepeat,主要是为了拿到KeServiceDescriptorTable或者KeServiceDescriptorTableShadow的值,在这里就是
35 05 9F 00和AE B6 8E 00
我们只需要改特征值找到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 ; } }
dps nt!KeServiceDescriptorTableShadow
对比一下SSDT
多了更多的win32k的函数,并且这里是看不了win32k!W32pServiceTable的
附加到使用了win32k的GUI用户态进程就可以看到了,这里attach winlogon.exe
这个是文件管理器explore.exe看到的
到了这里大多数网上的教程就讲的不清楚了,所以我也是慢慢调试出来的
突破点是尝试看下win32k!NtUserQueryWindow
地址居然在win32k!W32pServiceTable之前(比win32k!W32pServiceTable小),而且注意到win32k!W32pServiceTable中的值均为ff开头,不免的让人想起这是做减法
win32k!NtUserQueryWindow在table前0x70c3c,尝试使用table的第一个数做减法
总结出公式
RealFuncAddr = table - (0xFFFFFFFF-(DWORD)W32pServiceTable[index])>>4
测试代码可见附录[《Shadow SSDT 枚举》](##Shadow SSDT 枚举)
DriverEntry中的HANDLE pid = (HANDLE)600;需要修改,这里我用的是winlogon.exe的pid
关于结构体SYSTEM_SERVICE_TABLE的定义可以精简
Hook :手法和SSDT的一致
从KiSystemCall64Shadow开始
这个时候要完成两件事情了,KiSystemCall64Shadow找到SSDT和ShadowSSDT
跟踪KiSystemCall64Shadow找到KiSystemServiceUser
KiSystemServiceUser向下就会找到老熟人
然后用老方法即可,无非就是多几段字节序列搜索
引用
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; 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 ) ;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); 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); 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 + 0x2C ); UNICODE_STRING fakeReg; RtlInitUnicodeString(&fakeReg, L"\\Registry\\Machine\\System\\Fake" ); return entry(DriverObject, &fakeReg); } 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); RtlCopyMemory(imageBase, buffer, nt->OptionalHeader.SizeOfHeaders); 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); 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" , &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; 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 , 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 , 0xFF , 0xE0 }; *(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; }