WindowsAPI编程核心技术-SSDT Hook

SSDT入门

SSDT系统服务描述符表。SSDT把Ring3的WIN32 API函数与Ring0的内核API函数联系起来。Ring3下一些API最终对应于ntdll.dll里的一个以“Nt”为前缀的函数。例如CreateFile最终会调用ntdll.dll里的NtCreateFile函数,NtCreateFile将系统服务号放入EAX,再调用系统服务分发函数KiSystemService进入到内核中,这样完成一次系统服务调用。

SSDT Hook就是修改此表的函数地址,对常用的Windows函数进行挂钩,对一些核心系统动作进行过滤、监控。

获取SSDT函数索引号

ZwCreateSection

创建一个节对象。

1
2
3
4
5
6
7
8
9
NTSTATUS ZwCreateSection(
_Out_ PHANDLE SectionHandle, //接收段对象句柄
_In_ ACCESS_MASK DesiredAccess, //请求的对象访问 SECTION_MAP_READ SECTION_MAP_WRITE 读取/编写该部分视图
_In_opt_ POBJECT_ATTRIBTUES ObjectAttributes, //对象名称和其他属性
_In_opt_ PLARGE_INTEGER MaximumSize, //指定部分最大值 单位字节 此值将自动被转换为最接近PAGE_SIZE的倍数值 如果该部分由分页文件支持则指定该部分实际大小 由普通文件支持则指定可以扩展或映射到的最大值
_In_ ULONG SectionPageProtection, //每个页面要放置的保护 PAGE_READWRITE读写访问
_In_ ULONG AllocationAttributes, //分配属性 SEC_IMAGE以PE结构中SectionAlignment大小对齐映射文件。
_In_opt_ HANDLE FileHandle //打开文件对象的句柄 NULL由分页文件支持
)

ZwMapViewOfSection

将一个节表视图映射到内核虚拟地址空间。

1
2
3
4
5
6
7
8
9
10
11
12
NTSTATUS ZwMapViewOfSection(
_In_ HANDLE SectionHandle, //节对象句柄
_In_ HANDLE ProcessHandle, //视图应映射到进程的句柄
_Inout_ PVOID* BaseAddress, //接收视图基地址
_In_ ULONG_PTR ZeroBits, //指定截面视图基地址中必须为零的高位地址位数 仅当BaseAddress为NULL才使用
_In_ SIZE_T CommitSize, //视图初始提交区域大小 单位字节
_Inout_opt_ PLARGE_INTEGER SectionOffset, //从字节开始到视图结束以字节为单位的偏移量
_Inout_ PSIZE_T ViewSize, //视图大小
_In_ SECTION_INHERIT InheritDisposition, //视图如何与子进程共享 ViewShare该视图将映射到将来创建的任何子进程中 驱动一般设置为ViewUnmap不映射到子进程中
_In_ ULONG AllocationType, //分配类型 MEM_TOP_DOWN在尽可能高的地址分配内存
_In_ ULONG Win32Protect //最初提交的页面区域的保护类型 一般PAGE_READWRITE
)

例子

首先调用InitializeObjectAttributes宏初始化文件对象,它主要初始化文件路径(内核下加上前缀“/??/”),调用ZwOpenFile打开想要映射的文件,获得句柄。

然后调用ZwCreateSection生成一个内存映射队形并得到句柄。该内存映射对象对试图读写具有SECTION_MAP_READ和SECION_MAP_WRITE访问权限。同时设置内存映射方式为SEC_IMAGE(0下000000,以PE结构中SectionAlignment大小对齐映射文件)。

接着用ZwMapViewOfSecion把文件某个区域或整个区域映射到内存中,得到指向映射到内存的第一个字节的指针。接下来可直接用该指针读写内存,等同于直接修改文件数据。

最后用ZwUnmapViewOfSection解除文件映射,用ZwClose关闭内存映射文件,用ZwClose关闭文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, PHANDLE phFile, PHANDLE phSection, PVOID* ppBaseAddress) {
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 打开 DLL 文件, 并获取文件句柄
InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status)) {
KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
return status;
};
// 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, SEC_IMAGE, hFile);
if (!NT_SUCCESS(status)) {
ZwClose(hFile);
KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
return status;
};
// 映射到内存
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status)) {
ZwClose(hSection);
ZwClose(hFile);
KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
return status;
};
// 返回数据
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;
return status;
};

获取文件映射基址,根据PE结构找到IMAGE_DOS_HEADER和IMAGE_NT_HEADERS计算出OptionalHeader,获取DataDirectory中EAT的RVA地址,计算出EAT再内存中的地址。

