
简介本资源是一份面向Windows内核安全研究者与高级逆向工程师的64位SSDT Hook实战教程聚焦于绕过Process GuardPG保护机制后实现系统服务表劫持的核心技术。内容涵盖二次挑战式绕过PG的原理分析、SSDT修改流程及配套驱动开发实践适用于内核驱动开发、EDR对抗研究与安全加固验证等场景。压缩包共6个文件含2个头文件.h定义关键结构与函数声明1个C源码.c实现hook逻辑1个Makefile与1个sources构建配置以及1个编译日志.log整体仅5KB轻量但高度聚焦内核层操作细节。已有1086人学习下载读者可直接获取完整可编译的驱动工程框架、SSDT函数地址解析方法、PG绕过时序控制策略及AMD64平台适配要点代码目录结构简洁模块职责明确便于快速理解与二次开发。1. “src_过pg_过windowspg_64位SSDThook实现方法_过PG_”不是漏洞利用脚本而是Windows平台下PostgreSQL驱动层绕过检测的工程化调试路径这个标题常被误读为“绕过PG安全机制”的黑产术语实际指向一类特定场景在64位Windows系统中对使用PostgreSQL协议通信的客户端如pgAdmin、自研管理工具、ETL连接器进行驱动级行为干预目标是规避基于SSDTSystem Service Descriptor Table钩子的内核级监控策略。典型需求来自合规审计环境下的白盒测试——例如某金融客户需验证其数据库审计代理是否能捕获所有libpq.dll发起的PQconnectdb调用但又不能修改业务程序源码或替换驱动。此时“过PG”并非指绕过PostgreSQL服务端认证而是让客户端侧的网络请求行为不触发SSDT Hook的拦截逻辑从而完成链路可观测性验证。适用人群是Windows内核驱动开发工程师、数据库中间件安全测试人员、以及需要深度适配国产化信创环境如统信UOS人大金仓兼容层的DBA。它不涉及SQL注入、提权或协议破解核心是理解Windows x64下SSDT Hook的生效边界与libpq调用栈特征。2. 为什么必须用64位SSDT Hook而非用户态Hook从PostgreSQL客户端调用链看拦截失效点2.1 PostgreSQL客户端在Windows上的真实调用路径从PQexec到NtWriteFile的跨层跃迁当一个64位PostgreSQL客户端如pgAdmin 4 v7.5执行PQexec(conn, SELECT version();)时其底层调用链并非简单经过Winsock API而是存在多层封装// libpq源码关键路径简化 PQexec → pqSendQuery → pqFlush → pqPutMsgEnd → → send() [WS2_32.dll] → NtWriteFile [ntdll.dll] → KiSystemServiceCopyEnd [ntoskrnl.exe]提示send()在Windows上是用户态API但其最终会通过NtWriteFile进入内核。若仅在send()处Hook如Detours则无法捕获libpq内部重试逻辑触发的隐式调用而SSDT Hook直接作用于NtWriteFile等内核服务入口理论上可覆盖所有路径。但x64 Windows自Vista起启用PatchGuard禁止直接修改SSDT因此实际工程中采用的是Shadow SSDT Hook通过KiServiceTable间接跳转或SSDT Shadow HookHookKiSystemServiceCopyEnd后的分发逻辑。2.2 64位Windows下SSDT Hook的三大硬约束条件约束类型具体表现对PostgreSQL客户端的影响内核模式签名强制Windows 10 RS1要求所有驱动必须有EV签名否则蓝屏0x000000C4ssdthook.sys若未签名在Win10 1809无法加载导致PQconnectdb调用直接失败而非被绕过KVA Shadow隔离Kernel Virtual Address Shadowing使SSDT地址随机化且KeServiceDescriptorTable不可写直接读取KeServiceDescriptorTable[0].Base会返回无效地址必须通过KeGetExecutingProcessorNumberExKeGetCurrentProcessorNumberEx定位当前CPU的Shadow SSDT表PatchGuard保护范围检测SSDT、IDT、GDT、CR3等关键结构但不检测SSDT Shadow表工程实践选择HookKiServiceTableShadow SSDT而非主SSDT规避PatchGuard触发2.3 验证SSDT Hook是否生效用!ssdt和!drvobj命令定位libpq调用点在WinDbg中加载调试符号后执行以下命令确认Hook位置# 查看当前SSDT表基址主表通常被PatchGuard保护 kd !ssdt # 查看Shadow SSDT表实际Hook目标 kd dd nt!KiServiceTable l10 # 定位libpq.dll加载基址需先在目标进程attach kd !process 0 0 pgadmin.exe kd .process /p /r EPROCESS地址 kd lm m libpq # 在libpq中设置断点验证调用链 kd bp libpq!pqSendQuery kd g注意!ssdt输出中的0x00000000表示该索引未被Hook若看到类似0xfffff80003e2a000的非零地址则说明对应服务如NtWriteFile索引为0x3b已被重定向。libpq!pqSendQuery断点命中后用k命令查看栈回溯确认是否经过ntdll!NtWriteFile——这是SSDT Hook生效的关键证据。3. 实现64位SSDT Hook绕过PG检测从驱动编译到libpq调用劫持3.1 编写可加载的64位内核驱动ssdthook.sys使用WDK 10.0.22621.0Win11 22H2构建关键代码段如下// ssdthook.cpp #include ntddk.h #include windef.h PVOID OriginalNtWriteFile NULL; PVOID KeServiceDescriptorTableShadow NULL; // 声明未导出函数地址需通过内核内存扫描获取 extern C { NTSTATUS NTAPI MyNtWriteFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key ); } // 获取Shadow SSDT基址绕过PatchGuard PVOID GetShadowSSDTBase() { PUCHAR pKernelBase (PUCHAR)MmGetSystemRoutineAddress((UNICODE_STRING*)LKeInitializeEvent); if (!pKernelBase) return NULL; // 扫描特征码查找 mov rax, gs:[0x188] 后的 mov rax, [rax0x10] 指令 for (SIZE_T i 0; i 0x100000; i) { if (pKernelBase[i] 0x65 pKernelBase[i1] 0x48 pKernelBase[i2] 0x8B pKernelBase[i3] 0x04 pKernelBase[i4] 0x25 pKernelBase[i5] 0x88 pKernelBase[i6] 0x01 pKernelBase[i7] 0x00 pKernelBase[i8] 0x00) { // 向后偏移找到KiServiceTable地址 return *(PVOID*)(pKernelBase i 0x10); } } return NULL; } // DriverEntry入口 extern C NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { KeServiceDescriptorTableShadow GetShadowSSDTBase(); if (!KeServiceDescriptorTableShadow) { DbgPrint(Failed to get Shadow SSDT base\n); return STATUS_UNSUCCESSFUL; } // NtWriteFile索引为0x3bx64 Windows 10/11 LONG NtWriteFileIndex 0x3b; OriginalNtWriteFile ((PVOID*)KeServiceDescriptorTableShadow)[NtWriteFileIndex]; // 修改Shadow SSDT表需禁用写保护 SIZE_T OldCr0; __asm { mov eax, cr0 mov OldCr0, rax and eax, 0xfffeffff mov cr0, rax } ((PVOID*)KeServiceDescriptorTableShadow)[NtWriteFileIndex] (PVOID)MyNtWriteFile; __asm { mov rax, OldCr0 mov cr0, rax } DriverObject-DriverUnload DriverUnload; return STATUS_SUCCESS; }逻辑说明GetShadowSSDTBase()通过扫描内核内存特征码定位KiServiceTable地址避免依赖易变的符号名__asm段临时关闭CR0.WP位以写入Shadow SSDT表MyNtWriteFile需实现完整参数转发逻辑此处省略确保libpq调用不崩溃。参数说明NtWriteFileIndex0x3b是x64 Windows下该服务的固定索引可通过!ntsd -v在WinDbg中验证。3.2 编译与签名使用EV证书生成可部署驱动# 1. 设置WDK环境 C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\setenv.bat C:\WinDDK win10 fre # 2. 编译驱动生成ssdthook.sys build -Zn # 3. 使用EV证书签名假设cert.pfx为有效企业EV证书 signtool sign /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com /a /f cert.pfx /p password ssdthook.sys # 4. 验证签名有效性 signtool verify /pa ssdthook.sys注意signtool verify /pa必须返回Successfully verified否则驱动在Win10 1809将拒绝加载。若无EV证书可临时启用测试模式bcdedit /set testsigning on但生产环境严禁使用。3.3 在PostgreSQL客户端进程中注入Hook通过Process Hollowing劫持libpq调用由于SSDT Hook全局生效需精准控制作用域避免影响系统其他进程。采用Process Hollowing技术# inject_pg_hook.pyPython 3.9 import ctypes, sys, os from ctypes import wintypes kernel32 ctypes.WinDLL(kernel32, use_last_errorTrue) psapi ctypes.WinDLL(psapi, use_last_errorTrue) def inject_into_pgadmin(): # 查找pgadmin.exe进程 pid None for proc in psapi.EnumProcesses(): if proc.szExeFile.lower() pgadmin4.exe: pid proc.th32ProcessID break hProcess kernel32.OpenProcess(0x1F0FFF, False, pid) # PROCESS_ALL_ACCESS hThread kernel32.OpenThread(0x00000040, False, 0) # THREAD_SUSPEND_RESUME # 分配远程内存并写入shellcode跳转到ssdthook.sys初始化逻辑 remote_mem kernel32.VirtualAllocEx(hProcess, 0, 0x1000, 0x3000, 0x40) shellcode b\x48\xb8 b\x00*8 b\xff\xe0 # mov rax, addr; jmp rax kernel32.WriteProcessMemory(hProcess, remote_mem, shellcode, len(shellcode), 0) # 创建远程线程执行 kernel32.CreateRemoteThread(hProcess, None, 0, remote_mem, 0, 0, 0) kernel32.CloseHandle(hProcess) if __name__ __main__: inject_into_pgadmin()逻辑说明此脚本不直接加载驱动而是向pgadmin4.exe进程注入shellcode触发其主动调用LoadLibraryW(Lssdthook.sys)。libpq.dll后续所有NtWriteFile调用将经由MyNtWriteFile处理从而实现“过PG”——即让审计系统看到的是Hook后的流量特征如添加自定义HTTP头而非原始libpq行为。4. 验证SSDThook是否成功绕过PG检测三步交叉校验法4.1 步骤一检查内核日志确认Hook注册状态在PowerShell中执行# 启用内核日志需管理员权限 wevtutil sl Microsoft-Windows-Kernel-General /e:true # 查看最近10条SSDT相关事件 Get-WinEvent -FilterHashtable {LogNameMicrosoft-Windows-Kernel-General; ID12} -MaxEvents 10 | Where-Object {$_.Message -like *ssdthook*} | Format-List TimeCreated, Message若输出包含ssdthook.sys registered NtWriteFile hook at index 0x3b说明驱动已成功注册。若无输出检查dmesg通过Get-WinEvent是否有STATUS_INVALID_IMAGE_HASH错误——这表明签名失败。4.2 步骤二抓包比对libpq原始流量与Hook后流量使用Wireshark过滤PostgreSQL协议流量# 过滤pgAdmin发起的流量默认端口5432 tcp.port 5432 ip.addr PG服务器IP # 关键字段比对 # - 原始流量Packet 123: StartupMessage (protocol196608) # - Hook后流量Packet 123: StartupMessage custom tag X-PG-Bypass: true技巧在MyNtWriteFile中添加自定义协议头如memcpy(Buffer, X-PG-Bypass: true\r\n, 18)Wireshark中用http.request.full_uri contains X-PG-Bypass即可快速定位Hook生效包。若未出现该字段说明libpq未走NtWriteFile路径可能使用了WSASend异步IO。4.3 步骤三用ProcMon验证libpq.dll调用栈完整性启动Sysinternals ProcMon设置过滤器过滤条件值说明Process Namepgadmin4.exe目标进程OperationWriteFile捕获文件写入含网络SocketPathcontains libpq确保关联到libpq模块观察Detail列正常应显示C:\Program Files\pgAdmin 4\venv\Lib\site-packages\psycopg2\_psycopg.cp39-win_amd64.pyd调用ntdll.dll!NtWriteFile。若看到libpq.dll!PQexec直接调用ws2_32.dll!send则说明SSDT Hook未覆盖该路径——需检查是否启用了TCP_NODELAY或SOCK_CLOEXEC等选项导致绕过内核路径。5. 调试常见失败场景从蓝屏0x0000007E到libpq连接超时的根因分析5.1 蓝屏0x0000007EINACCESSIBLE_BOOT_DEVICE的三大根因现象根因解决方案驱动加载后立即蓝屏ssdthook.sys未签名或签名链不完整缺少交叉证书使用signtool verify /v /kp ssdthook.sys验证完整证书链确保证书颁发机构为Microsoft Trusted Root CA仅在特定PG版本下蓝屏libpq使用WSASend替代send导致NtWriteFileHook未触发驱动误判为非法调用在MyNtWriteFile中添加if (FileHandle INVALID_HANDLE_VALUE) return STATUS_INVALID_HANDLE;防御性检查多次加载卸载后蓝屏Shadow SSDT表未恢复原值导致后续系统调用错乱在DriverUnload中必须还原((PVOID*)KeServiceDescriptorTableShadow)[0x3b] OriginalNtWriteFile;5.2 PostgreSQL客户端连接超时但无报错SSDT Hook引发的TIME_WAIT风暴当MyNtWriteFile未正确转发IoStatusBlock参数时libpq会持续重试连接表现为# pgAdmin日志 2023-10-15 14:22:31 CET LOG: could not connect to server: Connection timed out Is the server running on host 127.0.0.1 and accepting TCP/IP connections on port 5432?根本原因是MyNtWriteFile中遗漏了IoStatusBlock-Information赋值// 错误写法导致libpq认为写入失败 NTSTATUS NTAPI MyNtWriteFile(...) { // ... 忘记设置IoStatusBlock-Information Length; return STATUS_SUCCESS; } // 正确写法 NTSTATUS NTAPI MyNtWriteFile(...) { NTSTATUS status ((pfnNtWriteFile)OriginalNtWriteFile)( FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, Buffer, Length, ByteOffset, Key ); if (NT_SUCCESS(status)) { IoStatusBlock-Information Length; // 关键否则libpq重试 } return status; }提示IoStatusBlock-Information记录实际写入字节数libpq据此判断网络包是否发出。若该值为0pqFlush会无限循环重试最终触发连接超时。5.3 Windows 11 22H2下Hook失效KVASKernel Virtual Address Space隔离策略升级Win11 22H2默认启用KVAS使每个进程拥有独立的内核地址空间视图导致KeServiceDescriptorTableShadow地址在不同进程间不一致MyNtWriteFile只能拦截当前进程的NtWriteFile调用解决方案是改用ETWEvent Tracing for Windows替代SSDT Hook!-- 在驱动中启用ETW Provider -- instrumentationManifest xmlnshttp://schemas.microsoft.com/win/2004/08/events events provider namePGTraceProvider guid{a1a1a1a1-1a1a-1a1a-1a1a-1a1a1a1a1a1a} channels channel namePGNetworkChannel chidPGNetwork symbolPGNetwork enabledtrue / /channels events event value1 symbolPGSendEvent channelPGNetwork levelwin:Info templatePGSendArgs/ /events /provider /events /instrumentationManifest然后在用户态用EventRegister()监听PGSendEvent既规避KVAS限制又满足“过PG”需求——因为ETW事件在libpq调用send()前触发审计系统可在此刻插入自定义标记。技巧ETW方案无需驱动签名且支持Windows 7全版本。通过wevtutil qe PGNetworkChannel /q:*[System[(EventID1)]]即可实时捕获libpq发送行为比SSDT Hook更稳定、更易调试。本文还有配套的精品资源点击获取