ldrPE.cpp revision db16206b8106b6df2761dbb4dbe246c70a6716de
/* $Id$ */
/** @file
* IPRT - Binary Image Loader, Portable Executable (PE).
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP RTLOGGROUP_LDR
#ifndef IPRT_WITHOUT_LDR_VERIFY
#endif
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** Converts rva to a type.
* @param pvBits Pointer to base of image bits.
* @param rva Relative virtual address.
* @param type Type.
*/
/** The max size of the security directory. */
#ifdef IN_RING3
# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _4M
#else
# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _1M
#endif
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* The PE loader structure.
*/
typedef struct RTLDRMODPE
{
/** Core module structure. */
/** Pointer to internal copy of image bits.
* @todo the reader should take care of this. */
void *pvBits;
/** The offset of the NT headers. */
/** The offset of the first byte after the section table. */
/** The machine type (IMAGE_FILE_HEADER::Machine). */
/** The file flags (IMAGE_FILE_HEADER::Characteristics). */
/** Number of sections (IMAGE_FILE_HEADER::NumberOfSections). */
unsigned cSections;
/** Pointer to an array of the section headers related to the file. */
/** The RVA of the entry point (IMAGE_OPTIONAL_HEADER32::AddressOfEntryPoint). */
/** The base address of the image at link time (IMAGE_OPTIONAL_HEADER32::ImageBase). */
/** The size of the loaded image (IMAGE_OPTIONAL_HEADER32::SizeOfImage). */
/** Size of the header (IMAGE_OPTIONAL_HEADER32::SizeOfHeaders). */
/** The image timestamp. */
/** The number of imports. UINT32_MAX if not determined. */
/** Set if the image is 64-bit, clear if 32-bit. */
bool f64Bit;
/** The import data directory entry. */
/** The base relocation data directory entry. */
/** The export data directory entry. */
/** The debug directory entry. */
/** The security directory entry. */
/** Offset of the first PKCS \#7 SignedData signature if present. */
/** Size of the first PKCS \#7 SignedData. */
/** Copy of the optional header field DllCharacteristics. */
} RTLDRMODPE;
/** Pointer to the instance data for a PE loader module. */
typedef RTLDRMODPE *PRTLDRMODPE;
/**
* PE Loader module operations.
*
* The PE loader has one operation which is a bit different between 32-bit and 64-bit PE images,
* and for historical and performance reasons have been split into separate functions. Thus the
* PE loader extends the RTLDROPS structure with this one entry.
*/
typedef struct RTLDROPSPE
{
/** The usual ops. */
/**
* Resolves all imports.
*
* @returns iprt status code.
* @param pModPe Pointer to the PE loader module structure.
* @param pvBitsR Where to read raw image bits. (optional)
* @param pvBitsW Where to store the imports. The size of this buffer is equal or
* larger to the value returned by pfnGetImageSize().
* @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals).
* @param pvUser User argument to pass to the callback.
*/
DECLCALLBACKMEMBER(int, pfnResolveImports)(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser);
/** Dummy entry to make sure we've initialized it all. */
} RTLDROPSPE, *PRTLDROPSPE;
/**
* PE hash context union.
*/
typedef union RTLDRPEHASHCTXUNION
{
/** Pointer to a PE hash context union. */
typedef RTLDRPEHASHCTXUNION *PRTLDRPEHASHCTXUNION;
/**
* PE hash digests
*/
typedef union RTLDRPEHASHRESUNION
{
/** Pointer to a PE hash work set. */
typedef RTLDRPEHASHRESUNION *PRTLDRPEHASHRESUNION;
/**
* Special places to watch out for when hashing a PE image.
*/
typedef struct RTLDRPEHASHSPECIALS
{
/** Pointer to the structure with the special hash places. */
typedef RTLDRPEHASHSPECIALS *PRTLDRPEHASHSPECIALS;
#ifndef IPRT_WITHOUT_LDR_VERIFY
/**
* Parsed signature data.
*/
typedef struct RTLDRPESIGNATURE
{
/** The outer content info wrapper. */
/** Pointer to the decoded SignedData inside the ContentInfo member. */
/** Pointer to the indirect data content. */
/** The digest type employed by the signature. */
/** Pointer to the raw signatures. This is allocated in the continuation of
* this structure to keep things simple. The size is given by the security
* export directory. */
WIN_CERTIFICATE const *pRawData;
/** Hash scratch data. */
/** Hash result. */
/** Pointed to SigneData parsing stat and output. */
typedef RTLDRPESIGNATURE *PRTLDRPESIGNATURE;
#endif
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, RTUINTPTR OldBaseAddress);
/**
* Reads a section of a PE image given by RVA + size, using mapped bits if
* available or allocating heap memory and reading from the file.
*
* @returns IPRT status code.
* @param pThis Pointer to the PE loader module structure.
* @param pvBits Read only bits if available. NULL if not.
* @param uRva The RVA to read at.
* @param cbMem The number of bytes to read.
* @param ppvMem Where to return the memory on success (heap or
* inside pvBits).
*/
static int rtldrPEReadPartByRva(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void const **ppvMem)
{
if (!cbMem)
return VINF_SUCCESS;
/*
* Use bits if we've got some.
*/
if (pvBits)
{
return VINF_SUCCESS;
}
{
return VINF_SUCCESS;
}
/*
* Allocate a buffer and read the bits from the file (or whatever).
*/
return VERR_ACCESS_DENIED;
if (!pbMem)
return VERR_NO_MEMORY;
/* Do the reading on a per section base. */
for (;;)
{
/* Translate the RVA into a file offset. */
{
/* Special header section. */
/* The following capping is an approximation. */
if (offFile >= offFirstRawData)
cbToRead = 0;
}
else
{
/* Find the matching section and its mapping size. */
uint32_t j = 0;
uint32_t offSection = 0;
{
if (offSection < cbMapping)
break;
j++;
}
if (j >= cbMapping)
break; /* This shouldn't happen, just return zeros if it does. */
/* Adjust the sizes and calc the file offset. */
{
}
else
{
cbToRead = 0;
}
}
/* Perform the read after adjusting a little (paranoia). */
cbToRead = 0;
if (cbToRead)
{
if (RT_FAILURE(rc))
{
return rc;
}
}
/* Advance */
break;
}
return VINF_SUCCESS;
}
/**
* Reads a part of a PE file from the file and into a heap block.
*
* @returns IRPT status code.
* @param pThis Pointer to the PE loader module structure..
* @param offFile The file offset.
* @param cbMem The number of bytes to read.
* @param ppvMem Where to return the heap block with the bytes on
* success.
*/
static int rtldrPEReadPartFromFile(PRTLDRMODPE pThis, uint32_t offFile, uint32_t cbMem, void const **ppvMem)
{
if (!cbMem)
return VINF_SUCCESS;
/*
* Allocate a buffer and read the bits from the file (or whatever).
*/
return VERR_ACCESS_DENIED;
if (!pbMem)
return VERR_NO_MEMORY;
if (RT_FAILURE(rc))
{
return rc;
}
return VINF_SUCCESS;
}
/**
* Reads a part of a PE image into memory one way or another.
*
* Either the RVA or the offFile must be valid. We'll prefer the RVA if
* possible.
*
* @returns IPRT status code.
* @param pThis Pointer to the PE loader module structure.
* @param pvBits Read only bits if available. NULL if not.
* @param uRva The RVA to read at.
* @param offFile The file offset.
* @param cbMem The number of bytes to read.
* @param ppvMem Where to return the memory on success (heap or
* inside pvBits).
*/
{
if ( uRva == NIL_RTLDRADDR
{
return VERR_INVALID_PARAMETER;
}
}
/**
* Frees up memory returned by rtldrPEReadPart*.
*
* @param pThis Pointer to the PE loader module structure..
* @param pvBits Read only bits if available. NULL if not..
* @param pvMem The memory we were given by the reader method.
*/
{
if (!pvMem)
return;
return;
return;
}
/** @copydoc RTLDROPS::pfnGetImageSize */
{
}
/**
* Reads the image into memory.
*
* @returns iprt status code.
* @param pModPe The PE module.
* @param pvBits Where to store the bits, this buffer is at least pItem->Core.cbImage in size.
*/
{
/*
* Both these checks are related to pfnDone().
*/
if (!pReader)
{
AssertMsgFailed(("You've called done!\n"));
return VERR_WRONG_ORDER;
}
if (!pvBits)
return VERR_NO_MEMORY;
/*
* Zero everything (could be done per section).
*/
#ifdef PE_FILE_OFFSET_EQUALS_RVA
/*
* Read the entire image / file.
*/
if (RT_FAILURE(rc))
Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!! (the entire image)\n",
#else
/*
* Read the headers.
*/
if (RT_SUCCESS(rc))
{
/*
* Read the sections.
*/
{
rc = pReader->pfnRead(pReader, (uint8_t *)pvBits + pSH->VirtualAddress, pSH->SizeOfRawData, pSH->PointerToRawData);
if (RT_FAILURE(rc))
{
Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc - section #%d '%.*s'!!!\n",
break;
}
}
}
else
Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!!\n",
#endif
return rc;
}
/**
* Reads the bits into the internal buffer pointed to by PRTLDRMODPE::pvBits.
*
* @returns iprt status code.
* @param pModPe The PE module.
*/
{
if (!pvBitsW)
return VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
else
return rc;
}
/** @copydoc RTLDROPS::pfnGetBits */
static DECLCALLBACK(int) rtldrPEGetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
/*
* Read the image.
*/
if (RT_SUCCESS(rc))
{
/*
* Resolve imports.
*/
if (pfnGetImport)
if (RT_SUCCESS(rc))
{
/*
* Apply relocations.
*/
if (RT_SUCCESS(rc))
return rc;
}
else
}
return rc;
}
/** @copydoc RTLDROPSPE::pfnResolveImports */
static DECLCALLBACK(int) rtldrPEResolveImports32(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
/*
* Check if there is actually anything to work on.
*/
return 0;
/*
* Walk the IMAGE_IMPORT_DESCRIPTOR table.
*/
int rc = VINF_SUCCESS;
pImps++)
{
Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n"
"RTLdrPE: TimeDateStamp = %#RX32\n"
"RTLdrPE: ForwarderChain = %#RX32\n"
"RTLdrPE: Name = %#RX32\n"
"RTLdrPE: FirstThunk = %#RX32\n",
/*
* Walk the thunks table(s).
*/
PIMAGE_THUNK_DATA32 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA32); /* update this. */
{
{
rc = pfnGetImport(&pModPe->Core, pszModName, NULL, IMAGE_ORDINAL32(pThunk->u1.Ordinal), &Value, pvUser);
}
{
rc = pfnGetImport(&pModPe->Core, pszModName, PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
(uint32_t)Value, PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc));
}
else
{
AssertMsgFailed(("bad import data thunk!\n"));
}
{
AssertMsgFailed(("external symbol address to big!\n"));
}
pThunk++;
pFirstThunk++;
}
}
return rc;
}
/** @copydoc RTLDROPSPE::pfnResolveImports */
static DECLCALLBACK(int) rtldrPEResolveImports64(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser)
{
/*
* Check if there is actually anything to work on.
*/
return 0;
/*
* Walk the IMAGE_IMPORT_DESCRIPTOR table.
*/
int rc = VINF_SUCCESS;
pImps++)
{
Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n"
"RTLdrPE: TimeDateStamp = %#RX32\n"
"RTLdrPE: ForwarderChain = %#RX32\n"
"RTLdrPE: Name = %#RX32\n"
"RTLdrPE: FirstThunk = %#RX32\n",
/*
* Walk the thunks table(s).
*/
PIMAGE_THUNK_DATA64 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA64); /* update this. */
{
{
rc = pfnGetImport(&pModPe->Core, pszModName, NULL, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), &Value, pvUser);
}
{
/** @todo add validation of the string pointer! */
rc = pfnGetImport(&pModPe->Core, pszModName, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *),
}
else
{
AssertMsgFailed(("bad import data thunk!\n"));
}
pThunk++;
pFirstThunk++;
}
}
return rc;
}
/**
* Applies fixups.
*/
static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, RTUINTPTR OldBaseAddress)
{
return 0;
/*
* Apply delta fixups iterating fixup chunks.
*/
PIMAGE_BASE_RELOCATION pbr = PE_RVA2TYPE(pvBitsR, pModPe->RelocDir.VirtualAddress, PIMAGE_BASE_RELOCATION);
Log2(("RTLdrPE: Fixups: uDelta=%#RTptr BaseAddress=%#RTptr OldBaseAddress=%#RTptr\n", uDelta, BaseAddress, OldBaseAddress));
Log4(("RTLdrPE: BASERELOC: VirtualAddres=%RX32 Size=%RX32\n", pModPe->RelocDir.VirtualAddress, pModPe->RelocDir.Size));
while ( (uintptr_t)pbr - (uintptr_t)pBaseRelocs + 8 < cbBaseRelocs /* 8= VirtualAddress and SizeOfBlock members */
{
Log3(("RTLdrPE: base relocs for %#010x, size %#06x (%d relocs)\n", pbr->VirtualAddress, pbr->SizeOfBlock, cRelocations));
/* Some bound checking just to be sure it works... */
cRelocations = (uint32_t)( (((uintptr_t)pBaseRelocs + cbBaseRelocs) - (uintptr_t)pbr - sizeof(IMAGE_BASE_RELOCATION))
/ sizeof(uint16_t) );
/*
* Loop thru the fixups in this chunk.
*/
while (cRelocations != 0)
{
/*
* Common fixup
*/
static const char * const s_apszReloc[16] =
{
"ABS", "HIGH", "LOW", "HIGHLOW", "HIGHADJ", "MIPS_JMPADDR", "RES6", "RES7",
"RES8", "IA64_IMM64", "DIR64", "HIGH3ADJ", "RES12", "RES13", "RES14", "RES15"
}; NOREF(s_apszReloc);
union
{
} u;
switch (fType)
{
case IMAGE_REL_BASED_HIGHLOW: /* 32-bit, add delta. */
break;
case IMAGE_REL_BASED_DIR64: /* 64-bit, add delta. */
break;
case IMAGE_REL_BASED_ABSOLUTE: /* Alignment placeholder. */
break;
/* odd ones */
case IMAGE_REL_BASED_LOW: /* 16-bit, add 1st 16-bit part of the delta. */
break;
case IMAGE_REL_BASED_HIGH: /* 16-bit, add 2nd 16-bit part of the delta. */
break;
/* never ever seen these next two, and I'm not 100% sure they are correctly implemented here. */
case IMAGE_REL_BASED_HIGHADJ:
{
if (cRelocations <= 1)
{
AssertMsgFailed(("HIGHADJ missing 2nd record!\n"));
return VERR_BAD_EXE_FORMAT;
}
cRelocations--;
pwoffFixup++;
break;
}
case IMAGE_REL_BASED_HIGH3ADJ:
{
if (cRelocations <= 2)
{
AssertMsgFailed(("HIGHADJ3 missing 2nd record!\n"));
return VERR_BAD_EXE_FORMAT;
}
cRelocations -= 2;
pwoffFixup++;
break;
}
default:
break;
}
/*
*/
pwoffFixup++;
cRelocations--;
} /* while loop */
/*
* Next Fixup chunk. (i.e. next page)
*/
} /* while loop */
return 0;
}
/** @copydoc RTLDROPS::pfnRelocate. */
static DECLCALLBACK(int) rtldrPERelocate(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress,
{
/*
* Do we have to read the image bits?
*/
{
if (RT_FAILURE(rc))
return rc;
}
/*
* Process imports.
*/
int rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pModPe->pvBits, pvBits, pfnGetImport, pvUser);
if (RT_SUCCESS(rc))
{
/*
* Apply relocations.
*/
}
return rc;
}
/**
* Internal worker for pfnGetSymbolEx and pfnQueryForwarderInfo.
*
* @returns IPRT status code.
* @param pModPe The PE module instance.
* @param iOrdinal The symbol ordinal, UINT32_MAX if named symbol.
* @param pszSymbol The symbol name.
* @param puRvaExport Where to return the symbol RVA.
* @param puOrdinal Where to return the ordinal number. Optional.
*/
{
/*
* Check if there is actually anything to work on.
*/
return VERR_SYMBOL_NOT_FOUND;
/*
* No bits supplied? Do we need to read the bits?
*/
if (!pvBits)
{
{
if (RT_FAILURE(rc))
return rc;
}
}
PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
int iExpOrdinal = 0; /* index into address table. */
if (iOrdinal != UINT32_MAX)
{
/*
* Find ordinal export: Simple table lookup.
*/
return VERR_SYMBOL_NOT_FOUND;
}
else
{
/*
* Find Named Export: Do binary search on the name table.
*/
int iStart = 1;
for (;;)
{
/* end of search? */
{
#ifdef RT_STRICT
/* do a linear search just to verify the correctness of the above algorithm */
for (unsigned i = 0; i < pExpDir->NumberOfNames; i++)
{
AssertMsg(i == 0 || strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *)) > 0,
("bug in binary export search!!!\n"));
("bug in binary export search!!!\n"));
}
#endif
return VERR_SYMBOL_NOT_FOUND;
}
if (diff > 0) /* pszExpName > pszSymbol: search chunck before i */
iEnd = i - 1;
else if (diff) /* pszExpName < pszSymbol: search chunk after i */
iStart = i + 1;
else /* pszExpName == pszSymbol */
{
break;
}
} /* binary search thru name table */
}
/*
* Found export (iExpOrdinal).
*/
if (puOrdinal)
*puOrdinal = iExpOrdinal;
return VINF_SUCCESS;
}
/** @copydoc RTLDROPS::pfnGetSymbolEx. */
static DECLCALLBACK(int) rtldrPEGetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress,
{
if (RT_SUCCESS(rc))
{
/* Get plain export address */
else
{
/* Return the approximate length of the forwarder buffer. */
}
}
return rc;
}
/** @copydoc RTLDROPS::pfnQueryForwarderInfo. */
static DECLCALLBACK(int) rtldrPE_QueryForwarderInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, uint32_t iOrdinal,
{
if (RT_SUCCESS(rc))
{
{
/*
* Parse and validate the string. We must make sure it's valid
* UTF-8, so we restrict it to ASCII.
*/
if (pszEnd)
{
/* The module name. */
char ch;
{
return VERR_LDR_BAD_FORWARDER;
off++;
}
return VERR_LDR_BAD_FORWARDER;
off++;
/* The function name or ordinal number. Ordinals starts with a hash. */
{
{
return VERR_LDR_BAD_FORWARDER;
off++;
}
return VERR_LDR_BAD_FORWARDER;
}
else
{
return VERR_LDR_BAD_FORWARDER;
}
/*
* Enough buffer?
*/
uint32_t cbNeeded = RT_OFFSETOF(RTLDRIMPORTINFO, szModule[iImpOrdinal != UINT32_MAX ? offDot + 1 : off + 1]);
return VERR_BUFFER_OVERFLOW;
/*
* Fill in the return buffer.
*/
if (iImpOrdinal == UINT32_MAX)
{
}
else
{
}
rc = VINF_SUCCESS;
}
else
}
else
}
return rc;
}
/**
* Slow version of rtldrPEEnumSymbols that'll work without all of the image
* being accessible.
*
* This is mainly for use in debuggers and similar.
*/
{
/*
* We enumerates by ordinal, which means using a slow linear search for
* getting any name
*/
(void const **)&pExpDir);
if (RT_FAILURE(rc))
return rc;
(void const **)&paAddress);
rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNames, pExpDir->NumberOfNames * sizeof(uint32_t),
(void const **)&paRVANames);
rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNameOrdinals, pExpDir->NumberOfNames * sizeof(uint16_t),
(void const **)&paOrdinals);
if (RT_SUCCESS(rc))
{
{
{
/*
* Look for name.
*/
/* Search from previous + 1 to the end. */
{
{
break;
}
uName++;
}
if (uRvaName == UINT32_MAX)
{
/* Search from start to the previous. */
uName = 0;
{
{
break;
}
}
}
/*
* Get address.
*/
{
if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
{
/* Resolve forwarder. */
AssertMsgFailed(("Forwarders are not supported!\n"));
}
continue;
}
/* Get plain export address */
/* Read in the name if found one. */
char szAltName[32];
if (uRvaName != UINT32_MAX)
{
cbName = 128;
{
break;
cbName += 128;
}
}
if (!pszName)
{
}
/*
* Call back.
*/
if (rc)
break;
}
}
}
return rc;
}
/** @copydoc RTLDROPS::pfnEnumSymbols */
static DECLCALLBACK(int) rtldrPEEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, RTUINTPTR BaseAddress,
{
/*
* Check if there is actually anything to work on.
*/
return VERR_SYMBOL_NOT_FOUND;
/*
* No bits supplied? Do we need to read the bits?
*/
if (!pvBits)
{
{
if (RT_FAILURE(rc))
}
}
/*
* We enumerates by ordinal, which means using a slow linear search for
* getting any name
*/
PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY);
{
{
/*
* Look for name.
*/
/* Search from previous + 1 to the end. */
{
{
break;
}
uName++;
}
if (!pszName)
{
/* Search from start to the previous. */
uName = 0;
{
{
break;
}
}
}
/*
* Get address.
*/
{
if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD))
{
/* Resolve forwarder. */
AssertMsgFailed(("Forwarders are not supported!\n"));
}
continue;
}
/* Get plain export address */
/*
* Call back.
*/
if (rc)
return rc;
}
}
return VINF_SUCCESS;
}
/** @copydoc RTLDROPS::pfnEnumDbgInfo. */
{
int rc;
/*
* Debug info directory empty?
*/
return VINF_SUCCESS;
/*
* Allocate temporary memory for a path buffer (this code is also compiled
* and maybe even used in stack starved environments).
*/
if (!pszPath)
return VERR_NO_TMP_MEMORY;
/*
* Get the debug directory.
*/
if (!pvBits)
int rcRet = rtldrPEReadPartByRva(pModPe, pvBits, pModPe->DebugDir.VirtualAddress, pModPe->DebugDir.Size,
(void const **)&paDbgDir);
if (RT_FAILURE(rcRet))
{
return rcRet;
}
/*
* Enumerate the debug directory.
*/
{
continue;
continue;
rc = VINF_SUCCESS;
{
)
{
rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
if (RT_SUCCESS(rc))
{
&& pCv20->offDbgInfo == 0
{
}
{
}
}
else
}
break;
case IMAGE_DEBUG_TYPE_MISC:
{
else
rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart);
if (RT_SUCCESS(rc))
{
{
else
{
if (RT_SUCCESS(rc))
else
}
}
}
else
}
break;
case IMAGE_DEBUG_TYPE_COFF:
break;
default:
break;
}
/* Fix (hack) the file name encoding. We don't have Windows-1252 handy,
so we'll be using Latin-1 as a reasonable approximation.
(I don't think we know exactly which encoding this is anyway, as
generating the image anyways.) */
{
if (RT_FAILURE(rc))
{
}
}
if (DbgInfo.pszExtFile)
if (rc != VINF_SUCCESS)
{
break;
}
}
return rcRet;
}
/** @copydoc RTLDROPS::pfnEnumSegments. */
static DECLCALLBACK(int) rtldrPE_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser)
{
/*
* The first section is a fake one covering the headers.
*/
/*
* Then all the normal sections.
*/
{
char szName[32];
{
}
{
}
{
}
else
{
}
{
}
else
{
}
}
return rc;
}
/** @copydoc RTLDROPS::pfnLinkAddressToSegOffset. */
static DECLCALLBACK(int) rtldrPE_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress,
{
/* Special header segment. */
{
*piSeg = 0;
*poffSeg = LinkAddress;
return VINF_SUCCESS;
}
/*
* Search the normal sections. (Could do this in binary fashion, they're
* sorted, but too much bother right now.)
*/
return VERR_LDR_INVALID_LINK_ADDRESS;
while (i-- > 0)
{
if (LinkAddress >= uAddr)
{
*piSeg = i + 1;
return VINF_SUCCESS;
}
}
return VERR_LDR_INVALID_LINK_ADDRESS;
}
/** @copydoc RTLDROPS::pfnLinkAddressToRva. */
static DECLCALLBACK(int) rtldrPE_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva)
{
return VERR_LDR_INVALID_LINK_ADDRESS;
*pRva = LinkAddress;
return VINF_SUCCESS;
}
/** @copydoc RTLDROPS::pfnSegOffsetToRva. */
static DECLCALLBACK(int) rtldrPE_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg,
{
return VERR_LDR_INVALID_SEG_OFFSET;
/** @todo should validate offSeg here... too lazy right now. */
if (iSeg == 0)
return VERR_LDR_INVALID_SEG_OFFSET;
else
return VINF_SUCCESS;
}
/** @copydoc RTLDROPS::pfnRvaToSegOffset. */
{
if (RT_FAILURE(rc))
return rc;
}
/**
* Worker for rtLdrPE_QueryProp and rtLdrPE_QueryImportModule that counts the
* number of imports, storing the result in RTLDRMODPE::cImports.
*
* @returns IPRT status code.
* @param pThis The PE module instance.
* @param pvBits Image bits if the caller had them available, NULL if
* not. Saves a couple of file accesses.
*/
{
int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ImportDir.VirtualAddress, pThis->ImportDir.Size,
(void const **)&paImpDescs);
if (RT_SUCCESS(rc))
{
uint32_t i = 0;
while ( i < cMax
i++;
}
return rc;
}
/**
* Worker for rtLdrPE_QueryProp that retrievs the name of an import DLL.
*
* @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size.
* @param pThis The PE module instance.
* @param pvBits Image bits if the caller had them available, NULL if
* not. Saves a couple of file accesses.
* @param iImport The index of the import table descriptor to fetch
* the name from.
* @param pvBuf The output buffer.
* @param cbBuf The buffer size.
* @param pcbRet Where to return the number of bytes we've returned
* (or in case of VERR_BUFFER_OVERFLOW would have).
*/
{
/*
* Make sure we got the import count.
*/
int rc;
{
if (RT_FAILURE(rc))
return rc;
}
/*
* Check the index first, converting it to an RVA.
*/
{
/*
* Retrieve the import table descriptor.
*/
if (RT_SUCCESS(rc))
{
{
/*
* Limit the name to 1024 bytes (more than enough for everyone).
*/
if (cchNameMax > 1024)
cchNameMax = 1024;
char *pszName;
if (RT_SUCCESS(rc))
{
/*
* Make sure it's null terminated and valid UTF-8 encoding.
*
* Which encoding this really is isn't defined, I think,
* but we need to make sure we don't get bogus UTF-8 into
* the process, so making sure it's valid UTF-8 is a good
* as anything else since it covers ASCII.
*/
if (cchName < cchNameMax)
{
if (RT_SUCCESS(rc))
{
/*
* Copy out the result and we're done.
* (We have to do all the cleanup code though, so no return success here.)
*/
else
}
}
else
}
}
else
}
}
else
rc = VERR_NOT_FOUND;
if (RT_SUCCESS(rc))
return VINF_SUCCESS;
*pcbRet = 0;
return rc;
}
/** @interface_method_impl{RTLDROPS,pfnQueryProp} */
static DECLCALLBACK(int) rtldrPE_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits,
{
switch (enmProp)
{
else
break;
case RTLDRPROP_IS_SIGNED:
break;
{
if (pModPe->cbPkcs7SignedData == 0)
return VERR_NOT_FOUND;
return VERR_BUFFER_OVERFLOW;
}
break;
case RTLDRPROP_IMPORT_COUNT:
{
if (RT_FAILURE(rc))
return rc;
}
break;
case RTLDRPROP_IMPORT_MODULE:
default:
return VERR_NOT_FOUND;
}
return VINF_SUCCESS;
}
/*
* Lots of Authenticode fun ahead.
*/
/**
* Initializes the hash context.
*
* @returns VINF_SUCCESS or VERR_NOT_SUPPORTED.
* @param pHashCtx The hash context union.
* @param enmDigest The hash type we're calculating..
*/
{
switch (enmDigest)
{
default: AssertFailedReturn(VERR_NOT_SUPPORTED);
}
return VINF_SUCCESS;
}
/**
* Updates the hash with more data.
*
* @param pHashCtx The hash context union.
* @param enmDigest The hash type we're calculating..
* @param pvBuf Pointer to a buffer with bytes to add to thash.
* @param cbBuf How many bytes to add from @a pvBuf.
*/
static void rtLdrPE_HashUpdate(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, void const *pvBuf, size_t cbBuf)
{
switch (enmDigest)
{
default: AssertReleaseFailed();
}
}
/**
* Finalizes the hash calculations.
*
* @param pHashCtx The hash context union.
* @param enmDigest The hash type we're calculating..
* @param pHashRes The hash result union.
*/
static void rtLdrPE_HashFinalize(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, PRTLDRPEHASHRESUNION pHashRes)
{
switch (enmDigest)
{
default: AssertReleaseFailed();
}
}
/**
* Returns the digest size for the given digest type.
*
* @returns Size in bytes.
* @param enmDigest The hash type in question.
*/
{
switch (enmDigest)
{
case RTDIGESTTYPE_SHA512: return RTSHA512_HASH_SIZE;
case RTDIGESTTYPE_SHA256: return RTSHA256_HASH_SIZE;
case RTDIGESTTYPE_SHA1: return RTSHA1_HASH_SIZE;
case RTDIGESTTYPE_MD5: return RTMD5_HASH_SIZE;
default: AssertReleaseFailedReturn(0);
}
}
/**
* Calculate the special too watch out for when hashing the image.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param pPlaces The structure where to store the special places.
* @param pErrInfo Optional error info.
*/
static int rtldrPe_CalcSpecialHashPlaces(PRTLDRMODPE pModPe, PRTLDRPEHASHSPECIALS pPlaces, PRTERRINFO pErrInfo)
{
/*
* If we're here despite a missing signature, we need to get the file size.
*/
{
}
/*
* Calculate the special places.
*/
return VINF_SUCCESS;
}
/**
* Calculates the whole image hash.
*
* The Authenticode_PE.docx version 1.0 explains how the hash is calculated,
* points 8 thru 14 are bogus. If you study them a little carefully, it is
* clear that the algorithm will only work if the raw data for the section have
* no gaps between them or in front of them. So, this elaborate section sorting
* by PointerToRawData and working them section by section could simply be
* replaced by one point:
*
* 8. Add all the file content between SizeOfHeaders and the
* attribute certificate table to the hash. Then finalize
* the hash.
*
* Not sure if Microsoft is screwing with us on purpose here or whether they
* assigned some of this work to less talented engineers and tech writers. I
* love fact that they say it's "simplified" and should yield the correct hash
* for "almost all" files. Stupid, Stupid, Microsofties!!
*
* My simplified implementation that just hashes the entire file up to the
* signature or end of the file produces the same SHA1 values as "signtool
* sections raw data and normal executables without any gaps.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param pvScratch Scratch buffer.
* @param cbScratch Size of the scratch buffer.
* @param enmDigest The hash digest type we're calculating.
* @param pHashCtx Hash context scratch area.
* @param pHashRes Hash result buffer.
* @param pErrInfo Optional error info buffer.
*/
static int rtldrPE_HashImageCommon(PRTLDRMODPE pModPe, void *pvScratch, uint32_t cbScratch, RTDIGESTTYPE enmDigest,
{
if (RT_FAILURE(rc))
return rc;
/*
* Calculate the special places.
*/
if (RT_FAILURE(rc))
return rc;
/*
* Work our way thru the image data.
*/
{
if (RT_FAILURE(rc))
return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH, "Hash read error at %#x: %Rrc (cbRead=%#zx)",
{
{
/* Hash everything up to the checksum. */
}
{
/* Skip the checksum */
}
{
/* Hash everything between the checksum and the data dir entry. */
}
{
/* Skip the security data directory entry. */
}
}
/* Advance */
}
/*
* If there isn't a signature, experiments with signtool indicates that we
* have to zero padd the file size until it's a multiple of 8. (This is
* most likely to give 64-bit values in the certificate a natural alignment
* when memory mapped.)
*/
{
}
/*
* Done. Finalize the hashes.
*/
return VINF_SUCCESS;
}
#ifndef IPRT_WITHOUT_LDR_VERIFY
/**
* Verifies image preconditions not checked by the open validation code.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param pErrInfo Optional error info buffer.
*/
{
/*
* Validate the sections. While doing so, track the amount of section raw
* section data in the file so we can use this to validate the signature
* table location later.
*/
{
uint64_t offEnd = (uint64_t)pModPe->paSections[i].PointerToRawData + pModPe->paSections[i].SizeOfRawData;
{
"Section %#u specifies file data after 2GB: PointerToRawData=%#x SizeOfRawData=%#x",
}
}
/*
* Validate the signature.
*/
if ( cbSignature <= sizeof(WIN_CERTIFICATE)
|| offSignature >= _2G)
if (offSignature < offEndOfSectionData)
"Invalid security data dir entry offset: %#x offEndOfSectionData=%#x",
"Misaligned security dir entry offset: %#x (alignment=%#x)",
return VINF_SUCCESS;
}
/**
* Reads and checks the raw signature data.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param ppSignature Where to return the pointer to the parsed
* signature data. Pass to
* rtldrPE_VerifySignatureDestroy when done.
* @param pErrInfo Optional error info buffer.
*/
static int rtldrPE_VerifySignatureRead(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE *ppSignature, PRTERRINFO pErrInfo)
{
*ppSignature = NULL;
/*
* Allocate memory for reading and parsing it.
*/
PRTLDRPESIGNATURE pSignature = (PRTLDRPESIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size);
if (!pSignature)
/*
* Read it.
*/
if (RT_SUCCESS(rc))
{
/*
* Check the table we've read in.
*/
for (;;)
{
"Bad WIN_CERTIFICATE length: %#x (max %#x, signature=%u)",
"Unsupported WIN_CERTIFICATE revision value: %#x (signature=%u)",
"Unsupported WIN_CERTIFICATE certificate type: %#x (signature=%u)",
pEntry->wCertificateType, 0);
else
{
/* advance */
break;
/* For now, only one entry is supported. */
rc = RTErrInfoSet(pErrInfo, VERR_LDRVI_BAD_CERT_MULTIPLE, "Multiple WIN_CERTIFICATE entries are not supported.");
}
break;
}
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
else
return rc;
}
/**
* Destroys the parsed signature.
*
* @param pModPe The PE module.
* @param pSignature The signature data to destroy.
*/
{
}
/**
* Decodes the raw signature.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param pSignature The signature data.
* @param pErrInfo Optional error info buffer.
*/
static int rtldrPE_VerifySignatureDecode(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
{
&pEntry->bCertificate[0],
0,
"WinCert");
if (RT_SUCCESS(rc))
{
{
/*
* Decode the authenticode bits.
*/
if (!strcmp(pSignature->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID))
{
/*
* Check that things add up.
*/
if (RT_SUCCESS(rc))
pErrInfo, "SD");
if (RT_SUCCESS(rc))
pErrInfo);
if (RT_SUCCESS(rc))
{
AssertReturn(pSignature->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */
}
}
else
"Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)",
}
}
return rc;
}
static int rtldrPE_VerifyAllPageHashes(PRTLDRMODPE pModPe, PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib, RTDIGESTTYPE enmDigest,
{
/*
* Calculate the special places.
*/
if (RT_FAILURE(rc))
return rc;
"Page hashes size issue: cb=%#x cbHash=%#x",
/*
* Walk the table.
*/
uint32_t cbScratchRead = 0;
uint32_t offScratchRead = 0;
#ifdef COMPLICATED_AND_WRONG
#endif
{
/* Decode the page offset. */
uint32_t const offPageInFile = RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]);
"Page hash entry #%u is beyond the signature table start: %#x, %#x",
"Page hash table is not strictly sorted: entry #%u @%#x, previous @%#x\n",
#ifdef COMPLICATED_AND_WRONG
/* Figure out how much to read and how much to zero. Need keep track
of the on-disk section boundraries. */
if (offPageInFile >= offSectEnd)
{
iSh++;
&& offPageInFile - pModPe->paSections[iSh].PointerToRawData < pModPe->paSections[iSh].SizeOfRawData)
else
{
iSh = 0;
&& offPageInFile - pModPe->paSections[iSh].PointerToRawData >= pModPe->paSections[iSh].SizeOfRawData)
iSh++;
else
}
}
#else
/* Figure out how much to read and how much take as zero. Use the next
page offset and the signature as upper boundraries. */
#endif
#ifdef COMPLICATED_AND_WRONG
#else
{
}
#endif
/* Did we get a cache hit? */
&& offPageInFile >= offScratchRead)
/* Missed, read more. */
else
{
#ifdef COMPLICATED_AND_WRONG
#else
#endif
if (cbScratchRead > cbScratchReadMax)
if (RT_FAILURE(rc))
"Page hash read error at %#x: %Rrc (cbScratchRead=%#zx)",
}
/*
* Hash it.
*/
/* Deal with special places. */
{
{
/* Hash everything up to the checksum. */
}
{
/* Skip the checksum */
}
{
/* Hash everything between the checksum and the data dir entry. */
}
{
/* Skip the security data directory entry. */
}
}
if (cbPageInFile < _4K)
/*
* Finish the hash calculation and compare the result.
*/
pbHashTab += 4;
"Page hash failed for page #%u, @%#x, %#x bytes: %.*Rhxs != %.*Rhxs",
}
/*
* Check that the last table entry has a hash value of zero.
*/
"Maltform final page hash table entry: #%u %#010x %.*Rhxs",
return VINF_SUCCESS;
}
/**
* Validates the image hash, including page hashes if present.
*
* @returns IPRT status code.
* @param pModPe The PE module.
* @param pSignature The decoded signature data.
* @param pErrInfo Optional error info buffer.
*/
static int rtldrPE_VerifySignatureValidateHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo)
{
AssertReturn(pSignature->enmDigest > RTDIGESTTYPE_INVALID && pSignature->enmDigest < RTDIGESTTYPE_END, VERR_INTERNAL_ERROR_4);
AssertReturn(RTASN1CORE_IS_PRESENT(&pSignature->pIndData->DigestInfo.Digest.Asn1Core), VERR_INTERNAL_ERROR_5);
/*
* Allocate a temporary memory buffer.
* Note! The _4K that gets subtracted is to avoid that the 16-byte heap
* block header in ring-0 (iprt) caused any unnecessary internal
* heap fragmentation.
*/
#ifdef IN_RING0
#else
#endif
if (!pvScratch)
{
if (!pvScratch)
return RTErrInfoSet(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate 4KB of scratch space for hashing image.");
}
/*
* Calculate and compare the full image hash.
*/
if (RT_SUCCESS(rc))
{
if (!memcmp(&pSignature->HashRes, pSignature->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash))
{
/*
* Compare the page hashes if present.
*
* Seems the difference between V1 and V2 page hash attributes is
* that v1 uses SHA-1 while v2 uses SHA-256. The data structures to
* be identical otherwise. Initially we assumed the digest
* algorithm was supposed to be RTCRSPCINDIRECTDATACONTENT::DigestInfo,
* i.e. the same as for the whole image hash. The initial approach
* worked just fine, but this makes more sense.
*
* (See also comments in osslsigncode.c (google it).)
*/
if (pAttrib)
rc = rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA256, pvScratch, cbScratch, pErrInfo);
else
{
if (pAttrib)
rc = rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA1, pvScratch, cbScratch, pErrInfo);
}
}
else
"Full image signature mismatch: %.*Rhxs, expected %.*Rhxs",
}
return rc;
}
#endif /* !IPRT_WITHOUT_LDR_VERIFY */
/** @interface_method_impl{RTLDROPS,pfnVerifySignature} */
static DECLCALLBACK(int) rtldrPE_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser,
{
#ifndef IPRT_WITHOUT_LDR_VERIFY
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
if (RT_SUCCESS(rc))
{
}
}
}
return rc;
#else
return VERR_NOT_SUPPORTED;
#endif
}
/** @interface_method_impl{RTLDROPS,pfnHashImage} */
static DECLCALLBACK(int) rtldrPE_HashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, char *pszDigest, size_t cbDigest)
{
/*
* Allocate a temporary memory buffer.
*/
if (!pvScratch)
{
if (!pvScratch)
return VERR_NO_TMP_MEMORY;
}
/*
* Do the hashing.
*/
int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, enmDigest, &HashCtx, &HashRes, NULL);
if (RT_SUCCESS(rc))
{
/*
* Format the digest into as human readable hash string.
*/
switch (enmDigest)
{
default: AssertFailedReturn(VERR_INTERNAL_ERROR_3);
}
}
return rc;
}
/** @copydoc RTLDROPS::pfnDone */
{
{
}
return VINF_SUCCESS;
}
/** @copydoc RTLDROPS::pfnClose */
{
if (pModPe->paSections)
{
}
{
}
return VINF_SUCCESS;
}
/**
* Operations for a 32-bit PE module.
*/
static const RTLDROPSPE s_rtldrPE32Ops =
{
{
"pe32",
NULL,
/* ext */
NULL,
42
},
42
};
/**
* Operations for a 64-bit PE module.
*/
static const RTLDROPSPE s_rtldrPE64Ops =
{
{
"pe64",
NULL,
/* ext */
NULL,
42
},
42
};
/**
* Converts the optional header from 32 bit to 64 bit.
* This is a rather simple task, if you start from the right end.
*
* @param pOptHdr On input this is a PIMAGE_OPTIONAL_HEADER32.
* On output this will be a PIMAGE_OPTIONAL_HEADER64.
*/
{
/*
* volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
*/
/* from LoaderFlags and out the difference is 4 * 32-bits. */
Assert(RT_OFFSETOF(IMAGE_OPTIONAL_HEADER32, LoaderFlags) + 16 == RT_OFFSETOF(IMAGE_OPTIONAL_HEADER64, LoaderFlags));
uint32_t volatile *pu32Dst = (uint32_t *)&pOptHdr64->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
const uint32_t volatile *pu32Src = (uint32_t *)&pOptHdr32->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1;
while (pu32Src >= pu32SrcLast)
/* the previous 4 fields are 32/64 and needs special attention. */
/* The rest matches except for BaseOfData which has been merged into ImageBase in the 64-bit version..
* Thus, ImageBase needs some special treatment. It will probably work fine assigning one to the
* other since this is all declared volatile, but taking now chances, we'll use a temp variable.
*/
Assert(RT_OFFSETOF(IMAGE_OPTIONAL_HEADER32, SizeOfStackReserve) == RT_OFFSETOF(IMAGE_OPTIONAL_HEADER64, SizeOfStackReserve));
Assert(RT_OFFSETOF(IMAGE_OPTIONAL_HEADER32, BaseOfData) == RT_OFFSETOF(IMAGE_OPTIONAL_HEADER64, ImageBase));
Assert(RT_OFFSETOF(IMAGE_OPTIONAL_HEADER32, SectionAlignment) == RT_OFFSETOF(IMAGE_OPTIONAL_HEADER64, SectionAlignment));
}
/**
* Converts the load config directory from 32 bit to 64 bit.
* This is a rather simple task, if you start from the right end.
*
* @param pLoadCfg On input this is a PIMAGE_LOAD_CONFIG_DIRECTORY32.
* On output this will be a PIMAGE_LOAD_CONFIG_DIRECTORY64.
*/
{
/*
* volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff.
*/
IMAGE_LOAD_CONFIG_DIRECTORY32_V4 volatile *pLoadCfg32 = (IMAGE_LOAD_CONFIG_DIRECTORY32_V4 volatile *)pLoadCfg;
pLoadCfg64->ProcessHeapFlags = pLoadCfg32->ProcessHeapFlags; /* switched place with ProcessAffinityMask, but we're more than 16 byte off by now so it doesn't matter. */
/* the rest is equal. */
}
/**
* Validates the file header.
*
* @returns iprt status code.
* @param pFileHdr Pointer to the file header that needs validating.
* @param fFlags Valid RTLDR_O_XXX combination.
* @param pszLogName The log name to prefix the errors with.
* @param penmArch Where to store the CPU architecture.
*/
static int rtldrPEValidateFileHeader(PIMAGE_FILE_HEADER pFileHdr, uint32_t fFlags, const char *pszLogName, PRTLDRARCH penmArch)
{
{
case IMAGE_FILE_MACHINE_I386:
cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32);
break;
case IMAGE_FILE_MACHINE_AMD64:
cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64);
break;
default:
Log(("rtldrPEOpen: %s: Unsupported Machine=%#x\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: SizeOfOptionalHeader=%#x expected %#x\n",
return VERR_BAD_EXE_FORMAT;
}
/* This restriction needs to be implemented elsewhere. */
{
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: NumberOfSections=%d - our limit is 42, please raise it if the binary makes sense.(!!!)\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: NumberOfSections=%d - we can't have an image without sections (!!!)\n",
return VERR_BAD_EXE_FORMAT;
}
return VINF_SUCCESS;
}
/**
* Validates the optional header (64/32-bit)
*
* @returns iprt status code.
* @param pOptHdr Pointer to the optional header which needs validation.
* @param pszLogName The log name to prefix the errors with.
* @param offNtHdrs The offset of the NT headers from the start of the file.
* @param pFileHdr Pointer to the file header (valid).
* @param cbRawImage The raw image size.
* @param fFlags Loader flags, RTLDR_O_XXX.
*/
static int rtldrPEValidateOptionalHeader(const IMAGE_OPTIONAL_HEADER64 *pOptHdr, const char *pszLogName, RTFOFF offNtHdrs,
{
{
return VERR_BAD_EXE_FORMAT;
}
{
return VERR_BAD_EXE_FORMAT;
}
const uint32_t cbMinImageSize = pFileHdr->SizeOfOptionalHeader + sizeof(*pFileHdr) + 4 + (uint32_t)offNtHdrs;
if (cbImage < cbMinImageSize)
{
Log(("rtldrPEOpen: %s: SizeOfImage=%#x to small, minimum %#x!!!\n", pszLogName, cbImage, cbMinImageSize));
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: AddressOfEntryPoint=%#x - beyond image size (%#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: BaseOfCode=%#x - beyond image size (%#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
#if 0/* only in 32-bit header */
{
Log(("rtldrPEOpen: %s: BaseOfData=%#x - beyond image size (%#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
#endif
{
Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - beyond image size (%#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
/* don't know how to do the checksum, so ignore it. */
{
return VERR_BAD_EXE_FORMAT;
}
if (pOptHdr->SizeOfHeaders < cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER))
{
Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n",
return VERR_BAD_EXE_FORMAT;
}
/* DataDirectory */
{
return VERR_BAD_EXE_FORMAT;
}
{
continue;
switch (i)
{
case IMAGE_DIRECTORY_ENTRY_EXPORT: // 0
case IMAGE_DIRECTORY_ENTRY_IMPORT: // 1
case IMAGE_DIRECTORY_ENTRY_RESOURCE: // 2
case IMAGE_DIRECTORY_ENTRY_EXCEPTION: // 3
case IMAGE_DIRECTORY_ENTRY_BASERELOC: // 5
case IMAGE_DIRECTORY_ENTRY_DEBUG: // 6
case IMAGE_DIRECTORY_ENTRY_COPYRIGHT: // 7
case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: // 11
case IMAGE_DIRECTORY_ENTRY_IAT: // 12 /* Import Address Table */
break;
case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: // 10 - need to check for lock prefixes.
/* Delay inspection after section table is validated. */
break;
case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: // 13
break;
Log(("rtldrPEOpen: %s: dir no. %d (DELAY_IMPORT) VirtualAddress=%#x Size=%#x is not supported!!!\n",
return VERR_LDRPE_DELAY_IMPORT;
case IMAGE_DIRECTORY_ENTRY_SECURITY: // 4
/* The VirtualAddress is a PointerToRawData. */
Log(("rtldrPEOpen: %s: dir no. %d (SECURITY) VirtualAddress=%#x Size=%#x is not supported!!!\n",
{
return VERR_LDRPE_CERT_MALFORMED;
}
{
return VERR_LDRPE_CERT_MALFORMED;
}
{
Log(("rtldrPEOpen: %s: Security directory is misaligned: %#x\n", pszLogName, i, pDir->VirtualAddress));
return VERR_LDRPE_CERT_MALFORMED;
}
/* When using the in-memory reader with a debugger, we may get
into trouble here since we might not have access to the whole
physical file. So skip the tests below. Makes VBoxGuest.sys
load and check out just fine, for instance. */
if (fFlags & RTLDR_O_FOR_DEBUG)
continue;
break;
case IMAGE_DIRECTORY_ENTRY_GLOBALPTR: // 8 /* (MIPS GP) */
Log(("rtldrPEOpen: %s: dir no. %d (GLOBALPTR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
return VERR_LDRPE_GLOBALPTR;
case IMAGE_DIRECTORY_ENTRY_TLS: // 9
break;
Log(("rtldrPEOpen: %s: dir no. %d (TLS) VirtualAddress=%#x Size=%#x is not supported!!!\n",
return VERR_LDRPE_TLS;
case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:// 14
break;
Log(("rtldrPEOpen: %s: dir no. %d (COM_DESCRIPTOR) VirtualAddress=%#x Size=%#x is not supported!!!\n",
return VERR_LDRPE_COM_DESCRIPTOR;
default:
Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x Size=%#x is not supported!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x is invalid (limit %#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)!!!\n",
return VERR_BAD_EXE_FORMAT;
}
}
return VINF_SUCCESS;
}
/**
* Validates the section headers.
*
* @returns iprt status code.
* @param paSections Pointer to the array of sections that is to be validated.
* @param cSections Number of sections in that array.
* @param pszLogName The log name to prefix the errors with.
* @param pOptHdr Pointer to the optional header (valid).
* @param cbRawImage The raw image size.
* @param fFlags Loader flags, RTLDR_O_XXX.
* @param fNoCode Verify that the image contains no code.
*/
static int rtldrPEValidateSectionHeaders(const IMAGE_SECTION_HEADER *paSections, unsigned cSections, const char *pszLogName,
{
Log3(("RTLdrPE: Section Headers:\n"));
{
Log3(("RTLdrPE: #%d '%-8.8s' Characteristics: %08RX32\n"
"RTLdrPE: VirtAddr: %08RX32 VirtSize: %08RX32\n"
"RTLdrPE: FileOff: %08RX32 FileSize: %08RX32\n"
"RTLdrPE: RelocOff: %08RX32 #Relocs: %08RX32\n"
"RTLdrPE: LineOff: %08RX32 #Lines: %08RX32\n",
if ( ( pSH->Characteristics & (IMAGE_SCN_MEM_PURGEABLE | IMAGE_SCN_MEM_PRELOAD | IMAGE_SCN_MEM_FARDATA) )
{
Log(("rtldrPEOpen: %s: Unsupported section flag(s) %#x section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
&& !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) /* binutils uses this for '.stab' even if it's reserved/obsoleted by MS. */
{
{
Log(("rtldrPEOpen: %s: Overlaps previous section or sections aren't in ascending order, VirtualAddress=%#x uRvaPrev=%#x - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: VirtualAddress=%#x - beyond image size (%#x) - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: VirtualAddress=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
#ifdef PE_FILE_OFFSET_EQUALS_RVA
/* Our loader code assume rva matches the file offset. */
if ( pSH->SizeOfRawData
{
Log(("rtldrPEOpen: %s: ASSUMPTION FAILED: file offset %#x != RVA %#x - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
#endif
}
///@todo only if SizeOfRawData > 0 ?
{
Log(("rtldrPEOpen: %s: PointerToRawData=%#x SizeOfRawData=%#x - beyond end of file (%#x) - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
{
Log(("rtldrPEOpen: %s: PointerToRawData=%#x misaligned (%#x) - section #%d '%.*s'!!!\n",
return VERR_BAD_EXE_FORMAT;
}
/* ignore the relocations and linenumbers. */
}
/*
* Do a separate run if we need to validate the no-code claim from the
* optional header.
*/
if (fNoCode)
{
pSH = &paSections[0];
return VERR_LDR_ARCH_MISMATCH;
}
/** @todo r=bird: more sanity checks! */
return VINF_SUCCESS;
}
/**
* Reads image data by RVA using the section headers.
*
* @returns iprt status code.
* @param pModPe The PE module instance.
* @param pvBuf Where to store the bits.
* @param cb Number of bytes to tread.
* @param RVA Where to read from.
*/
{
int rc;
/*
* Is it the headers, i.e. prior to the first section.
*/
{
|| RT_FAILURE(rc))
return rc;
}
/* In the zero space between headers and the first section? */
{
return VINF_SUCCESS;
}
/*
* Iterate the sections.
*/
cLeft > 0;
{
{
|| RT_FAILURE(rc))
return rc;
}
{
return VINF_SUCCESS;
}
}
AssertFailed();
return VERR_INTERNAL_ERROR;
}
/**
* Validates the data of some selected data directories entries and remember
* important bits for later.
*
* This requires a valid section table and thus has to wait till after we've
* read and validated it.
*
* @returns iprt status code.
* @param pModPe The PE module instance.
* @param pOptHdr Pointer to the optional header (valid).
* @param fFlags Loader flags, RTLDR_O_XXX.
*/
static int rtldrPEValidateDirectoriesAndRememberStuff(PRTLDRMODPE pModPe, const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint32_t fFlags)
{
union /* combine stuff we're reading to help reduce stack usage. */
{
} u;
/*
* The load config entry may include lock prefix tables and whatnot which we don't implement.
* It does also include a lot of stuff which we can ignore, so we'll have to inspect the
* actual data before we can make up our mind about it all.
*/
{
? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V4)
: sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V4);
? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V3)
: sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V3);
? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V2)
: sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2);
? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V1)
: sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2) /*No V1*/;
{
Log(("rtldrPEOpen: %s: load cfg dir: unexpected dir size of %d bytes, expected %d, %d, %d, or %d.\n",
return VERR_LDRPE_LOAD_CONFIG_SIZE;
}
/*
* Read and convert to 64-bit.
*/
if (RT_FAILURE(rc))
return rc;
{
/* Kludge #1, seen ati shipping 32-bit DLLs and EXEs with Dir.Size=0x40
and Cfg64.Size=0x5c or 0x48. Windows seems to deal with it, so
lets do so as well. */
{
Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the ATI kludge\n",
if (RT_FAILURE(rc))
return rc;
}
/* Kludge #2, ntdll.dll from XP seen with Dir.Size=0x40 and Cfg64.Size=0x00. */
{
}
{
Log(("rtldrPEOpen: %s: load cfg dir: unexpected header size of %d bytes, expected %d.\n",
return VERR_LDRPE_LOAD_CONFIG_SIZE;
}
}
{
Log(("rtldrPEOpen: %s: load cfg dir: lock prefix table at %RX64. We don't support lock prefix tables!\n",
return VERR_LDRPE_LOCK_PREFIX_TABLE;
}
#if 0/* this seems to be safe to ignore. */
if ( u.Cfg64.SEHandlerTable
|| u.Cfg64.SEHandlerCount)
{
Log(("rtldrPEOpen: %s: load cfg dir: SEHandlerTable=%RX64 and SEHandlerCount=%RX64 are unsupported!\n",
return VERR_BAD_EXE_FORMAT;
}
#endif
{
Log(("rtldrPEOpen: %s: load cfg dir: EditList=%RX64 is unsupported!\n",
return VERR_BAD_EXE_FORMAT;
}
/** @todo GuardCFC? Possibly related to:
* Not trusting something designed by bakas who don't know how to modify a
* structure without messing up its natural alignment. */
if ( ( u.Cfg64.GuardCFCCheckFunctionPointer
|| u.Cfg64.GuardFlags)
{
Log(("rtldrPEOpen: %s: load cfg dir: Guard stuff: %RX64,%RX64,%RX64,%RX64,%RX32!\n",
return VERR_BAD_EXE_FORMAT;
}
}
/*
* If the image is signed and we're not doing this for debug purposes,
* take a look at the signature.
*/
{
if (!pFirst)
return VERR_NO_TMP_MEMORY;
if (RT_SUCCESS(rc))
{
do
{
/* validate the members. */
{
Log(("rtldrPEOpen: %s: cert at %#x/%#x: dwLength=%#x\n", pszLogName, off, Dir.Size, pCur->dwLength));
break;
}
{
Log(("rtldrPEOpen: %s: cert at %#x/%#x: wRevision=%#x\n", pszLogName, off, Dir.Size, pCur->wRevision));
rc = pCur->wRevision >= WIN_CERT_REVISION_1_0 ? VERR_LDRPE_CERT_UNSUPPORTED : VERR_LDRPE_CERT_MALFORMED;
break;
}
/*&& pCur->wCertificateType != WIN_CERT_TYPE_RESERVED_1*/
/*&& pCur->wCertificateType != WIN_CERT_TYPE_TS_STACK_SIGNED*/
)
{
Log(("rtldrPEOpen: %s: cert at %#x/%#x: wRevision=%#x\n", pszLogName, off, Dir.Size, pCur->wRevision));
break;
}
/* Remember the first signed data certificate. */
&& pModPe->offPkcs7SignedData == 0)
{
}
/* next */
}
if (RT_FAILURE(rc))
return rc;
}
return VINF_SUCCESS;
}
/**
* Open a PE image.
*
* @returns iprt status code.
* @param pReader The loader reader instance which will provide the raw image bits.
* @param fFlags Loader flags, RTLDR_O_XXX.
* @param enmArch Architecture specifier.
* @param offNtHdrs The offset of the NT headers (where you find "PE\0\0").
* @param phLdrMod Where to store the handle.
* @param pErrInfo Where to return extended error information. Optional.
*/
{
/*
* Read and validate the file header.
*/
if (RT_FAILURE(rc))
return rc;
if (RT_FAILURE(rc))
return rc;
/*
* Match the CPU architecture.
*/
bool fArchNoCodeCheckPending = false;
if ( enmArch != enmArchImage
&& ( enmArch != RTLDRARCH_WHATEVER
&& !(fFlags & RTLDR_O_WHATEVER_ARCH)) )
{
if (!(fFlags & RTLDR_O_IGNORE_ARCH_IF_NO_CODE))
return VERR_LDR_ARCH_MISMATCH;
fArchNoCodeCheckPending = true;
}
/*
* Read and validate the "optional" header. Convert 32->64 if necessary.
*/
rc = pReader->pfnRead(pReader, &OptHdr, FileHdr.SizeOfOptionalHeader, offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER));
if (RT_FAILURE(rc))
return rc;
rc = rtldrPEValidateOptionalHeader(&OptHdr, pszLogName, offNtHdrs, &FileHdr, pReader->pfnSize(pReader), fFlags);
if (RT_FAILURE(rc))
return rc;
return VERR_LDR_ARCH_MISMATCH;
/*
* Read and validate section headers.
*/
if (!paSections)
return VERR_NO_MEMORY;
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
/*
* Allocate and initialize the PE module structure.
*/
if (pModPe)
{
else
pModPe->offEndOfHdrs = offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader + cbSections;
/*
* Perform validation of some selected data directories which requires
* inspection of the actual data. This also saves some certificate
* information.
*/
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
else
rc = VERR_NO_MEMORY;
}
}
return rc;
}