根据EAT的结构IMAGE_EXPORT_DIRECTORY获取导出函数名称个数及导出函数名的地址。遍历找要查找的函数名称,若匹配则从AddressOfNamesOrdinal中获取导出函数名称对应的导出函数索引值,直接在AddressOfFunctions中导出函数地址表中获取导出函数的地址。

在x86下,ntdll.dll导出函数开头总为:

1
mov eax, 4字节索引号

在x64下开通总为:

1
2
mov r10,rcx
mov eax, 4字节索引号

x86读函数偏移1字节,x64偏移4字节,都读4字节数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName) {
ULONG ulFunctionIndex = 0;
// Dos Header
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT Header
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// Export Table
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名称的导出函数个数
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
// 导出函数名称地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 开始遍历导出表
for (ULONG i = 0; i < ulNumberOfNames; i++) {
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 判断是否查找的函数
if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName))) {
// 获取导出函数地址
USHORT uHint = *(PUSHORT)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 获取 SSDT 函数 Index
#ifdef _WIN64
ulFunctionIndex = *(PULONG)((PUCHAR)lpFuncAddr + 4);
#else
ulFunctionIndex = *(PULONG)((PUCHAR)lpFuncAddr + 1);
#endif
break;
};
};
return ulFunctionIndex;
};

源代码

Driver.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _DRIVER_H_
#define _DRIVER_H_


#include <ntddk.h>


VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp);


#endif

SSDTFunctionIndex.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef _SSDT_FUNCTION_INDEX_H_
#define _SSDT_FUNCTION_INDEX_H_


#include <ntddk.h>
#include <ntimage.h>


// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName);

// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress);

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName);


#endif

Driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "Driver.h"
#include "SSDTFunctionIndex.h"


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
DbgPrint("Enter DriverEntry\n");

NTSTATUS status = STATUS_SUCCESS;
pDriverObject->DriverUnload = DriverUnload;
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DriverDefaultHandle;
}

// 从 ntdll.dll 中获取 SSDT 函数索引号
UNICODE_STRING ustrDllFileName;
RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
ULONG ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, "ZwOpenProcess");
DbgPrint("ZwOpenProcess[%d]\n", ulSSDTFunctionIndex);

DbgPrint("Leave DriverEntry\n");
return status;
}



VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}


NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return status;
}

SSDTFunctionIndex.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "SSDTFunctionIndex.h"


// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;

// 内存映射文件
status = DllFileMap(ustrDllFileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
KdPrint(("DllFileMap Error!\n"));
return ulFunctionIndex;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ulFunctionIndex = GetIndexFromExportTable(pBaseAddress, pszFunctionName);

// 释放
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);

return ulFunctionIndex;
}


// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 打开 DLL 文件, 并获取文件句柄
InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status))
{
KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
return status;
}
// 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
if (!NT_SUCCESS(status))
{
ZwClose(hFile);
KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
return status;
}
// 映射到内存
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
ZwClose(hSection);
ZwClose(hFile);
KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
return status;
}

// 返回数据
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;

return status;
}


// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
// Dos Header
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT Header
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// Export Table
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名称的导出函数个数
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
// 导出函数名称地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 开始遍历导出表
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 判断是否查找的函数
if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)))
{
// 获取导出函数地址
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 获取 SSDT 函数 Index
#ifdef _WIN64
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
#else
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 1);
#endif
break;
}
}

return ulFunctionIndex;
}

获取SSDT及函数地址

x86下的实现

SSDT是Ntoskrnl.exe导出的一张表,导出符号为KeServiceDescriptorTable,该表带有一个指针指向SSDT且包含Ntoskrnl.exe实现的核心服务。

1
2
3
4
5
6
7
8
9
10
11
#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE{
PULONG ServiceTableBase; // SSDT基址
PULONG ServiceCounterTableBase; // SSDT中服务被调用次数计数器
ULONG NumberOfService; // SSDT服务个数
PUCHAR ParamTableBase; // 系统服务参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()

// 直接获取 SSDT
extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;

获取SSDT函数地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName) {
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pFunctionAddress = NULL;
RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);
// 根据索引号, 从SSDT表中获取对应函数地址
pFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];
// 显示
DbgPrint("[%s][Index:%d][Address:0x%p]\n", pszFunctionName, ulSSDTFunctionIndex, pFunctionAddress);
return pFunctionAddress;
};

x64下实现

微软没公布x64咋获取,只能自己去逆。

MSR特别模块寄存器,它控制CPU工作环境,标示CPU工作状态等信息,如倍频、最大TDP、危险警报温度,Ring0层能读能写。

