这里实现一个 LoadImage() 函数,用来加载目标 image 文件。如下面的用法:
1 ... ... 2 3 4 RtlInitUnicodeString(&ImageFile, L"\\??\\C:\\windows\\system32\\ntkrnlpa.exe"); // 目标文件为 ntkrnlpa.exe 5 LoadImage(&ImageFile, &ImageBase); // 加载目标 image, ImageBase 返回 image 基址 6 7 8 ... ... 9 10 11 ExFreePoolWithTag(ImageBase, ‘fubi‘); // 最后释放 Image 占用内存
LoadImage() 内部实现了基址重定位以及导入符号绑定操作。
1 BOOLEAN LoadImage( 2 IN PUNICODE_STRING ImageFile, 3 OUT PVOID *OutImageBase 4 ) 5 /*++ 6 LoadImage(): 7 加载目标映像文件到目标 buffer 中。 8 input: 9 ImageFile - 提供目标映像文件名。 10 ImageBase - 提供输出的目标 buffer,这个 buffer 必须能装下映像文件。 11 函数内部分配 buffer,并返回 buffer 给 caller。当失败时返回 NULL。 12 ImageBase 使用 ExAllocatePoolWithTag() 分配,caller 需要使用 13 ExFreePoolWithTag() 来释放,Tag = ‘fubi‘。 14 output: 15 TRUE - 成功, 否则返回 FALSE 指示失败,OutImageBase 返回 NULL。 16 --*/ 17 { 18 NTSTATUS Status; 19 OBJECT_ATTRIBUTES ObjAttributes; 20 HANDLE FileHandle; 21 IO_STATUS_BLOCK IoStatusBlock; 22 LARGE_INTEGER Offset; 23 24 IMAGE_DOS_HEADER DosHeader; // DOS header 25 IMAGE_NT_HEADERS NtHeader; // NT header 26 PIMAGE_SECTION_HEADER SectionHeader; // section header 27 PIMAGE_BASE_RELOCATION BaseRelocation; // base relocation 28 PIMAGE_BASE_RELOCATION BaseRelocationTop; // base relocation 区域顶部 29 PBASE_RELOCATION_ENTRY BaseRelocEntry; // base relocation entry 30 PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor; // image import descriptor 区域 31 PIMPORT_LOOKUP_TABLE_ENTRY LookupTableEntry; // 符号查找表项 32 33 IMAGE_IMPORT_MAINTAIN_TABLE ImportMaintainTable = { 0, NULL, { 0 } }; 34 35 36 ULONG BaseRelocationSize; // 重定位区域 size 37 PULONG_PTR RelocationAddress; // 需要重定位的地址 38 ULONG_PTR RelocationValue; // 需要重定位的值 39 PULONG_PTR BoundSymbolAddress; // 需要绑定符号的地址 40 PCHAR Symbol; // 通用符号名地址 41 42 PVOID OriginalImageBase = NULL; 43 PVOID ImageBase = NULL; 44 PVOID ImageSection = NULL; 45 ULONG NumberOfSections; 46 ULONG SectionOffset; 47 ULONG Index = 0; 48 BOOLEAN Result = TRUE; 49 50 *OutImageBase = NULL; // 初始设置 OutImageBase 51 52 InitializeObjectAttributes( 53 &ObjAttributes, 54 ImageFile, 55 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 56 NULL, 57 NULL); 58 59 // 60 // 打开映像文件 61 // 62 Status = ZwCreateFile( 63 &FileHandle, 64 FILE_ALL_ACCESS, 65 &ObjAttributes, 66 &IoStatusBlock, 67 NULL, 68 FILE_ATTRIBUTE_NORMAL, 69 FILE_SHARE_READ, 70 FILE_OPEN, 71 FILE_NON_DIRECTORY_FILE, 72 NULL, 73 0); 74 75 if (NT_SUCCESS(Status) == FALSE) 76 { 77 KdPrint(("failure to open image file")); 78 return FALSE; 79 } 80 81 // 82 // 读取文件内容: 83 // 1. DOS header 84 // 2. NT header 85 // 3. Section header 86 // 87 Offset.QuadPart = 0; 88 Status = ZwReadFile( 89 FileHandle, 90 NULL, 91 NULL, 92 NULL, 93 &IoStatusBlock, 94 &DosHeader, // DOS 头部 95 sizeof(IMAGE_DOS_HEADER), 96 &Offset, 97 NULL); 98 99 if (NT_SUCCESS(Status) == FALSE) 100 { 101 KdPrint(("failure to read image file")); 102 ZwClose(FileHandle); 103 return FALSE; 104 } 105 106 // 107 // 读取 NT header 108 // 109 Offset.QuadPart = DosHeader.e_lfanew; 110 Status = ZwReadFile( 111 FileHandle, 112 NULL, 113 NULL, 114 NULL, 115 &IoStatusBlock, 116 &NtHeader, // NT 头部 117 sizeof(IMAGE_NT_HEADERS), 118 &Offset, 119 NULL); 120 121 if (NT_SUCCESS(Status) == FALSE) 122 { 123 KdPrint(("failure to read image file")); 124 ZwClose(FileHandle); 125 return FALSE; 126 } 127 128 129 130 // 131 // 根据映像文件大小分配 pool 132 // 133 ImageBase = ExAllocatePoolWithTag( 134 NonPagedPool, // non-paged 135 NtHeader.OptionalHeader.SizeOfImage, // pool size = SizeOfImage 136 ‘fubi‘); // ‘ibuf‘ 137 if (ImageBase == NULL) 138 { 139 KdPrint(("failure to allocate image pool")); 140 ZwClose(FileHandle); 141 return FALSE; 142 } 143 144 // 145 // 定位 section table 146 // 147 NumberOfSections = NtHeader.FileHeader.NumberOfSections; 148 SectionOffset = DosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS); 149 SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)ImageBase + SectionOffset); 150 151 152 // 153 // 读取 section header 154 // 155 Offset.QuadPart = SectionOffset; 156 Status = ZwReadFile( 157 FileHandle, 158 NULL, 159 NULL, 160 NULL, 161 &IoStatusBlock, 162 SectionHeader, 163 NumberOfSections * sizeof(IMAGE_SECTION_HEADER), 164 &Offset, 165 NULL); 166 167 if (NT_SUCCESS(Status) == FALSE) 168 { 169 KdPrint(("failure to read image file")); 170 ZwClose(FileHandle); 171 return FALSE; 172 } 173 174 175 // 176 // 下面加载映像的每个 section 177 // 178 for (Index = 0; Index < NumberOfSections; Index++) 179 { 180 // 181 // 如果 SizeOfRawData = 0 时,跳过 section 182 // 183 if (SectionHeader[Index].SizeOfRawData == 0) 184 { 185 continue; 186 } 187 188 // 189 // 从文件的 PointerToRawData 偏移量里读取内容到 ImageBase + RAV 地址 190 // size 为 SizeOfRawData 191 // 192 Offset.QuadPart = SectionHeader[Index].PointerToRawData; 193 ImageSection = (PVOID)((ULONG_PTR)ImageBase + SectionHeader[Index].VirtualAddress); 194 Status = ZwReadFile( 195 FileHandle, 196 NULL, 197 NULL, 198 NULL, 199 &IoStatusBlock, 200 ImageSection, 201 SectionHeader[Index].SizeOfRawData, 202 &Offset, 203 NULL); 204 205 if (NT_SUCCESS(Status) == FALSE) 206 { 207 KdPrint(("failure to read image file")); 208 ZwClose(FileHandle); 209 return FALSE; 210 } 211 } 212 213 214 // 215 // 复制 DOS header 和 NT header 到 Image 区域 216 // 217 RtlCopyMemory(ImageBase, &DosHeader, sizeof(DosHeader)); 218 RtlCopyMemory((PVOID)((ULONG_PTR)ImageBase + (ULONG_PTR)DosHeader.e_lfanew), &NtHeader, sizeof(NtHeader)); 219 220 221 // 222 // 下面进行 base 重定位 223 // 224 225 // 226 // 找到 IMAGE_BASE_RELOCATION 表区域 227 // 228 BaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)ImageBase + 229 (ULONG_PTR)NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); 230 BaseRelocationSize = NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; 231 BaseRelocationTop = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)BaseRelocation + BaseRelocationSize - 1); 232 OriginalImageBase = NtHeader.OptionalHeader.ImageBase; // 映像文件的原始 ImageBase 值 233 234 while (BaseRelocation < BaseRelocationTop) 235 { 236 // 237 // 对每一个 IMAGE_BASE_RELOCATION 表区域内的 entry 进行调整 238 // 239 for (BaseRelocEntry = (PBASE_RELOCATION_ENTRY)((ULONG_PTR)BaseRelocation + 8); 240 (ULONG_PTR)BaseRelocEntry < ((ULONG_PTR)BaseRelocation + BaseRelocation->SizeOfBlock); 241 BaseRelocEntry++) 242 { 243 switch (BaseRelocEntry->Type) 244 { 245 case IMAGE_REL_BASED_HIGHLOW: 246 // 247 // 找到需要进行重定位的地址 248 // 249 RelocationAddress = (PULONG_PTR)((ULONG_PTR)ImageBase 250 + BaseRelocation->VirtualAddress 251 + (ULONG_PTR)BaseRelocEntry->Offset); 252 // 253 // 读取原值进行调整 254 // 255 RelocationValue = *RelocationAddress; 256 *RelocationAddress = RelocationValue - (ULONG_PTR)OriginalImageBase + (ULONG_PTR)ImageBase; 257 break; 258 case IMAGE_REL_BASED_DIR64: 259 break; 260 } 261 } 262 263 // 264 // 指向下一个 IMAGE_BASE_RELOCATION 表区域 265 // 266 BaseRelocation = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)BaseRelocation + BaseRelocation->SizeOfBlock); 267 } 268 269 // 270 // 下面进行导入符号的绑定 271 // 272 273 ImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)ImageBase + 274 (ULONG_PTR)NtHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 275 276 // 277 // 得到 image 的所有导入模块及基址 278 // 279 Result = InitImportMaintainTable(&ImportMaintainTable, ImageBase, ImportDescriptor); 280 281 // 282 // 进行导入符号绑定处理 283 // 284 if (Result == TRUE) 285 Result = ProcessImportMaintainTable(&ImportMaintainTable); 286 287 288 ZwClose(FileHandle); 289 *OutImageBase = ImageBase; // 返回 image buffer 290 291 return TRUE; 292 }
InitImportMaintainTable() 函数用来生成初始化的 IMAGE_IMPORT_MAINTAIN_TABLE 结构,它的定义如下:
1 // 2 // 预定义 image 导入模块的最大数量 3 // 4 #define IMPORT_MODULE_MAX_NUMBER 100 5 6 7 // 8 // image 所引用的模块表 9 // 10 typedef struct _IMAGE_REFERENCE_MODULE_TABLE 11 { 12 PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor; // 导入模块信息 13 PVOID ModuleBase; // 模块基址 14 } IMAGE_REFERENCE_MODULE_TABLE, *PIMAGE_REFERENCE_MODULE_TABLE; 15 16 17 18 // 19 // image 导入维护表 20 // 21 typedef struct _IMAGE_IMPORT_MAINTAIN_TABLE 22 { 23 ULONG NumberOfReferenceModules; // 引用模块的数量 24 PVOID ImageBase; // image 模块的基址 25 26 // 27 // 预定义导入 IMPORT_MODULE_MAX_NUMBER 模块 28 // 29 IMAGE_REFERENCE_MODULE_TABLE Entry[IMPORT_MODULE_MAX_NUMBER]; 30 31 } IMAGE_IMPORT_MAINTAIN_TABLE, *PIMAGE_IMPORT_MAINTAIN_TABLE;
IMAGE_IMPORT_MAINTAIN_TABLE 结构用来处理 image 导入符号的绑定,这项工作由 ProcessImportMaintainTable() 函数负责。ProcessImportMaintainTable() 调用 GetAddressOfExportSymbol() 函数得到符号在引用模块的导出地址值。
下面是 InitImportMaintainTable(),ProcessImportMaintainTable(),GetAddressOfExportSymbol() 以及 SerachBinaryForSymbol() 函数的实现:
1 BOOLEAN InitImportMaintainTable( 2 INOUT PIMAGE_IMPORT_MAINTAIN_TABLE ImportMaintainTable, 3 IN PVOID ImageBase, 4 IN PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor 5 ) 6 /*++ 7 InitImportMaintainTable(): 8 根据提供的 image 导入描述符表初始化 ImportMaintainTable。 9 input: 10 ImportMaintainTable - image 导入维护表。 11 ImageBase - image 基址 12 ImportDescriptor - image 的导入描述符表。 13 output: 14 成功返回 TRUE,否则返回 FALSE。 15 --*/ 16 { 17 PIMAGE_REFERENCE_MODULE_TABLE ReferenceModuleTable = ImportMaintainTable->Entry; 18 ULONG NumberOfModules = 0; 19 NTSTATUS Status; 20 ULONG RequiredLength; 21 PLIST_ENTRY Next; 22 PRTL_PROCESS_MODULE_INFORMATION ModuleInfo; 23 PKLDR_DATA_TABLE_ENTRY LdrDataTableEntry; 24 ANSI_STRING AnsiString; 25 26 PCHAR ModuleName; 27 ULONG Index; 28 ULONG Result = 0; 29 30 31 while(!IS_NULL_IMAGE_IMPORT_DESCRIPTOR(*ImportDescriptor)) 32 { 33 // 34 // 查找对应的导入文件模块 35 // 36 ReferenceModuleTable->ImportDescriptor = ImportDescriptor; 37 ImportDescriptor++; 38 ReferenceModuleTable++; 39 NumberOfModules++; 40 } 41 42 ImportMaintainTable->NumberOfReferenceModules = NumberOfModules; // image 引用的模块数 43 ImportMaintainTable->ImageBase = ImageBase; // image 基址 44 45 46 // 47 // 遍历 PsLoadedModuleList 列表,从已加载的模块里找到 image 所引用模块的基址 48 // 注意: 49 // 这里保留未实现未加载的模块! 50 // 51 for (Next = PsLoadedModuleListPointer->Flink; 52 Next != PsLoadedModuleListPointer; Next = Next->Flink) 53 { 54 LdrDataTableEntry = CONTAINING_RECORD(Next, 55 KLDR_DATA_TABLE_ENTRY, 56 InLoadOrderLinks 57 ); 58 // 59 // 匹配 image 导入模块名字 60 // 61 for (Index = 0; Index < ImportMaintainTable->NumberOfReferenceModules; Index++) 62 { 63 // 64 // 检查模块名,相等则找到引用模块的 Base 值 65 // 66 ModuleName = (PCHAR)((ULONG_PTR)ImageBase + 67 ImportMaintainTable->Entry[Index].ImportDescriptor->Name); 68 69 // 70 // 说明: 71 // 1)加载模块链中的模块名字是 UNICODE 字符串,而 image 导入模块名字是 ANSI 字符串。 72 // 2)需要在 UNICODE 串与 ANSI 串之间进行对比。模块名字是忽略大小写的,因此,使用 TRUE 参数。 73 // 74 if (CompareAnsiCharToUnicodeChar( 75 LdrDataTableEntry->BaseDllName.Buffer, // 需要对比的模块名 76 ModuleName, // image 所引用的模块名 77 TRUE) == 0) 78 { 79 ImportMaintainTable->Entry[Index].ModuleBase = LdrDataTableEntry->DllBase; 80 Result++; 81 } 82 } 83 } 84 85 if (Result == ImportMaintainTable->NumberOfReferenceModules) 86 return TRUE; 87 88 return FALSE; 89 } 90 91 92 93 94 95 96 97 98 99 BOOLEAN ProcessImportMaintainTable( 100 IN PIMAGE_IMPORT_MAINTAIN_TABLE ImportMaintainTable 101 ) 102 /*++ 103 ProcessImportMaintainTable(): 104 根据导入维护表处理 image 导入符号的重定位 105 input: 106 ImportMainTainTable - image 导入维护表 107 output: 108 成功时返回 TURE,否则返回 FALSE 109 --*/ 110 { 111 PIMAGE_IMPORT_DESCRIPTOR ImportDescriptor; 112 ULONG NumberOfReferenceModules = ImportMaintainTable->NumberOfReferenceModules; 113 PVOID ImageBase = ImportMaintainTable->ImageBase; 114 ULONG ModuleIndex, Index; 115 PCHAR SymbolName; 116 PVOID SymbolAddress; 117 118 PIMPORT_LOOKUP_TABLE_ENTRY ImportLookupTable; 119 PIMPORT_ADDRESS_TABLE_ENTRY ImportAddressTable; 120 PIMAGE_IMPORT_BY_NAME HitNameTable; 121 122 for (ModuleIndex = 0; ModuleIndex < NumberOfReferenceModules; ModuleIndex++) 123 { 124 // 125 // 说明: 126 // 1)从 import descriptor 里得到 import lookup table 与 import address table 127 // 2)import descriptor 的 OrignalFirstThunk 指向 import lookup table 128 // 3)import descriptor 的 FirstThunk 指向 import address table 129 // 4)用 import lookup table entry 指向的 IMPORT_BY_NAME table entry 在引用模块里查找符号地址。 130 // 131 ImportDescriptor = ImportMaintainTable->Entry[ModuleIndex].ImportDescriptor; 132 ImportLookupTable = (PIMPORT_LOOKUP_TABLE_ENTRY) 133 ((ULONG_PTR)ImageBase + ImportDescriptor->OriginalFirstThunk); 134 ImportAddressTable = (PIMPORT_ADDRESS_TABLE_ENTRY) 135 ((ULONG_PTR)ImageBase + ImportDescriptor->FirstThunk); 136 137 for (Index = 0; ImportLookupTable[Index].NameTable != 0; Index++) 138 { 139 HitNameTable = (PIMAGE_IMPORT_BY_NAME) 140 ((ULONG_PTR)ImageBase + ImportLookupTable[Index].NameTable); 141 SymbolName = (PCHAR)HitNameTable->Name; 142 143 // 144 // 在引用的模块里查找符号的地址,SymbolAddress 返回地址值 145 // SymbolAddress = NULL 时,表明查找失败。 146 // 147 SymbolAddress = GetAddressOfExportSymbol( 148 ImportMaintainTable->Entry[ModuleIndex].ModuleBase, 149 SymbolName); 150 151 if (SymbolAddress == NULL) 152 { 153 return FALSE; 154 } 155 156 // 157 // 将符号的真实地址绑定到 import address table entry 里。 158 // 159 ImportAddressTable[Index].AddressOfSymbol = (ULONG_PTR)SymbolAddress; 160 } 161 } 162 163 return TRUE; 164 } 165 166 167 168 169 PVOID GetAddressOfExportSymbol( 170 IN PVOID ModuleBase, 171 IN PCHAR SymbolName 172 ) 173 /*++ 174 GetAddressOfExportSymbol(): 175 从引用模块里找到符号的导出地址值 176 input: 177 ModuleBase - 模块的基址 178 SymbolName - 符号名 179 output: 180 成功时返回符号地址,否则返回 NULL 181 --*/ 182 { 183 PIMAGE_NT_HEADERS NtHeader; // 模块 NT 头 184 PIMAGE_EXPORT_DIRECTORY ExportDirectory; // 导出目录表 185 186 PULONG ExportAddressTable; // 导出地址表 187 PULONG ExportNamePointerTable; // 导出名字指针表 188 PUSHORT ExportNameOrdinalTable; // 导出名字序数表 189 ULONG OrdinalBase; // 导出序数基值 190 PCHAR ExportName; // 导出的符号名字 191 192 ULONG NumberOfFunctions; // 导出地址表 entries 数量 193 ULONG NumberOfNames; // 导出名字指针表 entries 数量 194 195 196 PVOID SymbolAddress = NULL; 197 PULONG NamePointer; 198 LONG Index; 199 200 NtHeader = (PIMAGE_NT_HEADERS)((ULONG_PTR)ModuleBase + 201 (ULONG_PTR)((PIMAGE_DOS_HEADER)ModuleBase)->e_lfanew); 202 ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)ModuleBase + 203 (ULONG_PTR)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)); 204 205 // 206 // 获取导出表格 207 // 208 ExportAddressTable = (PULONG)((ULONG_PTR)ModuleBase + 209 (ULONG_PTR)(ExportDirectory->AddressOfFunctions)); 210 ExportNamePointerTable = (PULONG)((ULONG_PTR)ModuleBase + 211 (ULONG_PTR)(ExportDirectory->AddressOfNames)); 212 ExportNameOrdinalTable = (PUSHORT)((ULONG_PTR)ModuleBase + 213 (ULONG_PTR)(ExportDirectory->AddressOfNameOrdinals)); 214 215 216 NumberOfFunctions = ExportDirectory->NumberOfFunctions; 217 NumberOfNames = ExportDirectory->NumberOfNames; 218 OrdinalBase = ExportDirectory->Base; 219 220 // 221 // 查找符号算法: 222 // 1) 使用二分查找法,在 name pointer table 里找到匹配的符号名,得到一个 name index 值。 223 // 2)将 name index 作为 name ordinal table 的 index 值,获得一个 oridnal 值。 224 // 3)将 ordinal - OrdinalBase 作为 index 值,在 export address table 里找到最终的符号地址。 225 // 226 NamePointer = SerachBinaryForSymbol(ExportNamePointerTable, // Begin 227 ExportNamePointerTable + (NumberOfNames - 1), // End 228 NumberOfNames, // Count 229 SymbolName, // SourceString 230 ModuleBase); // Base of module 231 232 if (NamePointer == NULL) 233 return NULL; 234 235 // 236 // 说明: 237 // 1) NamePointer 返回 Begin 到 End 间匹配元素的地址值,NamePointe - Begin 得到 index 值。 238 // 2)用这个 index 值在 name ordinal table 里找到对应的 ordinal 值。 239 // 3)用这个 ordinal 值在 export address table 里找到符号的真实地址。 240 // 241 // 注意: 242 // 在 microsoft PE 文件档里,明确指出从 name pointer table 里得到的 index 值需要减去 Ordinal Base 值, 243 // 但是经过测试,实际上并不需要减去 ordinal base 值。也就是: 244 // Index = (ULONG)ExportNameOrdinalTable[Index] - OrdinalBase; // 这个是错误 245 // 而是:Index = (ULONG)ExportNameOrdinalTable[Index]; // 不能减 ordinal base,否则计算错误。 246 // 247 248 Index = NamePointer - ExportNamePointerTable; 249 // 250 //Index = (ULONG)ExportNameOrdinalTable[Index] - OrdinalBase; // 减 ordinal base 值计算有误。 251 // 252 Index = (ULONG)ExportNameOrdinalTable[Index]; // 实际中不需要减 ordinal base 253 SymbolAddress = (PVOID)((ULONG_PTR)ModuleBase + ExportAddressTable[Index]); 254 255 return SymbolAddress; 256 } 257 258 259 260 261 PULONG SerachBinaryForSymbol( 262 IN PULONG Begin, 263 IN PULONG End, 264 IN ULONG Count, 265 IN PCHAR SourceString, 266 IN PVOID ImageBase 267 ) 268 /*++ 269 SerachBinaryForSymbol(): 270 在一个区域内使用二分查找匹配的符号名,返回匹配的元素值。 271 Begin 与 End 指定一个查找区间。 272 input: 273 Begin - 查找数组的起始点 274 End - 查找数组的结束点 275 Count - 元素个数 276 SourceString - 源串 277 output: 278 成功时返回匹配元素地址,失败时返回 NULL 279 --*/ 280 { 281 PCHAR DestString; 282 ULONG Index = 0; 283 LONG Result; 284 285 if (Begin > End) 286 return NULL; 287 288 Index = Count / 2; 289 DestString = (PCHAR)((ULONG_PTR)ImageBase + (ULONG_PTR)Begin[Index]); 290 291 // 292 // 说明: 293 // 1)导入符号(源串)与导出符号(目标串)是 ANSI char 字符串。因此,需要进行 ANSI char 字符串对比。 294 // 2)需要区分字符的大小写。因此,使用 FALSE 参数。 295 // 296 Result = CompareAnsiChar(DestString, SourceString, FALSE); 297 298 if (Result == 0) 299 return (Begin + Index); // 如果匹配,则返回元素地址值。 300 301 if (Result < 0) 302 Begin = Begin + (Index + 1); // 源串大于目标串,则在区间上半部分继续查找 303 else 304 End = Begin + (Index - 1); // 源串小于目标串,由在区间下半部分继续查找 305 306 Count = Count - Index - 1; 307 308 return SerachBinaryForSymbol(Begin, End, Count, SourceString, ImageBase); 309 }
使用 LoadImage() 加载 ntkrnlpa.exe 模块后,可以在 windbg 调试验证结果:
上图显示的 ImageBase 值为 0x87176000,这是 ntkrnlpa.exe 加载的基址。我们随便找一个 NT 模块的函数。例如:nt!HalAllocateCommonBuffer 函数,它使用了导入的 HAL 模块 HalAllocateCommonBuffer() 函数。
下面观察我们加载的 ntkrnlpa.exe 模块情况如何:它加载的基址在 0x87176000 里。
这个反汇编结果显示,函数是一样的。说明我们模块的基址重定位正确!下面我们参观一下导入函数绑定是否正确。图中从 0x871770e8 处读取 HalAllocateCommonBuffer 函数地址。
图中说明我们的导入符号绑定是正确的!注意:这个测试在 windows 2003 下进行,没有测试其它平台。WIN 64 平台下没有测试。