1、课程目标
- 理解Windows系统调用机制
- 掌握SSDT和SSDT Shadow的结构
- 学会分析系统调用流程
- 理解系统调用在安全中的应用
2、名词解释
| 术语 |
解释 |
| SSDT |
System Service Descriptor Table系统服务描述符表 |
| SSDT Shadow |
GUI线程的系统服务表 |
| syscall |
x64系统调用指令 |
| MSR |
Model Specific Register特殊寄存器 |
| LSTAR |
系统调用入口点MSR寄存器 |
3、使用工具
| 工具 |
用途 |
| WinDbg |
分析SSDT |
| IDA Pro |
逆向分析 |
| PCHunter |
查看SSDT |
4、技术原理
4.1、系统调用流程
┌─────────────────────────────────────────────────────────────┐
│ 系统调用流程 (x64) │
│ │
│ 用户模式 (Ring3) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ NtCreateFile() │ │
│ │ ↓ │ │
│ │ mov r10, rcx │ │
│ │ mov eax, <syscall_number> ; 例如 0x55 │ │
│ │ syscall ; 进入内核 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ───────────── Ring0/Ring3 边界 ──────────────── │
│ │ │
│ ↓ │
│ 内核模式 (Ring0) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ KiSystemCall64 (MSR LSTAR) │ │
│ │ ↓ │ │
│ │ KiSystemServiceRepeat │ │
│ │ ↓ │ │
│ │ 查找 SSDT[syscall_number] │ │
│ │ ↓ │ │
│ │ 调用 NtCreateFile (内核实现) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.2、SSDT结构
// SSDT结构定义
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
PULONG_PTR Base; // 服务函数表基址
PULONG Count; // 计数(未使用)
ULONG Limit; // 服务数量
PUCHAR Number; // 参数字节数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
// x64 SSDT表项编码
// 低4位:参数栈空间/4
// 高28位:相对偏移
// 实际地址 = Base + (TableEntry >> 4)
5、代码实现
5.1、示例1:获取SSDT基址
// SSDTUtils.c - SSDT工具函数
#include <ntddk.h>
typedef struct _KSERVICE_TABLE_DESCRIPTOR {
PULONG_PTR Base;
PULONG Count;
ULONG Limit;
PUCHAR Number;
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
// 全局变量
PKSERVICE_TABLE_DESCRIPTOR g_KeServiceDescriptorTable = NULL;
// 通过特征码搜索SSDT
PVOID FindSSDT() {
// 方法1:通过导出符号(只有32位有效)
// extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
// 方法2:通过KiSystemServiceRepeat特征码搜索
UNICODE_STRING funcName;
RtlInitUnicodeString(&funcName, L"KeAddSystemServiceTable");
PUCHAR funcAddr = (PUCHAR)MmGetSystemRoutineAddress(&funcName);
if (!funcAddr) return NULL;
// 在函数附近搜索SSDT引用
// 这是一个简化的示例,实际需要更复杂的特征匹配
for (ULONG i = 0; i < 0x100; i++) {
// 搜索lea指令模式
if (funcAddr[i] == 0x4C && funcAddr[i+1] == 0x8D) {
// 找到可能的SSDT引用
LONG offset = *(PLONG)(funcAddr + i + 3);
PVOID ssdt = funcAddr + i + 7 + offset;
return ssdt;
}
}
return NULL;
}
// 获取系统调用地址
PVOID GetSyscallAddress(ULONG SyscallNumber) {
if (!g_KeServiceDescriptorTable) {
g_KeServiceDescriptorTable = (PKSERVICE_TABLE_DESCRIPTOR)FindSSDT();
if (!g_KeServiceDescriptorTable) {
return NULL;
}
}
if (SyscallNumber >= g_KeServiceDescriptorTable->Limit) {
return NULL;
}
#ifdef _WIN64
// x64: 表项是相对偏移
LONG offset = g_KeServiceDescriptorTable->Base[SyscallNumber];
offset = offset >> 4; // 去掉参数信息
return (PVOID)((PUCHAR)g_KeServiceDescriptorTable->Base + offset);
#else
// x86: 表项是绝对地址
return (PVOID)g_KeServiceDescriptorTable->Base[SyscallNumber];
#endif
}
// 获取系统调用号
ULONG GetSyscallNumber(PVOID FunctionAddress) {
// 从ntdll中的存根函数获取系统调用号
// mov eax, <syscall_number>
// syscall / sysenter / int 2e
PUCHAR code = (PUCHAR)FunctionAddress;
// 检查是否是mov eax, imm32指令
if (code[0] == 0xB8) {
return *(PULONG)(code + 1);
}
// x64 ntdll pattern: mov r10, rcx; mov eax, imm32
if (code[0] == 0x4C && code[1] == 0x8B && code[2] == 0xD1 &&
code[3] == 0xB8) {
return *(PULONG)(code + 4);
}
return (ULONG)-1;
}
5.2、示例2:枚举SSDT
// SSDTEnum.c - 枚举SSDT
#include <ntddk.h>
typedef struct _SSDT_ENTRY {
ULONG Index;
PVOID Address;
CHAR Name[64];
} SSDT_ENTRY, *PSSDT_ENTRY;
// 已知的系统调用名称(部分)
const char* g_SyscallNames[] = {
"NtAccessCheck",
"NtWorkerFactoryWorkerReady",
"NtAcceptConnectPort",
"NtMapUserPhysicalPagesScatter",
"NtWaitForSingleObject",
"NtCallbackReturn",
"NtReadFile",
"NtDeviceIoControlFile",
"NtWriteFile",
"NtRemoveIoCompletion",
"NtReleaseSemaphore",
"NtReplyWaitReceivePort",
"NtReplyPort",
"NtSetInformationThread",
"NtSetEvent",
"NtClose",
// ... 更多
};
// 枚举SSDT
VOID EnumerateSSDT() {
PKSERVICE_TABLE_DESCRIPTOR ssdt = (PKSERVICE_TABLE_DESCRIPTOR)FindSSDT();
if (!ssdt) {
DbgPrint("[SSDT] Failed to find SSDT\n");
return;
}
DbgPrint("[SSDT] Base: 0x%p\n", ssdt->Base);
DbgPrint("[SSDT] Limit: %d\n", ssdt->Limit);
for (ULONG i = 0; i < min(ssdt->Limit, 50); i++) {
#ifdef _WIN64
LONG offset = ((PLONG)ssdt->Base)[i] >> 4;
PVOID funcAddr = (PVOID)((PUCHAR)ssdt->Base + offset);
#else
PVOID funcAddr = (PVOID)ssdt->Base[i];
#endif
DbgPrint("[SSDT] [%3d] 0x%p\n", i, funcAddr);
}
}
// 检查SSDT是否被HOOK
BOOLEAN CheckSSDTHook(ULONG SyscallNumber, PVOID ExpectedAddress) {
PVOID actualAddress = GetSyscallAddress(SyscallNumber);
if (actualAddress != ExpectedAddress) {
DbgPrint("[SSDT] HOOK detected! Syscall %d\n", SyscallNumber);
DbgPrint("[SSDT] Expected: 0x%p\n", ExpectedAddress);
DbgPrint("[SSDT] Actual: 0x%p\n", actualAddress);
return TRUE;
}
return FALSE;
}
// 验证SSDT地址是否在ntoskrnl范围内
BOOLEAN ValidateSSDTEntry(PVOID Address) {
// 获取ntoskrnl的地址范围
PVOID ntosBase = NULL;
ULONG ntosSize = 0;
// 通过ZwQuerySystemInformation获取模块列表
// 检查地址是否在ntoskrnl范围内
UNICODE_STRING funcName;
RtlInitUnicodeString(&funcName, L"NtClose");
PVOID ntosAddr = MmGetSystemRoutineAddress(&funcName);
if (!ntosAddr) return FALSE;
// 简化检查:地址应该在内核空间
if ((ULONG_PTR)Address < (ULONG_PTR)0xFFFF800000000000ULL) {
return FALSE;
}
return TRUE;
}
5.3、示例3:系统调用监控
// SyscallMonitor.c - 系统调用监控
#include <ntddk.h>
// 使用Infinity Hook或类似技术监控系统调用
// 这里展示基本原理
typedef NTSTATUS (*PNTOPENPROCESS)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);
PNTOPENPROCESS g_OriginalNtOpenProcess = NULL;
// Hook函数
NTSTATUS HookedNtOpenProcess(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
) {
// 记录调用信息
if (ClientId && ClientId->UniqueProcess) {
HANDLE targetPid = ClientId->UniqueProcess;
HANDLE currentPid = PsGetCurrentProcessId();
DbgPrint("[Syscall] NtOpenProcess: PID %d -> %d, Access: 0x%X\n",
(ULONG)(ULONG_PTR)currentPid,
(ULONG)(ULONG_PTR)targetPid,
DesiredAccess);
// 可以在这里添加拦截逻辑
// if (IsProtectedProcess(targetPid)) {
// return STATUS_ACCESS_DENIED;
// }
}
// 调用原始函数
return g_OriginalNtOpenProcess(ProcessHandle, DesiredAccess,
ObjectAttributes, ClientId);
}
// 注意:直接修改SSDT在x64上会触发PatchGuard
// 需要使用其他技术如Infinity Hook
5.4、示例4:通过MSR获取系统调用入口
// MSRSyscall.c - MSR系统调用入口
#include <ntddk.h>
#define MSR_LSTAR 0xC0000082 // x64 syscall入口
#define MSR_CSTAR 0xC0000083 // x64 compat syscall入口
#define MSR_STAR 0xC0000081 // syscall segments
// 读取MSR
ULONG64 ReadMSR(ULONG msr) {
return __readmsr(msr);
}
// 获取系统调用入口点
PVOID GetSyscallEntryPoint() {
return (PVOID)ReadMSR(MSR_LSTAR);
}
// 分析系统调用入口
VOID AnalyzeSyscallEntry() {
PVOID entryPoint = GetSyscallEntryPoint();
DbgPrint("[MSR] LSTAR: 0x%p (KiSystemCall64)\n", entryPoint);
DbgPrint("[MSR] STAR: 0x%llX\n", ReadMSR(MSR_STAR));
// 检查入口点是否被修改
// 正常应该指向KiSystemCall64
UNICODE_STRING funcName;
RtlInitUnicodeString(&funcName, L"KeQueryPerformanceCounter");
PVOID ntosAddr = MmGetSystemRoutineAddress(&funcName);
// 简单验证:检查是否在ntoskrnl范围
// 完整验证需要检查模块边界
}
// 检测MSR Hook
BOOLEAN DetectMSRHook() {
PVOID currentEntry = GetSyscallEntryPoint();
// 可以保存原始值在驱动加载时
// 然后定期检查是否被修改
// 简化检查
PUCHAR code = (PUCHAR)currentEntry;
// KiSystemCall64正常开始:swapgs
if (code[0] != 0x0F || code[1] != 0x01 || code[2] != 0xF8) {
DbgPrint("[MSR] Syscall entry may be hooked!\n");
return TRUE;
}
return FALSE;
}
5.5、示例5:用户模式系统调用号提取
// SyscallExtract.c - 从ntdll提取系统调用号
#include <windows.h>
#include <stdio.h>
typedef struct _SYSCALL_INFO {
char Name[64];
DWORD Number;
PVOID Address;
} SYSCALL_INFO;
// 从ntdll提取系统调用号
DWORD GetSyscallNumber(PVOID FuncAddress) {
PBYTE code = (PBYTE)FuncAddress;
// x64 ntdll stub pattern:
// 4C 8B D1 mov r10, rcx
// B8 XX XX XX XX mov eax, syscall_number
// 0F 05 syscall
// C3 ret
if (code[0] == 0x4C && code[1] == 0x8B && code[2] == 0xD1 &&
code[3] == 0xB8) {
return *(DWORD*)(code + 4);
}
// Win7 x64 pattern (different)
if (code[0] == 0xB8) {
return *(DWORD*)(code + 1);
}
return -1;
}
void ExtractNtdllSyscalls() {
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
if (!hNtdll) return;
// 常见系统调用列表
const char* syscalls[] = {
"NtOpenProcess",
"NtReadVirtualMemory",
"NtWriteVirtualMemory",
"NtAllocateVirtualMemory",
"NtProtectVirtualMemory",
"NtCreateThreadEx",
"NtQuerySystemInformation",
"NtQueryInformationProcess",
"NtSetInformationProcess",
"NtCreateFile",
"NtClose"
};
printf("Syscall Numbers:\n");
printf("%-30s %-10s %s\n", "Name", "Number", "Address");
printf("----------------------------------------------\n");
for (int i = 0; i < sizeof(syscalls)/sizeof(syscalls[0]); i++) {
PVOID addr = GetProcAddress(hNtdll, syscalls[i]);
if (addr) {
DWORD num = GetSyscallNumber(addr);
printf("%-30s 0x%-8X 0x%p\n", syscalls[i], num, addr);
}
}
}
int main() {
ExtractNtdllSyscalls();
return 0;
}
6、课后作业
- 编写程序枚举完整的SSDT
- 实现SSDT Hook检测功能
- 分析不同Windows版本的系统调用号变化
- 研究Infinity Hook原理
7、扩展阅读
- Windows系统调用详解
- SSDT Hook防护与检测
- PatchGuard机制分析