用WinDBG调试内核函数KiSystemCall64中,有:

1
lea r10, [nt!KeServiceDescriptorTable]

这条代码可通过搜索特征码4C8D15来寻找,SSDT地址即为特征码地址+7+偏移量,KiSystemCall64的地址可通过读取第0xC0000082号MSR寄存器得到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 根据特征码, 从 KiSystemCall64 中获取 SSDT 地址
PVOID GetSSDTAddress(VOID) {
PVOID pServiceDescriptorTable = NULL;
PVOID pKiSystemCall64 = NULL;
UCHAR ulCode1 = 0;
UCHAR ulCode2 = 0;
UCHAR ulCode3 = 0;
// 注意使用有符号整型
LONG lOffset = 0;
// 获取 KiSystemCall64 函数地址
pKiSystemCall64 = (PVOID)__readmsr(0xC0000082);
// 搜索特征码 4C8D15
for (ULONG i = 0; i < 1024; i++) {
// 获取内存数据
ulCode1 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i));
ulCode2 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i + 1));
ulCode3 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i + 2));
// 判断
if (0x4C == ulCode1 && 0x8D == ulCode2 && 0x15 == ulCode3) {
// 获取偏移
lOffset = *((PLONG)((PUCHAR)pKiSystemCall64 + i + 3));
// 根据偏移计算地址
pServiceDescriptorTable = (PVOID)(((PUCHAR)pKiSystemCall64 + i) + 7 + lOffset);
break;
};
};
return pServiceDescriptorTable;
};

x64下SSDT结构:

1
2
3
4
5
6
7
8
#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE {
PULONG ServiceTableBase; // SSDT基址
PVOID ServiceCounterTableBase; // SSDT中服务被调用次数计数器
ULONGLONG NumberOfService; // SSDT服务个数 变成了8字节
PVOID ParamTableBase; // 系统服务参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()

SSDT函数地址的计算方法也有不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName) {
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pFunctionAddress = NULL;
PSSDTEntry pServiceDescriptorTable = NULL;
ULONG ulOffset = 0;
RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);
// 根据特征码, 从 KiSystemCall64 中获取 SSDT 地址
pServiceDescriptorTable = GetSSDTAddress();
// 根据索引号, 从SSDT表中获取对应函数偏移地址并计算出函数地址
ulOffset = pServiceDescriptorTable->ServiceTableBase[ulSSDTFunctionIndex] >> 4;
pFunctionAddress = (PVOID)((PUCHAR)pServiceDescriptorTable->ServiceTableBase + ulOffset);
// 显示
DbgPrint("[%s][SSDT Addr:0x%p][Index:%d][Address:0x%p]\n", pszFunctionName, pServiceDescriptorTable, ulSSDTFunctionIndex, pFunctionAddress);
return pFunctionAddress;
};

源代码

x86

Driver.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _DRIVER_H_
#define _DRIVER_H_


#include <ntddk.h>


VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp);


#endif

SSDTFunction.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef _SSDT_FUNCTION_H_
#define _SSDT_FUNCTION_H_


#include <ntddk.h>
#include <ntimage.h>


#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE
{
PULONG ServiceTableBase; // SSDT基址
PULONG ServiceCounterTableBase; // SSDT中服务被调用次数计数器
ULONG NumberOfService; // SSDT服务个数
PUCHAR ParamTableBase; // 系统服务参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()

// 直接获取 SSDT
extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName);

// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName);
// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress);
// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName);


#endif

Driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "Driver.h"
#include "SSDTFunction.h"


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
DbgPrint("Enter DriverEntry\n");

NTSTATUS status = STATUS_SUCCESS;
pDriverObject->DriverUnload = DriverUnload;
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DriverDefaultHandle;
}

// 获取 SSDT 函数地址
GetSSDTFunction("NtOpenProcess");

DbgPrint("Leave DriverEntry\n");
return status;
}



VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}


NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return status;
}

SSDTFunction.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include "SSDTFunction.h"


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName)
{
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pFunctionAddress = NULL;

RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);

// 根据索引号, 从SSDT表中获取对应函数地址
pFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];

// 显示
DbgPrint("[%s][Index:%d][Address:0x%p]\n", pszFunctionName, ulSSDTFunctionIndex, pFunctionAddress);

return pFunctionAddress;
}


// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;

// 内存映射文件
status = DllFileMap(ustrDllFileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
KdPrint(("DllFileMap Error!\n"));
return ulFunctionIndex;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ulFunctionIndex = GetIndexFromExportTable(pBaseAddress, pszFunctionName);

// 释放
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);

return ulFunctionIndex;
}

// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 打开 DLL 文件, 并获取文件句柄
InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status))
{
KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
return status;
}
// 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
if (!NT_SUCCESS(status))
{
ZwClose(hFile);
KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
return status;
}
// 映射到内存
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
ZwClose(hSection);
ZwClose(hFile);
KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
return status;
}

// 返回数据
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;

return status;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
// Dos Header
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT Header
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// Export Table
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名称的导出函数个数
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
// 导出函数名称地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 开始遍历导出表
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 判断是否查找的函数
if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)))
{
// 获取导出函数地址
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 获取 SSDT 函数 Index
#ifdef _WIN64
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
#else
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 1);
#endif
break;
}
}

return ulFunctionIndex;
}

x64

Driver.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _DRIVER_H_
#define _DRIVER_H_


#include <ntddk.h>


VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp);


#endif

SSDTFunction.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef _SSDT_FUNCTION_H_
#define _SSDT_FUNCTION_H_


#include <ntddk.h>
#include <ntimage.h>


#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE
{
PULONG ServiceTableBase; // SSDT基址
PVOID ServiceCounterTableBase; // SSDT中服务被调用次数计数器
ULONGLONG NumberOfService; // SSDT服务个数
PVOID ParamTableBase; // 系统服务参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName);

// 根据特征码, 从 KiSystemCall64 中获取 SSDT 地址
PVOID GetSSDTAddress();

// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName);
// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress);
// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName);


#endif

Driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "Driver.h"
#include "SSDTFunction.h"


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
DbgPrint("Enter DriverEntry\n");

NTSTATUS status = STATUS_SUCCESS;
pDriverObject->DriverUnload = DriverUnload;
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DriverDefaultHandle;
}

// 获取 SSDT 函数地址
GetSSDTFunction("NtOpenProcess");

DbgPrint("Leave DriverEntry\n");
return status;
}



VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
}


NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return status;
}

SSDTFunction.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include "SSDTFunction.h"


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName)
{
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pFunctionAddress = NULL;
PSSDTEntry pServiceDescriptorTable = NULL;
ULONG ulOffset = 0;

RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);

// 根据特征码, 从 KiSystemCall64 中获取 SSDT 地址
pServiceDescriptorTable = GetSSDTAddress();

// 根据索引号, 从SSDT表中获取对应函数偏移地址并计算出函数地址
ulOffset = pServiceDescriptorTable->ServiceTableBase[ulSSDTFunctionIndex] >> 4;
pFunctionAddress = (PVOID)((PUCHAR)pServiceDescriptorTable->ServiceTableBase + ulOffset);

// 显示
DbgPrint("[%s][SSDT Addr:0x%p][Index:%d][Address:0x%p]\n", pszFunctionName, pServiceDescriptorTable, ulSSDTFunctionIndex, pFunctionAddress);

return pFunctionAddress;
}


// 根据特征码, 从 KiSystemCall64 中获取 SSDT 地址
PVOID GetSSDTAddress()
{
PVOID pServiceDescriptorTable = NULL;
PVOID pKiSystemCall64 = NULL;
UCHAR ulCode1 = 0;
UCHAR ulCode2 = 0;
UCHAR ulCode3 = 0;
// 注意使用有符号整型
LONG lOffset = 0;

// 获取 KiSystemCall64 函数地址
pKiSystemCall64 = (PVOID)__readmsr(0xC0000082);
// 搜索特征码 4C8D15
for (ULONG i = 0; i < 1024; i++)
{
// 获取内存数据
ulCode1 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i));
ulCode2 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i + 1));
ulCode3 = *((PUCHAR)((PUCHAR)pKiSystemCall64 + i + 2));
// 判断
if (0x4C == ulCode1 &&
0x8D == ulCode2 &&
0x15 == ulCode3)
{
// 获取偏移
lOffset = *((PLONG)((PUCHAR)pKiSystemCall64 + i + 3));
// 根据偏移计算地址
pServiceDescriptorTable = (PVOID)(((PUCHAR)pKiSystemCall64 + i) + 7 + lOffset);
break;
}
}

return pServiceDescriptorTable;
}


// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;

// 内存映射文件
status = DllFileMap(ustrDllFileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
KdPrint(("DllFileMap Error!\n"));
return ulFunctionIndex;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ulFunctionIndex = GetIndexFromExportTable(pBaseAddress, pszFunctionName);

// 释放
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);

return ulFunctionIndex;
}

// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 打开 DLL 文件, 并获取文件句柄
InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status))
{
KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
return status;
}
// 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
if (!NT_SUCCESS(status))
{
ZwClose(hFile);
KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
return status;
}
// 映射到内存
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
ZwClose(hSection);
ZwClose(hFile);
KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
return status;
}

