mirror of
https://github.com/reactos/reactos.git
synced 2024-05-16 10:11:53 +00:00
fec827eeef
* [NTOS:MM] Misc improvements for cookie generation code - Improve support for 64 bit images - Improve LdrpFetchAddressOfSecurityCookie code * [FREELDR] Add security cookie generation to FreeLoader CORE-17808
1012 lines
34 KiB
C
1012 lines
34 KiB
C
/*
|
|
* PROJECT: FreeLoader
|
|
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
|
|
* PURPOSE: Provides routines for loading PE files.
|
|
* (Deprecated remark) To be merged with arch/i386/loader.c in future.
|
|
*
|
|
* COPYRIGHT: Copyright 1998-2003 Brian Palmer <brianp@sginet.com>
|
|
* Copyright 2006-2019 Aleksey Bragin <aleksey@reactos.org>
|
|
*
|
|
* NOTES: The source code in this file is based on the work of respective
|
|
* authors of PE loading code in ReactOS and Brian Palmer and
|
|
* Alex Ionescu's arch/i386/loader.c, and my research project
|
|
* (creating a native EFI loader for Windows).
|
|
*
|
|
* This article was very handy during development:
|
|
* http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <freeldr.h>
|
|
|
|
#include <debug.h>
|
|
DBG_DEFAULT_CHANNEL(PELOADER);
|
|
|
|
/* GLOBALS *******************************************************************/
|
|
|
|
PELDR_IMPORTDLL_LOAD_CALLBACK PeLdrImportDllLoadCallback = NULL;
|
|
|
|
#ifdef _WIN64
|
|
#define COOKIE_MAX 0x0000FFFFFFFFFFFFll
|
|
#define DEFAULT_SECURITY_COOKIE 0x00002B992DDFA232ll
|
|
#else
|
|
#define DEFAULT_SECURITY_COOKIE 0xBB40E64E
|
|
#endif
|
|
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
static PVOID
|
|
PeLdrpFetchAddressOfSecurityCookie(PVOID BaseAddress, ULONG SizeOfImage)
|
|
{
|
|
PIMAGE_LOAD_CONFIG_DIRECTORY ConfigDir;
|
|
ULONG DirSize;
|
|
PULONG_PTR Cookie = NULL;
|
|
|
|
/* Get the pointer to the config directory */
|
|
ConfigDir = RtlImageDirectoryEntryToData(BaseAddress,
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG,
|
|
&DirSize);
|
|
|
|
/* Check for sanity */
|
|
if (!ConfigDir ||
|
|
DirSize < RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY, SecurityCookie))
|
|
{
|
|
/* Invalid directory*/
|
|
return NULL;
|
|
}
|
|
|
|
/* Now get the cookie */
|
|
Cookie = VaToPa((PULONG_PTR)ConfigDir->SecurityCookie);
|
|
|
|
/* Check this cookie */
|
|
if ((PCHAR)Cookie <= (PCHAR)BaseAddress ||
|
|
(PCHAR)Cookie >= (PCHAR)BaseAddress + SizeOfImage - sizeof(*Cookie))
|
|
{
|
|
Cookie = NULL;
|
|
}
|
|
|
|
/* Return validated security cookie */
|
|
return Cookie;
|
|
}
|
|
|
|
/* DllName - physical, UnicodeString->Buffer - virtual */
|
|
static BOOLEAN
|
|
PeLdrpCompareDllName(
|
|
IN PCH DllName,
|
|
IN PUNICODE_STRING UnicodeName)
|
|
{
|
|
PWSTR Buffer;
|
|
SIZE_T i, Length;
|
|
|
|
/* First obvious check: for length of two names */
|
|
Length = strlen(DllName);
|
|
|
|
#if DBG
|
|
{
|
|
UNICODE_STRING UnicodeNamePA;
|
|
UnicodeNamePA.Length = UnicodeName->Length;
|
|
UnicodeNamePA.MaximumLength = UnicodeName->MaximumLength;
|
|
UnicodeNamePA.Buffer = VaToPa(UnicodeName->Buffer);
|
|
TRACE("PeLdrpCompareDllName: %s and %wZ, Length = %d "
|
|
"UN->Length %d\n", DllName, &UnicodeNamePA, Length, UnicodeName->Length);
|
|
}
|
|
#endif
|
|
|
|
if ((Length * sizeof(WCHAR)) > UnicodeName->Length)
|
|
return FALSE;
|
|
|
|
/* Store pointer to unicode string's buffer */
|
|
Buffer = VaToPa(UnicodeName->Buffer);
|
|
|
|
/* Loop character by character */
|
|
for (i = 0; i < Length; i++)
|
|
{
|
|
/* Compare two characters, uppercasing them */
|
|
if (toupper(*DllName) != toupper((CHAR)*Buffer))
|
|
return FALSE;
|
|
|
|
/* Move to the next character */
|
|
DllName++;
|
|
Buffer++;
|
|
}
|
|
|
|
/* Check, if strings either fully match, or match till the "." (w/o extension) */
|
|
if ((UnicodeName->Length == Length * sizeof(WCHAR)) || (*Buffer == L'.'))
|
|
{
|
|
/* Yes they do */
|
|
return TRUE;
|
|
}
|
|
|
|
/* Strings don't match, return FALSE */
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN
|
|
PeLdrpLoadAndScanReferencedDll(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PCCH DirectoryPath,
|
|
IN PCH ImportName,
|
|
IN PLIST_ENTRY Parent OPTIONAL,
|
|
OUT PLDR_DATA_TABLE_ENTRY *DataTableEntry);
|
|
|
|
static BOOLEAN
|
|
PeLdrpBindImportName(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PVOID DllBase,
|
|
IN PVOID ImageBase,
|
|
IN PIMAGE_THUNK_DATA ThunkData,
|
|
IN PIMAGE_EXPORT_DIRECTORY ExportDirectory,
|
|
IN ULONG ExportSize,
|
|
IN BOOLEAN ProcessForwards,
|
|
IN PCSTR DirectoryPath,
|
|
IN PLIST_ENTRY Parent)
|
|
{
|
|
ULONG Ordinal;
|
|
PULONG NameTable, FunctionTable;
|
|
PUSHORT OrdinalTable;
|
|
LONG High, Low, Middle, Result;
|
|
ULONG Hint;
|
|
PIMAGE_IMPORT_BY_NAME ImportData;
|
|
PCHAR ExportName, ForwarderName;
|
|
BOOLEAN Success;
|
|
|
|
//TRACE("PeLdrpBindImportName(): DllBase 0x%X, ImageBase 0x%X, ThunkData 0x%X, ExportDirectory 0x%X, ExportSize %d, ProcessForwards 0x%X\n",
|
|
// DllBase, ImageBase, ThunkData, ExportDirectory, ExportSize, ProcessForwards);
|
|
|
|
/* Check passed DllBase param */
|
|
if (DllBase == NULL)
|
|
{
|
|
WARN("DllBase == NULL!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Convert all non-critical pointers to PA from VA */
|
|
ThunkData = VaToPa(ThunkData);
|
|
|
|
/* Is the reference by ordinal? */
|
|
if (IMAGE_SNAP_BY_ORDINAL(ThunkData->u1.Ordinal) && !ProcessForwards)
|
|
{
|
|
/* Yes, calculate the ordinal */
|
|
Ordinal = (ULONG)(IMAGE_ORDINAL(ThunkData->u1.Ordinal) - (UINT32)ExportDirectory->Base);
|
|
//TRACE("PeLdrpBindImportName(): Ordinal %d\n", Ordinal);
|
|
}
|
|
else
|
|
{
|
|
/* It's reference by name, we have to look it up in the export directory */
|
|
if (!ProcessForwards)
|
|
{
|
|
/* AddressOfData in thunk entry will become a virtual address (from relative) */
|
|
//TRACE("PeLdrpBindImportName(): ThunkData->u1.AOD was %p\n", ThunkData->u1.AddressOfData);
|
|
ThunkData->u1.AddressOfData =
|
|
(ULONG_PTR)RVA(ImageBase, ThunkData->u1.AddressOfData);
|
|
//TRACE("PeLdrpBindImportName(): ThunkData->u1.AOD became %p\n", ThunkData->u1.AddressOfData);
|
|
}
|
|
|
|
/* Get the import name */
|
|
ImportData = VaToPa((PVOID)ThunkData->u1.AddressOfData);
|
|
|
|
/* Get pointers to Name and Ordinal tables (RVA -> VA) */
|
|
NameTable = VaToPa(RVA(DllBase, ExportDirectory->AddressOfNames));
|
|
OrdinalTable = VaToPa(RVA(DllBase, ExportDirectory->AddressOfNameOrdinals));
|
|
|
|
//TRACE("NameTable 0x%X, OrdinalTable 0x%X, ED->AddressOfNames 0x%X, ED->AOFO 0x%X\n",
|
|
// NameTable, OrdinalTable, ExportDirectory->AddressOfNames, ExportDirectory->AddressOfNameOrdinals);
|
|
|
|
/* Get the hint, convert it to a physical pointer */
|
|
Hint = ((PIMAGE_IMPORT_BY_NAME)VaToPa((PVOID)ThunkData->u1.AddressOfData))->Hint;
|
|
//TRACE("HintIndex %d\n", Hint);
|
|
|
|
/* Get the export name from the hint */
|
|
ExportName = VaToPa(RVA(DllBase, NameTable[Hint]));
|
|
|
|
/* If Hint is less than total number of entries in the export directory,
|
|
and import name == export name, then we can just get it from the OrdinalTable */
|
|
if ((Hint < ExportDirectory->NumberOfNames) &&
|
|
(strcmp(ExportName, (PCHAR)ImportData->Name) == 0))
|
|
{
|
|
Ordinal = OrdinalTable[Hint];
|
|
//TRACE("PeLdrpBindImportName(): Ordinal %d\n", Ordinal);
|
|
}
|
|
else
|
|
{
|
|
/* It's not the easy way, we have to lookup import name in the name table.
|
|
Let's use a binary search for this task. */
|
|
|
|
//TRACE("PeLdrpBindImportName() looking up the import name using binary search...\n");
|
|
|
|
/* Low boundary is set to 0, and high boundary to the maximum index */
|
|
Low = 0;
|
|
High = ExportDirectory->NumberOfNames - 1;
|
|
|
|
/* Perform a binary-search loop */
|
|
while (High >= Low)
|
|
{
|
|
/* Divide by 2 by shifting to the right once */
|
|
Middle = (Low + High) / 2;
|
|
|
|
/* Get the name from the name table */
|
|
ExportName = VaToPa(RVA(DllBase, NameTable[Middle]));
|
|
|
|
/* Compare the names */
|
|
Result = strcmp(ExportName, (PCHAR)ImportData->Name);
|
|
|
|
// TRACE("Binary search: comparing Import '__', Export '%s'\n",
|
|
// VaToPa(&((PIMAGE_IMPORT_BY_NAME)VaToPa(ThunkData->u1.AddressOfData))->Name[0]),
|
|
// (PCHAR)VaToPa(RVA(DllBase, NameTable[Middle])));
|
|
|
|
// TRACE("TE->u1.AOD %p, fulladdr %p\n",
|
|
// ThunkData->u1.AddressOfData,
|
|
// ((PIMAGE_IMPORT_BY_NAME)VaToPa(ThunkData->u1.AddressOfData))->Name );
|
|
|
|
/* Depending on result of strcmp, perform different actions */
|
|
if (Result > 0)
|
|
{
|
|
/* Adjust top boundary */
|
|
High = Middle - 1;
|
|
}
|
|
else if (Result < 0)
|
|
{
|
|
/* Adjust bottom boundary */
|
|
Low = Middle + 1;
|
|
}
|
|
else
|
|
{
|
|
/* Yay, found it! */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If high boundary is less than low boundary, then no result found */
|
|
if (High < Low)
|
|
{
|
|
ERR("Did not find export '%s'!\n", (PCHAR)ImportData->Name);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Everything alright, get the ordinal */
|
|
Ordinal = OrdinalTable[Middle];
|
|
|
|
//TRACE("PeLdrpBindImportName() found Ordinal %d\n", Ordinal);
|
|
}
|
|
}
|
|
|
|
/* Check ordinal number for validity! */
|
|
if (Ordinal >= ExportDirectory->NumberOfFunctions)
|
|
{
|
|
ERR("Ordinal number is invalid!\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get a pointer to the function table */
|
|
FunctionTable = (PULONG)VaToPa(RVA(DllBase, ExportDirectory->AddressOfFunctions));
|
|
|
|
/* Save a pointer to the function */
|
|
ThunkData->u1.Function = (ULONG_PTR)RVA(DllBase, FunctionTable[Ordinal]);
|
|
|
|
/* Is it a forwarder? (function pointer is within the export directory) */
|
|
ForwarderName = (PCHAR)VaToPa((PVOID)ThunkData->u1.Function);
|
|
if (((ULONG_PTR)ForwarderName > (ULONG_PTR)ExportDirectory) &&
|
|
((ULONG_PTR)ForwarderName < ((ULONG_PTR)ExportDirectory + ExportSize)))
|
|
{
|
|
PLDR_DATA_TABLE_ENTRY DataTableEntry;
|
|
PIMAGE_EXPORT_DIRECTORY RefExportDirectory;
|
|
ULONG RefExportSize;
|
|
CHAR ForwardDllName[256];
|
|
|
|
TRACE("PeLdrpBindImportName(): ForwarderName %s\n", ForwarderName);
|
|
|
|
/* Save the name of the forward dll */
|
|
RtlCopyMemory(ForwardDllName, ForwarderName, sizeof(ForwardDllName));
|
|
|
|
/* Strip out the symbol name */
|
|
*strrchr(ForwardDllName, '.') = ANSI_NULL;
|
|
|
|
/* Check if the target image is already loaded */
|
|
if (!PeLdrCheckForLoadedDll(ModuleListHead, ForwardDllName, &DataTableEntry))
|
|
{
|
|
/* Check if the forward dll name has an extension */
|
|
if (strchr(ForwardDllName, '.') == NULL)
|
|
{
|
|
/* Name does not have an extension, append '.dll' */
|
|
RtlStringCbCatA(ForwardDllName, sizeof(ForwardDllName), ".dll");
|
|
}
|
|
|
|
/* Now let's try to load it! */
|
|
Success = PeLdrpLoadAndScanReferencedDll(ModuleListHead,
|
|
DirectoryPath,
|
|
ForwardDllName,
|
|
Parent,
|
|
&DataTableEntry);
|
|
if (!Success)
|
|
{
|
|
ERR("PeLdrpLoadAndScanReferencedDll() failed to load forwarder dll.\n");
|
|
return Success;
|
|
}
|
|
}
|
|
|
|
/* Get pointer to the export directory of loaded DLL */
|
|
RefExportDirectory = (PIMAGE_EXPORT_DIRECTORY)
|
|
RtlImageDirectoryEntryToData(VaToPa(DataTableEntry->DllBase),
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_EXPORT,
|
|
&RefExportSize);
|
|
|
|
/* Fail if it's NULL */
|
|
if (RefExportDirectory)
|
|
{
|
|
UCHAR Buffer[128];
|
|
IMAGE_THUNK_DATA RefThunkData;
|
|
PIMAGE_IMPORT_BY_NAME ImportByName;
|
|
PCHAR ImportName;
|
|
|
|
/* Get pointer to the import name */
|
|
ImportName = strrchr(ForwarderName, '.') + 1;
|
|
|
|
/* Create a IMAGE_IMPORT_BY_NAME structure, pointing to the local Buffer */
|
|
ImportByName = (PIMAGE_IMPORT_BY_NAME)Buffer;
|
|
|
|
/* Fill the name with the import name */
|
|
RtlCopyMemory(ImportByName->Name, ImportName, strlen(ImportName)+1);
|
|
|
|
/* Set Hint to 0 */
|
|
ImportByName->Hint = 0;
|
|
|
|
/* And finally point ThunkData's AddressOfData to that structure */
|
|
RefThunkData.u1.AddressOfData = (ULONG_PTR)ImportByName;
|
|
|
|
/* And recursively call ourselves */
|
|
Success = PeLdrpBindImportName(ModuleListHead,
|
|
DataTableEntry->DllBase,
|
|
ImageBase,
|
|
&RefThunkData,
|
|
RefExportDirectory,
|
|
RefExportSize,
|
|
TRUE,
|
|
DirectoryPath,
|
|
Parent);
|
|
|
|
/* Fill out the ThunkData with data from RefThunkData */
|
|
ThunkData->u1 = RefThunkData.u1;
|
|
|
|
/* Return what we got from the recursive call */
|
|
return Success;
|
|
}
|
|
else
|
|
{
|
|
/* Fail if ExportDirectory is NULL */
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Success! */
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN
|
|
PeLdrpLoadAndScanReferencedDll(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PCCH DirectoryPath,
|
|
IN PCH ImportName,
|
|
IN PLIST_ENTRY Parent OPTIONAL,
|
|
OUT PLDR_DATA_TABLE_ENTRY *DataTableEntry)
|
|
{
|
|
CHAR FullDllName[256];
|
|
BOOLEAN Success;
|
|
PVOID BasePA = NULL;
|
|
|
|
/* Prepare the full path to the file to be loaded */
|
|
RtlStringCbCopyA(FullDllName, sizeof(FullDllName), DirectoryPath);
|
|
RtlStringCbCatA(FullDllName, sizeof(FullDllName), ImportName);
|
|
|
|
TRACE("Loading referenced DLL: %s\n", FullDllName);
|
|
|
|
if (PeLdrImportDllLoadCallback)
|
|
PeLdrImportDllLoadCallback(FullDllName);
|
|
|
|
/* Load the image */
|
|
Success = PeLdrLoadImage(FullDllName, LoaderBootDriver, &BasePA);
|
|
if (!Success)
|
|
{
|
|
ERR("PeLdrLoadImage('%s') failed\n", FullDllName);
|
|
return Success;
|
|
}
|
|
|
|
/* Allocate DTE for newly loaded DLL */
|
|
Success = PeLdrAllocateDataTableEntry(Parent ? Parent->Blink : ModuleListHead,
|
|
ImportName,
|
|
FullDllName,
|
|
BasePA,
|
|
DataTableEntry);
|
|
if (!Success)
|
|
{
|
|
/* Cleanup and bail out */
|
|
ERR("PeLdrAllocateDataTableEntry('%s') failed\n", FullDllName);
|
|
MmFreeMemory(BasePA);
|
|
return Success;
|
|
}
|
|
|
|
/* Init security cookie */
|
|
PeLdrInitSecurityCookie(*DataTableEntry);
|
|
|
|
(*DataTableEntry)->Flags |= LDRP_DRIVER_DEPENDENT_DLL;
|
|
|
|
/* Scan its dependencies too */
|
|
TRACE("PeLdrScanImportDescriptorTable() calling ourselves for %S\n",
|
|
VaToPa((*DataTableEntry)->BaseDllName.Buffer));
|
|
Success = PeLdrScanImportDescriptorTable(ModuleListHead, DirectoryPath, *DataTableEntry);
|
|
if (!Success)
|
|
{
|
|
/* Cleanup and bail out */
|
|
ERR("PeLdrScanImportDescriptorTable() failed\n");
|
|
PeLdrFreeDataTableEntry(*DataTableEntry);
|
|
MmFreeMemory(BasePA);
|
|
return Success;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN
|
|
PeLdrpScanImportAddressTable(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PVOID DllBase,
|
|
IN PVOID ImageBase,
|
|
IN PIMAGE_THUNK_DATA ThunkData,
|
|
IN PCSTR DirectoryPath,
|
|
IN PLIST_ENTRY Parent)
|
|
{
|
|
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
|
BOOLEAN Success;
|
|
ULONG ExportSize;
|
|
|
|
TRACE("PeLdrpScanImportAddressTable(): DllBase 0x%X, "
|
|
"ImageBase 0x%X, ThunkData 0x%X\n", DllBase, ImageBase, ThunkData);
|
|
|
|
/* Obtain the export table from the DLL's base */
|
|
if (DllBase == NULL)
|
|
{
|
|
ERR("Error, DllBase == NULL!\n");
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
ExportDirectory =
|
|
(PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData(VaToPa(DllBase),
|
|
TRUE,
|
|
IMAGE_DIRECTORY_ENTRY_EXPORT,
|
|
&ExportSize);
|
|
}
|
|
|
|
TRACE("PeLdrpScanImportAddressTable(): ExportDirectory 0x%X\n", ExportDirectory);
|
|
|
|
/* If pointer to Export Directory is */
|
|
if (ExportDirectory == NULL)
|
|
{
|
|
ERR("DllBase=%p(%p)\n", DllBase, VaToPa(DllBase));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Go through each entry in the thunk table and bind it */
|
|
while (((PIMAGE_THUNK_DATA)VaToPa(ThunkData))->u1.AddressOfData != 0)
|
|
{
|
|
/* Bind it */
|
|
Success = PeLdrpBindImportName(ModuleListHead,
|
|
DllBase,
|
|
ImageBase,
|
|
ThunkData,
|
|
ExportDirectory,
|
|
ExportSize,
|
|
FALSE,
|
|
DirectoryPath,
|
|
Parent);
|
|
|
|
/* Move to the next entry */
|
|
ThunkData++;
|
|
|
|
/* Return error if binding was unsuccessful */
|
|
if (!Success)
|
|
return Success;
|
|
}
|
|
|
|
/* Return success */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* FUNCTIONS *****************************************************************/
|
|
|
|
PVOID
|
|
PeLdrInitSecurityCookie(PLDR_DATA_TABLE_ENTRY LdrEntry)
|
|
{
|
|
PULONG_PTR Cookie;
|
|
ULONG_PTR NewCookie;
|
|
|
|
/* Fetch address of the cookie */
|
|
Cookie = PeLdrpFetchAddressOfSecurityCookie(VaToPa(LdrEntry->DllBase), LdrEntry->SizeOfImage);
|
|
|
|
if (!Cookie)
|
|
return NULL;
|
|
|
|
/* Check if it's a default one */
|
|
if ((*Cookie == DEFAULT_SECURITY_COOKIE) ||
|
|
(*Cookie == 0))
|
|
{
|
|
/* Generate new cookie using cookie address and time as seed */
|
|
NewCookie = (ULONG_PTR)Cookie ^ (ULONG_PTR)ArcGetRelativeTime();
|
|
#ifdef _WIN64
|
|
/* Some images expect first 16 bits to be kept clean (like in default cookie) */
|
|
if (NewCookie > COOKIE_MAX)
|
|
{
|
|
NewCookie >>= 16;
|
|
}
|
|
#endif
|
|
/* If the result is 0 or the same as we got, just add one to the default value */
|
|
if ((NewCookie == 0) || (NewCookie == *Cookie))
|
|
{
|
|
NewCookie = DEFAULT_SECURITY_COOKIE + 1;
|
|
}
|
|
|
|
/* Set the new cookie value */
|
|
*Cookie = NewCookie;
|
|
}
|
|
|
|
return Cookie;
|
|
}
|
|
|
|
/* Returns TRUE if DLL has already been loaded - looks in LoadOrderList in LPB */
|
|
BOOLEAN
|
|
PeLdrCheckForLoadedDll(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PCH DllName,
|
|
OUT PLDR_DATA_TABLE_ENTRY *LoadedEntry)
|
|
{
|
|
PLDR_DATA_TABLE_ENTRY DataTableEntry;
|
|
LIST_ENTRY *ModuleEntry;
|
|
|
|
TRACE("PeLdrCheckForLoadedDll: DllName %s\n", DllName);
|
|
|
|
/* Just go through each entry in the LoadOrderList and compare loaded module's
|
|
name with a given name */
|
|
ModuleEntry = ModuleListHead->Flink;
|
|
while (ModuleEntry != ModuleListHead)
|
|
{
|
|
/* Get pointer to the current DTE */
|
|
DataTableEntry = CONTAINING_RECORD(ModuleEntry,
|
|
LDR_DATA_TABLE_ENTRY,
|
|
InLoadOrderLinks);
|
|
|
|
TRACE("PeLdrCheckForLoadedDll: DTE %p, EP %p, base %p name '%.*ws'\n",
|
|
DataTableEntry, DataTableEntry->EntryPoint, DataTableEntry->DllBase,
|
|
DataTableEntry->BaseDllName.Length / 2, VaToPa(DataTableEntry->BaseDllName.Buffer));
|
|
|
|
/* Compare names */
|
|
if (PeLdrpCompareDllName(DllName, &DataTableEntry->BaseDllName))
|
|
{
|
|
/* Yes, found it, report pointer to the loaded module's DTE
|
|
to the caller and increase load count for it */
|
|
*LoadedEntry = DataTableEntry;
|
|
DataTableEntry->LoadCount++;
|
|
TRACE("PeLdrCheckForLoadedDll: LoadedEntry %X\n", DataTableEntry);
|
|
return TRUE;
|
|
}
|
|
|
|
/* Go to the next entry */
|
|
ModuleEntry = ModuleEntry->Flink;
|
|
}
|
|
|
|
/* Nothing found */
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
PeLdrScanImportDescriptorTable(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PCCH DirectoryPath,
|
|
IN PLDR_DATA_TABLE_ENTRY ScanDTE)
|
|
{
|
|
PLDR_DATA_TABLE_ENTRY DataTableEntry;
|
|
PIMAGE_IMPORT_DESCRIPTOR ImportTable;
|
|
ULONG ImportTableSize;
|
|
PCH ImportName;
|
|
BOOLEAN Success;
|
|
|
|
/* Get a pointer to the import table of this image */
|
|
ImportTable = (PIMAGE_IMPORT_DESCRIPTOR)RtlImageDirectoryEntryToData(VaToPa(ScanDTE->DllBase),
|
|
TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ImportTableSize);
|
|
|
|
#if DBG
|
|
{
|
|
UNICODE_STRING BaseName;
|
|
BaseName.Buffer = VaToPa(ScanDTE->BaseDllName.Buffer);
|
|
BaseName.MaximumLength = ScanDTE->BaseDllName.MaximumLength;
|
|
BaseName.Length = ScanDTE->BaseDllName.Length;
|
|
TRACE("PeLdrScanImportDescriptorTable(): %wZ ImportTable = 0x%X\n",
|
|
&BaseName, ImportTable);
|
|
}
|
|
#endif
|
|
|
|
/* If image doesn't have any import directory - just return success */
|
|
if (ImportTable == NULL)
|
|
return TRUE;
|
|
|
|
/* Loop through all entries */
|
|
for (;(ImportTable->Name != 0) && (ImportTable->FirstThunk != 0);ImportTable++)
|
|
{
|
|
/* Get pointer to the name */
|
|
ImportName = (PCH)VaToPa(RVA(ScanDTE->DllBase, ImportTable->Name));
|
|
TRACE("PeLdrScanImportDescriptorTable(): Looking at %s\n", ImportName);
|
|
|
|
/* In case we get a reference to ourselves - just skip it */
|
|
if (PeLdrpCompareDllName(ImportName, &ScanDTE->BaseDllName))
|
|
continue;
|
|
|
|
/* Load the DLL if it is not already loaded */
|
|
if (!PeLdrCheckForLoadedDll(ModuleListHead, ImportName, &DataTableEntry))
|
|
{
|
|
Success = PeLdrpLoadAndScanReferencedDll(ModuleListHead,
|
|
DirectoryPath,
|
|
ImportName,
|
|
&ScanDTE->InLoadOrderLinks,
|
|
&DataTableEntry);
|
|
if (!Success)
|
|
{
|
|
ERR("PeLdrpLoadAndScanReferencedDll() failed\n");
|
|
return Success;
|
|
}
|
|
}
|
|
|
|
/* Scan its import address table */
|
|
Success = PeLdrpScanImportAddressTable(ModuleListHead,
|
|
DataTableEntry->DllBase,
|
|
ScanDTE->DllBase,
|
|
(PIMAGE_THUNK_DATA)RVA(ScanDTE->DllBase, ImportTable->FirstThunk),
|
|
DirectoryPath,
|
|
&ScanDTE->InLoadOrderLinks);
|
|
|
|
if (!Success)
|
|
{
|
|
ERR("PeLdrpScanImportAddressTable() failed: ImportName = '%s', DirectoryPath = '%s'\n",
|
|
ImportName, DirectoryPath);
|
|
return Success;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOLEAN
|
|
PeLdrAllocateDataTableEntry(
|
|
IN OUT PLIST_ENTRY ModuleListHead,
|
|
IN PCCH BaseDllName,
|
|
IN PCCH FullDllName,
|
|
IN PVOID BasePA,
|
|
OUT PLDR_DATA_TABLE_ENTRY *NewEntry)
|
|
{
|
|
PVOID BaseVA = PaToVa(BasePA);
|
|
PWSTR BaseDllNameBuffer, Buffer;
|
|
PLDR_DATA_TABLE_ENTRY DataTableEntry;
|
|
PIMAGE_NT_HEADERS NtHeaders;
|
|
USHORT Length;
|
|
|
|
TRACE("PeLdrAllocateDataTableEntry('%s', '%s', %p)\n",
|
|
BaseDllName, FullDllName, BasePA);
|
|
|
|
/* Allocate memory for a data table entry, zero-initialize it */
|
|
DataTableEntry = (PLDR_DATA_TABLE_ENTRY)FrLdrHeapAlloc(sizeof(LDR_DATA_TABLE_ENTRY),
|
|
TAG_WLDR_DTE);
|
|
if (DataTableEntry == NULL)
|
|
return FALSE;
|
|
|
|
/* Get NT headers from the image */
|
|
NtHeaders = RtlImageNtHeader(BasePA);
|
|
|
|
/* Initialize corresponding fields of DTE based on NT headers value */
|
|
RtlZeroMemory(DataTableEntry, sizeof(LDR_DATA_TABLE_ENTRY));
|
|
DataTableEntry->DllBase = BaseVA;
|
|
DataTableEntry->SizeOfImage = NtHeaders->OptionalHeader.SizeOfImage;
|
|
DataTableEntry->EntryPoint = RVA(BaseVA, NtHeaders->OptionalHeader.AddressOfEntryPoint);
|
|
DataTableEntry->SectionPointer = 0;
|
|
DataTableEntry->CheckSum = NtHeaders->OptionalHeader.CheckSum;
|
|
|
|
/* Initialize BaseDllName field (UNICODE_STRING) from the Ansi BaseDllName
|
|
by simple conversion - copying each character */
|
|
Length = (USHORT)(strlen(BaseDllName) * sizeof(WCHAR));
|
|
Buffer = (PWSTR)FrLdrHeapAlloc(Length, TAG_WLDR_NAME);
|
|
if (Buffer == NULL)
|
|
{
|
|
FrLdrHeapFree(DataTableEntry, TAG_WLDR_DTE);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save Buffer, in case of later failure */
|
|
BaseDllNameBuffer = Buffer;
|
|
|
|
DataTableEntry->BaseDllName.Length = Length;
|
|
DataTableEntry->BaseDllName.MaximumLength = Length;
|
|
DataTableEntry->BaseDllName.Buffer = PaToVa(Buffer);
|
|
|
|
RtlZeroMemory(Buffer, Length);
|
|
Length /= sizeof(WCHAR);
|
|
while (Length--)
|
|
{
|
|
*Buffer++ = *BaseDllName++;
|
|
}
|
|
|
|
/* Initialize FullDllName field (UNICODE_STRING) from the Ansi FullDllName
|
|
using the same method */
|
|
Length = (USHORT)(strlen(FullDllName) * sizeof(WCHAR));
|
|
Buffer = (PWSTR)FrLdrHeapAlloc(Length, TAG_WLDR_NAME);
|
|
if (Buffer == NULL)
|
|
{
|
|
FrLdrHeapFree(BaseDllNameBuffer, TAG_WLDR_NAME);
|
|
FrLdrHeapFree(DataTableEntry, TAG_WLDR_DTE);
|
|
return FALSE;
|
|
}
|
|
|
|
DataTableEntry->FullDllName.Length = Length;
|
|
DataTableEntry->FullDllName.MaximumLength = Length;
|
|
DataTableEntry->FullDllName.Buffer = PaToVa(Buffer);
|
|
|
|
RtlZeroMemory(Buffer, Length);
|
|
Length /= sizeof(WCHAR);
|
|
while (Length--)
|
|
{
|
|
*Buffer++ = *FullDllName++;
|
|
}
|
|
|
|
/* Initialize what's left - LoadCount which is 1, and set Flags so that
|
|
we know this entry is processed */
|
|
DataTableEntry->Flags = LDRP_ENTRY_PROCESSED;
|
|
DataTableEntry->LoadCount = 1;
|
|
|
|
/* Honour the FORCE_INTEGRITY flag */
|
|
if (NtHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY)
|
|
{
|
|
/*
|
|
* On Vista and above, the LDRP_IMAGE_INTEGRITY_FORCED flag must be set
|
|
* if IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY is set in the image header.
|
|
* This is done after the image has been loaded and the digital signature
|
|
* check has passed successfully. (We do not do it yet!)
|
|
*
|
|
* Several OS functionality depend on the presence of this flag.
|
|
* For example, when using Object-Manager callbacks the latter will call
|
|
* MmVerifyCallbackFunction() to verify whether the flag is present.
|
|
* If not callbacks will not work.
|
|
* (See Windows Internals Part 1, 6th edition, p. 176.)
|
|
*/
|
|
DataTableEntry->Flags |= LDRP_IMAGE_INTEGRITY_FORCED;
|
|
}
|
|
|
|
/* Insert this DTE to a list in the LPB */
|
|
InsertTailList(ModuleListHead, &DataTableEntry->InLoadOrderLinks);
|
|
TRACE("Inserting DTE %p, name='%.*S' DllBase=%p\n", DataTableEntry,
|
|
DataTableEntry->BaseDllName.Length / sizeof(WCHAR),
|
|
VaToPa(DataTableEntry->BaseDllName.Buffer),
|
|
DataTableEntry->DllBase);
|
|
|
|
/* Save pointer to a newly allocated and initialized entry */
|
|
*NewEntry = DataTableEntry;
|
|
|
|
/* Return success */
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
PeLdrFreeDataTableEntry(
|
|
// _In_ PLIST_ENTRY ModuleListHead,
|
|
_In_ PLDR_DATA_TABLE_ENTRY Entry)
|
|
{
|
|
// ASSERT(ModuleListHead);
|
|
ASSERT(Entry);
|
|
|
|
RemoveEntryList(&Entry->InLoadOrderLinks);
|
|
FrLdrHeapFree(VaToPa(Entry->FullDllName.Buffer), TAG_WLDR_NAME);
|
|
FrLdrHeapFree(VaToPa(Entry->BaseDllName.Buffer), TAG_WLDR_NAME);
|
|
FrLdrHeapFree(Entry, TAG_WLDR_DTE);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Loads the specified image from the file.
|
|
*
|
|
* PeLdrLoadImage doesn't perform any additional operations on the file path,
|
|
* it just directly calls the file I/O routines. It then relocates the image
|
|
* so that it's ready to be used when paging is enabled.
|
|
*
|
|
* @note
|
|
* Addressing mode: physical.
|
|
**/
|
|
BOOLEAN
|
|
PeLdrLoadImage(
|
|
_In_ PCSTR FilePath,
|
|
_In_ TYPE_OF_MEMORY MemoryType,
|
|
_Out_ PVOID* ImageBasePA)
|
|
{
|
|
ULONG FileId;
|
|
PVOID PhysicalBase;
|
|
PVOID VirtualBase = NULL;
|
|
UCHAR HeadersBuffer[SECTOR_SIZE * 2];
|
|
PIMAGE_NT_HEADERS NtHeaders;
|
|
PIMAGE_SECTION_HEADER SectionHeader;
|
|
ULONG VirtualSize, SizeOfRawData, NumberOfSections;
|
|
ARC_STATUS Status;
|
|
LARGE_INTEGER Position;
|
|
ULONG i, BytesRead;
|
|
|
|
TRACE("PeLdrLoadImage('%s', %ld)\n", FilePath, MemoryType);
|
|
|
|
/* Open the image file */
|
|
Status = ArcOpen((PSTR)FilePath, OpenReadOnly, &FileId);
|
|
if (Status != ESUCCESS)
|
|
{
|
|
WARN("ArcOpen('%s') failed. Status: %u\n", FilePath, Status);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Load the first 2 sectors of the image so we can read the PE header */
|
|
Status = ArcRead(FileId, HeadersBuffer, SECTOR_SIZE * 2, &BytesRead);
|
|
if (Status != ESUCCESS)
|
|
{
|
|
ERR("ArcRead('%s') failed. Status: %u\n", FilePath, Status);
|
|
ArcClose(FileId);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Now read the MZ header to get the offset to the PE Header */
|
|
NtHeaders = RtlImageNtHeader(HeadersBuffer);
|
|
if (!NtHeaders)
|
|
{
|
|
ERR("No NT header found in \"%s\"\n", FilePath);
|
|
ArcClose(FileId);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Ensure this is executable image */
|
|
if (((NtHeaders->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0))
|
|
{
|
|
ERR("Not an executable image \"%s\"\n", FilePath);
|
|
ArcClose(FileId);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Store number of sections to read and a pointer to the first section */
|
|
NumberOfSections = NtHeaders->FileHeader.NumberOfSections;
|
|
SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
|
|
|
|
/* Try to allocate this memory; if it fails, allocate somewhere else */
|
|
PhysicalBase = MmAllocateMemoryAtAddress(NtHeaders->OptionalHeader.SizeOfImage,
|
|
(PVOID)((ULONG)NtHeaders->OptionalHeader.ImageBase & (KSEG0_BASE - 1)),
|
|
MemoryType);
|
|
|
|
if (PhysicalBase == NULL)
|
|
{
|
|
/* Don't fail, allocate again at any other "low" place */
|
|
PhysicalBase = MmAllocateMemoryWithType(NtHeaders->OptionalHeader.SizeOfImage, MemoryType);
|
|
|
|
if (PhysicalBase == NULL)
|
|
{
|
|
ERR("Failed to alloc %lu bytes for image %s\n", NtHeaders->OptionalHeader.SizeOfImage, FilePath);
|
|
ArcClose(FileId);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* This is the real image base, in form of a virtual address */
|
|
VirtualBase = PaToVa(PhysicalBase);
|
|
|
|
TRACE("Base PA: 0x%X, VA: 0x%X\n", PhysicalBase, VirtualBase);
|
|
|
|
/* Copy headers from already read data */
|
|
RtlCopyMemory(PhysicalBase, HeadersBuffer, min(NtHeaders->OptionalHeader.SizeOfHeaders, sizeof(HeadersBuffer)));
|
|
/* If headers are quite big, request next bytes from file */
|
|
if (NtHeaders->OptionalHeader.SizeOfHeaders > sizeof(HeadersBuffer))
|
|
{
|
|
Status = ArcRead(FileId, (PUCHAR)PhysicalBase + sizeof(HeadersBuffer), NtHeaders->OptionalHeader.SizeOfHeaders - sizeof(HeadersBuffer), &BytesRead);
|
|
if (Status != ESUCCESS)
|
|
{
|
|
ERR("ArcRead('%s') failed. Status: %u\n", FilePath, Status);
|
|
// UiMessageBox("Error reading headers.");
|
|
ArcClose(FileId);
|
|
goto Failure;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* On Vista and above, a digital signature check is performed when the image
|
|
* has the IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY flag set in its header.
|
|
* (We of course do not perform this check yet!)
|
|
*/
|
|
|
|
/* Reload the NT Header */
|
|
NtHeaders = RtlImageNtHeader(PhysicalBase);
|
|
|
|
/* Load the first section */
|
|
SectionHeader = IMAGE_FIRST_SECTION(NtHeaders);
|
|
|
|
/* Walk through each section and read it (check/fix any possible
|
|
bad situations, if they arise) */
|
|
for (i = 0; i < NumberOfSections; i++)
|
|
{
|
|
VirtualSize = SectionHeader->Misc.VirtualSize;
|
|
SizeOfRawData = SectionHeader->SizeOfRawData;
|
|
|
|
/* Handle a case when VirtualSize equals 0 */
|
|
if (VirtualSize == 0)
|
|
VirtualSize = SizeOfRawData;
|
|
|
|
/* If PointerToRawData is 0, then force its size to be also 0 */
|
|
if (SectionHeader->PointerToRawData == 0)
|
|
{
|
|
SizeOfRawData = 0;
|
|
}
|
|
else
|
|
{
|
|
/* Cut the loaded size to the VirtualSize extents */
|
|
if (SizeOfRawData > VirtualSize)
|
|
SizeOfRawData = VirtualSize;
|
|
}
|
|
|
|
/* Actually read the section (if its size is not 0) */
|
|
if (SizeOfRawData != 0)
|
|
{
|
|
/* Seek to the correct position */
|
|
Position.QuadPart = SectionHeader->PointerToRawData;
|
|
Status = ArcSeek(FileId, &Position, SeekAbsolute);
|
|
|
|
TRACE("SH->VA: 0x%X\n", SectionHeader->VirtualAddress);
|
|
|
|
/* Read this section from the file, size = SizeOfRawData */
|
|
Status = ArcRead(FileId, (PUCHAR)PhysicalBase + SectionHeader->VirtualAddress, SizeOfRawData, &BytesRead);
|
|
if (Status != ESUCCESS)
|
|
{
|
|
ERR("PeLdrLoadImage(): Error reading section from file!\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Size of data is less than the virtual size: fill up the remainder with zeroes */
|
|
if (SizeOfRawData < VirtualSize)
|
|
{
|
|
TRACE("PeLdrLoadImage(): SORD %d < VS %d\n", SizeOfRawData, VirtualSize);
|
|
RtlZeroMemory((PVOID)(SectionHeader->VirtualAddress + (ULONG_PTR)PhysicalBase + SizeOfRawData), VirtualSize - SizeOfRawData);
|
|
}
|
|
|
|
SectionHeader++;
|
|
}
|
|
|
|
/* We are done with the file, close it */
|
|
ArcClose(FileId);
|
|
|
|
/* If loading failed, return right now */
|
|
if (Status != ESUCCESS)
|
|
goto Failure;
|
|
|
|
/* Relocate the image, if it needs it */
|
|
if (NtHeaders->OptionalHeader.ImageBase != (ULONG_PTR)VirtualBase)
|
|
{
|
|
WARN("Relocating %p -> %p\n", NtHeaders->OptionalHeader.ImageBase, VirtualBase);
|
|
Status = LdrRelocateImageWithBias(PhysicalBase,
|
|
(ULONG_PTR)VirtualBase - (ULONG_PTR)PhysicalBase,
|
|
"FreeLdr",
|
|
ESUCCESS,
|
|
ESUCCESS, /* In case of conflict still return success */
|
|
ENOEXEC);
|
|
if (Status != ESUCCESS)
|
|
goto Failure;
|
|
}
|
|
|
|
/* Fill output parameters */
|
|
*ImageBasePA = PhysicalBase;
|
|
|
|
TRACE("PeLdrLoadImage() done, PA = %p\n", *ImageBasePA);
|
|
return TRUE;
|
|
|
|
Failure:
|
|
/* Cleanup and bail out */
|
|
MmFreeMemory(PhysicalBase);
|
|
return FALSE;
|
|
}
|