0%

在内核中获取进程的DLL和导出函数(1)

简介

在很多情况下,我们需要获取某进程所加载的DLL信息,以及DLL中的导出函数地址,
这里参照 Blackbone 的部分代码进行修改:https://github.com/DarthTon/Blackbone

工作原理

1.获取进程ID

在驱动中遍历进程需要使用 ZwQuerySystemInformation 函数,指定其class参数为5
(SystemProcessesAndThreadsInformation) 时,可以获取一个全部进程的快照链表

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
18
19
20
21
22
23
24
25
typedef struct _SYSTEM_PROCESS_INFO {
ULONG NextEntryOffset; // 下个节点
ULONG NumberOfThreads;
UCHAR Reserved1[48];
UNICODE_STRING ImageName; // 进程名
KPRIORITY BasePriority;
HANDLE UniqueProcessId; // 进程PID
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;

这里以 explorer.exe 进程为例,获取其PID信息

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
NTSTATUS GetExplorerProcessId(OUT HANDLE *ProcessId)
{
ULONG RetLen = 0;
PVOID Buffer = NULL;
NTSTATUS Status = STATUS_SUCCESS;
PSYSTEM_PROCESS_INFO ProcInfo = NULL;
// 查询所有进程信息
if (!ProcessId) return STATUS_UNSUCCESSFUL;
// SystemProcessesAndThreadsInformation 5
Status = ZwQuerySystemInformation(5, NULL, 0, &RetLen);
if (Status != STATUS_INFO_LENGTH_MISMATCH) return Status;
Buffer = ExAllocatePool(NonPagedPoolNx, RetLen * 2);
if (!Buffer) return STATUS_INSUFFICIENT_RESOURCES;
Status = ZwQuerySystemInformation(5, Buffer, RetLen * 2, &RetLen);
if (!NT_SUCCESS(Status))
{
ExFreePool(Buffer);
return Status;
}
// 循环遍历进程链表
Status = STATUS_UNSUCCESSFUL;
ProcInfo = (PSYSTEM_PROCESS_INFO)Buffer;
while (1)
{
// 因为explorer.exe不在会话0中
if (ProcInfo->SessionId && (ProcInfo->UniqueProcessId > (HANDLE)4))
{
if (ProcInfo->ImageName.Buffer && ProcInfo->ImageName.Length)
{
// 对比进程名称
if (!_wcsnicmp(ProcInfo->ImageName.Buffer,
L"explorer.exe", ProcInfo->ImageName.Length / 2))
{
*ProcessId = ProcInfo->UniqueProcessId;
Status = STATUS_SUCCESS;
break;
}
}
}
if (!ProcInfo->NextEntryOffset) break;
ProcInfo = (PSYSTEM_PROCESS_INFO)(
(PCHAR)ProcInfo + ProcInfo->NextEntryOffset);
}
ExFreePool(Buffer);
return Status;
}
2.获取DLL地址

进程所加载的DLL列表在 PEB->LDR 中保存,我们可以遍历LDR链表来查询DLL模块地址,
注意:在驱动中访问应用层地址,必须加 try/exceptProbeForRead/Write 来检测有效性

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
// 64位PEB相关结构体定义
typedef struct _PEB_LDR_DATA {
ULONG Length;
UCHAR Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef struct _PEB {
UCHAR InheritedAddressSpace;
UCHAR ReadImageFileExecOptions;
UCHAR BeingDebugged;
UCHAR BitField;
PVOID Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PVOID ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PVOID FastPebLock;
PVOID AtlThunkSListPtr;
PVOID IFEOKey;
PVOID CrossProcessFlags;
PVOID UserSharedInfoPtr;
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
PVOID ApiSetMap;
} PEB, *PPEB;
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
// 32位PEB相关结构体定义
typedef struct _PEB_LDR_DATA32 {
ULONG Length;
UCHAR Initialized;
ULONG SsHandle;
LIST_ENTRY32 InLoadOrderModuleList;
LIST_ENTRY32 InMemoryOrderModuleList;
LIST_ENTRY32 InInitializationOrderModuleList;
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;

typedef struct _LDR_DATA_TABLE_ENTRY32 {
LIST_ENTRY32 InLoadOrderLinks;
LIST_ENTRY32 InMemoryOrderLinks;
LIST_ENTRY32 InInitializationOrderLinks;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING32 FullDllName;
UNICODE_STRING32 BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY32 HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;

typedef struct _PEB32 {
UCHAR InheritedAddressSpace;
UCHAR ReadImageFileExecOptions;
UCHAR BeingDebugged;
UCHAR BitField;
ULONG Mutant;
ULONG ImageBaseAddress;
ULONG Ldr;
ULONG ProcessParameters;
ULONG SubSystemData;
ULONG ProcessHeap;
ULONG FastPebLock;
ULONG AtlThunkSListPtr;
ULONG IFEOKey;
ULONG CrossProcessFlags;
ULONG UserSharedInfoPtr;
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
ULONG ApiSetMap;
} PEB32, *PPEB32;

需要用到的已导出但未文档化相关函数的声明

1
2
3
NTSYSAPI BOOLEAN NTAPI PsIsProtectedProcess(IN PEPROCESS Process); // 检测进程是否受保护
NTSYSAPI PVOID NTAPI PsGetProcessPeb(IN PEPROCESS Process); // 获取进程的PEB
NTSYSAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process); // 获取WOW64进程的PEB

这里以获取 explorer.exe 程序的 kernel32.dll 模块地址为例,需要注意的是:
遍历某进程的模块,需要先 KeStackAttachProcess 到目标进程中,但是受保护的进程不能Attach,
访问 PEB 的成员信息,要使用 __try{} __except(EXCEPTION_EXECUTE_HANDLER){} 来检测有效性

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
PVOID GetKernel32Address(IN PEPROCESS Process, IN BOOLEAN IsWow64)
{
LARGE_INTEGER Timeout = { 0 };
__try {
#ifdef AMD64
if (IsWow64)
{
// 在64位进程中遍历WOW64模块
PLIST_ENTRY32 ListEntry32 = NULL;
PPEB_LDR_DATA32 PebLdrData32 = NULL;
PLDR_DATA_TABLE_ENTRY32 TableEntry32 = NULL;
PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(Process);
if (!Peb32) return NULL;
// 等待Ldr已初始化完毕
Timeout.QuadPart = -10LL * 1000 * 250; // 250毫秒
for (INT i = 0; (!Peb32->Ldr) && (i < 10); i++)
KeDelayExecutionThread(KernelMode, FALSE, &Timeout);
if (!Peb32->Ldr) return NULL;
// 遍历已加载模块列表(注意多线程竞争问题)
// 在R3中是用快照的方式获取,其中使用的是临界区
PebLdrData32 = (PPEB_LDR_DATA32)Peb32->Ldr;
ListEntry32 = (PLIST_ENTRY32)PebLdrData32->InLoadOrderModuleList.Flink;
while (ListEntry32 != &PebLdrData32->InLoadOrderModuleList)
{
TableEntry32 = CONTAINING_RECORD(
ListEntry32, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
if (TableEntry32->BaseDllName.Buffer && TableEntry32->BaseDllName.Length)
{
// 对比模块名称
if (!_wcsnicmp((PWCHAR)TableEntry32->BaseDllName.Buffer,
L"Kernel32.dll", TableEntry32->BaseDllName.Length / 2))
{
return (PVOID)TableEntry32->DllBase;
}
}
ListEntry32 = (PLIST_ENTRY32)ListEntry32->Flink;
}
}
else
{
#endif
// 获取进程的PEB信息
PLIST_ENTRY ListEntry = NULL;
PPEB_LDR_DATA PebLdrData = NULL;
PLDR_DATA_TABLE_ENTRY TableEntry = NULL;
PPEB Peb = PsGetProcessPeb(Process);
if (!Peb) return NULL;
#ifndef AMD64
UNREFERENCED_PARAMETER(IsWow64);
#endif
// 等待Ldr已初始化完毕
Timeout.QuadPart = -10LL * 1000 * 250; // 250毫秒
for (INT i = 0; (!Peb->Ldr) && (i < 10); i++)
KeDelayExecutionThread(KernelMode, FALSE, &Timeout);
if (!Peb->Ldr) return NULL;
// 遍历已加载模块列表(注意多线程竞争问题)
// 在R3中是用快照的方式获取,其中使用的是临界区
PebLdrData = (PPEB_LDR_DATA)Peb->Ldr;
ListEntry = (PLIST_ENTRY)PebLdrData->InLoadOrderModuleList.Flink;
while (ListEntry != &PebLdrData->InLoadOrderModuleList)
{
TableEntry = CONTAINING_RECORD(
ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
if (TableEntry->BaseDllName.Buffer && TableEntry->BaseDllName.Length)
{
// 对比模块名称
if (!_wcsnicmp(TableEntry->BaseDllName.Buffer,
L"Kernel32.dll", TableEntry->BaseDllName.Length / 2))
{
return (PVOID)TableEntry->DllBase;
}
}
ListEntry = (PLIST_ENTRY)ListEntry->Flink;
}
#ifdef AMD64
}
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return NULL;
}
3.获取导出函数

获取导出表的信息需要解析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
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
#define IMAGE_DOS_SIGN 0x5A4D     // MZ
#define IMAGE_NT_SIGN 0x00004550 // PE00
#define IMAGE_NT_OPT_HDR32_MAGIC 0x10B // 32位PE
#define IMAGE_NT_OPT_HDR64_MAGIC 0x20B // 64位PE

typedef struct _IMAGE_DOS_HDR {
USHORT e_magic;
USHORT e_cblp;
USHORT e_cp;
USHORT e_crlc;
USHORT e_cparhdr;
USHORT e_minalloc;
USHORT e_maxalloc;
USHORT e_ss;
USHORT e_sp;
USHORT e_csum;
USHORT e_ip;
USHORT e_cs;
USHORT e_lfarlc;
USHORT e_ovno;
USHORT e_res[4];
USHORT e_oemid;
USHORT e_oeminfo;
USHORT e_res2[10];
LONG e_lfanew;
} IMAGE_DOS_HDR, *PIMAGE_DOS_HDR;

typedef struct _IMAGE_FILE_HDR {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HDR, *PIMAGE_FILE_HDR;

typedef struct _IMAGE_DATA_DIR {
ULONG VirtualAddress;
ULONG Size;
} IMAGE_DATA_DIR, *PIMAGE_DATA_DIR;

typedef struct _IMAGE_OPT_HDR64 {
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONGLONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Win32VersionValue;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIR DataDirectory[16];
} IMAGE_OPT_HDR64, *PIMAGE_OPT_HDR64;

typedef struct _IMAGE_OPT_HDR32 {
USHORT Magic;
UCHAR MajorLinkerVersion;
UCHAR MinorLinkerVersion;
ULONG SizeOfCode;
ULONG SizeOfInitializedData;
ULONG SizeOfUninitializedData;
ULONG AddressOfEntryPoint;
ULONG BaseOfCode;
ULONG BaseOfData;
ULONG ImageBase;
ULONG SectionAlignment;
ULONG FileAlignment;
USHORT MajorOperatingSystemVersion;
USHORT MinorOperatingSystemVersion;
USHORT MajorImageVersion;
USHORT MinorImageVersion;
USHORT MajorSubsystemVersion;
USHORT MinorSubsystemVersion;
ULONG Win32VersionValue;
ULONG SizeOfImage;
ULONG SizeOfHeaders;
ULONG CheckSum;
USHORT Subsystem;
USHORT DllCharacteristics;
ULONG SizeOfStackReserve;
ULONG SizeOfStackCommit;
ULONG SizeOfHeapReserve;
ULONG SizeOfHeapCommit;
ULONG LoaderFlags;
ULONG NumberOfRvaAndSizes;
IMAGE_DATA_DIR DataDirectory[16];
} IMAGE_OPT_HDR32, *PIMAGE_OPT_HDR32;

typedef struct _IMAGE_NT_HDR32 {
ULONG Signature;
IMAGE_FILE_HDR FileHeader;
IMAGE_OPT_HDR32 OptHeader32;
} IMAGE_NT_HDR32, *PIMAGE_NT_HDR32;

typedef struct _IMAGE_NT_HDR64 {
ULONG Signature;
IMAGE_FILE_HDR FileHeader;
IMAGE_OPT_HDR64 OptHeader64;
} IMAGE_NT_HDR64, *PIMAGE_NT_HDR64;

typedef struct _IMAGE_EXPORT_DIR {
ULONG Characteristics;
ULONG TimeDateStamp;
USHORT MajorVersion;
USHORT MinorVersion;
ULONG Name;
ULONG Base;
ULONG NumberOfFunctions;
ULONG NumberOfNames;
ULONG AddressOfFunctions;
ULONG AddressOfNames;
ULONG AddressOfNameOrdinals;
} IMAGE_EXPORT_DIR, *PIMAGE_EXPORT_DIR;

typedef struct _IMAGE_SECTION_HDR {
UCHAR Name[8];
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HDR, *PIMAGE_SECTION_HDR;

这里以获取 Kernel32.dllWinExec 函数地址为例,
注意:在访问不确定的地址时,一定要记得加 __try 异常处理

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
PVOID GetExportFuncAddr(IN PVOID DllBase, IN PCSTR FuncName)
{
PULONG Ent = NULL; // 名称表
PULONG Eat = NULL; // 地址表
PUSHORT Eot = NULL; // 序号表
PIMAGE_DOS_HDR DosHdr = NULL;
PIMAGE_NT_HDR32 NtHdr32 = NULL;
PIMAGE_NT_HDR64 NtHdr64 = NULL;
PIMAGE_EXPORT_DIR ExportDir = NULL;
// 检测参数是否有效
if (!DllBase || !FuncName) return NULL;
__try {
// 检测DOS头是否匹配
DosHdr = (PIMAGE_DOS_HDR)DllBase;
if (DosHdr->e_magic != IMAGE_DOS_SIGN) return NULL;
// 检测NT头是否匹配
NtHdr32 = (PIMAGE_NT_HDR32)((PUCHAR)DllBase + DosHdr->e_lfanew);
NtHdr64 = (PIMAGE_NT_HDR64)((PUCHAR)DllBase + DosHdr->e_lfanew);
if (NtHdr32->Signature != IMAGE_NT_SIGN) return NULL;
// 检测是32位PE文件还是64位PE文件
if (NtHdr32->OptHeader32.Magic == IMAGE_NT_OPT_HDR32_MAGIC)
{
ExportDir = (PIMAGE_EXPORT_DIR)(NtHdr32->OptHeader32.
DataDirectory[0].VirtualAddress + (ULONG_PTR)DllBase);
}
else
{
ExportDir = (PIMAGE_EXPORT_DIR)(NtHdr64->OptHeader64.
DataDirectory[0].VirtualAddress + (ULONG_PTR)DllBase);
}
// 获取导出表的相关信息
Eot = (PUSHORT)(ExportDir->AddressOfNameOrdinals + (ULONG_PTR)DllBase);
Ent = (PULONG)(ExportDir->AddressOfNames + (ULONG_PTR)DllBase);
Eat = (PULONG)(ExportDir->AddressOfFunctions + (ULONG_PTR)DllBase);
// 对比名称表中的函数名是否匹配
for (ULONG i = 0; i < ExportDir->NumberOfNames; i++)
{
// 在序号表中相同的位置存储了地址表的索引
if (!strcmp((PCHAR)(Ent[i] + (ULONG_PTR)DllBase), FuncName))
return (PVOID)(Eat[Eot[i]] + (ULONG_PTR)DllBase);
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
return NULL;
}