// 返回数据
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;

return status;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
// Dos Header
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT Header
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// Export Table
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名称的导出函数个数
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
// 导出函数名称地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 开始遍历导出表
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 判断是否查找的函数
if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)))
{
// 获取导出函数地址
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 获取 SSDT 函数 Index
#ifdef _WIN64
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
#else
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 1);
#endif
break;
}
}

return ulFunctionIndex;
}

SSDT HOOK

SSDT HOOK只能在x86下有效,x64下有Patch Guard机制,重点保护SSDT的内存,想用必须想办法绕过Patch Guard机制。本节以隐藏文件为例。

将SSDT存储的ZwQueryDirectoryFile函数地址改为新函数的地址。因为SSDT内存有写保护,采用MDL方式可以绕过写保护。下面是HOOK的实现部分,文件摘链操作看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// SSDT Hook
BOOLEAN SSDTHook(VOID) {
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PMDL pMdl = NULL;
PVOID pNewAddress = NULL;
ULONG ulNewFuncAddr = 0;
RtlInitUnicodeString(&ustrDllFileName, TEXT("\\??\\C:\\Windows\\System32\\ntdll.dll"));
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, "ZwQueryDirectoryFile");
// 根据索引号, 从SSDT表中获取对应函数地址
g_pOldSSDTFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];
if (NULL == g_pOldSSDTFunctionAddress) {
DbgPrint("Get SSDT Function Error!\n");
return FALSE;
};
// 使用 MDL 方式修改 SSDT
pMdl = MmCreateMdl(NULL, &KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex], sizeof(ULONG));
if (NULL == pMdl) {
DbgPrint("MmCreateMdl Error!\n");
return FALSE;
};
MmBuildMdlForNonPagedPool(pMdl);
pNewAddress = MmMapLockedPages(pMdl, KernelMode);
if (NULL == pNewAddress) {
IoFreeMdl(pMdl);
DbgPrint("MmMapLockedPages Error!\n");
return FALSE;
};
// 写入新函数地址
ulNewFuncAddr = (ULONG)New_ZwQueryDirectoryFile;
RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));
// 释放
MmUnmapLockedPages(pNewAddress, pMdl);
IoFreeMdl(pMdl);
return TRUE;
};

源代码

Driver.h:

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _DRIVER_H_
#define _DRIVER_H_


#include <ntddk.h>


VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp);


#endif

SSDTFunction.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef _SSDT_FUNCTION_H_
#define _SSDT_FUNCTION_H_


#include <ntddk.h>
#include <ntimage.h>


#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE
{
PULONG ServiceTableBase; // SSDT基址
PULONG ServiceCounterTableBase; // SSDT中服务被调用次数计数器
ULONG NumberOfService; // SSDT服务个数
PUCHAR ParamTableBase; // 系统服务参数表基址
}SSDTEntry, *PSSDTEntry;
#pragma pack()

// 直接获取 SSDT
extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName);

// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName);
// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress);
// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName);


#endif

SSDTHook.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#ifndef _SSDT_HOOK_H_
#define _SSDT_HOOK_H_


#include <ntifs.h>


// SSDT Hook
BOOLEAN SSDTHook();

// SSDT Unhook
BOOLEAN SSDTUnhook();

// 新函数
NTSTATUS New_ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileMask OPTIONAL,
IN BOOLEAN RestartScan
);

// 从各种文件信息类型中获取文件名称
VOID GetEntryFileName(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo, PWCHAR pwszFileName, ULONG ulBufferSize);

// 在各种文件信息类型中设置下一个文件的偏移
VOID SetNextEntryOffset(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo, IN ULONG Offset);

// 从各种文件信息类型中获取下一个文件的偏移
ULONG GetNextEntryOffset(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo);


// 保存原函数地址
PVOID g_pOldSSDTFunctionAddress;


#endif

Driver.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "SSDTHook.h"
#include "Driver.h"


NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
DbgPrint("Enter DriverEntry\n");

NTSTATUS status = STATUS_SUCCESS;
pDriverObject->DriverUnload = DriverUnload;
for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DriverDefaultHandle;
}

// SSDT Hook
SSDTHook();

DbgPrint("Leave DriverEntry\n");
return status;
}



VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
// SSDT Unhook
SSDTUnhook();
}


NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);

return status;
}

SSDTFunction.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include "SSDTFunction.h"


// 获取 SSDT 函数地址
PVOID GetSSDTFunction(PCHAR pszFunctionName)
{
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pFunctionAddress = NULL;

RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, pszFunctionName);

