0%

在内核中获取SSDT地址和函数索引(2)

简介

在32位系统中SSDT已经直接导出,这里主要是在64位系统中获取SSDT地址,
同样参照 Blackbone 的部分代码进行修改:https://github.com/DarthTon/Blackbone

工作原理

1.获取系统模块地址

与上一篇文章中获取进程的DLL类似,这里使用 ZwQuerySystemInformation 函数,
指定其class参数为11(SystemModuleInformation) 时,可以获取系统全部模块的数组

1
2
3
4
5
NTSTATUS NTAPI ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL);

对应数组的结构体如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _PROCESS_MODULE_INFO {
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[256];
} PROCESS_MODULE_INFO, *PPROCESS_MODULE_INFO;

typedef struct _PROCESS_MODULES {
ULONG NumberOfModules;
PROCESS_MODULE_INFO Modules[1];
} PROCESS_MODULES, *PPROCESS_MODULES;

为了查找内核模块,我们使用 "NtOpenFile" 函数的地址作为定位条件

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
ULONG gKernelSize = 0;
PVOID gKernelAddr = NULL;

PVOID GetKernelAddress()
{
NTSTATUS Status = STATUS_SUCCESS;
UNICODE_STRING FuncName = { 0 };
PUCHAR FuncAddr = NULL;
ULONG RetLen = 0;
PPROCESS_MODULES ProcMods = NULL;
PPROCESS_MODULE_INFO ModInfo = NULL;
if (gKernelAddr)
return gKernelAddr;
// 获取NtOpenFile函数的地址
RtlInitUnicodeString(&FuncName, L"NtOpenFile");
FuncAddr = (PUCHAR)MmGetSystemRoutineAddress(&FuncName);
if (!FuncAddr)
return NULL;
// 查询需要的大小 SystemModuleInformation 11
Status = ZwQuerySystemInformation(11, NULL, 0, &RetLen);
if (Status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;
// 申请内存空间
ProcMods = (PPROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPool, RetLen * 2, 'ssss');
if (!ProcMods)
return NULL;
RtlZeroMemory(ProcMods, RetLen * 2);
// 查询系统模块的信息
Status = ZwQuerySystemInformation(11, ProcMods, RetLen * 2, &RetLen);
if (!NT_SUCCESS(Status))
{
ExFreePoolWithTag(ProcMods, POOL_TAG);
return NULL;
}
// 循环匹配NtOpenFile地址是否在目标模块中
ModInfo = ProcMods->Modules;
for (ULONG i = 0; i < ProcMods->NumberOfModules; i++)
{
if ((FuncAddr >= (PUCHAR)ModInfo[i].ImageBase) &&
(FuncAddr < (PUCHAR)ModInfo[i].ImageBase + ModInfo[i].ImageSize))
{
gKernelAddr = ModInfo[i].ImageBase;
gKernelSize = ModInfo[i].ImageSize;
break;
}
}
ExFreePoolWithTag(ProcMods, POOL_TAG);
return gKernelAddr;
}
2.获取SSDT地址

使用搜索特征码的方式查找,特征码搜索函数如下所示

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
NTSTATUS SearchPattern(
IN PUCHAR Pattern, IN UCHAR Wildcard, IN ULONG Len,
IN PUCHAR BaseAddr, IN ULONG Size, OUT PUCHAR* Target)
{
if (!Pattern || !BaseAddr ||!Target)
return STATUS_UNSUCCESSFUL;
// 循环匹配特征码
for (ULONG i = 0; i < (Size - Len); i++)
{
BOOLEAN Found = TRUE;
for (ULONG j = 0; j < Len; j++)
{
if ((Pattern[j] != Wildcard) && (Pattern[j] != BaseAddr[i + j]))
{
Found = FALSE;
break;
}
}
// 检查是否符合
if (Found)
{
*Target = BaseAddr + i;
return STATUS_SUCCESS;
}
}
return STATUS_UNSUCCESSFUL;
}

在WIN7中,使用 __readmsr(0xC0000082) 读取 C0000082 寄存器,得到 KiSystemCall64
的地址,然后再搜索 4C8D15 字节码,就可以找到 KeServiceDescriptorTable 的地址,但是
在WIN10中有变化,所以我们这里改为,在代码段中搜索带通配符的特征码来定位SSDT地址,
需要在上篇文章中定义的相关PE结构体信息

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
typedef struct _SYSTEM_SERVICE_TABLE {
PULONG_PTR ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG_PTR NumberOfServices;
PUCHAR ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
PSYSTEM_SERVICE_TABLE gSsdtAddr = NULL;

PSYSTEM_SERVICE_TABLE GetSSDTAddress()
{
PUCHAR KernelAddr = NULL;
PIMAGE_DOS_HDR DosHdr = NULL;
PIMAGE_NT_HDR64 NtHdr64 = NULL;
PIMAGE_SECTION_HDR SecHdr = NULL;
NTSTATUS Status = STATUS_SUCCESS;
if (gSsdtAddr)
return gSsdtAddr;
// 获取内核模块地址
KernelAddr = GetKernelAddress();
if (!KernelAddr)
return NULL;
// 检测DOS头是否匹配
DosHdr = (PIMAGE_DOS_HDR)KernelAddr;
if (DosHdr->e_magic != IMAGE_DOS_SIGN)
return NULL;
// 检测NT头是否匹配
NtHdr64 = (PIMAGE_NT_HDR64)(KernelAddr + DosHdr->e_lfanew);
if (NtHdr64->Signature != IMAGE_NT_SIGN)
return NULL;
// 检测是不是64位PE文件
if (NtHdr64->OptHeader64.Magic != IMAGE_NT_OPT_HDR64_MAGIC)
return NULL;
// 循环遍历代码段
SecHdr = (PIMAGE_SECTION_HDR)(NtHdr64 + 1);
for (USHORT i = 0; i < NtHdr64->FileHeader.NumberOfSections; i++)
{
if ((SecHdr[i].Characteristics & IMAGE_SCN_MEM_NOT_PAGED) &&
(SecHdr[i].Characteristics & IMAGE_SCN_MEM_EXECUTE) &&
!(SecHdr[i].Characteristics & IMAGE_SCN_MEM_DISCARDABLE) &&
(*(PULONG)SecHdr[i].Name != 'TINI') &&
(*(PULONG)SecHdr[i].Name != 'EGAP'))
{
PUCHAR Target = NULL;
UCHAR Pattern[] = "\x4C\x8D\x15\xcc\xcc\xcc\xcc\x4C\x8D\x1D\xcc\xcc\xcc\xcc\xF7";
Status = SearchPattern(Pattern, 0xCC, sizeof(Pattern) - 1,
KernelAddr + SecHdr[i].VirtualAddress, SecHdr[i].Misc.VirtualSize, &Target);
if (NT_SUCCESS(Status))
{
gSsdtAddr = (PSYSTEM_SERVICE_TABLE)(Target + *(PULONG)(Target + 3) + 7);
return gSsdtAddr;
}
}
}
return NULL;
}
3.获取函数的索引

从应用层API进入内核时,通过调用对应SSDT表中的索引,来调用对应函数,
这里借助于32位的 ntdll.dll 中导出的函数,根据字节码 B8 获取调用号,
要需要在上篇文章中获取导出函数的函数 GetExportFuncAddr

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
PVOID GetSSDTEntryByNtdll(IN PCSTR name)
{
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID DllBase = NULL;
SIZE_T ViewSize = 0;
NTSTATUS Status = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatus = { 0 };
OBJECT_ATTRIBUTES ObjAttrib = { 0 };
UNICODE_STRING NtDllName = { 0 };
PUCHAR FuncAddr = NULL;
ULONG Index = 0;
PVOID KernelAddr = NULL;
PSYSTEM_SERVICE_TABLE SSDTAddr = NULL;
// 获取32位的ntdll.dll
#ifdef AMD64
RtlInitUnicodeString(&NtDllName, L"\\SystemRoot\\SysWOW64\\ntdll.dll");
#else
RtlInitUnicodeString(&NtDllName, L"\\SystemRoot\\System32\\ntdll.dll");
#endif
// 打开ntdll.dll并进行映射
InitializeObjectAttributes(&ObjAttrib, &NtDllName,
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
Status = ZwOpenFile(&hFile, FILE_GENERIC_READ, &ObjAttrib,
&IoStatus, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
if (!NT_SUCCESS(Status))
return NULL;
InitializeObjectAttributes(&ObjAttrib, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
Status = ZwCreateSection(&hSection,
SECTION_MAP_READ, &ObjAttrib, 0, PAGE_READONLY, 0x01000000, hFile);
if (!NT_SUCCESS(Status))
{
ZwClose(hFile);
return NULL;
}
Status = ZwMapViewOfSection(hSection, ZwCurrentProcess(),
&DllBase, 0, 0, 0, &ViewSize, ViewShare, MEM_TOP_DOWN, PAGE_READONLY);
if (!NT_SUCCESS(Status))
{
ZwClose(hSection);
ZwClose(hFile);
return NULL;
}
// 获取导出函数的地址
FuncAddr = GetExportFuncAddr(DllBase, name);
if (!FuncAddr)
{
ZwUnmapViewOfSection(ZwCurrentProcess(), DllBase);
ZwClose(hSection);
ZwClose(hFile);
return NULL;
}
// 获取调用号 字节码B8xxxxxxxx
Index = *(PULONG)(FuncAddr + 1);
ZwUnmapViewOfSection(ZwCurrentProcess(), DllBase);
ZwClose(hSection);
ZwClose(hFile);
// 根据索引获取对应地址
KernelAddr = GetKernelAddress();
SSDTAddr = GetSSDTAddress();
if (KernelAddr && SSDTAddr)
{
if (Index > SSDTAddr->NumberOfServices)
return NULL;
return (PVOID)((PUCHAR)SSDTAddr->ServiceTableBase +
(((PLONG)SSDTAddr->ServiceTableBase)[Index] >> 4));
}
return NULL;
}