// 根据索引号, 从SSDT表中获取对应函数地址
pFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];

// 显示
// DbgPrint("[%s][Index:%d][Address:0x%p]\n", pszFunctionName, ulSSDTFunctionIndex, pFunctionAddress);

return pFunctionAddress;
}


// 从 ntdll.dll 中获取 SSDT 函数索引号
ULONG GetSSDTFunctionIndex(UNICODE_STRING ustrDllFileName, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;

// 内存映射文件
status = DllFileMap(ustrDllFileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
KdPrint(("DllFileMap Error!\n"));
return ulFunctionIndex;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ulFunctionIndex = GetIndexFromExportTable(pBaseAddress, pszFunctionName);

// 释放
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);

return ulFunctionIndex;
}

// 内存映射文件
NTSTATUS DllFileMap(UNICODE_STRING ustrDllFileName, HANDLE *phFile, HANDLE *phSection, PVOID *ppBaseAddress)
{
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
OBJECT_ATTRIBUTES objectAttributes = { 0 };
IO_STATUS_BLOCK iosb = { 0 };
PVOID pBaseAddress = NULL;
SIZE_T viewSize = 0;
// 打开 DLL 文件, 并获取文件句柄
InitializeObjectAttributes(&objectAttributes, &ustrDllFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenFile(&hFile, GENERIC_READ, &objectAttributes, &iosb,
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(status))
{
KdPrint(("ZwOpenFile Error! [error code: 0x%X]", status));
return status;
}
// 创建一个节对象, 以 PE 结构中的 SectionALignment 大小对齐映射文件
status = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
if (!NT_SUCCESS(status))
{
ZwClose(hFile);
KdPrint(("ZwCreateSection Error! [error code: 0x%X]", status));
return status;
}
// 映射到内存
status = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddress, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
if (!NT_SUCCESS(status))
{
ZwClose(hSection);
ZwClose(hFile);
KdPrint(("ZwMapViewOfSection Error! [error code: 0x%X]", status));
return status;
}

// 返回数据
*phFile = hFile;
*phSection = hSection;
*ppBaseAddress = pBaseAddress;

return status;
}

// 根据导出表获取导出函数地址, 从而获取 SSDT 函数索引号
ULONG GetIndexFromExportTable(PVOID pBaseAddress, PCHAR pszFunctionName)
{
ULONG ulFunctionIndex = 0;
// Dos Header
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
// NT Header
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
// Export Table
PIMAGE_EXPORT_DIRECTORY pExportTable = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[0].VirtualAddress);
// 有名称的导出函数个数
ULONG ulNumberOfNames = pExportTable->NumberOfNames;
// 导出函数名称地址表
PULONG lpNameArray = (PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfNames);
PCHAR lpName = NULL;
// 开始遍历导出表
for (ULONG i = 0; i < ulNumberOfNames; i++)
{
lpName = (PCHAR)((PUCHAR)pDosHeader + lpNameArray[i]);
// 判断是否查找的函数
if (0 == _strnicmp(pszFunctionName, lpName, strlen(pszFunctionName)))
{
// 获取导出函数地址
USHORT uHint = *(USHORT *)((PUCHAR)pDosHeader + pExportTable->AddressOfNameOrdinals + 2 * i);
ULONG ulFuncAddr = *(PULONG)((PUCHAR)pDosHeader + pExportTable->AddressOfFunctions + 4 * uHint);
PVOID lpFuncAddr = (PVOID)((PUCHAR)pDosHeader + ulFuncAddr);
// 获取 SSDT 函数 Index
#ifdef _WIN64
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 4);
#else
ulFunctionIndex = *(ULONG *)((PUCHAR)lpFuncAddr + 1);
#endif
break;
}
}

return ulFunctionIndex;
}

SSDTHook.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#include "SSDTHook.h"
#include "SSDTFunction.h"


// SSDT Hook
BOOLEAN SSDTHook()
{
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PMDL pMdl = NULL;
PVOID pNewAddress = NULL;
ULONG ulNewFuncAddr = 0;

RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, "ZwQueryDirectoryFile");
// 根据索引号, 从SSDT表中获取对应函数地址
g_pOldSSDTFunctionAddress = (PVOID)KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex];
if (NULL == g_pOldSSDTFunctionAddress)
{
DbgPrint("Get SSDT Function Error!\n");
return FALSE;
}
// 使用 MDL 方式修改 SSDT
pMdl = MmCreateMdl(NULL, &KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex], sizeof(ULONG));
if (NULL == pMdl)
{
DbgPrint("MmCreateMdl Error!\n");
return FALSE;
}
MmBuildMdlForNonPagedPool(pMdl);
pNewAddress = MmMapLockedPages(pMdl, KernelMode);
if (NULL == pNewAddress)
{
IoFreeMdl(pMdl);
DbgPrint("MmMapLockedPages Error!\n");
return FALSE;
}
// 写入新函数地址
ulNewFuncAddr = (ULONG)New_ZwQueryDirectoryFile;
RtlCopyMemory(pNewAddress, &ulNewFuncAddr, sizeof(ULONG));

// 释放
MmUnmapLockedPages(pNewAddress, pMdl);
IoFreeMdl(pMdl);

return TRUE;
}


// SSDT Unhook
BOOLEAN SSDTUnhook()
{
UNICODE_STRING ustrDllFileName;
ULONG ulSSDTFunctionIndex = 0;
PVOID pSSDTFunctionAddress = NULL;
PMDL pMdl = NULL;
PVOID pNewAddress = NULL;
ULONG ulOldFuncAddr = 0;

RtlInitUnicodeString(&ustrDllFileName, L"\\??\\C:\\Windows\\System32\\ntdll.dll");
// 从 ntdll.dll 中获取 SSDT 函数索引号
ulSSDTFunctionIndex = GetSSDTFunctionIndex(ustrDllFileName, "ZwQueryDirectoryFile");
// 使用 MDL 方式修改 SSDT
pMdl = MmCreateMdl(NULL, &KeServiceDescriptorTable.ServiceTableBase[ulSSDTFunctionIndex], sizeof(ULONG));
if (NULL == pMdl)
{
DbgPrint("MmCreateMdl Error!\n");
return FALSE;
}
MmBuildMdlForNonPagedPool(pMdl);
pNewAddress = MmMapLockedPages(pMdl, KernelMode);
if (NULL == pNewAddress)
{
IoFreeMdl(pMdl);
DbgPrint("MmMapLockedPages Error!\n");
return FALSE;
}
// 写入原函数地址
ulOldFuncAddr = (ULONG)g_pOldSSDTFunctionAddress;
RtlCopyMemory(pNewAddress, &ulOldFuncAddr, sizeof(ULONG));

// 释放
MmUnmapLockedPages(pNewAddress, pMdl);
IoFreeMdl(pMdl);

return TRUE;
}


// 新函数
NTSTATUS New_ZwQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileMask OPTIONAL,
IN BOOLEAN RestartScan
)
{
NTSTATUS status = STATUS_SUCCESS;
typedef NTSTATUS(*typedef_ZwQueryDirectoryFile)(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileMask OPTIONAL,
IN BOOLEAN RestartScan
);
// 执行原函数
status = ((typedef_ZwQueryDirectoryFile)g_pOldSSDTFunctionAddress)(FileHandle,
Event,
ApcRoutine,
ApcContext,
IoStatusBlock,
FileInformation,
Length,
FileInformationClass,
ReturnSingleEntry,
FileMask,
RestartScan);

//这里判定函数是否执行成功,而且获取的是否是文件或目录
if (NT_SUCCESS(status) && (
FileInformationClass == FileDirectoryInformation ||
FileInformationClass == FileFullDirectoryInformation ||
FileInformationClass == FileIdFullDirectoryInformation ||
FileInformationClass == FileBothDirectoryInformation ||
FileInformationClass == FileIdBothDirectoryInformation ||
FileInformationClass == FileNamesInformation
))
{
PVOID pCurrent = FileInformation;
PVOID pPre = NULL;
ULONG ulNextOffset = 0;
ULONG ulBufferSize = 1024;
PWCHAR pwszFileName = ExAllocatePool(NonPagedPool, ulBufferSize);
if (NULL == pwszFileName)
{
return status;
}

do
{
// 获取下一个文件信息的偏移
ulNextOffset = GetNextEntryOffset(pCurrent, FileInformationClass);
// 获取当前节点的文件名称
RtlZeroMemory(pwszFileName, ulBufferSize);
GetEntryFileName(pCurrent, FileInformationClass, pwszFileName, ulBufferSize);
DbgPrint("[%S]\n", pwszFileName);
// 隐藏指定的名称的文件或者目录
if (NULL != wcsstr(pwszFileName, L"520.exe"))
{
DbgPrint("Have Hide File Or Directory![%S]\n", pwszFileName);

// 如果是最后一个文件信息
if (0 == ulNextOffset)
{
// 判断是否为第一个文件
if (NULL == pPre)
{
status = STATUS_NO_MORE_FILES;
}
else
{
// 将上一个文件信息的下一文件信息偏移大小置为 0
SetNextEntryOffset(pPre, FileInformationClass, 0);
}
break;
}
else
{
// 把剩下的文件信息覆盖到当前文件信息中
ULONG ulCurrentOffset = (ULONG)((PUCHAR)pCurrent - (PUCHAR)FileInformation);
ULONG ulLeftInfoData = (ULONG)Length - (ulCurrentOffset + ulNextOffset);
RtlCopyMemory(pCurrent, (PVOID)((PUCHAR)pCurrent + ulNextOffset), ulLeftInfoData);

continue;
}
}
// 继续遍历
pPre = pCurrent;
pCurrent = ((PUCHAR)pCurrent + ulNextOffset);
} while (0 != ulNextOffset);

// 释放
if (pwszFileName)
{
ExFreePool(pwszFileName);
pwszFileName = NULL;
}
}

return status;
}


// 从各种文件信息类型中获取文件名称
VOID GetEntryFileName(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo, PWCHAR pwszFileName, ULONG ulBufferSize)
{
PWCHAR result = NULL;
ULONG ulLength = 0;

switch (FileInfo)
{
case FileDirectoryInformation:
result = (PWCHAR)&((PFILE_DIRECTORY_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_DIRECTORY_INFORMATION)pData)->FileNameLength;
break;
case FileFullDirectoryInformation:
result = (PWCHAR)&((PFILE_FULL_DIR_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_FULL_DIR_INFORMATION)pData)->FileNameLength;
break;
case FileIdFullDirectoryInformation:
result = (PWCHAR)&((PFILE_ID_FULL_DIR_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_ID_FULL_DIR_INFORMATION)pData)->FileNameLength;
break;
case FileBothDirectoryInformation:
result = (PWCHAR)&((PFILE_BOTH_DIR_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_BOTH_DIR_INFORMATION)pData)->FileNameLength;
break;
case FileIdBothDirectoryInformation:
result = (PWCHAR)&((PFILE_ID_BOTH_DIR_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_ID_BOTH_DIR_INFORMATION)pData)->FileNameLength;
break;
case FileNamesInformation:
result = (PWCHAR)&((PFILE_NAMES_INFORMATION)pData)->FileName[0];
ulLength = ((PFILE_NAMES_INFORMATION)pData)->FileNameLength;
break;
}

RtlZeroMemory(pwszFileName, ulBufferSize);
RtlCopyMemory(pwszFileName, result, ulLength);
}


// 在各种文件信息类型中设置下一个文件的偏移
VOID SetNextEntryOffset(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo, IN ULONG Offset)
{
switch (FileInfo)
{
case FileDirectoryInformation:
((PFILE_DIRECTORY_INFORMATION)pData)->NextEntryOffset = Offset;
break;
case FileFullDirectoryInformation:
((PFILE_FULL_DIR_INFORMATION)pData)->NextEntryOffset = Offset;
break;
case FileIdFullDirectoryInformation:
((PFILE_ID_FULL_DIR_INFORMATION)pData)->NextEntryOffset = Offset;
break;
case FileBothDirectoryInformation:
((PFILE_BOTH_DIR_INFORMATION)pData)->NextEntryOffset = Offset;
break;
case FileIdBothDirectoryInformation:
((PFILE_ID_BOTH_DIR_INFORMATION)pData)->NextEntryOffset = Offset;
break;
case FileNamesInformation:
((PFILE_NAMES_INFORMATION)pData)->NextEntryOffset = Offset;
break;
}
}


// 从各种文件信息类型中获取下一个文件的偏移
ULONG GetNextEntryOffset(IN PVOID pData, IN FILE_INFORMATION_CLASS FileInfo)
{
ULONG result = 0;
switch (FileInfo){
case FileDirectoryInformation:
result = ((PFILE_DIRECTORY_INFORMATION)pData)->NextEntryOffset;
break;
case FileFullDirectoryInformation:
result = ((PFILE_FULL_DIR_INFORMATION)pData)->NextEntryOffset;
break;
case FileIdFullDirectoryInformation:
result = ((PFILE_ID_FULL_DIR_INFORMATION)pData)->NextEntryOffset;
break;
case FileBothDirectoryInformation:
result = ((PFILE_BOTH_DIR_INFORMATION)pData)->NextEntryOffset;
break;
case FileIdBothDirectoryInformation:
result = ((PFILE_ID_BOTH_DIR_INFORMATION)pData)->NextEntryOffset;
break;
case FileNamesInformation:
result = ((PFILE_NAMES_INFORMATION)pData)->NextEntryOffset;
break;
}
return result;
}