HyperDbg/hyperdbg/libhyperdbg/code/debugger/user-level/pe-parser.cpp
2026-06-15 15:57:57 +02:00

4655 lines
172 KiB
C++

/**
* @file pe-parser.cpp
* @author Sina Karvandi (sina@hyperdbg.org)
* @brief Portable Executable parser
* @details
* @version 0.1
* @date 2021-12-26
*
* @copyright This project is released under the GNU Public License v3.
*
*/
#include "pch.h"
/**
* @brief Returns a human-readable name for a PE subsystem identifier
*
* Maps the Subsystem field from IMAGE_OPTIONAL_HEADER to a descriptive string.
* Unknown subsystem values return "Unknown".
*
* @param Subsystem The Subsystem value from the PE optional header
*
* @return const CHAR* A static string describing the subsystem
*/
static const CHAR *
PeGetSubsystemName(WORD Subsystem)
{
switch (Subsystem)
{
case IMAGE_SUBSYSTEM_NATIVE:
return "Device Driver(Native windows Process)";
case IMAGE_SUBSYSTEM_WINDOWS_GUI:
return "Windows GUI";
case IMAGE_SUBSYSTEM_WINDOWS_CUI:
return "Windows CLI";
case IMAGE_SUBSYSTEM_WINDOWS_CE_GUI:
return "Windows CE GUI";
default:
return "Unknown";
}
}
/**
* @brief Prints the set DLL characteristics flags from a PE optional header
*
* Iterates a table of known IMAGE_DLLCHARACTERISTICS_* flag values and prints
* the name of each flag that is set in DllCharacteristics, separated by commas.
* Prints "None" when no flags are set.
*
* @param DllCharacteristics The DllCharacteristics field from the PE optional header
*/
static VOID
PeShowDllCharacteristics(WORD DllCharacteristics)
{
BOOLEAN AnyFlag = FALSE;
struct DLL_CHARACTERISTIC_NAME
{
WORD Flag;
const CHAR * Name;
};
static const DLL_CHARACTERISTIC_NAME CommonDllCharacteristics[] = {
{IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA, "High Entropy VA"},
{IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, "Dynamic Base"},
{IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY, "Force Integrity"},
{IMAGE_DLLCHARACTERISTICS_NX_COMPAT, "NX Compatible"},
{IMAGE_DLLCHARACTERISTICS_NO_ISOLATION, "No Isolation"},
{IMAGE_DLLCHARACTERISTICS_NO_SEH, "No SEH"},
{IMAGE_DLLCHARACTERISTICS_NO_BIND, "No Bind"},
{IMAGE_DLLCHARACTERISTICS_APPCONTAINER, "AppContainer"},
{IMAGE_DLLCHARACTERISTICS_WDM_DRIVER, "WDM Driver"},
{IMAGE_DLLCHARACTERISTICS_GUARD_CF, "Guard CF"},
{IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, "Terminal Server Aware"},
};
for (UINT32 Index = 0; Index < RTL_NUMBER_OF(CommonDllCharacteristics); Index++)
{
if ((DllCharacteristics & CommonDllCharacteristics[Index].Flag) == CommonDllCharacteristics[Index].Flag)
{
ShowMessages("%s%s", AnyFlag ? ", " : "", CommonDllCharacteristics[Index].Name);
AnyFlag = TRUE;
}
}
if (!AnyFlag)
{
ShowMessages("None");
}
}
/**
* @brief Prints the raw file offset corresponding to the PE entrypoint RVA
*
* Resolves AddressOfEntryPoint through the section table to obtain a file offset
* and prints it. Prints "not mapped" when the RVA cannot be resolved.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param AddressOfEntryPoint The AddressOfEntryPoint field from the PE optional header
*/
static VOID
PeShowEntrypointFileOffset(PPE_IMAGE_READER Reader, DWORD AddressOfEntryPoint)
{
SIZE_T FileOffset = 0;
if (PeImageReaderRvaToFileOffset(Reader, AddressOfEntryPoint, 1, &FileOffset))
{
ShowMessages("\n%-36s%#llx", "Entrypoint file offset :", (UINT64)FileOffset);
}
else
{
ShowMessages("\n%-36s%s", "Entrypoint file offset :", "not mapped");
}
}
/**
* @brief Returns the name of a PE data directory by its index
*
* Maps IMAGE_DIRECTORY_ENTRY_* index values to descriptive strings using
* the standard ordering defined in the PE specification. Returns "Unknown"
* for indices at or beyond IMAGE_NUMBEROF_DIRECTORY_ENTRIES.
*
* @param Index Zero-based data directory index (IMAGE_DIRECTORY_ENTRY_*)
*
* @return const CHAR* A static string naming the data directory
*/
static const CHAR *
PeGetDataDirectoryName(UINT32 Index)
{
static const CHAR * DirectoryNames[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] = {
"Export Table",
"Import Table",
"Resource Table",
"Exception Table",
"Certificate Table",
"Base Relocation Table",
"Debug",
"Architecture",
"Global Ptr",
"TLS Table",
"Load Config Table",
"Bound Import",
"Import Address Table",
"Delay Import Descriptor",
"CLR Runtime Header",
"Reserved",
};
if (Index < RTL_NUMBER_OF(DirectoryNames))
{
return DirectoryNames[Index];
}
return "Unknown";
}
/**
* @brief Adds two DWORD values with overflow detection
*
* Returns FALSE without modifying Result when the addition would exceed MAXDWORD;
* otherwise writes the sum to *Result and returns TRUE.
*
* @param Left First operand
* @param Right Second operand
* @param Result Output pointer that receives the sum on success; must not be NULL
*
* @return BOOLEAN TRUE if the addition succeeded, FALSE on overflow or NULL pointer
*/
static BOOLEAN
PeAddDword(DWORD Left, DWORD Right, DWORD * Result)
{
if (Result == NULL || Right > MAXDWORD - Left)
{
return FALSE;
}
*Result = Left + Right;
return TRUE;
}
/**
* @brief Adds two ULONGLONG values with overflow detection
*
* Returns FALSE without modifying Result when the addition would overflow a
* 64-bit unsigned integer; otherwise writes the sum to *Result and returns TRUE.
*
* @param Left First operand
* @param Right Second operand
* @param Result Output pointer that receives the sum on success; must not be NULL
*
* @return BOOLEAN TRUE if the addition succeeded, FALSE on overflow or NULL pointer
*/
static BOOLEAN
PeAddUlonglong(ULONGLONG Left, ULONGLONG Right, ULONGLONG * Result)
{
if (Result == NULL || Right > (~((ULONGLONG)0) - Left))
{
return FALSE;
}
*Result = Left + Right;
return TRUE;
}
/**
* @brief Comparator for sorting PE_RAW_SECTION_RANGE entries by ascending start offset
*
* Intended for use with qsort(). Entries with the same Start are further ordered
* by End in ascending order.
*
* @param Left Pointer to the first PE_RAW_SECTION_RANGE to compare
* @param Right Pointer to the second PE_RAW_SECTION_RANGE to compare
*
* @return INT Negative if Left < Right, positive if Left > Right, zero if equal
*/
static INT
PeCompareRawSectionRange(const VOID * Left, const VOID * Right)
{
const PE_RAW_SECTION_RANGE * LeftRange = (const PE_RAW_SECTION_RANGE *)Left;
const PE_RAW_SECTION_RANGE * RightRange = (const PE_RAW_SECTION_RANGE *)Right;
if (LeftRange->Start < RightRange->Start)
{
return -1;
}
if (LeftRange->Start > RightRange->Start)
{
return 1;
}
if (LeftRange->End < RightRange->End)
{
return -1;
}
if (LeftRange->End > RightRange->End)
{
return 1;
}
return 0;
}
/**
* @brief Checks whether an RVA range is fully contained within another RVA range
*
* Both ranges are expressed as a base RVA and a size. Returns FALSE if any
* addition overflows or if [Rva, Rva+Size) extends outside [RangeRva, RangeRva+RangeSize).
*
* @param RangeRva Base RVA of the enclosing range
* @param RangeSize Size of the enclosing range in bytes
* @param Rva Base RVA of the range to test
* @param Size Size of the range to test in bytes
*
* @return BOOLEAN TRUE if [Rva, Rva+Size) lies entirely within [RangeRva, RangeRva+RangeSize)
*/
static BOOLEAN
PeRvaContainsRange(DWORD RangeRva, DWORD RangeSize, DWORD Rva, DWORD Size)
{
DWORD RangeEnd = 0;
DWORD RvaEnd = 0;
if (!PeAddDword(RangeRva, RangeSize, &RangeEnd) || !PeAddDword(Rva, Size, &RvaEnd))
{
return FALSE;
}
return Rva >= RangeRva && RvaEnd <= RangeEnd;
}
/**
* @brief Returns a validated pointer into the image at the location mapped by an RVA
*
* Translates Rva to a raw file offset via PeImageReaderRvaToFileOffset and then
* validates the range through PeImageReaderGetPointerAtOffset.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Rva Relative virtual address to look up
* @param Length Number of bytes that must be accessible at the resolved offset
* @param Pointer Output pointer set to the resolved image location on success; must not be NULL
*
* @return BOOLEAN TRUE on success, FALSE if the RVA cannot be resolved or is out of bounds
*/
static BOOLEAN
PeGetPointerAtRva(PPE_IMAGE_READER Reader, DWORD Rva, DWORD Length, const BYTE ** Pointer)
{
SIZE_T FileOffset = 0;
if (!PeImageReaderRvaToFileOffset(Reader, Rva, Length, &FileOffset))
{
return FALSE;
}
return PeImageReaderGetPointerAtOffset(Reader, FileOffset, Length, Pointer);
}
typedef enum _PE_ASCII_STRING_STATUS
{
PeAsciiStringInvalid,
PeAsciiStringOk,
PeAsciiStringTruncated,
} PE_ASCII_STRING_STATUS;
/**
* @brief Reads an ASCII string from the image at the given RVA
*
* Reads up to MaxLength bytes starting at Rva, stopping at the first null
* terminator. Non-printable bytes are replaced with '.'. The output buffer is
* always null-terminated. Returns PeAsciiStringInvalid if any mapped byte
* cannot be resolved, PeAsciiStringOk when the null terminator is found within
* MaxLength, and PeAsciiStringTruncated otherwise.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Rva Starting RVA of the string
* @param MaxLength Maximum number of bytes to scan
* @param Buffer Destination buffer for the null-terminated output
* @param BufferSize Size of Buffer in bytes; must be at least 1
*
* @return PE_ASCII_STRING_STATUS Result indicating whether the string was read successfully
*/
static PE_ASCII_STRING_STATUS
PeReadAsciiStringAtRva(PPE_IMAGE_READER Reader, DWORD Rva, DWORD MaxLength, CHAR * Buffer, SIZE_T BufferSize)
{
if (Buffer == NULL || BufferSize == 0 || MaxLength == 0)
{
return PeAsciiStringInvalid;
}
Buffer[0] = '\0';
SIZE_T OutputIndex = 0;
for (DWORD Index = 0; Index < MaxLength; Index++)
{
DWORD CharacterRva = 0;
const BYTE * Character = NULL;
if (!PeAddDword(Rva, Index, &CharacterRva) || !PeGetPointerAtRva(Reader, CharacterRva, 1, &Character))
{
return PeAsciiStringInvalid;
}
if (*Character == '\0')
{
return PeAsciiStringOk;
}
if (OutputIndex + 1 < BufferSize)
{
Buffer[OutputIndex++] = (*Character >= 0x20 && *Character <= 0x7e) ? (CHAR)*Character : '.';
Buffer[OutputIndex] = '\0';
}
}
return PeAsciiStringTruncated;
}
/**
* @brief Returns a human-readable description of a PE_ASCII_STRING_STATUS value
*
* @param Status The status value returned by PeReadAsciiStringAtRva or related functions
*
* @return const CHAR* A static string describing the status
*/
static const CHAR *
PeAsciiStatusName(PE_ASCII_STRING_STATUS Status)
{
switch (Status)
{
case PeAsciiStringInvalid:
return "invalid mapping";
case PeAsciiStringTruncated:
return "truncated or unterminated";
default:
return "readable";
}
}
/**
* @brief Converts an absolute virtual address to a relative virtual address
*
* Subtracts ImageBase from Va and checks that the difference fits in a DWORD.
* Returns FALSE when Va < ImageBase or when the difference exceeds MAXDWORD.
*
* @param Va Absolute virtual address to convert
* @param ImageBase Base address of the image
* @param Rva Output pointer that receives the RVA on success; must not be NULL
*
* @return BOOLEAN TRUE on success, FALSE when the conversion is not representable
*/
static BOOLEAN
PeVaToRva(ULONGLONG Va, ULONGLONG ImageBase, DWORD * Rva)
{
ULONGLONG Difference = 0;
if (Rva == NULL || Va < ImageBase)
{
return FALSE;
}
Difference = Va - ImageBase;
if (Difference > MAXDWORD)
{
return FALSE;
}
*Rva = (DWORD)Difference;
return TRUE;
}
/**
* @brief Reads a DWORD from a byte buffer at the specified byte offset
*
* Uses CopyMemory to avoid strict-aliasing and alignment issues.
*
* @param Buffer Pointer to the source byte buffer
* @param Offset Byte offset within Buffer at which to read
*
* @return DWORD The 32-bit value read from Buffer + Offset
*/
static DWORD
PeReadDwordFromBuffer(const BYTE * Buffer, SIZE_T Offset)
{
DWORD Value = 0;
CopyMemory(&Value, Buffer + Offset, sizeof(Value));
return Value;
}
/**
* @brief Reads a WORD from a byte buffer at the specified byte offset
*
* Uses CopyMemory to avoid strict-aliasing and alignment issues.
*
* @param Buffer Pointer to the source byte buffer
* @param Offset Byte offset within Buffer at which to read
*
* @return WORD The 16-bit value read from Buffer + Offset
*/
static WORD
PeReadWordFromBuffer(const BYTE * Buffer, SIZE_T Offset)
{
WORD Value = 0;
CopyMemory(&Value, Buffer + Offset, sizeof(Value));
return Value;
}
/**
* @brief Reads a BYTE from a byte buffer at the specified byte offset
*
* Uses CopyMemory to provide a consistent read interface.
*
* @param Buffer Pointer to the source byte buffer
* @param Offset Byte offset within Buffer at which to read
*
* @return BYTE The 8-bit value read from Buffer + Offset
*/
static BYTE
PeReadByteFromBuffer(const BYTE * Buffer, SIZE_T Offset)
{
BYTE Value = 0;
CopyMemory(&Value, Buffer + Offset, sizeof(Value));
return Value;
}
/**
* @brief Reads a ULONGLONG from a byte buffer at the specified byte offset
*
* Uses CopyMemory to avoid strict-aliasing and alignment issues.
*
* @param Buffer Pointer to the source byte buffer
* @param Offset Byte offset within Buffer at which to read
*
* @return ULONGLONG The 64-bit value read from Buffer + Offset
*/
static ULONGLONG
PeReadQwordFromBuffer(const BYTE * Buffer, SIZE_T Offset)
{
ULONGLONG Value = 0;
CopyMemory(&Value, Buffer + Offset, sizeof(Value));
return Value;
}
/**
* @brief Reads a pointer-sized value from a load configuration structure
*
* Reads a DWORD when Is32Bit is TRUE (PE32 pointer), or a QWORD when FALSE
* (PE32+ pointer), returning the result as a ULONGLONG in both cases.
*
* @param Buffer Pointer to the load configuration byte buffer
* @param Offset Byte offset within Buffer of the field to read
* @param Is32Bit TRUE to read a 32-bit pointer, FALSE to read a 64-bit pointer
*
* @return ULONGLONG The pointer value widened to 64 bits
*/
static ULONGLONG
PeReadLoadConfigPointer(const BYTE * Buffer, SIZE_T Offset, BOOLEAN Is32Bit)
{
return Is32Bit ? PeReadDwordFromBuffer(Buffer, Offset) : PeReadQwordFromBuffer(Buffer, Offset);
}
/**
* @brief Reads an ASCII string from a raw byte pointer up to MaxLength bytes
*
* Scans StringPointer[0..MaxLength-1] for a null terminator. Non-printable
* bytes are replaced with '.'. The output is always null-terminated.
* Returns PeAsciiStringOk when the terminator is found, PeAsciiStringTruncated
* when MaxLength is exhausted without a terminator, and PeAsciiStringInvalid
* on NULL arguments.
*
* @param StringPointer Pointer to the source ASCII data; must not be NULL
* @param MaxLength Maximum number of bytes to scan
* @param Buffer Destination buffer for the null-terminated output
* @param BufferSize Size of Buffer in bytes; must be at least 1
*
* @return PE_ASCII_STRING_STATUS Result indicating whether the string was read successfully
*/
static PE_ASCII_STRING_STATUS
PeReadAsciiStringFromBuffer(const BYTE * StringPointer, DWORD MaxLength, CHAR * Buffer, SIZE_T BufferSize)
{
if (StringPointer == NULL || Buffer == NULL || BufferSize == 0 || MaxLength == 0)
{
return PeAsciiStringInvalid;
}
Buffer[0] = '\0';
SIZE_T OutputIndex = 0;
for (DWORD Index = 0; Index < MaxLength; Index++)
{
if (StringPointer[Index] == '\0')
{
return PeAsciiStringOk;
}
if (OutputIndex + 1 < BufferSize)
{
Buffer[OutputIndex++] = (StringPointer[Index] >= 0x20 && StringPointer[Index] <= 0x7e) ? (CHAR)StringPointer[Index] : '.';
Buffer[OutputIndex] = '\0';
}
}
return PeAsciiStringTruncated;
}
/**
* @brief Reads an ASCII string located at a fixed offset within a PE data directory
*
* Computes the target RVA as Directory->VirtualAddress + Offset, clamps the
* maximum read length to the remaining bytes in the directory, and delegates
* to PeReadAsciiStringAtRva.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Directory Pointer to the data directory entry that contains the string
* @param Offset Byte offset within the directory at which the string begins
* @param MaxLength Maximum number of bytes to scan for the null terminator
* @param Buffer Destination buffer for the null-terminated output
* @param BufferSize Size of Buffer in bytes
*
* @return PE_ASCII_STRING_STATUS Result indicating whether the string was read successfully
*/
static PE_ASCII_STRING_STATUS
PeReadAsciiStringInDirectory(PPE_IMAGE_READER Reader,
const IMAGE_DATA_DIRECTORY * Directory,
DWORD Offset,
DWORD MaxLength,
CHAR * Buffer,
SIZE_T BufferSize)
{
DWORD StringRva = 0;
if (Directory == NULL || Offset >= Directory->Size || !PeAddDword(Directory->VirtualAddress, Offset, &StringRva))
{
return PeAsciiStringInvalid;
}
DWORD Remaining = Directory->Size - Offset;
return PeReadAsciiStringAtRva(Reader, StringRva, Remaining < MaxLength ? Remaining : MaxLength, Buffer, BufferSize);
}
/**
* @brief Computes the standard PE file checksum
*
* Implements the algorithm used by the Windows PE loader: sums all 16-bit
* words of the image (treating the checksum field itself as zero), folds the
* carry, and adds the total image size. The result matches the value stored
* in the optional header CheckSum field for well-formed images.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ChecksumOffset Raw file offset of the 4-byte checksum field (excluded from summation)
* @param Checksum Output pointer that receives the computed checksum on success
*
* @return BOOLEAN TRUE on success, FALSE on NULL arguments or invalid checksum offset
*/
static BOOLEAN
PeComputeChecksum(PPE_IMAGE_READER Reader, SIZE_T ChecksumOffset, DWORD * Checksum)
{
ULONGLONG Sum = 0;
if (Reader == NULL || Checksum == NULL || ChecksumOffset > Reader->ImageSize || sizeof(DWORD) > Reader->ImageSize - ChecksumOffset)
{
return FALSE;
}
for (SIZE_T Offset = 0; Offset < Reader->ImageSize; Offset += sizeof(WORD))
{
WORD Value = 0;
if (Offset >= ChecksumOffset && Offset < ChecksumOffset + sizeof(DWORD))
{
Value = 0;
}
else if (Offset + 1 < Reader->ImageSize)
{
CopyMemory(&Value, Reader->ImageBase + Offset, sizeof(Value));
}
else
{
Value = Reader->ImageBase[Offset];
}
Sum += Value;
Sum = (Sum & 0xffff) + (Sum >> 16);
}
Sum = (Sum & 0xffff) + (Sum >> 16);
*Checksum = (DWORD)Sum + (DWORD)Reader->ImageSize;
return TRUE;
}
/**
* @brief Prints the stored and computed PE optional header checksums
*
* Displays HeaderChecksum as read from the optional header and, when the image
* is small enough, also computes and displays the expected value via
* PeComputeChecksum. Skips computation for images larger than 64 MiB.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER (may be NULL to skip computation)
* @param HeaderChecksum Checksum value as stored in the PE optional header
* @param ChecksumOffset Raw file offset of the checksum field within the image
*/
static VOID
PeShowChecksum(PPE_IMAGE_READER Reader, DWORD HeaderChecksum, SIZE_T ChecksumOffset)
{
const SIZE_T MaxChecksumBytes = 64 * 1024 * 1024;
DWORD ComputedChecksum = 0;
ShowMessages("\n%-36s%#x", "Optional header checksum :", HeaderChecksum);
if (Reader != NULL && Reader->ImageSize > MaxChecksumBytes)
{
ShowMessages("\n%-36s%s", "Computed PE checksum :", "skipped, file is larger than local cap");
return;
}
if (PeComputeChecksum(Reader, ChecksumOffset, &ComputedChecksum))
{
ShowMessages("\n%-36s%#x (%s)", "Computed PE checksum :", ComputedChecksum, ComputedChecksum == HeaderChecksum ? "matches" : "differs");
}
else
{
ShowMessages("\n%-36s%s", "Computed PE checksum :", "not available");
}
}
/**
* @brief Prints the contents of the PE certificate (security) directory
*
* The certificate directory is stored at a raw file offset rather than an RVA.
* Iterates WIN_CERTIFICATE entries, printing the file offset, length, revision,
* and type of each. Stops when the directory is empty, invalid, or a certificate
* length or alignment error is detected.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param SecurityDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_SECURITY data directory entry
*/
static VOID
PeShowCertificateTable(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * SecurityDirectory)
{
const DWORD MaxCertificates = 0x1000;
ShowMessages("\n\nCertificate table\n-----------------");
if (SecurityDirectory->VirtualAddress == 0 || SecurityDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Certificate table :", "empty");
return;
}
const BYTE * DirectoryPointer = NULL;
if (!PeImageReaderGetPointerAtOffset(Reader, SecurityDirectory->VirtualAddress, SecurityDirectory->Size, &DirectoryPointer))
{
ShowMessages("\n%-36s%s", "Certificate table :", "invalid bounds");
return;
}
DWORD Offset = 0;
DWORD Count = 0;
while (Offset < SecurityDirectory->Size && Count < MaxCertificates)
{
if (SecurityDirectory->Size - Offset < 8)
{
ShowMessages("\n%-36s%#llx", "Warning, truncated certificate at :", (UINT64)SecurityDirectory->VirtualAddress + Offset);
break;
}
const BYTE * Entry = DirectoryPointer + Offset;
DWORD Length = PeReadDwordFromBuffer(Entry, 0);
WORD Revision = PeReadWordFromBuffer(Entry, 4);
WORD Type = PeReadWordFromBuffer(Entry, 6);
ShowMessages("\n[%u] file offset %#llx length %#x revision %#x type %#x", Count, (UINT64)SecurityDirectory->VirtualAddress + Offset, Length, Revision, Type);
if (Length < 8)
{
ShowMessages("\n%-36s%s", "Warning :", "certificate length is smaller than header");
break;
}
if (Length > SecurityDirectory->Size - Offset)
{
ShowMessages("\n%-36s%s", "Warning :", "certificate entry extends past directory");
break;
}
DWORD AlignedLength = (Length + 7) & ~7u;
if (AlignedLength < Length || AlignedLength > SecurityDirectory->Size - Offset)
{
ShowMessages("\n%-36s%s", "Warning :", "certificate alignment extends past directory");
break;
}
Offset += AlignedLength;
Count++;
}
ShowMessages("\n%-36s%u", "Certificate entry count :", Count);
if (Count == MaxCertificates)
{
ShowMessages("\n%-36s%#x", "Warning, certificate cap reached :", MaxCertificates);
}
}
/**
* @brief Prints base relocation blocks and per-type entry counts
*
* Iterates IMAGE_BASE_RELOCATION blocks within the .reloc directory,
* printing the page RVA, block size, and entry count for each. Accumulates
* a per-type summary and emits warnings for malformed or oversized data.
* Processing stops at 0x10000 blocks or 0x100000 entries.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param RelocDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_BASERELOC data directory entry
*/
static VOID
PeShowBaseRelocations(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * RelocDirectory)
{
const DWORD MaxBlocks = 0x10000;
const DWORD MaxEntries = 0x100000;
const DWORD MaxPrintedBlocks = 0x20;
DWORD TypeCounts[16] = {0};
DWORD BlockCount = 0;
ULONGLONG EntryCount = 0;
BOOLEAN EntryCapped = FALSE;
ShowMessages("\n\nBase relocations\n----------------");
if (RelocDirectory->VirtualAddress == 0 || RelocDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Base relocation table :", "empty");
return;
}
DWORD Offset = 0;
while (Offset < RelocDirectory->Size && BlockCount < MaxBlocks && EntryCount < MaxEntries)
{
DWORD BlockRva = 0;
if (!PeAddDword(RelocDirectory->VirtualAddress, Offset, &BlockRva) || RelocDirectory->Size - Offset < sizeof(IMAGE_BASE_RELOCATION))
{
ShowMessages("\n%-36s%s", "Warning :", "relocation block header is truncated");
break;
}
const BYTE * BlockPointer = NULL;
if (!PeGetPointerAtRva(Reader, BlockRva, sizeof(IMAGE_BASE_RELOCATION), &BlockPointer))
{
ShowMessages("\n%-36s%#x", "Warning, invalid relocation block RVA :", BlockRva);
break;
}
DWORD PageRva = PeReadDwordFromBuffer(BlockPointer, 0);
DWORD SizeOfBlock = PeReadDwordFromBuffer(BlockPointer, 4);
if (SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION))
{
ShowMessages("\n%-36s%s", "Warning :", "relocation block size is smaller than header");
break;
}
if (SizeOfBlock > RelocDirectory->Size - Offset)
{
ShowMessages("\n%-36s%s", "Warning :", "relocation block extends past directory");
break;
}
if (((SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) & 1) != 0)
{
ShowMessages("\n%-36s%#x", "Warning, odd relocation block size :", SizeOfBlock);
}
DWORD EntriesInBlock = (SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
if (BlockCount < MaxPrintedBlocks)
{
ShowMessages("\n[%u] page RVA %#x block size %#x entries %u", BlockCount, PageRva, SizeOfBlock, EntriesInBlock);
}
DWORD EntriesRva = 0;
if (!PeAddDword(BlockRva, sizeof(IMAGE_BASE_RELOCATION), &EntriesRva))
{
ShowMessages("\n%-36s%s", "Warning :", "relocation entries RVA overflow");
break;
}
const BYTE * Entries = NULL;
if (EntriesInBlock != 0 && !PeGetPointerAtRva(Reader, EntriesRva, EntriesInBlock * sizeof(WORD), &Entries))
{
ShowMessages("\n%-36s%#x", "Warning, invalid relocation entries RVA :", EntriesRva);
break;
}
for (DWORD EntryIndex = 0; EntryIndex < EntriesInBlock && EntryCount < MaxEntries; EntryIndex++)
{
WORD Entry = PeReadWordFromBuffer(Entries, EntryIndex * sizeof(WORD));
WORD Type = (Entry >> 12) & 0xf;
TypeCounts[Type]++;
EntryCount++;
if (EntryCount == MaxEntries)
{
EntryCapped = TRUE;
break;
}
if (Type == IMAGE_REL_BASED_HIGHADJ)
{
if (EntryIndex + 1 >= EntriesInBlock)
{
ShowMessages("\n%-36s%s", "Warning :", "HIGHADJ relocation is missing its pair entry");
}
else
{
EntryIndex++;
EntryCount++;
}
}
}
if (EntryCount == MaxEntries)
{
EntryCapped = TRUE;
}
Offset += SizeOfBlock;
BlockCount++;
}
ShowMessages("\n%-36s%u", "Relocation block count :", BlockCount);
ShowMessages("\n%-36s%llu", "Relocation entry count :", EntryCount);
for (DWORD Type = 0; Type < RTL_NUMBER_OF(TypeCounts); Type++)
{
if (TypeCounts[Type] != 0)
{
ShowMessages("\n%-36s%u = %u", "Relocation type count :", Type, TypeCounts[Type]);
}
}
if (BlockCount == MaxBlocks)
{
ShowMessages("\n%-36s%#x", "Warning, relocation block cap reached :", MaxBlocks);
}
if (EntryCapped)
{
ShowMessages("\n%-36s%#x", "Warning, relocation entry cap reached :", MaxEntries);
}
if (BlockCount > MaxPrintedBlocks)
{
ShowMessages("\n%-36s%#x", "Info, printed relocation blocks :", MaxPrintedBlocks);
}
}
/**
* @brief Prints bound import descriptors and their forwarder references
*
* Iterates IMAGE_BOUND_IMPORT_DESCRIPTOR entries, printing the module name,
* timestamp, and forwader reference count for each. Also expands forwader
* references inline. Processing stops after 0x1000 descriptors.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param BoundImportDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT data directory entry
*/
static VOID
PeShowBoundImports(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * BoundImportDirectory)
{
const DWORD MaxDescriptors = 0x1000;
const DWORD MaxNameLength = 0x200;
ShowMessages("\n\nBound imports\n-------------");
if (BoundImportDirectory->VirtualAddress == 0 || BoundImportDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Bound import directory :", "empty");
return;
}
DWORD Offset = 0;
DWORD DescriptorCount = 0;
while (Offset < BoundImportDirectory->Size && DescriptorCount < MaxDescriptors)
{
DWORD DescriptorRva = 0;
if (!PeAddDword(BoundImportDirectory->VirtualAddress, Offset, &DescriptorRva) || BoundImportDirectory->Size - Offset < sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR))
{
ShowMessages("\n%-36s%s", "Warning :", "bound import descriptor is truncated");
break;
}
const BYTE * Descriptor = NULL;
if (!PeGetPointerAtRva(Reader, DescriptorRva, sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR), &Descriptor))
{
ShowMessages("\n%-36s%#x", "Warning, invalid bound descriptor RVA :", DescriptorRva);
break;
}
DWORD TimeDateStamp = PeReadDwordFromBuffer(Descriptor, 0);
WORD OffsetModuleName = PeReadWordFromBuffer(Descriptor, 4);
WORD NumberOfModuleForwarders = PeReadWordFromBuffer(Descriptor, 6);
if (TimeDateStamp == 0 && OffsetModuleName == 0 && NumberOfModuleForwarders == 0)
{
break;
}
CHAR ModuleName[MaxNameLength + 1] = {0};
PE_ASCII_STRING_STATUS NameStatus = PeReadAsciiStringInDirectory(Reader,
BoundImportDirectory,
OffsetModuleName,
MaxNameLength,
ModuleName,
sizeof(ModuleName));
ShowMessages("\n[%u] module %s timestamp %#x forwarder refs %u",
DescriptorCount,
NameStatus == PeAsciiStringOk ? ModuleName : PeAsciiStatusName(NameStatus),
TimeDateStamp,
NumberOfModuleForwarders);
DWORD DescriptorSpan = sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR) + (NumberOfModuleForwarders * (DWORD)sizeof(IMAGE_BOUND_FORWARDER_REF));
if (DescriptorSpan > BoundImportDirectory->Size - Offset)
{
ShowMessages("\n%-36s%s", "Warning :", "bound import forwarder refs extend past directory");
break;
}
for (WORD ForwarderIndex = 0; ForwarderIndex < NumberOfModuleForwarders; ForwarderIndex++)
{
DWORD ForwarderOffset = Offset + sizeof(IMAGE_BOUND_IMPORT_DESCRIPTOR) + (ForwarderIndex * (DWORD)sizeof(IMAGE_BOUND_FORWARDER_REF));
DWORD ForwarderRva = 0;
if (!PeAddDword(BoundImportDirectory->VirtualAddress, ForwarderOffset, &ForwarderRva))
{
ShowMessages("\n%-36s%s", "Warning :", "bound forwarder RVA overflow");
break;
}
const BYTE * Forwarder = NULL;
if (!PeGetPointerAtRva(Reader, ForwarderRva, sizeof(IMAGE_BOUND_FORWARDER_REF), &Forwarder))
{
ShowMessages("\n%-36s%#x", "Warning, invalid bound forwarder RVA :", ForwarderRva);
break;
}
DWORD ForwarderTimeDateStamp = PeReadDwordFromBuffer(Forwarder, 0);
WORD ForwarderOffsetModuleName = PeReadWordFromBuffer(Forwarder, 4);
CHAR ForwarderName[MaxNameLength + 1] = {0};
PE_ASCII_STRING_STATUS ForwarderNameStatus = PeReadAsciiStringInDirectory(Reader,
BoundImportDirectory,
ForwarderOffsetModuleName,
MaxNameLength,
ForwarderName,
sizeof(ForwarderName));
ShowMessages("\n [%u] forwarder %s timestamp %#x",
ForwarderIndex,
ForwarderNameStatus == PeAsciiStringOk ? ForwarderName : PeAsciiStatusName(ForwarderNameStatus),
ForwarderTimeDateStamp);
}
Offset += DescriptorSpan;
DescriptorCount++;
}
ShowMessages("\n%-36s%u", "Bound import descriptor count :", DescriptorCount);
if (DescriptorCount == MaxDescriptors)
{
ShowMessages("\n%-36s%#x", "Warning, bound import cap reached :", MaxDescriptors);
}
}
/**
* @brief Computes the 64-bit FNV-1a hash of a byte buffer
*
* Uses the standard FNV-1a parameters: offset basis 14695981039346656037
* and prime 1099511628211. Suitable for non-cryptographic fingerprinting.
*
* @param Data Pointer to the input data
* @param Size Number of bytes to hash
*
* @return ULONGLONG The 64-bit FNV-1a hash value
*/
static ULONGLONG
PeFnv1a64(const BYTE * Data, DWORD Size)
{
ULONGLONG Hash = 14695981039346656037ull;
for (DWORD Index = 0; Index < Size; Index++)
{
Hash ^= Data[Index];
Hash *= 1099511628211ull;
}
return Hash;
}
/**
* @brief Computes the Shannon entropy of a byte buffer
*
* Calculates the frequency of each byte value (0-255) and applies the
* standard information-entropy formula. A return value near 8.0 indicates
* high entropy (e.g. compressed or encrypted data); values near 0.0 indicate
* low entropy (e.g. sparse, mostly-zero data).
*
* @param Data Pointer to the input data
* @param Size Number of bytes to analyse
*
* @return double Shannon entropy in bits per byte (0.0 to 8.0)
*/
static double
PeCalculateEntropy(const BYTE * Data, DWORD Size)
{
DWORD Counts[256] = {0};
double Entropy = 0.0;
for (DWORD Index = 0; Index < Size; Index++)
{
Counts[Data[Index]]++;
}
for (DWORD Index = 0; Index < RTL_NUMBER_OF(Counts); Index++)
{
if (Counts[Index] != 0)
{
double Probability = (double)Counts[Index] / (double)Size;
Entropy -= Probability * (log(Probability) / log(2.0));
}
}
return Entropy;
}
/**
* @brief Reads a 16-bit word from the image at the location mapped by an RVA
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Rva Relative virtual address to read from
* @param Value Output pointer that receives the WORD value on success; must not be NULL
*
* @return BOOLEAN TRUE on success, FALSE if the RVA cannot be resolved or Value is NULL
*/
static BOOLEAN
PeReadWordAtRva(PPE_IMAGE_READER Reader, DWORD Rva, WORD * Value)
{
const BYTE * Pointer = NULL;
if (Value == NULL || !PeGetPointerAtRva(Reader, Rva, sizeof(WORD), &Pointer))
{
return FALSE;
}
CopyMemory(Value, Pointer, sizeof(*Value));
return TRUE;
}
/**
* @brief Reads an import/export thunk value (DWORD or QWORD) from an RVA
*
* Reads sizeof(DWORD) bytes when Is32Bit is TRUE, or sizeof(ULONGLONG) bytes
* when FALSE, and widens the result to a ULONGLONG.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Rva Relative virtual address of the thunk entry
* @param Is32Bit TRUE to read a 32-bit thunk, FALSE to read a 64-bit thunk
* @param Value Output pointer that receives the thunk value on success; must not be NULL
*
* @return BOOLEAN TRUE on success, FALSE if the RVA cannot be resolved or Value is NULL
*/
static BOOLEAN
PeReadThunkAtRva(PPE_IMAGE_READER Reader, DWORD Rva, BOOLEAN Is32Bit, ULONGLONG * Value)
{
const BYTE * Pointer = NULL;
DWORD Size = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
if (Value == NULL || !PeGetPointerAtRva(Reader, Rva, Size, &Pointer))
{
return FALSE;
}
if (Is32Bit)
{
DWORD Value32 = 0;
CopyMemory(&Value32, Pointer, sizeof(Value32));
*Value = Value32;
}
else
{
CopyMemory(Value, Pointer, sizeof(*Value));
}
return TRUE;
}
/**
* @brief Prints a named resource type identifier and its occurrence count
*
* Maps well-known RT_* integer type identifiers (1-12, 14, 16, 24) to
* human-readable names. Unknown or unnamed type IDs are silently ignored.
*
* @param TypeId Numeric resource type identifier (RT_* value)
* @param Count Number of resources of this type found in the resource directory
*/
static VOID
PeShowResourceTypeCount(DWORD TypeId, DWORD Count)
{
const CHAR * TypeName = NULL;
switch (TypeId)
{
case 1:
TypeName = "cursor";
break;
case 2:
TypeName = "bitmap";
break;
case 3:
TypeName = "icon";
break;
case 4:
TypeName = "menu";
break;
case 5:
TypeName = "dialog";
break;
case 6:
TypeName = "string";
break;
case 7:
TypeName = "font directory";
break;
case 8:
TypeName = "font";
break;
case 9:
TypeName = "accelerator";
break;
case 10:
TypeName = "rcdata";
break;
case 11:
TypeName = "message table";
break;
case 12:
TypeName = "group cursor";
break;
case 14:
TypeName = "group icon";
break;
case 16:
TypeName = "version";
break;
case 24:
TypeName = "manifest";
break;
default:
TypeName = NULL;
break;
}
if (TypeName != NULL)
{
ShowMessages("\n%-36s%s (%u) = %u", "Resource type count :", TypeName, TypeId, Count);
}
}
/**
* @brief Traverses and prints the PE resource directory tree
*
* Performs an iterative depth-first walk of the three-level resource directory
* (type / name or id / language), printing directory and data-entry statistics.
* Accumulates per-type counts for well-known resource types and reports total
* declared and mapped data bytes. Processing is capped at 0x4000 nodes and
* 0x10000 entries to prevent runaway on corrupt images.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ResourceDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_RESOURCE data directory entry
*/
static VOID
PeShowResources(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * ResourceDirectory)
{
const DWORD MaxDepth = 8;
const DWORD MaxNodes = 0x4000;
const DWORD MaxEntries = 0x10000;
const DWORD MaxVisitedOffsets = 0x4000;
typedef struct _RESOURCE_NODE
{
DWORD Offset;
DWORD Depth;
DWORD TypeId;
} RESOURCE_NODE;
RESOURCE_NODE * Stack = NULL;
DWORD * Visited = NULL;
DWORD StackCount = 0;
DWORD VisitedCount = 0;
DWORD TypeCounts[25] = {0};
DWORD DirectoryCount = 0;
DWORD EntryCount = 0;
DWORD DeclaredDataEntryCount = 0;
DWORD MappedDataEntryCount = 0;
DWORD InvalidEntryCount = 0;
DWORD NamedRootTypeCount = 0;
ULONGLONG DeclaredDataBytes = 0;
ULONGLONG MappedDataBytes = 0;
BOOLEAN NodeCapReached = FALSE;
BOOLEAN EntryCapReached = FALSE;
ShowMessages("\n\nResources\n---------");
if (ResourceDirectory->VirtualAddress == 0 || ResourceDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Resource directory :", "empty");
return;
}
if (ResourceDirectory->Size < 16)
{
ShowMessages("\n%-36s%s", "Warning :", "resource directory is smaller than one header");
return;
}
Stack = new (std::nothrow) RESOURCE_NODE[MaxNodes];
Visited = new (std::nothrow) DWORD[MaxVisitedOffsets];
if (Stack == NULL || Visited == NULL)
{
ShowMessages("\n%-36s%s", "Resource directory :", "memory allocation failed");
delete[] Stack;
delete[] Visited;
return;
}
ZeroMemory(Stack, sizeof(RESOURCE_NODE) * MaxNodes);
ZeroMemory(Visited, sizeof(DWORD) * MaxVisitedOffsets);
Stack[StackCount].Offset = 0;
Stack[StackCount].Depth = 0;
Stack[StackCount].TypeId = 0;
StackCount++;
while (StackCount != 0)
{
RESOURCE_NODE Node = Stack[--StackCount];
if (DirectoryCount >= MaxNodes)
{
NodeCapReached = TRUE;
break;
}
if (Node.Depth > MaxDepth)
{
InvalidEntryCount++;
continue;
}
BOOLEAN Seen = FALSE;
for (DWORD Index = 0; Index < VisitedCount; Index++)
{
if (Visited[Index] == Node.Offset)
{
Seen = TRUE;
break;
}
}
if (Seen)
{
InvalidEntryCount++;
continue;
}
if (VisitedCount < MaxVisitedOffsets)
{
Visited[VisitedCount++] = Node.Offset;
}
else
{
NodeCapReached = TRUE;
break;
}
DWORD DirectoryRva = 0;
if (!PeAddDword(ResourceDirectory->VirtualAddress, Node.Offset, &DirectoryRva) ||
Node.Offset > ResourceDirectory->Size || ResourceDirectory->Size - Node.Offset < 16)
{
InvalidEntryCount++;
continue;
}
const BYTE * Directory = NULL;
if (!PeGetPointerAtRva(Reader, DirectoryRva, 16, &Directory))
{
InvalidEntryCount++;
continue;
}
WORD NamedEntries = PeReadWordFromBuffer(Directory, 12);
WORD IdEntries = PeReadWordFromBuffer(Directory, 14);
DWORD EntryTotal = (DWORD)NamedEntries + (DWORD)IdEntries;
DWORD EntryBytes = 0;
if (EntryTotal > (MAXDWORD - 16) / 8 || !PeAddDword(16, EntryTotal * 8, &EntryBytes) ||
EntryBytes > ResourceDirectory->Size - Node.Offset)
{
InvalidEntryCount++;
continue;
}
DirectoryCount++;
const BYTE * Entries = NULL;
if (EntryTotal != 0 && !PeGetPointerAtRva(Reader, DirectoryRva, EntryBytes, &Entries))
{
InvalidEntryCount++;
continue;
}
for (DWORD EntryIndex = 0; EntryIndex < EntryTotal; EntryIndex++)
{
if (EntryCount >= MaxEntries)
{
EntryCapReached = TRUE;
break;
}
const BYTE * Entry = Entries + 16 + (EntryIndex * 8);
DWORD NameOrId = PeReadDwordFromBuffer(Entry, 0);
DWORD OffsetToData = PeReadDwordFromBuffer(Entry, 4);
DWORD ChildOffset = OffsetToData & 0x7fffffff;
DWORD TypeId = Node.TypeId;
EntryCount++;
if ((NameOrId & 0x80000000) != 0)
{
DWORD StringOffset = NameOrId & 0x7fffffff;
DWORD StringRva = 0;
if (StringOffset > ResourceDirectory->Size || ResourceDirectory->Size - StringOffset < sizeof(WORD) ||
!PeAddDword(ResourceDirectory->VirtualAddress, StringOffset, &StringRva))
{
InvalidEntryCount++;
}
else
{
const BYTE * String = NULL;
if (!PeGetPointerAtRva(Reader, StringRva, sizeof(WORD), &String))
{
InvalidEntryCount++;
}
else
{
WORD StringLength = PeReadWordFromBuffer(String, 0);
DWORD StringBytes = (DWORD)sizeof(WORD) + ((DWORD)StringLength * sizeof(WCHAR));
if (StringBytes > ResourceDirectory->Size - StringOffset || !PeGetPointerAtRva(Reader, StringRva, StringBytes, &String))
{
InvalidEntryCount++;
}
}
}
if (Node.Depth == 0)
{
NamedRootTypeCount++;
}
}
else if (Node.Depth == 0)
{
TypeId = NameOrId & 0xffff;
if (TypeId < RTL_NUMBER_OF(TypeCounts))
{
TypeCounts[TypeId]++;
}
}
if ((OffsetToData & 0x80000000) != 0)
{
if (ChildOffset > ResourceDirectory->Size || ResourceDirectory->Size - ChildOffset < 16 || Node.Depth + 1 > MaxDepth)
{
InvalidEntryCount++;
continue;
}
if (StackCount >= MaxNodes)
{
NodeCapReached = TRUE;
continue;
}
Stack[StackCount].Offset = ChildOffset;
Stack[StackCount].Depth = Node.Depth + 1;
Stack[StackCount].TypeId = TypeId;
StackCount++;
}
else
{
DWORD DataEntryRva = 0;
if (ChildOffset > ResourceDirectory->Size || ResourceDirectory->Size - ChildOffset < 16 ||
!PeAddDword(ResourceDirectory->VirtualAddress, ChildOffset, &DataEntryRva))
{
InvalidEntryCount++;
continue;
}
const BYTE * DataEntry = NULL;
if (!PeGetPointerAtRva(Reader, DataEntryRva, 16, &DataEntry))
{
InvalidEntryCount++;
continue;
}
DWORD DataRva = PeReadDwordFromBuffer(DataEntry, 0);
DWORD DataSize = PeReadDwordFromBuffer(DataEntry, 4);
BOOLEAN PayloadMapped = TRUE;
if (DataSize != 0)
{
const BYTE * Payload = NULL;
if (!PeGetPointerAtRva(Reader, DataRva, DataSize, &Payload))
{
InvalidEntryCount++;
PayloadMapped = FALSE;
}
}
DeclaredDataBytes += DataSize;
DeclaredDataEntryCount++;
if (PayloadMapped)
{
MappedDataBytes += DataSize;
MappedDataEntryCount++;
}
}
}
if (EntryCapReached)
{
break;
}
}
ShowMessages("\n%-36s%u", "Resource directory count :", DirectoryCount);
ShowMessages("\n%-36s%u", "Resource entry count :", EntryCount);
ShowMessages("\n%-36s%u", "Resource declared data entries :", DeclaredDataEntryCount);
ShowMessages("\n%-36s%u", "Resource mapped data entries :", MappedDataEntryCount);
ShowMessages("\n%-36s%u", "Resource invalid entry count :", InvalidEntryCount);
ShowMessages("\n%-36s%u", "Resource named root types :", NamedRootTypeCount);
ShowMessages("\n%-36s%#llx", "Resource declared data bytes :", DeclaredDataBytes);
ShowMessages("\n%-36s%#llx", "Resource mapped data bytes :", MappedDataBytes);
for (DWORD TypeId = 0; TypeId < RTL_NUMBER_OF(TypeCounts); TypeId++)
{
if (TypeCounts[TypeId] != 0)
{
PeShowResourceTypeCount(TypeId, TypeCounts[TypeId]);
}
}
if (NodeCapReached)
{
ShowMessages("\n%-36s%#x", "Warning, resource node cap reached :", MaxNodes);
}
if (EntryCapReached)
{
ShowMessages("\n%-36s%#x", "Warning, resource entry cap reached :", MaxEntries);
}
delete[] Stack;
delete[] Visited;
}
/**
* @brief Prints RUNTIME_FUNCTION entries from the x64 exception directory
*
* Decodes IMAGE_RUNTIME_FUNCTION_ENTRY records for AMD64 images, printing
* the begin address, end address, unwind RVA, and key unwind info fields
* for the first 0x20 entries. For non-AMD64 machines the directory size
* is reported without per-entry decoding. Processing is capped at 0x100000
* entries.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ExceptionDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_EXCEPTION data directory entry
* @param Machine Machine type from the PE file header (IMAGE_FILE_MACHINE_*)
* @param Is32Bit TRUE if the PE image is 32-bit
*/
static VOID
PeShowExceptions(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * ExceptionDirectory, WORD Machine, BOOLEAN Is32Bit)
{
const DWORD RuntimeFunctionSize = 12;
const DWORD MaxEntries = 0x100000;
const DWORD MaxPrintedEntries = 0x20;
const DWORD MaxWarnings = 8;
ShowMessages("\n\nExceptions\n----------");
if (ExceptionDirectory->VirtualAddress == 0 || ExceptionDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Exception directory :", "empty");
return;
}
if (Machine != IMAGE_FILE_MACHINE_AMD64 || Is32Bit)
{
ShowMessages("\n%-36s%s", "Exception directory :", "present, x64 runtime-function decoding skipped for this machine");
ShowMessages("\n%-36s%#x", "Raw exception directory size :", ExceptionDirectory->Size);
return;
}
if (ExceptionDirectory->Size < RuntimeFunctionSize)
{
ShowMessages("\n%-36s%s", "Warning :", "exception directory is smaller than one runtime function entry");
return;
}
if ((ExceptionDirectory->Size % RuntimeFunctionSize) != 0)
{
ShowMessages("\n%-36s%#x", "Warning, malformed exception size :", ExceptionDirectory->Size);
}
DWORD EntryCount = ExceptionDirectory->Size / RuntimeFunctionSize;
BOOLEAN EntryCapped = FALSE;
DWORD RangeWarnings = 0;
DWORD UnwindWarnings = 0;
DWORD SkippedNullEntries = 0;
DWORD PrintedEntries = 0;
if (EntryCount > MaxEntries)
{
EntryCount = MaxEntries;
EntryCapped = TRUE;
}
ShowMessages("\n%-36s%u", "Runtime function count :", EntryCount);
for (DWORD EntryIndex = 0; EntryIndex < EntryCount; EntryIndex++)
{
DWORD EntryRva = 0;
if (!PeAddDword(ExceptionDirectory->VirtualAddress, EntryIndex * RuntimeFunctionSize, &EntryRva))
{
ShowMessages("\n%-36s%s", "Warning :", "runtime function entry RVA overflow");
break;
}
const BYTE * Entry = NULL;
if (!PeGetPointerAtRva(Reader, EntryRva, RuntimeFunctionSize, &Entry))
{
ShowMessages("\n%-36s%#x", "Warning, invalid runtime entry RVA :", EntryRva);
break;
}
DWORD BeginAddress = PeReadDwordFromBuffer(Entry, 0);
DWORD EndAddress = PeReadDwordFromBuffer(Entry, 4);
DWORD UnwindRva = PeReadDwordFromBuffer(Entry, 8);
if (BeginAddress == 0 && EndAddress == 0 && UnwindRva == 0)
{
SkippedNullEntries++;
continue;
}
if (BeginAddress >= EndAddress)
{
if (RangeWarnings < MaxWarnings && PrintedEntries < MaxPrintedEntries)
{
ShowMessages("\n%-36s%u", "Warning, invalid runtime range index :", EntryIndex);
}
RangeWarnings++;
}
const BYTE * Unwind = NULL;
BOOLEAN UnwindMapped = UnwindRva != 0 && PeGetPointerAtRva(Reader, UnwindRva, 4, &Unwind);
if (!UnwindMapped)
{
if (UnwindWarnings < MaxWarnings && PrintedEntries < MaxPrintedEntries)
{
ShowMessages("\n%-36s%#x", "Warning, invalid unwind RVA :", UnwindRva);
}
UnwindWarnings++;
}
if (PrintedEntries < MaxPrintedEntries)
{
if (UnwindMapped)
{
BYTE VersionAndFlags = PeReadByteFromBuffer(Unwind, 0);
BYTE Frame = PeReadByteFromBuffer(Unwind, 3);
ShowMessages("\n[%u] begin %#x end %#x unwind %#x version %u flags %#x prolog %#x codes %u frame reg %u frame off %u",
EntryIndex,
BeginAddress,
EndAddress,
UnwindRva,
VersionAndFlags & 0x7,
VersionAndFlags >> 3,
PeReadByteFromBuffer(Unwind, 1),
PeReadByteFromBuffer(Unwind, 2),
Frame & 0xf,
Frame >> 4);
}
else
{
ShowMessages("\n[%u] begin %#x end %#x unwind %#x", EntryIndex, BeginAddress, EndAddress, UnwindRva);
}
PrintedEntries++;
}
}
if (EntryCapped)
{
ShowMessages("\n%-36s%#x", "Warning, exception entry cap reached :", MaxEntries);
}
if (EntryCount > MaxPrintedEntries)
{
ShowMessages("\n%-36s%u", "Info, printed exception entries :", PrintedEntries);
}
if (SkippedNullEntries != 0)
{
ShowMessages("\n%-36s%u", "Info, skipped null runtime entries :", SkippedNullEntries);
}
if (RangeWarnings > MaxWarnings)
{
ShowMessages("\n%-36s%#x", "Warning, invalid range warnings shown :", MaxWarnings);
}
if (UnwindWarnings > MaxWarnings)
{
ShowMessages("\n%-36s%#x", "Warning, invalid unwind warnings shown :", MaxWarnings);
}
}
/**
* @brief Resolves a delay-import address field to a relative virtual address
*
* Handles both the legacy VA-based format (UsesRva == FALSE) and the newer
* RVA-based format (UsesRva == TRUE). For VA-based descriptors the address is
* converted via PeVaToRva using the supplied image base.
*
* @param Address Raw address value read from the delay-import descriptor
* @param UsesRva TRUE if the descriptor uses RVAs, FALSE if it uses VAs
* @param ImageBase Image base used for VA-to-RVA conversion when UsesRva is FALSE
* @param Rva Output pointer that receives the resolved RVA on success; must not be NULL
*
* @return BOOLEAN TRUE on success, FALSE when the address cannot be converted to an RVA
*/
static BOOLEAN
PeResolveDelayImportAddress(ULONGLONG Address, BOOLEAN UsesRva, ULONGLONG ImageBase, DWORD * Rva)
{
if (Rva == NULL)
{
return FALSE;
}
if (UsesRva)
{
if (Address > MAXDWORD)
{
return FALSE;
}
*Rva = (DWORD)Address;
return TRUE;
}
return PeVaToRva(Address, ImageBase, Rva);
}
/**
* @brief Prints delay-import descriptors and their imported function names or ordinals
*
* Iterates ImgDelayDescr records, resolving DLL names and import thunk arrays.
* Supports both legacy VA-based and RVA-based descriptor formats. Processing
* is capped at 0x1000 descriptors and 0x2000 total imported symbols.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param DelayImportDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT data directory entry
* @param Is32Bit TRUE if the PE image is 32-bit
* @param ImageBase Preferred image base used for VA-to-RVA conversion
*/
static VOID
PeShowDelayImports(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * DelayImportDirectory, BOOLEAN Is32Bit, ULONGLONG ImageBase)
{
const DWORD MaxDescriptors = 0x1000;
const DWORD MaxTotalImports = 0x2000;
const DWORD MaxDllNameLength = 0x200;
const DWORD MaxImportNameLength = 0x200;
const DWORD DescriptorSize = 32;
ShowMessages("\n\nDelay imports\n-------------");
if (DelayImportDirectory->VirtualAddress == 0 || DelayImportDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Delay import directory :", "empty");
return;
}
if (DelayImportDirectory->Size < DescriptorSize)
{
ShowMessages("\n%-36s%s", "Warning :", "delay import directory is smaller than one descriptor");
return;
}
DWORD DescriptorCount = DelayImportDirectory->Size / DescriptorSize;
if ((DelayImportDirectory->Size % DescriptorSize) != 0)
{
ShowMessages("\n%-36s%#x", "Warning, malformed delay import size :", DelayImportDirectory->Size);
}
BOOLEAN DescriptorCapped = FALSE;
if (DescriptorCount > MaxDescriptors)
{
DescriptorCount = MaxDescriptors;
DescriptorCapped = TRUE;
}
DWORD TotalImports = 0;
BOOLEAN FoundTerminator = FALSE;
BOOLEAN TotalImportsCapped = FALSE;
for (DWORD DescriptorIndex = 0; DescriptorIndex < DescriptorCount; DescriptorIndex++)
{
DWORD DescriptorRva = 0;
if (!PeAddDword(DelayImportDirectory->VirtualAddress, DescriptorIndex * DescriptorSize, &DescriptorRva))
{
ShowMessages("\n%-36s%s", "Warning :", "delay import descriptor RVA overflow");
break;
}
const BYTE * Descriptor = NULL;
if (!PeGetPointerAtRva(Reader, DescriptorRva, DescriptorSize, &Descriptor))
{
ShowMessages("\n%-36s%#x", "Warning, invalid delay descriptor RVA :", DescriptorRva);
break;
}
DWORD Attributes = PeReadDwordFromBuffer(Descriptor, 0);
DWORD NameValue = PeReadDwordFromBuffer(Descriptor, 4);
DWORD IatValue = PeReadDwordFromBuffer(Descriptor, 12);
DWORD IntValue = PeReadDwordFromBuffer(Descriptor, 16);
DWORD BoundIatValue = PeReadDwordFromBuffer(Descriptor, 20);
DWORD UnloadIatValue = PeReadDwordFromBuffer(Descriptor, 24);
DWORD TimeDateStamp = PeReadDwordFromBuffer(Descriptor, 28);
if (Attributes == 0 && NameValue == 0 && PeReadDwordFromBuffer(Descriptor, 8) == 0 && IatValue == 0 && IntValue == 0 &&
BoundIatValue == 0 && UnloadIatValue == 0 && TimeDateStamp == 0)
{
FoundTerminator = TRUE;
break;
}
BOOLEAN UsesRva = (Attributes & 1) != 0;
DWORD NameRva = 0;
DWORD LookupThunkRva = 0;
DWORD IatThunkRva = 0;
if (!UsesRva)
{
ShowMessages("\n%-36s%s", "Info :", "delay import descriptor uses VA fields");
}
if (!PeResolveDelayImportAddress(NameValue, UsesRva, ImageBase, &NameRva))
{
ShowMessages("\n[%u] DLL name %s", DescriptorIndex, "invalid VA/RVA");
ShowMessages("\n%-36s%s", "Warning :", "skipping delay imports for descriptor with unreadable DLL name");
continue;
}
CHAR DllName[MaxDllNameLength + 1] = {0};
PE_ASCII_STRING_STATUS DllNameStatus = PeReadAsciiStringAtRva(Reader,
NameRva,
MaxDllNameLength,
DllName,
sizeof(DllName));
if (DllNameStatus != PeAsciiStringOk)
{
ShowMessages("\n[%u] DLL name %s", DescriptorIndex, PeAsciiStatusName(DllNameStatus));
ShowMessages("\n%-36s%s", "Warning :", "skipping delay imports for descriptor with unreadable DLL name");
continue;
}
ShowMessages("\n[%u] DLL name %s attributes %#x timestamp %#x", DescriptorIndex, DllName, Attributes, TimeDateStamp);
if (!PeResolveDelayImportAddress(IntValue != 0 ? IntValue : IatValue, UsesRva, ImageBase, &LookupThunkRva) ||
!PeResolveDelayImportAddress(IatValue, UsesRva, ImageBase, &IatThunkRva))
{
ShowMessages("\n%-36s%s", "Warning :", "delay import thunk VA/RVA is invalid");
continue;
}
DWORD ThunkSize = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
for (DWORD ThunkIndex = 0; TotalImports < MaxTotalImports; ThunkIndex++)
{
DWORD LookupEntryRva = 0;
DWORD IatEntryRva = 0;
if (!PeAddDword(LookupThunkRva, ThunkIndex * ThunkSize, &LookupEntryRva) ||
!PeAddDword(IatThunkRva, ThunkIndex * ThunkSize, &IatEntryRva))
{
ShowMessages("\n%-36s%s", "Warning :", "delay import thunk RVA overflow");
break;
}
ULONGLONG ThunkValue = 0;
if (!PeReadThunkAtRva(Reader, LookupEntryRva, Is32Bit, &ThunkValue))
{
ShowMessages("\n%-36s%#x", "Warning, invalid delay thunk RVA :", LookupEntryRva);
break;
}
if (ThunkValue == 0)
{
break;
}
ULONGLONG OrdinalFlag = Is32Bit ? IMAGE_ORDINAL_FLAG32 : IMAGE_ORDINAL_FLAG64;
if ((ThunkValue & OrdinalFlag) != 0)
{
ShowMessages("\n [%u] ordinal %u thunk RVA %#x", ThunkIndex, (UINT32)(ThunkValue & 0xffff), IatEntryRva);
TotalImports++;
continue;
}
DWORD NameRvaFromThunk = 0;
if (!PeResolveDelayImportAddress(ThunkValue, UsesRva, ImageBase, &NameRvaFromThunk))
{
ShowMessages("\n%-36s%#llx", "Warning, invalid delay import name VA/RVA :", (UINT64)ThunkValue);
TotalImports++;
continue;
}
WORD Hint = 0;
if (!PeReadWordAtRva(Reader, NameRvaFromThunk, &Hint))
{
ShowMessages("\n%-36s%#x", "Warning, invalid delay import hint RVA :", NameRvaFromThunk);
TotalImports++;
continue;
}
DWORD ImportNameRva = 0;
if (!PeAddDword(NameRvaFromThunk, sizeof(WORD), &ImportNameRva))
{
ShowMessages("\n%-36s%#x", "Warning, invalid delay import name RVA :", NameRvaFromThunk);
TotalImports++;
continue;
}
CHAR ImportName[MaxImportNameLength + 1] = {0};
PE_ASCII_STRING_STATUS ImportNameStatus = PeReadAsciiStringAtRva(Reader,
ImportNameRva,
MaxImportNameLength,
ImportName,
sizeof(ImportName));
if (ImportNameStatus != PeAsciiStringOk)
{
ShowMessages("\n%-36s%#x, %s", "Warning, invalid delay import name RVA :", ImportNameRva, PeAsciiStatusName(ImportNameStatus));
TotalImports++;
continue;
}
ShowMessages("\n [%u] hint %#x name %s thunk RVA %#x", ThunkIndex, Hint, ImportName, IatEntryRva);
TotalImports++;
}
if (TotalImports >= MaxTotalImports)
{
TotalImportsCapped = TRUE;
break;
}
}
if (!FoundTerminator)
{
ShowMessages("\n%-36s%s", "Warning :", "delay import descriptor terminator not found before bounds or cap");
}
if (DescriptorCapped)
{
ShowMessages("\n%-36s%#x", "Warning, delay descriptor cap reached :", MaxDescriptors);
}
if (TotalImportsCapped)
{
ShowMessages("\n%-36s%#x", "Warning, delay import cap reached :", MaxTotalImports);
}
}
/**
* @brief Prints the RVA and size of an IMAGE_DATA_DIRECTORY embedded within the CLR runtime header
*
* Reads two consecutive DWORDs from the CLR header buffer at Offset to obtain
* a sub-directory RVA and size. Also reports whether the range is mapped
* within the image.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Header Pointer to the CLR runtime header byte buffer
* @param AvailableSize Number of validated bytes available in Header
* @param Offset Byte offset within Header of the IMAGE_DATA_DIRECTORY to read
* @param Label Column label to print before the values
*/
static VOID
PeShowClrDirectoryBounds(PPE_IMAGE_READER Reader, const BYTE * Header, DWORD AvailableSize, SIZE_T Offset, const CHAR * Label)
{
if (Offset > AvailableSize || sizeof(IMAGE_DATA_DIRECTORY) > AvailableSize - Offset)
{
return;
}
DWORD Rva = PeReadDwordFromBuffer(Header, Offset);
DWORD Size = PeReadDwordFromBuffer(Header, Offset + sizeof(DWORD));
ShowMessages("\n%-36sRVA %#x size %#x", Label, Rva, Size);
if (Rva != 0 && Size != 0)
{
const BYTE * Pointer = NULL;
ShowMessages(", %s", PeGetPointerAtRva(Reader, Rva, Size, &Pointer) ? "mapped" : "not mapped");
}
}
/**
* @brief Prints CLR (.NET) runtime header information
*
* Reads the IMAGE_COR20_HEADER from the CLR data directory, printing the
* runtime version, flags, entry point token or RVA, and the bounds of each
* embedded sub-directory (metadata, resources, strong name, etc.).
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ClrDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR data directory entry
*/
static VOID
PeShowClrRuntime(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * ClrDirectory)
{
ShowMessages("\n\nCLR runtime\n-----------");
if (ClrDirectory->VirtualAddress == 0 || ClrDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "CLR runtime header :", "empty");
return;
}
if (ClrDirectory->Size < 0x48)
{
ShowMessages("\n%-36s%s", "Warning :", "CLR runtime header is smaller than required fields");
return;
}
const BYTE * Header = NULL;
if (!PeGetPointerAtRva(Reader, ClrDirectory->VirtualAddress, 0x48, &Header))
{
ShowMessages("\n%-36s%s", "CLR runtime header :", "not mapped");
return;
}
DWORD HeaderSize = PeReadDwordFromBuffer(Header, 0);
DWORD AvailableSize = ClrDirectory->Size < HeaderSize ? ClrDirectory->Size : HeaderSize;
ShowMessages("\n%-36s%#x", "Header size :", HeaderSize);
if (HeaderSize < 0x48 || AvailableSize < 0x48)
{
ShowMessages("\n%-36s%s", "Warning :", "CLR runtime header is truncated");
return;
}
if (!PeGetPointerAtRva(Reader, ClrDirectory->VirtualAddress, AvailableSize, &Header))
{
ShowMessages("\n%-36s%s", "CLR runtime header :", "invalid bounds");
return;
}
WORD MajorRuntimeVersion = PeReadWordFromBuffer(Header, 4);
WORD MinorRuntimeVersion = PeReadWordFromBuffer(Header, 6);
DWORD Flags = PeReadDwordFromBuffer(Header, 16);
DWORD EntryPoint = PeReadDwordFromBuffer(Header, 20);
ShowMessages("\n%-36s%u.%u", "Runtime version :", MajorRuntimeVersion, MinorRuntimeVersion);
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 8, "Metadata directory :");
ShowMessages("\n%-36s%#x", "Flags :", Flags);
ShowMessages("\n%-36s%#x (%s)", "Entry point token/RVA :", EntryPoint, (Flags & 0x10) != 0 ? "native RVA" : "managed token");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 24, "Resources directory :");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 32, "Strong name directory :");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 40, "Code manager table :");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 48, "VTable fixups :");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 56, "Export address table jumps :");
PeShowClrDirectoryBounds(Reader, Header, AvailableSize, 64, "Managed native header :");
}
/**
* @brief Prints Thread Local Storage (TLS) directory fields and TLS callback addresses
*
* Reads IMAGE_TLS_DIRECTORY32 or IMAGE_TLS_DIRECTORY64 depending on Is32Bit and
* prints all fields. Then walks the null-terminated callback VA array, converting
* each entry to an RVA and file offset. Processing stops at 0x200 callbacks.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param TlsDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_TLS data directory entry
* @param Is32Bit TRUE if the PE image is 32-bit
* @param ImageBase Preferred image base used for VA-to-RVA conversion
*/
static VOID
PeShowTls(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * TlsDirectory, BOOLEAN Is32Bit, ULONGLONG ImageBase)
{
const DWORD MaxCallbacks = 0x200;
DWORD TlsSize = Is32Bit ? sizeof(IMAGE_TLS_DIRECTORY32) : sizeof(IMAGE_TLS_DIRECTORY64);
ShowMessages("\n\nTLS\n---");
if (TlsDirectory->VirtualAddress == 0 || TlsDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "TLS directory :", "empty");
return;
}
if (!PeRvaContainsRange(TlsDirectory->VirtualAddress, TlsDirectory->Size, TlsDirectory->VirtualAddress, TlsSize))
{
ShowMessages("\n%-36s%s", "TLS directory :", "invalid bounds");
return;
}
const BYTE * TlsPointer = NULL;
if (!PeGetPointerAtRva(Reader, TlsDirectory->VirtualAddress, TlsSize, &TlsPointer))
{
ShowMessages("\n%-36s%s", "TLS directory :", "not mapped");
return;
}
ULONGLONG StartRawDataVa = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 0) : PeReadQwordFromBuffer(TlsPointer, 0);
ULONGLONG EndRawDataVa = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 4) : PeReadQwordFromBuffer(TlsPointer, 8);
ULONGLONG AddressOfIndexVa = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 8) : PeReadQwordFromBuffer(TlsPointer, 16);
ULONGLONG AddressCallbacksVa = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 12) : PeReadQwordFromBuffer(TlsPointer, 24);
DWORD SizeOfZeroFill = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 16) : PeReadDwordFromBuffer(TlsPointer, 32);
DWORD Characteristics = Is32Bit ? PeReadDwordFromBuffer(TlsPointer, 20) : PeReadDwordFromBuffer(TlsPointer, 36);
ShowMessages("\n%-36s%#llx", "Start address of raw data VA :", StartRawDataVa);
ShowMessages("\n%-36s%#llx", "End address of raw data VA :", EndRawDataVa);
ShowMessages("\n%-36s%#llx", "Address of index VA :", AddressOfIndexVa);
ShowMessages("\n%-36s%#llx", "Address of callbacks VA :", AddressCallbacksVa);
ShowMessages("\n%-36s%#x", "Size of zero fill :", SizeOfZeroFill);
ShowMessages("\n%-36s%#x", "Characteristics :", Characteristics);
DWORD CallbacksRva = 0;
if (AddressCallbacksVa == 0)
{
ShowMessages("\n%-36s%s", "TLS callbacks :", "empty");
return;
}
if (!PeVaToRva(AddressCallbacksVa, ImageBase, &CallbacksRva))
{
ShowMessages("\n%-36s%#llx", "Warning, invalid callbacks VA :", AddressCallbacksVa);
return;
}
DWORD EntrySize = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
for (DWORD CallbackIndex = 0; CallbackIndex < MaxCallbacks; CallbackIndex++)
{
DWORD EntryRva = 0;
if (!PeAddDword(CallbacksRva, CallbackIndex * EntrySize, &EntryRva))
{
ShowMessages("\n%-36s%s", "Warning :", "TLS callback RVA overflow");
return;
}
ULONGLONG CallbackVa = 0;
if (!PeReadThunkAtRva(Reader, EntryRva, Is32Bit, &CallbackVa))
{
ShowMessages("\n%-36s%#x", "Warning, invalid callback entry RVA :", EntryRva);
return;
}
if (CallbackVa == 0)
{
return;
}
DWORD CallbackRva = 0;
SIZE_T FileOffset = 0;
if (PeVaToRva(CallbackVa, ImageBase, &CallbackRva))
{
if (PeImageReaderRvaToFileOffset(Reader, CallbackRva, 1, &FileOffset))
{
ShowMessages("\n [%u] VA %#llx RVA %#x file offset %#llx", CallbackIndex, CallbackVa, CallbackRva, (UINT64)FileOffset);
}
else
{
ShowMessages("\n [%u] VA %#llx RVA %#x file offset %s", CallbackIndex, CallbackVa, CallbackRva, "not mapped");
}
}
else
{
ShowMessages("\n [%u] VA %#llx RVA %s", CallbackIndex, CallbackVa, "invalid");
}
}
ShowMessages("\n%-36s%#x", "Warning, TLS callback cap reached :", MaxCallbacks);
}
/**
* @brief Prints CodeView debug information embedded in a debug directory entry
*
* Handles both the RSDS (PDB 7.0) and NB10 (PDB 2.0) CodeView formats.
* For RSDS entries, prints the GUID, age, and PDB path. For NB10 entries,
* prints the offset, signature, age, and PDB path. Silently returns for
* non-CodeView entries or entries with insufficient data.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Entry Pointer to the IMAGE_DEBUG_DIRECTORY entry to decode
*/
static VOID
PeShowDebugCodeView(PPE_IMAGE_READER Reader, const IMAGE_DEBUG_DIRECTORY * Entry)
{
const DWORD MaxPdbPathLength = 0x400;
if (Entry->Type != IMAGE_DEBUG_TYPE_CODEVIEW || Entry->SizeOfData < sizeof(DWORD))
{
return;
}
const BYTE * Payload = NULL;
if ((Entry->PointerToRawData == 0 || !PeImageReaderGetPointerAtOffset(Reader, Entry->PointerToRawData, Entry->SizeOfData, &Payload)) &&
(Entry->AddressOfRawData == 0 || !PeGetPointerAtRva(Reader, Entry->AddressOfRawData, Entry->SizeOfData, &Payload)))
{
ShowMessages("\n%-36s%s", " CodeView payload :", "invalid bounds");
return;
}
if (memcmp(Payload, "RSDS", sizeof(DWORD)) == 0)
{
if (Entry->SizeOfData < 24)
{
ShowMessages("\n%-36s%s", " CodeView RSDS :", "truncated");
return;
}
DWORD Age = PeReadDwordFromBuffer(Payload, 20);
DWORD PathOffset = 24;
DWORD PathSize = Entry->SizeOfData - 24;
DWORD PathLimit = PathSize < MaxPdbPathLength ? PathSize : MaxPdbPathLength;
CHAR PdbPath[MaxPdbPathLength + 1] = {0};
const BYTE * Guid = Payload + 4;
PE_ASCII_STRING_STATUS PathStatus = PeReadAsciiStringFromBuffer(Payload + PathOffset, PathLimit, PdbPath, sizeof(PdbPath));
ShowMessages("\n%-36s%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
" CodeView RSDS GUID :",
Guid[3],
Guid[2],
Guid[1],
Guid[0],
Guid[5],
Guid[4],
Guid[7],
Guid[6],
Guid[8],
Guid[9],
Guid[10],
Guid[11],
Guid[12],
Guid[13],
Guid[14],
Guid[15]);
ShowMessages("\n%-36s%u", " CodeView age :", Age);
ShowMessages("\n%-36s%s", " CodeView PDB path :", PathStatus == PeAsciiStringOk ? PdbPath : PeAsciiStatusName(PathStatus));
}
else if (memcmp(Payload, "NB10", sizeof(DWORD)) == 0)
{
if (Entry->SizeOfData < 16)
{
ShowMessages("\n%-36s%s", " CodeView NB10 :", "truncated");
return;
}
DWORD Offset = PeReadDwordFromBuffer(Payload, 4);
DWORD Timestamp = PeReadDwordFromBuffer(Payload, 8);
DWORD Age = PeReadDwordFromBuffer(Payload, 12);
DWORD PathOffset = 16;
DWORD PathSize = Entry->SizeOfData - 16;
DWORD PathLimit = PathSize < MaxPdbPathLength ? PathSize : MaxPdbPathLength;
CHAR PdbPath[MaxPdbPathLength + 1] = {0};
PE_ASCII_STRING_STATUS PathStatus = PeReadAsciiStringFromBuffer(Payload + PathOffset, PathLimit, PdbPath, sizeof(PdbPath));
ShowMessages("\n%-36s%#x", " CodeView NB10 offset :", Offset);
ShowMessages("\n%-36s%#x", " CodeView NB10 signature :", Timestamp);
ShowMessages("\n%-36s%u", " CodeView age :", Age);
ShowMessages("\n%-36s%s", " CodeView PDB path :", PathStatus == PeAsciiStringOk ? PdbPath : PeAsciiStatusName(PathStatus));
}
}
/**
* @brief Prints all IMAGE_DEBUG_DIRECTORY entries in the debug data directory
*
* Iterates the debug directory, printing characteristics, timestamp, version,
* type, size, and address fields for each entry. Also validates the raw data
* bounds and delegates to PeShowDebugCodeView for CodeView entries.
* Processing is capped at 0x1000 entries.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param DebugDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_DEBUG data directory entry
*/
static VOID
PeShowDebug(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * DebugDirectory)
{
const DWORD MaxDebugEntries = 0x1000;
ShowMessages("\n\nDebug\n-----");
if (DebugDirectory->VirtualAddress == 0 || DebugDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Debug directory :", "empty");
return;
}
DWORD EntryCount = DebugDirectory->Size / sizeof(IMAGE_DEBUG_DIRECTORY);
if (EntryCount == 0)
{
ShowMessages("\n%-36s%s", "Warning :", "debug directory is smaller than one descriptor");
return;
}
if ((DebugDirectory->Size % sizeof(IMAGE_DEBUG_DIRECTORY)) != 0)
{
ShowMessages("\n%-36s%#x", "Warning, malformed debug size :", DebugDirectory->Size);
}
BOOLEAN EntryCapped = FALSE;
if (EntryCount > MaxDebugEntries)
{
EntryCount = MaxDebugEntries;
EntryCapped = TRUE;
}
for (DWORD EntryIndex = 0; EntryIndex < EntryCount; EntryIndex++)
{
DWORD EntryRva = 0;
if (!PeAddDword(DebugDirectory->VirtualAddress, EntryIndex * (DWORD)sizeof(IMAGE_DEBUG_DIRECTORY), &EntryRva))
{
ShowMessages("\n%-36s%s", "Warning :", "debug directory RVA overflow");
break;
}
const IMAGE_DEBUG_DIRECTORY * Entry = NULL;
if (!PeGetPointerAtRva(Reader, EntryRva, sizeof(IMAGE_DEBUG_DIRECTORY), (const BYTE **)&Entry))
{
ShowMessages("\n%-36s%s", "Warning :", "debug directory entry is not mapped");
break;
}
ShowMessages("\n[%u] characteristics %#x time date stamp %#x major %u minor %u type %u size %#x address %#x raw %#x",
EntryIndex,
Entry->Characteristics,
Entry->TimeDateStamp,
Entry->MajorVersion,
Entry->MinorVersion,
Entry->Type,
Entry->SizeOfData,
Entry->AddressOfRawData,
Entry->PointerToRawData);
if (Entry->SizeOfData != 0 && Entry->PointerToRawData != 0)
{
const BYTE * Payload = NULL;
ShowMessages("\n%-36s%s", " Payload bounds :", PeImageReaderGetPointerAtOffset(Reader, Entry->PointerToRawData, Entry->SizeOfData, &Payload) ? "valid" : "invalid");
}
PeShowDebugCodeView(Reader, Entry);
}
if (EntryCapped)
{
ShowMessages("\n%-36s%#x", "Warning, debug entry cap reached :", MaxDebugEntries);
}
}
/**
* @brief Checks whether a load configuration structure contains a specific field
*
* Returns TRUE when the range [Offset, Offset + FieldSize) lies entirely within
* the first AvailableSize bytes of the load config buffer.
*
* @param AvailableSize Number of bytes available in the load config buffer
* @param Offset Byte offset of the field within the load config structure
* @param FieldSize Size of the field in bytes
*
* @return BOOLEAN TRUE if the field is present, FALSE if it is beyond AvailableSize
*/
static BOOLEAN
PeLoadConfigHasField(DWORD AvailableSize, SIZE_T Offset, SIZE_T FieldSize)
{
return Offset <= AvailableSize && FieldSize <= AvailableSize - Offset;
}
/**
* @brief Prints a DWORD field from the load config structure if it is present
*
* Uses PeLoadConfigHasField to verify the field is within AvailableSize before
* reading and printing the value with the supplied label.
*
* @param Config Pointer to the load configuration byte buffer
* @param AvailableSize Number of validated bytes in Config
* @param Offset Byte offset of the DWORD field within Config
* @param Label Column label to print before the value
*/
static VOID
PeShowLoadConfigDword(const BYTE * Config, DWORD AvailableSize, SIZE_T Offset, const CHAR * Label)
{
if (PeLoadConfigHasField(AvailableSize, Offset, sizeof(DWORD)))
{
ShowMessages("\n%-36s%#x", Label, PeReadDwordFromBuffer(Config, Offset));
}
}
/**
* @brief Prints a pointer-sized field from the load config structure if it is present
*
* Reads sizeof(DWORD) or sizeof(ULONGLONG) depending on Is32Bit, verifies
* presence with PeLoadConfigHasField, and prints the value as a hex address.
*
* @param Config Pointer to the load configuration byte buffer
* @param AvailableSize Number of validated bytes in Config
* @param Offset Byte offset of the pointer field within Config
* @param Label Column label to print before the value
* @param Is32Bit TRUE to read a 32-bit pointer, FALSE for a 64-bit pointer
*/
static VOID
PeShowLoadConfigPointer(const BYTE * Config, DWORD AvailableSize, SIZE_T Offset, const CHAR * Label, BOOLEAN Is32Bit)
{
SIZE_T FieldSize = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
if (PeLoadConfigHasField(AvailableSize, Offset, FieldSize))
{
ShowMessages("\n%-36s%#llx", Label, PeReadLoadConfigPointer(Config, Offset, Is32Bit));
}
}
/**
* @brief Prints a count field (pointer-sized) from the load config structure if present
*
* Behaves identically to PeShowLoadConfigPointer but formats the value as a
* decimal count rather than a hex address.
*
* @param Config Pointer to the load configuration byte buffer
* @param AvailableSize Number of validated bytes in Config
* @param Offset Byte offset of the count field within Config
* @param Label Column label to print before the value
* @param Is32Bit TRUE to read a 32-bit count, FALSE for a 64-bit count
*/
static VOID
PeShowLoadConfigCount(const BYTE * Config, DWORD AvailableSize, SIZE_T Offset, const CHAR * Label, BOOLEAN Is32Bit)
{
SIZE_T FieldSize = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
if (PeLoadConfigHasField(AvailableSize, Offset, FieldSize))
{
ShowMessages("\n%-36s%llu", Label, PeReadLoadConfigPointer(Config, Offset, Is32Bit));
}
}
/**
* @brief Prints the names of set guard control-flow flags from a load config guard flags field
*
* Iterates a table of known guard flag bitmasks and prints the name of each
* set bit. Reports "None" when no known flags are set, and appends the
* unknown bitmask when unrecognised bits are present.
*
* @param GuardFlags The GuardFlags field value from the load configuration structure
*/
static VOID
PeShowLoadConfigGuardFlags(DWORD GuardFlags)
{
BOOLEAN AnyFlag = FALSE;
DWORD KnownMask = 0;
struct GUARD_FLAG_NAME
{
DWORD Flag;
const CHAR * Name;
};
static const GUARD_FLAG_NAME GuardFlagNames[] = {
{0x00000100, "CF instrumented"},
{0x00000200, "CFW instrumented"},
{0x00000400, "CF function table present"},
{0x00000800, "Security cookie unused"},
{0x00001000, "Protect delayed load IAT"},
{0x00002000, "Delayed load IAT in its own section"},
{0x00004000, "Export suppression info present"},
{0x00008000, "Enable export suppression"},
{0x00010000, "Long jump table present"},
{0x00020000, "RF instrumented"},
{0x00040000, "RF enable"},
{0x00080000, "RF strict"},
{0x00100000, "Retpoline present"},
{0x00200000, "EH continuation table present"},
{0x00400000, "XFG enabled"},
{0x00800000, "CastGuard present"},
{0x01000000, "Memcpy function present"},
};
for (UINT32 Index = 0; Index < RTL_NUMBER_OF(GuardFlagNames); Index++)
{
KnownMask |= GuardFlagNames[Index].Flag;
if ((GuardFlags & GuardFlagNames[Index].Flag) == GuardFlagNames[Index].Flag)
{
ShowMessages("%s%s", AnyFlag ? ", " : "", GuardFlagNames[Index].Name);
AnyFlag = TRUE;
}
}
if (!AnyFlag)
{
ShowMessages(GuardFlags == 0 ? "None" : "No known guard flags");
}
DWORD UnknownMask = GuardFlags & ~KnownMask;
if (UnknownMask != 0)
{
ShowMessages(", unknown mask %#x", UnknownMask);
}
}
/**
* @brief Prints extended load config fields introduced in Windows 8.1 and later
*
* Handles guard address-taken IAT, guard long-jump tables, dynamic value
* relocation, CHPE metadata, RF (return-flow) guards, EH continuation tables,
* XFG, CastGuard, and memcpy pointer fields. Each field is printed only when
* AvailableSize covers its offset.
*
* @param Config Pointer to the load configuration byte buffer
* @param AvailableSize Number of validated bytes in Config
* @param Is32Bit TRUE for PE32 field offsets, FALSE for PE32+ field offsets
*/
static VOID
PeShowLoadConfigModernFields(const BYTE * Config, DWORD AvailableSize, BOOLEAN Is32Bit)
{
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 104 : 160, "Guard address-taken IAT table :", Is32Bit);
PeShowLoadConfigCount(Config, AvailableSize, Is32Bit ? 108 : 168, "Guard address-taken IAT count :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 112 : 176, "Guard long jump target table :", Is32Bit);
PeShowLoadConfigCount(Config, AvailableSize, Is32Bit ? 116 : 184, "Guard long jump target count :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 120 : 192, "Dynamic value reloc table :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 124 : 200, "CHPE metadata pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 128 : 208, "Guard RF failure routine :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 132 : 216, "Guard RF failure routine ptr :", Is32Bit);
PeShowLoadConfigDword(Config, AvailableSize, Is32Bit ? 136 : 224, "Dynamic value reloc offset :");
if (PeLoadConfigHasField(AvailableSize, Is32Bit ? 140 : 228, sizeof(WORD)))
{
ShowMessages("\n%-36s%#x", "Dynamic value reloc section :", PeReadWordFromBuffer(Config, Is32Bit ? 140 : 228));
}
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 144 : 232, "Guard RF verify stack ptr :", Is32Bit);
PeShowLoadConfigDword(Config, AvailableSize, Is32Bit ? 148 : 240, "Hot patch table offset :");
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 156 : 248, "Enclave config pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 160 : 256, "Volatile metadata pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 164 : 264, "Guard EH continuation table :", Is32Bit);
PeShowLoadConfigCount(Config, AvailableSize, Is32Bit ? 168 : 272, "Guard EH continuation count :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 172 : 280, "Guard XFG check pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 176 : 288, "Guard XFG dispatch pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 180 : 296, "Guard XFG table dispatch ptr :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 184 : 304, "CastGuard failure mode :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 188 : 312, "Guard memcpy pointer :", Is32Bit);
}
/**
* @brief Prints the load configuration directory
*
* Reads the self-describing size field at the start of the load config
* structure, clamps to the directory bounds, and prints security cookie,
* SEH handler table, guard CF fields, guard flags, and extended modern
* fields via helper functions.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param LoadConfigDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG data directory entry
* @param Is32Bit TRUE if the PE image is 32-bit
*/
static VOID
PeShowLoadConfig(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * LoadConfigDirectory, BOOLEAN Is32Bit)
{
ShowMessages("\n\nLoad config\n-----------");
if (LoadConfigDirectory->VirtualAddress == 0 || LoadConfigDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Load config directory :", "empty");
return;
}
if (LoadConfigDirectory->Size < sizeof(DWORD))
{
ShowMessages("\n%-36s%s", "Warning :", "load config size is too small");
return;
}
const BYTE * Config = NULL;
if (!PeGetPointerAtRva(Reader, LoadConfigDirectory->VirtualAddress, sizeof(DWORD), &Config))
{
ShowMessages("\n%-36s%s", "Load config directory :", "not mapped");
return;
}
DWORD ConfigSize = PeReadDwordFromBuffer(Config, 0);
DWORD AvailableSize = LoadConfigDirectory->Size < ConfigSize ? LoadConfigDirectory->Size : ConfigSize;
ShowMessages("\n%-36s%#x", "Size :", ConfigSize);
if (AvailableSize < sizeof(DWORD))
{
ShowMessages("\n%-36s%s", "Warning :", "load config size is too small");
return;
}
if (!PeGetPointerAtRva(Reader, LoadConfigDirectory->VirtualAddress, AvailableSize, &Config))
{
ShowMessages("\n%-36s%s", "Load config directory :", "invalid bounds");
return;
}
PeShowLoadConfigDword(Config, AvailableSize, 4, "Time date stamp :");
if (PeLoadConfigHasField(AvailableSize, 8, sizeof(WORD)))
{
WORD MajorVersion = 0;
CopyMemory(&MajorVersion, Config + 8, sizeof(MajorVersion));
ShowMessages("\n%-36s%u", "Major version :", MajorVersion);
}
if (PeLoadConfigHasField(AvailableSize, 10, sizeof(WORD)))
{
WORD MinorVersion = 0;
CopyMemory(&MinorVersion, Config + 10, sizeof(MinorVersion));
ShowMessages("\n%-36s%u", "Minor version :", MinorVersion);
}
PeShowLoadConfigDword(Config, AvailableSize, 12, "Global flags clear :");
PeShowLoadConfigDword(Config, AvailableSize, 16, "Global flags set :");
PeShowLoadConfigDword(Config, AvailableSize, 20, "Critical section timeout :");
PeShowLoadConfigDword(Config, AvailableSize, Is32Bit ? 44 : 72, "Process heap flags :");
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 60 : 88, "Security cookie :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 64 : 96, "SE handler table :", Is32Bit);
PeShowLoadConfigCount(Config, AvailableSize, Is32Bit ? 68 : 104, "SE handler count :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 72 : 112, "Guard CF check pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 76 : 120, "Guard CF dispatch pointer :", Is32Bit);
PeShowLoadConfigPointer(Config, AvailableSize, Is32Bit ? 80 : 128, "Guard CF function table :", Is32Bit);
PeShowLoadConfigCount(Config, AvailableSize, Is32Bit ? 84 : 136, "Guard CF function count :", Is32Bit);
PeShowLoadConfigDword(Config, AvailableSize, Is32Bit ? 88 : 144, "Guard flags :");
if (PeLoadConfigHasField(AvailableSize, Is32Bit ? 88 : 144, sizeof(DWORD)))
{
DWORD GuardFlags = PeReadDwordFromBuffer(Config, Is32Bit ? 88 : 144);
ShowMessages("\n%-36s", "Guard flag names :");
PeShowLoadConfigGuardFlags(GuardFlags);
}
PeShowLoadConfigModernFields(Config, AvailableSize, Is32Bit);
}
/**
* @brief Prints a summary of all PE data directory entries
*
* Iterates IMAGE_NUMBEROF_DIRECTORY_ENTRIES slots, printing the name, RVA
* (or file offset for the certificate entry), size, and mapping status of
* each. Entries beyond NumberOfRvaAndSizes are marked as "not declared".
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Directories Pointer to the data directory array from the optional header
* @param NumberOfRvaAndSizes Value of NumberOfRvaAndSizes from the optional header
*/
static VOID
PeShowDataDirectories(PPE_IMAGE_READER Reader,
const IMAGE_DATA_DIRECTORY * Directories,
DWORD NumberOfRvaAndSizes)
{
ShowMessages("\n\nData directories\n----------------");
ShowMessages("\n%-36s%u", "Number of RVA and sizes :", NumberOfRvaAndSizes);
if (NumberOfRvaAndSizes > IMAGE_NUMBEROF_DIRECTORY_ENTRIES)
{
ShowMessages("\n%-36s%s", "Warning :", "number of RVA and sizes exceeds PE directory table");
}
for (UINT32 Index = 0; Index < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; Index++)
{
const CHAR * Name = PeGetDataDirectoryName(Index);
const CHAR * AddressLabel = Index == IMAGE_DIRECTORY_ENTRY_SECURITY ? "file offset" : "RVA";
if (Index >= NumberOfRvaAndSizes)
{
ShowMessages("\n[%2u] %-27s %s", Index, Name, "not declared");
continue;
}
const IMAGE_DATA_DIRECTORY * Directory = &Directories[Index];
if (Directory->VirtualAddress == 0 || Directory->Size == 0)
{
ShowMessages("\n[%2u] %-27s %s %#x, size %#x, %s",
Index,
Name,
AddressLabel,
Directory->VirtualAddress,
Directory->Size,
"empty");
continue;
}
const BYTE * Pointer = NULL;
SIZE_T FileOffset = 0;
if (Index == IMAGE_DIRECTORY_ENTRY_SECURITY)
{
BOOLEAN HasBounds = PeImageReaderGetPointerAtOffset(Reader,
Directory->VirtualAddress,
Directory->Size,
&Pointer);
ShowMessages("\n[%2u] %-27s file offset %#x, size %#x, %s",
Index,
Name,
Directory->VirtualAddress,
Directory->Size,
HasBounds ? "valid bounds" : "invalid bounds");
if (HasBounds)
{
ShowMessages(", mapped file offset %#llx", (UINT64)Directory->VirtualAddress);
}
continue;
}
if (PeImageReaderRvaToFileOffset(Reader, Directory->VirtualAddress, Directory->Size, &FileOffset))
{
ShowMessages("\n[%2u] %-27s RVA %#x, size %#x, mapped file offset %#llx",
Index,
Name,
Directory->VirtualAddress,
Directory->Size,
(UINT64)FileOffset);
}
else
{
ShowMessages("\n[%2u] %-27s RVA %#x, size %#x, %s",
Index,
Name,
Directory->VirtualAddress,
Directory->Size,
"not mapped");
}
}
}
/**
* @brief Prints overlay data information and PE layout warnings
*
* Identifies data appended after all mapped sections (overlay), checks for
* section overlaps and gaps, validates SizeOfImage, and emits warnings for
* mismatched optional header magic/machine combinations, sections that extend
* beyond SizeOfImage, and inconsistent section raw data boundaries.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param Machine Machine type from the PE file header
* @param OptionalHeaderMagic Magic value from the PE optional header
* @param AddressOfEntryPoint AddressOfEntryPoint from the PE optional header
* @param SizeOfImage SizeOfImage from the PE optional header
* @param SizeOfHeaders SizeOfHeaders from the PE optional header
* @param Directories Pointer to the data directory array
* @param NumberOfRvaAndSizes Value of NumberOfRvaAndSizes from the optional header
*/
static VOID
PeShowOverlayAndWarnings(PPE_IMAGE_READER Reader,
WORD Machine,
WORD OptionalHeaderMagic,
DWORD AddressOfEntryPoint,
DWORD SizeOfImage,
DWORD SizeOfHeaders,
const IMAGE_DATA_DIRECTORY * Directories,
DWORD NumberOfRvaAndSizes)
{
ULONGLONG OverlayOffset = SizeOfHeaders < Reader->ImageSize ? SizeOfHeaders : Reader->ImageSize;
BOOLEAN OverlayUnreliable = FALSE;
ShowMessages("\n\nOverlay and warnings\n--------------------");
if ((ULONGLONG)SizeOfHeaders > (ULONGLONG)Reader->ImageSize)
{
ShowMessages("\n%-36s%s", "Warning :", "size of headers is larger than file size");
}
if (OptionalHeaderMagic == IMAGE_NT_OPTIONAL_HDR64_MAGIC && Machine == IMAGE_FILE_MACHINE_I386)
{
ShowMessages("\n%-36s%s", "Warning :", "PE32+ optional header with i386 machine type");
}
else if (OptionalHeaderMagic == IMAGE_NT_OPTIONAL_HDR32_MAGIC &&
(Machine == IMAGE_FILE_MACHINE_AMD64 || Machine == IMAGE_FILE_MACHINE_IA64))
{
ShowMessages("\n%-36s%s", "Warning :", "PE32 optional header with 64-bit machine type");
}
for (UINT32 SectionIndex = 0; SectionIndex < Reader->FileHeader->NumberOfSections; SectionIndex++)
{
CHAR SectionName[IMAGE_SIZEOF_SHORT_NAME + 1];
const IMAGE_SECTION_HEADER * Section = &Reader->SectionHeaders[SectionIndex];
DWORD VirtualSpan = Section->Misc.VirtualSize > Section->SizeOfRawData ? Section->Misc.VirtualSize : Section->SizeOfRawData;
DWORD VirtualEnd = 0;
PeImageReaderGetSectionName(Section, SectionName, sizeof(SectionName));
if (VirtualSpan != 0)
{
if (!PeAddDword(Section->VirtualAddress, VirtualSpan, &VirtualEnd))
{
ShowMessages("\n%-36ssection '%s' RVA range overflows", "Warning :", SectionName);
}
else if (SizeOfImage != 0 && VirtualEnd > SizeOfImage)
{
ShowMessages("\n%-36ssection '%s' RVA range exceeds SizeOfImage", "Warning :", SectionName);
}
}
if (Section->SizeOfRawData != 0)
{
ULONGLONG RawStart = Section->PointerToRawData;
ULONGLONG RawEnd = 0;
if (!PeAddUlonglong(RawStart, Section->SizeOfRawData, &RawEnd))
{
ShowMessages("\n%-36ssection '%s' raw data range overflows", "Warning :", SectionName);
OverlayUnreliable = TRUE;
}
else if (RawStart > (ULONGLONG)Reader->ImageSize || RawEnd > (ULONGLONG)Reader->ImageSize)
{
ShowMessages("\n%-36ssection '%s' raw data is outside file", "Warning :", SectionName);
OverlayUnreliable = TRUE;
}
else if (RawEnd > OverlayOffset)
{
OverlayOffset = RawEnd;
}
}
}
if (Reader->FileHeader->NumberOfSections > 1)
{
PE_RAW_SECTION_RANGE * Ranges = (PE_RAW_SECTION_RANGE *)malloc(sizeof(PE_RAW_SECTION_RANGE) * Reader->FileHeader->NumberOfSections);
UINT32 Count = 0;
if (Ranges == NULL)
{
ShowMessages("\n%-36s%s", "Warning :", "raw section overlap check skipped, allocation failed");
}
else
{
for (UINT32 SectionIndex = 0; SectionIndex < Reader->FileHeader->NumberOfSections; SectionIndex++)
{
const IMAGE_SECTION_HEADER * Section = &Reader->SectionHeaders[SectionIndex];
ULONGLONG End;
if (Section->SizeOfRawData == 0 ||
!PeAddUlonglong(Section->PointerToRawData, Section->SizeOfRawData, &End) ||
Section->PointerToRawData > Reader->ImageSize || End > Reader->ImageSize)
{
continue;
}
Ranges[Count].Start = Section->PointerToRawData;
Ranges[Count].End = End;
Ranges[Count].Section = Section;
Count++;
}
if (Count > 1)
{
UINT32 MaxEndIndex = 0;
qsort(Ranges, Count, sizeof(Ranges[0]), PeCompareRawSectionRange);
for (UINT32 RangeIndex = 1; RangeIndex < Count; RangeIndex++)
{
if (Ranges[RangeIndex].Start < Ranges[MaxEndIndex].End)
{
CHAR LeftName[IMAGE_SIZEOF_SHORT_NAME + 1];
CHAR RightName[IMAGE_SIZEOF_SHORT_NAME + 1];
PeImageReaderGetSectionName(Ranges[MaxEndIndex].Section, LeftName, sizeof(LeftName));
PeImageReaderGetSectionName(Ranges[RangeIndex].Section, RightName, sizeof(RightName));
ShowMessages("\n%-36sraw data overlap detected between '%s' and '%s'; additional overlaps may be omitted",
"Warning :",
LeftName,
RightName);
}
if (Ranges[RangeIndex].End > Ranges[MaxEndIndex].End)
{
MaxEndIndex = RangeIndex;
}
}
}
free(Ranges);
}
}
if (AddressOfEntryPoint != 0)
{
BOOLEAN EntrypointFound = FALSE;
const IMAGE_SECTION_HEADER * EntrypointSection = NULL;
for (UINT32 SectionIndex = 0; SectionIndex < Reader->FileHeader->NumberOfSections; SectionIndex++)
{
const IMAGE_SECTION_HEADER * Section = &Reader->SectionHeaders[SectionIndex];
DWORD VirtualSpan = Section->Misc.VirtualSize > Section->SizeOfRawData ? Section->Misc.VirtualSize : Section->SizeOfRawData;
DWORD VirtualEnd = 0;
if (VirtualSpan == 0 || !PeAddDword(Section->VirtualAddress, VirtualSpan, &VirtualEnd))
{
continue;
}
if (AddressOfEntryPoint >= Section->VirtualAddress && AddressOfEntryPoint < VirtualEnd)
{
EntrypointFound = TRUE;
EntrypointSection = Section;
break;
}
}
if (!EntrypointFound)
{
ShowMessages("\n%-36s%s", "Warning :", "entrypoint is outside all sections");
}
else if ((EntrypointSection->Characteristics & IMAGE_SCN_MEM_EXECUTE) == 0)
{
CHAR SectionName[IMAGE_SIZEOF_SHORT_NAME + 1];
PeImageReaderGetSectionName(EntrypointSection, SectionName, sizeof(SectionName));
ShowMessages("\n%-36sentrypoint section '%s' is not executable", "Warning :", SectionName);
}
}
if (OverlayUnreliable)
{
ShowMessages("\n%-36s%s", "Overlay :", "not computed because section raw data is invalid");
}
else if ((ULONGLONG)Reader->ImageSize > OverlayOffset)
{
ULONGLONG OverlaySize = (ULONGLONG)Reader->ImageSize - OverlayOffset;
ShowMessages("\n%-36s%#llx", "Overlay offset :", OverlayOffset);
ShowMessages("\n%-36s%#llx", "Overlay size :", OverlaySize);
if (Directories != NULL && NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_SECURITY)
{
const IMAGE_DATA_DIRECTORY * SecurityDirectory = &Directories[IMAGE_DIRECTORY_ENTRY_SECURITY];
if (SecurityDirectory->VirtualAddress != 0 && SecurityDirectory->Size != 0 &&
(ULONGLONG)SecurityDirectory->VirtualAddress >= OverlayOffset)
{
ShowMessages("\n%-36s%s", "Info :", "overlay includes certificate data; not necessarily suspicious");
}
}
}
else
{
ShowMessages("\n%-36s%s", "Overlay :", "none");
}
}
/**
* @brief Prints all import descriptors and their imported function names or ordinals
*
* Iterates IMAGE_IMPORT_DESCRIPTOR records, resolving DLL names and walking
* the original-first-thunk (or first-thunk) arrays to print each imported
* symbol with its hint and IAT RVA. Warns when the null terminator descriptor
* is not found within the declared directory size. Processing is capped at
* 0x1000 descriptors and 0x2000 total imports.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ImportDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_IMPORT data directory entry
* @param Is32Bit TRUE if the PE image is 32-bit
*/
static VOID
PeShowImports(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * ImportDirectory, BOOLEAN Is32Bit)
{
const DWORD MaxImportDescriptors = 0x1000;
const DWORD MaxTotalImports = 0x2000;
const DWORD MaxDllNameLength = 0x200;
const DWORD MaxImportNameLength = 0x200;
ShowMessages("\n\nImports\n-------");
if (ImportDirectory->VirtualAddress == 0 || ImportDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Import directory :", "empty");
return;
}
const BYTE * DirectoryPointer = NULL;
if (!PeGetPointerAtRva(Reader, ImportDirectory->VirtualAddress, sizeof(IMAGE_IMPORT_DESCRIPTOR), &DirectoryPointer))
{
ShowMessages("\n%-36s%s", "Import directory :", "not mapped");
return;
}
DWORD DescriptorCount = ImportDirectory->Size / sizeof(IMAGE_IMPORT_DESCRIPTOR);
if (DescriptorCount == 0)
{
ShowMessages("\n%-36s%s", "Warning :", "import directory is smaller than one descriptor");
return;
}
BOOLEAN DescriptorCapped = FALSE;
if (DescriptorCount > MaxImportDescriptors)
{
DescriptorCount = MaxImportDescriptors;
DescriptorCapped = TRUE;
}
DWORD TotalImports = 0;
BOOLEAN FoundTerminator = FALSE;
BOOLEAN TotalImportsCapped = FALSE;
for (DWORD DescriptorIndex = 0; DescriptorIndex < DescriptorCount; DescriptorIndex++)
{
DWORD DescriptorRva = 0;
if (!PeAddDword(ImportDirectory->VirtualAddress,
DescriptorIndex * (DWORD)sizeof(IMAGE_IMPORT_DESCRIPTOR),
&DescriptorRva))
{
break;
}
const IMAGE_IMPORT_DESCRIPTOR * Descriptor = NULL;
if (!PeGetPointerAtRva(Reader,
DescriptorRva,
sizeof(IMAGE_IMPORT_DESCRIPTOR),
(const BYTE **)&Descriptor))
{
ShowMessages("\n%-36s%s", "Warning :", "import descriptor is not mapped");
break;
}
if (Descriptor->OriginalFirstThunk == 0 && Descriptor->TimeDateStamp == 0 && Descriptor->ForwarderChain == 0 &&
Descriptor->Name == 0 && Descriptor->FirstThunk == 0)
{
FoundTerminator = TRUE;
break;
}
CHAR DllName[MaxDllNameLength + 1] = {0};
PE_ASCII_STRING_STATUS DllNameStatus = PeReadAsciiStringAtRva(Reader,
Descriptor->Name,
MaxDllNameLength,
DllName,
sizeof(DllName));
if (DllNameStatus != PeAsciiStringOk)
{
ShowMessages("\n[%u] DLL name %s", DescriptorIndex, PeAsciiStatusName(DllNameStatus));
ShowMessages("\n%-36s%s", "Warning :", "skipping imports for descriptor with unreadable DLL name");
continue;
}
ShowMessages("\n[%u] DLL name %s", DescriptorIndex, DllName);
DWORD LookupThunkRva = Descriptor->OriginalFirstThunk != 0 ? Descriptor->OriginalFirstThunk : Descriptor->FirstThunk;
if (LookupThunkRva == 0 || Descriptor->FirstThunk == 0)
{
ShowMessages("\n%-36s%s", "Warning :", "import thunk array is empty");
continue;
}
DWORD ThunkSize = Is32Bit ? sizeof(DWORD) : sizeof(ULONGLONG);
for (DWORD ThunkIndex = 0; TotalImports < MaxTotalImports; ThunkIndex++)
{
DWORD LookupEntryRva = 0;
DWORD IatEntryRva = 0;
if (!PeAddDword(LookupThunkRva, ThunkIndex * ThunkSize, &LookupEntryRva) ||
!PeAddDword(Descriptor->FirstThunk, ThunkIndex * ThunkSize, &IatEntryRva))
{
ShowMessages("\n%-36s%s", "Warning :", "import thunk RVA overflow");
break;
}
ULONGLONG ThunkValue = 0;
if (!PeReadThunkAtRva(Reader, LookupEntryRva, Is32Bit, &ThunkValue))
{
ShowMessages("\n%-36s%#x", "Warning, invalid thunk RVA :", LookupEntryRva);
break;
}
if (ThunkValue == 0)
{
break;
}
ULONGLONG OrdinalFlag = Is32Bit ? IMAGE_ORDINAL_FLAG32 : IMAGE_ORDINAL_FLAG64;
if ((ThunkValue & OrdinalFlag) != 0)
{
ShowMessages("\n [%u] ordinal %u thunk RVA %#x", ThunkIndex, (UINT32)(ThunkValue & 0xffff), IatEntryRva);
TotalImports++;
continue;
}
if (ThunkValue > MAXDWORD)
{
ShowMessages("\n%-36s%#llx", "Warning, invalid import name RVA :", (UINT64)ThunkValue);
TotalImports++;
continue;
}
DWORD NameRva = (DWORD)ThunkValue;
WORD Hint = 0;
if (!PeReadWordAtRva(Reader, NameRva, &Hint))
{
ShowMessages("\n%-36s%#x", "Warning, invalid import hint RVA :", NameRva);
TotalImports++;
continue;
}
DWORD ImportNameRva = 0;
if (!PeAddDword(NameRva, sizeof(WORD), &ImportNameRva))
{
ShowMessages("\n%-36s%#x", "Warning, invalid import name RVA :", NameRva);
TotalImports++;
continue;
}
CHAR ImportName[MaxImportNameLength + 1] = {0};
PE_ASCII_STRING_STATUS ImportNameStatus = PeReadAsciiStringAtRva(Reader,
ImportNameRva,
MaxImportNameLength,
ImportName,
sizeof(ImportName));
if (ImportNameStatus != PeAsciiStringOk)
{
ShowMessages("\n%-36s%#x, %s", "Warning, invalid import name RVA :", ImportNameRva, PeAsciiStatusName(ImportNameStatus));
TotalImports++;
continue;
}
ShowMessages("\n [%u] hint %#x name %s thunk RVA %#x", ThunkIndex, Hint, ImportName, IatEntryRva);
TotalImports++;
}
if (TotalImports >= MaxTotalImports)
{
TotalImportsCapped = TRUE;
break;
}
}
if (!FoundTerminator)
{
ShowMessages("\n%-36s%s", "Warning :", "import descriptor terminator not found before bounds or cap");
}
if (DescriptorCapped)
{
ShowMessages("\n%-36s%#x", "Warning, descriptor cap reached :", MaxImportDescriptors);
}
if (TotalImportsCapped)
{
ShowMessages("\n%-36s%#x", "Warning, import cap reached :", MaxTotalImports);
}
}
/**
* @brief Validates and retrieves a pointer to a PE export table array
*
* Computes the total byte size of the table as Count * EntrySize, verifies
* that the multiplication does not overflow, and then resolves the table RVA
* via PeGetPointerAtRva. When Count is zero *Pointer is set to NULL and TRUE
* is returned without any range check.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param TableRva RVA of the export table array
* @param Count Number of entries in the table
* @param EntrySize Size of each entry in bytes
* @param Pointer Output pointer set to the validated table location on success
*
* @return BOOLEAN TRUE on success, FALSE on overflow or if the RVA is not mapped
*/
static BOOLEAN
PeValidateExportTable(PPE_IMAGE_READER Reader, DWORD TableRva, DWORD Count, DWORD EntrySize, const BYTE ** Pointer)
{
DWORD TableSize = 0;
if (Count == 0)
{
*Pointer = NULL;
return TRUE;
}
if (EntrySize != 0 && Count > MAXDWORD / EntrySize)
{
return FALSE;
}
TableSize = Count * EntrySize;
return PeGetPointerAtRva(Reader, TableRva, TableSize, Pointer);
}
/**
* @brief Prints one export entry including its ordinal, name, and function RVA or forwarder
*
* If FunctionRva falls within the export directory's RVA range the entry is
* treated as a forwarder string and that string is read and displayed.
* Otherwise the raw function RVA is printed.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ExportDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_EXPORT data directory entry
* @param Index Zero-based display index of this export entry
* @param Ordinal Export ordinal (base-adjusted)
* @param Name Export name string, or an error description if not available
* @param FunctionRva RVA of the exported function or forwarder string
*/
static VOID
PeShowExportEntry(PPE_IMAGE_READER Reader,
const IMAGE_DATA_DIRECTORY * ExportDirectory,
DWORD Index,
DWORD Ordinal,
const CHAR * Name,
DWORD FunctionRva)
{
const DWORD MaxForwarderLength = 0x1000;
if (PeRvaContainsRange(ExportDirectory->VirtualAddress, ExportDirectory->Size, FunctionRva, 1))
{
DWORD ForwarderRemaining = ExportDirectory->Size - (FunctionRva - ExportDirectory->VirtualAddress);
DWORD ForwarderMaxLength = ForwarderRemaining < MaxForwarderLength ? ForwarderRemaining : MaxForwarderLength;
CHAR Forwarder[MaxForwarderLength + 1] = {0};
PE_ASCII_STRING_STATUS ForwarderStatus = PeReadAsciiStringAtRva(Reader,
FunctionRva,
ForwarderMaxLength,
Forwarder,
sizeof(Forwarder));
if (ForwarderStatus == PeAsciiStringOk)
{
ShowMessages("\n[%u] ordinal %u name %s forwarder %s", Index, Ordinal, Name, Forwarder);
}
else
{
ShowMessages("\n[%u] ordinal %u name %s forwarder %s", Index, Ordinal, Name, PeAsciiStatusName(ForwarderStatus));
}
return;
}
ShowMessages("\n[%u] ordinal %u name %s RVA %#x", Index, Ordinal, Name, FunctionRva);
}
/**
* @brief Prints the export directory including the export address, name, and ordinal tables
*
* Reads the IMAGE_EXPORT_DIRECTORY header, validates the three export tables
* (AddressOfFunctions, AddressOfNames, AddressOfNameOrdinals), and iterates
* all exported functions. Named exports are matched via the name-ordinal table;
* unnamed exports are printed with their ordinal. Forwarder strings are handled
* via PeShowExportEntry. Processing is capped at 0x2000 total exports.
*
* @param Reader Pointer to an initialized PE_IMAGE_READER
* @param ExportDirectory Pointer to the IMAGE_DIRECTORY_ENTRY_EXPORT data directory entry
*/
static VOID
PeShowExports(PPE_IMAGE_READER Reader, const IMAGE_DATA_DIRECTORY * ExportDirectory)
{
const DWORD MaxExports = 0x2000;
const DWORD MaxDllNameLength = 0x200;
const DWORD MaxExportNameLength = 0x200;
ShowMessages("\n\nExports\n-------");
if (ExportDirectory->VirtualAddress == 0 || ExportDirectory->Size == 0)
{
ShowMessages("\n%-36s%s", "Export directory :", "empty");
return;
}
if (!PeRvaContainsRange(ExportDirectory->VirtualAddress,
ExportDirectory->Size,
ExportDirectory->VirtualAddress,
sizeof(IMAGE_EXPORT_DIRECTORY)))
{
ShowMessages("\n%-36s%s", "Export directory :", "invalid bounds");
return;
}
const IMAGE_EXPORT_DIRECTORY * Directory = NULL;
if (!PeGetPointerAtRva(Reader,
ExportDirectory->VirtualAddress,
sizeof(IMAGE_EXPORT_DIRECTORY),
(const BYTE **)&Directory))
{
ShowMessages("\n%-36s%s", "Export directory :", "not mapped");
return;
}
CHAR DllName[MaxDllNameLength + 1] = {0};
PE_ASCII_STRING_STATUS DllNameStatus = PeReadAsciiStringAtRva(Reader,
Directory->Name,
MaxDllNameLength,
DllName,
sizeof(DllName));
ShowMessages("\n%-36s%s", "DLL name :", DllNameStatus == PeAsciiStringOk ? DllName : PeAsciiStatusName(DllNameStatus));
ShowMessages("\n%-36s%u", "Ordinal base :", Directory->Base);
ShowMessages("\n%-36s%u", "Address table count :", Directory->NumberOfFunctions);
ShowMessages("\n%-36s%u", "Name pointer count :", Directory->NumberOfNames);
const BYTE * AddressTablePointer = NULL;
const BYTE * NamePointerTablePointer = NULL;
const BYTE * OrdinalTablePointer = NULL;
BOOLEAN AddressTableValid = PeValidateExportTable(Reader,
Directory->AddressOfFunctions,
Directory->NumberOfFunctions,
sizeof(DWORD),
&AddressTablePointer);
BOOLEAN NamePointerTableValid = PeValidateExportTable(Reader,
Directory->AddressOfNames,
Directory->NumberOfNames,
sizeof(DWORD),
&NamePointerTablePointer);
BOOLEAN OrdinalTableValid = PeValidateExportTable(Reader,
Directory->AddressOfNameOrdinals,
Directory->NumberOfNames,
sizeof(WORD),
&OrdinalTablePointer);
if (!AddressTableValid)
{
ShowMessages("\n%-36s%s", "Warning :", "export address table is not mapped");
return;
}
if (!NamePointerTableValid)
{
ShowMessages("\n%-36s%s", "Warning :", "export name pointer table is not mapped");
}
if (!OrdinalTableValid)
{
ShowMessages("\n%-36s%s", "Warning :", "export ordinal table is not mapped");
}
DWORD NamedCount = Directory->NumberOfNames;
BOOLEAN NamedCapped = FALSE;
if (NamedCount > MaxExports)
{
NamedCount = MaxExports;
NamedCapped = TRUE;
}
DWORD FunctionCount = Directory->NumberOfFunctions;
BOOLEAN FunctionCapped = FALSE;
if (FunctionCount > MaxExports)
{
FunctionCount = MaxExports;
FunctionCapped = TRUE;
}
BYTE NamedAddressIndexes[MaxExports] = {0};
if (NamePointerTableValid && OrdinalTableValid)
{
for (DWORD NameIndex = 0; NameIndex < NamedCount; NameIndex++)
{
DWORD NameRva = 0;
WORD AddressIndex = 0;
CopyMemory(&NameRva, NamePointerTablePointer + (NameIndex * sizeof(DWORD)), sizeof(NameRva));
CopyMemory(&AddressIndex, OrdinalTablePointer + (NameIndex * sizeof(WORD)), sizeof(AddressIndex));
if (AddressIndex >= Directory->NumberOfFunctions)
{
ShowMessages("\n%-36s%u", "Warning, invalid export ordinal index :", AddressIndex);
continue;
}
DWORD FunctionRva = 0;
CopyMemory(&FunctionRva, AddressTablePointer + (AddressIndex * sizeof(DWORD)), sizeof(FunctionRva));
CHAR ExportName[MaxExportNameLength + 1] = {0};
PE_ASCII_STRING_STATUS ExportNameStatus = PeReadAsciiStringAtRva(Reader,
NameRva,
MaxExportNameLength,
ExportName,
sizeof(ExportName));
if (ExportNameStatus != PeAsciiStringOk)
{
ShowMessages("\n%-36s%#x, %s", "Warning, invalid export name RVA :", NameRva, PeAsciiStatusName(ExportNameStatus));
continue;
}
DWORD DisplayOrdinal = 0;
if (!PeAddDword(Directory->Base, AddressIndex, &DisplayOrdinal))
{
ShowMessages("\n%-36s%u", "Warning, invalid export ordinal index :", AddressIndex);
continue;
}
if (AddressIndex < FunctionCount)
{
NamedAddressIndexes[AddressIndex] = TRUE;
}
PeShowExportEntry(Reader,
ExportDirectory,
NameIndex,
DisplayOrdinal,
ExportName,
FunctionRva);
}
}
for (DWORD AddressIndex = 0; AddressIndex < FunctionCount; AddressIndex++)
{
if (NamedAddressIndexes[AddressIndex])
{
continue;
}
DWORD FunctionRva = 0;
CopyMemory(&FunctionRva, AddressTablePointer + (AddressIndex * sizeof(DWORD)), sizeof(FunctionRva));
DWORD DisplayOrdinal = 0;
if (!PeAddDword(Directory->Base, AddressIndex, &DisplayOrdinal))
{
ShowMessages("\n%-36s%u", "Warning, invalid export ordinal index :", AddressIndex);
continue;
}
PeShowExportEntry(Reader,
ExportDirectory,
AddressIndex,
DisplayOrdinal,
"<ordinal-only>",
FunctionRva);
}
if (NamedCapped)
{
ShowMessages("\n%-36s%#x", "Warning, named export cap reached :", MaxExports);
}
if (FunctionCapped)
{
ShowMessages("\n%-36s%#x", "Warning, export cap reached :", MaxExports);
}
}
/**
* @brief Locates the Rich header signature in a PE file
*
* The Rich header is an undocumented Microsoft structure embedded in PE files
* that contains information about the tools and compilers used during the build process.
* This function searches for the "Rich" signature string within the DOS stub area.
*
* @param DosHeader Pointer to the DOS header structure of the PE file
* @param SearchSize Capped number of bytes to search
* @param StartOffset Offset to start searching from
* @param Key Output buffer to store the 4-byte XOR key found after "Rich" signature
*
* @note The Rich header is located between the DOS header and PE header
* @note The XOR key is used to decode the actual Rich header entries
**/
static INT
FindRichHeader(PIMAGE_DOS_HEADER DosHeader, DWORD SearchSize, DWORD StartOffset, CHAR Key[])
{
if (DosHeader == NULL || Key == NULL)
{
return -1;
}
//
// Get PE header offset - this defines our search boundary
//
LONG Offset = DosHeader->e_lfanew;
if (SearchSize < sizeof(IMAGE_DOS_HEADER) ||
DosHeader->e_magic != IMAGE_DOS_SIGNATURE ||
Offset < (LONG)sizeof(IMAGE_DOS_HEADER) ||
Offset < 8)
{
return -1;
}
CHAR * BaseAddr = (CHAR *)DosHeader;
DWORD SearchLimit = (DWORD)Offset;
if (SearchLimit > SearchSize)
{
SearchLimit = SearchSize;
}
if (StartOffset + 8 < StartOffset || StartOffset + 8 > SearchLimit)
{
return -1;
}
//
// Search for "Rich" signature
// We need 4 bytes for "Rich" and 4 bytes for the key before the PE header.
//
for (DWORD Offset = StartOffset; Offset + 8 <= SearchLimit; ++Offset)
{
//
// Check for "Rich" signature (4 ASCII bytes)
//
if (BaseAddr[Offset] == 'R' &&
BaseAddr[Offset + 1] == 'i' &&
BaseAddr[Offset + 2] == 'c' &&
BaseAddr[Offset + 3] == 'h')
{
//
// Extract the 4-byte XOR key that immediately follows "Rich"
//
memcpy(Key, BaseAddr + Offset + 4, 4);
//
// Return the offset where "Rich" signature was found
//
return Offset;
}
}
//
// Rich header signature not found
//
return -1;
}
/**
* @brief Decrypts Rich header data using XOR decryption and initializes header info
*
* The Rich header is encrypted using a simple XOR cipher with a 4-byte key.
* This function decrypts the entire header in-place and populates the
* PEFILE_RICH_HEADER_INFO structure with metadata about the decrypted data.
*
* @param RichHeaderPtr Pointer to the raw Rich header data to be decrypted
* @param RichHeaderSize Size of the Rich header data in bytes
* @param Key 4-byte XOR key used for decryption
* @param PeFileRichHeaderInfo structure containing Rich header metadata and buffer information
*
* @note The Rich header uses a repeating 4-byte XOR key for encryption
* @note After decryption, the header contains 16 bytes of metadata followed by 8-byte entries
* @note Each entry represents one compilation tool (compiler, linker, assembler, etc.)
*
*/
static BOOLEAN
FindRichEntries(CHAR * RichHeaderPtr,
INT RichHeaderSize,
CHAR Key[],
PRICH_HEADER_INFO PeFileRichHeaderInfo)
{
if (RichHeaderPtr == NULL || Key == NULL || PeFileRichHeaderInfo == NULL ||
RichHeaderSize < 16 || ((RichHeaderSize - 16) % 8) != 0)
{
return FALSE;
}
INT EntryCount = (RichHeaderSize - 16) / 8;
if (EntryCount <= 0)
{
return FALSE;
}
//
// Decrypt the entire Rich header using XOR with the 4-byte key
//
for (int i = 0; i < RichHeaderSize; i += 4)
{
//
// Apply XOR decryption to each 4-byte block
//
for (int x = 0; x < 4; x++)
{
RichHeaderPtr[i + x] ^= Key[x];
}
}
if (RichHeaderPtr[0] != 'D' || RichHeaderPtr[1] != 'a' ||
RichHeaderPtr[2] != 'n' || RichHeaderPtr[3] != 'S')
{
return FALSE;
}
for (int i = 4; i < 16; i++)
{
if (RichHeaderPtr[i] != 0)
{
return FALSE;
}
}
//
// Initialize the Rich header info structure
//
PeFileRichHeaderInfo->Size = RichHeaderSize;
PeFileRichHeaderInfo->PtrToBuffer = RichHeaderPtr;
//
// Calculate number of entries: subtract 16-byte header, divide by 8 bytes per entry
//
PeFileRichHeaderInfo->Entries = EntryCount;
return TRUE;
}
/**
* @brief Parses decrypted Rich header data into structured entries
*
* After decryption, the Rich header contains a series of 8-byte entries, each describing
* a compilation tool used during the build process. This function extracts and converts
* the little-endian binary data into structured RICH_ENTRY objects.
*
* Rich header format after decryption:
* - Bytes 0-15: Header metadata (DanS signature + padding)
* - Bytes 16+: 8-byte entries (prodID:2, buildID:2, useCount:4)
*
* @param RichHeaderSize Size of the entire Rich header in bytes
* @param RichHeaderPtr Pointer to the decrypted Rich header data
* @param PeFileRichHeader structure containing the parsed Rich header entries
*
* @warning Assumes the Rich header has been properly decrypted first
*/
static BOOLEAN
SetRichEntries(INT RichHeaderSize, CHAR * RichHeaderPtr, PRICH_HEADER PeFileRichHeader)
{
if (RichHeaderPtr == NULL || PeFileRichHeader == NULL || PeFileRichHeader->Entries == NULL ||
RichHeaderSize < 16 || ((RichHeaderSize - 16) % 8) != 0)
{
return FALSE;
}
INT EntryCount = (RichHeaderSize - 16) / 8;
if (EntryCount <= 0)
{
return FALSE;
}
//
// Start at offset 16 to skip the header metadata, process 8-byte entries
//
for (int i = 16, EntryIndex = 0; i + 8 <= RichHeaderSize; i += 8, EntryIndex++)
{
//
// Extract Product ID (bytes 2-3 of entry, little-endian)
//
WORD ProdID = ((UCHAR)RichHeaderPtr[i + 3] << 8) | (UCHAR)RichHeaderPtr[i + 2];
//
// Extract Build ID (bytes 0-1 of entry, little-endian)
//
WORD BuildID = ((UCHAR)RichHeaderPtr[i + 1] << 8) | (UCHAR)RichHeaderPtr[i];
//
// Extract Use Count (bytes 4-7 of entry, little-endian 32-bit)
//
DWORD UseCount = PeReadDwordFromBuffer((const BYTE *)RichHeaderPtr, i + 4);
//
// Store the parsed entry (adjust index: i/8 gives entry number, -2 for header offset)
//
PeFileRichHeader->Entries[EntryIndex] = {ProdID, BuildID, UseCount};
}
PeFileRichHeader->Entries[EntryCount] = {0x0000, 0x0000, 0x00000000};
return TRUE;
}
/**
* @brief Determines the size of the Rich header by finding the DanS signature
*
* The Rich header begins with the "DanS" signature (after decryption) and ends
* with the "Rich" signature. This function works backwards from the "Rich" signature
* to find the beginning and calculate the total size.
*
* Rich header structure:
* [DanS signature] [Padding] [Tool Entries] [Rich signature] [XOR Key]
*
* @param Key 4-byte XOR key for decryption (extracted from after "Rich" signature)
* @param Index Offset where "Rich" signature was found
* @param DataPtr Pointer to the beginning of the PE file data
*
* @return INT Size of the Rich header in bytes, or 0 if DanS signature not found
*
*/
static INT
DecryptRichHeader(CHAR Key[], INT Index, CHAR * DataPtr, INT DataSize)
{
if (Key == NULL || DataPtr == NULL || DataSize < 16 || Index < 16 || Index + 4 > DataSize)
{
return 0;
}
//
// Start searching backwards from just before the "Rich" signature
//
INT IndexPointer = Index - 4;
INT RichHeaderSize = 0;
//
// Search backwards for the DanS signature that marks the beginning
//
while (IndexPointer >= 0)
{
CHAR TmpChar[4];
//
// Read 4 bytes and decrypt them with the XOR key
//
memcpy(TmpChar, DataPtr + IndexPointer, 4);
for (int i = 0; i < 4; i++)
{
TmpChar[i] ^= Key[i];
}
//
// Move backwards and increment size counter
//
IndexPointer -= 4;
RichHeaderSize += 4;
//
// Check for DanS signature after decryption.
//
if (TmpChar[0] == 'D' && TmpChar[1] == 'a' && TmpChar[2] == 'n' && TmpChar[3] == 'S')
{
return RichHeaderSize;
}
}
return 0;
}
/**
* @brief Show hex dump of sections of PE
* @param Ptr
* @param Size
* @param SecAddress
*
* @return VOID
*/
static VOID
PeHexDump(CHAR * Ptr, SIZE_T Size, ULONGLONG SecAddress)
{
SIZE_T i = 1;
INT Temp = 0;
if (Ptr == NULL || Size == 0)
{
return;
}
//
// Buffer to store the character dump displayed at the
// right side
//
CHAR Buf[18];
ShowMessages("\n\n%llx: |", SecAddress);
Buf[Temp] = ' '; // initial space
Buf[Temp + 16] = ' '; // final space
Buf[Temp + 17] = 0; // End of Buf
Temp++; // Temp = 1;
for (; i <= Size; i++, Ptr++, Temp++)
{
Buf[Temp] = !iscntrl((*Ptr) & 0xff) ? (*Ptr) & 0xff : '.';
ShowMessages("%-3.2x", (*Ptr) & 0xff);
if (i % 16 == 0)
{
//
// print the character dump to the right
//
ShowMessages("%s\n", Buf);
if (i + 1 <= Size)
ShowMessages("%llx: ", SecAddress += 16);
Temp = 0;
}
if (i % 4 == 0)
ShowMessages("| ");
}
if (i % 16 != 0)
{
Buf[Temp] = 0;
for (; i % 16 != 0; i++)
ShowMessages("%-3.2c", ' ');
ShowMessages("%s\n", Buf);
}
}
/**
* @brief Show information about different sections of PE and the dump of sections
* @param AddressOfFile
* @param SectionToShow
* @param Is32Bit
*
* @return BOOLEAN
*/
BOOLEAN
PeShowSectionInformationAndDump(const WCHAR * AddressOfFile,
const CHAR * SectionToShow,
BOOLEAN Is32Bit)
{
RICH_HEADER_INFO PeFileRichHeaderInfo {0};
RICH_HEADER PeFileRichHeader {0};
BOOLEAN Result = FALSE, RichFound = FALSE, SectionFound = FALSE;
const DWORD MaxRichReadSize = 1024 * 1024;
const INT MaxRichHeaderSize = 256 * 1024;
const INT MaxRichHeaderEntries = 0x4000;
const DWORD MaxSectionScanBytes = 16 * 1024 * 1024;
const ULONGLONG MaxTotalSectionScanBytes = 64 * 1024 * 1024;
const SIZE_T MaxSectionDumpBytes = 1024 * 1024;
const ULONGLONG MaxTotalSectionDumpBytes = 4 * 1024 * 1024;
HANDLE FileHandle = INVALID_HANDLE_VALUE; // Open file handle (kept for the raw Rich-header read)
SIZE_T MappedSize = 0; // Size of the mapped file in bytes
UINT32 NumberOfSections; // Number of sections
LPVOID BaseAddr = NULL; // Pointer to the base memory of mapped file
PIMAGE_DOS_HEADER DosHeader; // Pointer to DOS Header
PIMAGE_NT_HEADERS32 NtHeader32 = NULL; // Pointer to NT Header 32 bit
PIMAGE_NT_HEADERS64 NtHeader64 = NULL; // Pointer to NT Header 64 bit
IMAGE_FILE_HEADER Header; // Pointer to image file header of NT Header
IMAGE_OPTIONAL_HEADER32 OpHeader32; // Optional Header of PE files present in NT Header structure
IMAGE_OPTIONAL_HEADER64 OpHeader64; // Optional Header of PE files present in NT Header structure
const IMAGE_SECTION_HEADER * SecHeader; // Section Header or Section Table Header
PE_IMAGE_READER Reader;
IMAGE_DATA_DIRECTORY EmptyDirectory = {0};
LARGE_INTEGER FileSize;
CHAR Key[4];
INT RichHeaderOffset = -1;
const CHAR * RichHeaderStatus = "not found";
BOOLEAN RichSearchCapped = FALSE;
ULONGLONG RemainingSectionScanBytes = MaxTotalSectionScanBytes;
ULONGLONG RemainingSectionDumpBytes = MaxTotalSectionDumpBytes;
time_t TimeDateStamp;
//
// Open and map the EXE file read-only. The wrapper also hands back the open
// file handle so the raw Rich-header read below can still seek/read it.
//
BaseAddr = PlatformMapFileReadOnly(AddressOfFile, &MappedSize, &FileHandle);
if (BaseAddr == NULL)
{
ShowMessages("err, could not open the file specified\n");
return FALSE;
}
FileSize.QuadPart = (LONGLONG)MappedSize;
if (FileSize.QuadPart < (LONGLONG)sizeof(IMAGE_DOS_HEADER))
{
PlatformUnmapFile(BaseAddr, MappedSize, FileHandle);
return FALSE;
}
//
// Get the DOS Header Base
//
DosHeader = (PIMAGE_DOS_HEADER)BaseAddr; // 0x04000000
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE ||
DosHeader->e_lfanew < sizeof(IMAGE_DOS_HEADER) ||
(LONGLONG)DosHeader->e_lfanew > FileSize.QuadPart ||
(LONGLONG)DosHeader->e_lfanew + (LONGLONG)(Is32Bit ? sizeof(IMAGE_NT_HEADERS32) : sizeof(IMAGE_NT_HEADERS64)) > FileSize.QuadPart)
{
ShowMessages("\nGiven File is not a valid PE file\n");
Result = FALSE;
goto Finished;
}
if (!PeImageReaderInitialize((const BYTE *)BaseAddr, (SIZE_T)FileSize.QuadPart, &Reader))
{
ShowMessages("\nGiven File is not a valid PE file\n");
Result = FALSE;
goto Finished;
}
if (DosHeader->e_lfanew > 0 && (ULONGLONG)DosHeader->e_lfanew <= (ULONGLONG)MAXDWORD)
{
CHAR * DataPtr = NULL;
DWORD BytesRead = 0;
DWORD RichReadSize = (DWORD)DosHeader->e_lfanew;
DWORD SearchOffset = 0;
BOOLEAN HadCandidate = FALSE;
LARGE_INTEGER FileStart = {0};
if (RichReadSize > MaxRichReadSize)
{
RichReadSize = MaxRichReadSize;
RichHeaderStatus = "not found in first 1 MiB";
RichSearchCapped = TRUE;
}
if (RichReadSize >= 8)
{
DataPtr = new (std::nothrow) CHAR[RichReadSize];
if (DataPtr == NULL)
{
RichHeaderStatus = "allocation failed";
goto SkipRichHeader;
}
if (!PlatformReadFileAtOffset(FileHandle, 0, DataPtr, RichReadSize, &BytesRead) || BytesRead != RichReadSize)
{
RichHeaderStatus = "read failed";
delete[] DataPtr;
goto SkipRichHeader;
}
for (;;)
{
CHAR * RichHeaderPtr = NULL;
RichHeaderOffset = FindRichHeader(DosHeader, RichReadSize, SearchOffset, Key);
if (RichHeaderOffset < 0)
{
break;
}
HadCandidate = TRUE;
RichHeaderStatus = RichSearchCapped ? "malformed candidate in first 1 MiB" : "malformed";
INT RichHeaderSize = DecryptRichHeader(Key, RichHeaderOffset, DataPtr, (INT)RichReadSize);
INT IndexPointer = RichHeaderOffset - RichHeaderSize;
if (RichHeaderSize < 16 || RichHeaderSize > MaxRichHeaderSize || ((RichHeaderSize - 16) % 8) != 0 ||
(RichHeaderSize - 16) / 8 == 0 || (RichHeaderSize - 16) / 8 > MaxRichHeaderEntries ||
IndexPointer < 0 || RichHeaderSize > (INT)RichReadSize || IndexPointer > (INT)RichReadSize - RichHeaderSize)
{
SearchOffset = (DWORD)RichHeaderOffset + 1;
continue;
}
RichHeaderPtr = new (std::nothrow) CHAR[RichHeaderSize];
if (RichHeaderPtr == NULL)
{
RichHeaderStatus = "allocation failed";
break;
}
memcpy(RichHeaderPtr, DataPtr + IndexPointer, RichHeaderSize);
if (!FindRichEntries(RichHeaderPtr, RichHeaderSize, Key, &PeFileRichHeaderInfo))
{
delete[] RichHeaderPtr;
SearchOffset = (DWORD)RichHeaderOffset + 1;
continue;
}
RichHeaderPtr = NULL;
if (PeFileRichHeaderInfo.Entries > MaxRichHeaderEntries || PeFileRichHeaderInfo.Entries >= INT_MAX / (INT)sizeof(RICH_HEADER_ENTRY) - 1)
{
delete[] PeFileRichHeaderInfo.PtrToBuffer;
PeFileRichHeaderInfo = {0};
SearchOffset = (DWORD)RichHeaderOffset + 1;
continue;
}
PeFileRichHeader.Entries = new (std::nothrow) RICH_HEADER_ENTRY[PeFileRichHeaderInfo.Entries + 1];
if (PeFileRichHeader.Entries == NULL)
{
RichHeaderStatus = "allocation failed";
delete[] PeFileRichHeaderInfo.PtrToBuffer;
PeFileRichHeaderInfo = {0};
break;
}
if (!SetRichEntries(RichHeaderSize, PeFileRichHeaderInfo.PtrToBuffer, &PeFileRichHeader))
{
delete[] PeFileRichHeaderInfo.PtrToBuffer;
PeFileRichHeaderInfo = {0};
delete[] PeFileRichHeader.Entries;
PeFileRichHeader.Entries = NULL;
SearchOffset = (DWORD)RichHeaderOffset + 1;
continue;
}
RichFound = TRUE;
break;
}
if (!RichFound && !HadCandidate && (DWORD)DosHeader->e_lfanew <= MaxRichReadSize)
{
RichHeaderStatus = "not found";
}
delete[] DataPtr;
}
}
SkipRichHeader:
//
// Check for Valid DOS file
//
if (DosHeader->e_magic == IMAGE_DOS_SIGNATURE)
{
//
// Dump the Dos Header info
//
ShowMessages("\nValid Dos Exe File\n------------------\n");
ShowMessages("\nDumping DOS Header Info....\n---------------------------");
ShowMessages("\n%-36s%s ",
"Magic number : ",
DosHeader->e_magic == 0x5a4d ? "MZ" : "-");
ShowMessages("\n%-36s%#x", "Bytes on last page of file :", DosHeader->e_cblp);
ShowMessages("\n%-36s%#x", "Pages in file : ", DosHeader->e_cp);
ShowMessages("\n%-36s%#x", "Relocation : ", DosHeader->e_crlc);
ShowMessages("\n%-36s%#x",
"Size of header in paragraphs : ",
DosHeader->e_cparhdr);
ShowMessages("\n%-36s%#x",
"Minimum extra paragraphs needed : ",
DosHeader->e_minalloc);
ShowMessages("\n%-36s%#x",
"Maximum extra paragraphs needed : ",
DosHeader->e_maxalloc);
ShowMessages("\n%-36s%#x", "Initial (relative) SS value : ", DosHeader->e_ss);
ShowMessages("\n%-36s%#x", "Initial SP value : ", DosHeader->e_sp);
ShowMessages("\n%-36s%#x", "Checksum : ", DosHeader->e_csum);
ShowMessages("\n%-36s%#x", "Initial IP value : ", DosHeader->e_ip);
ShowMessages("\n%-36s%#x", "Initial (relative) CS value : ", DosHeader->e_cs);
ShowMessages("\n%-36s%#x",
"File address of relocation table : ",
DosHeader->e_lfarlc);
ShowMessages("\n%-36s%#x", "Overlay number : ", DosHeader->e_ovno);
ShowMessages("\n%-36s%#x", "OEM identifier : ", DosHeader->e_oemid);
ShowMessages("\n%-36s%#x",
"OEM information(e_oemid specific) :",
DosHeader->e_oeminfo);
ShowMessages("\n%-36s%#x", "RVA address of PE header : ", DosHeader->e_lfanew);
ShowMessages("\n==============================================================="
"================\n");
}
else
{
ShowMessages("\nGiven File is not a valid DOS file\n");
Result = FALSE;
goto Finished;
}
if (RichFound)
{
ShowMessages("\n===============================================================================\n");
ShowMessages(" RICH HEADER \n");
ShowMessages("===============================================================================\n");
ShowMessages("Entries: %d\n\n", PeFileRichHeaderInfo.Entries);
ShowMessages("%-10s %-10s %-10s\n", "Build ID", "Prod ID", "Use Count");
ShowMessages("---------------------------------------\n");
for (int i = 0; i < PeFileRichHeaderInfo.Entries; i++)
{
ShowMessages("0x%08X 0x%08X %10u\n",
PeFileRichHeader.Entries[i].BuildID,
PeFileRichHeader.Entries[i].ProdID,
PeFileRichHeader.Entries[i].UseCount);
}
ShowMessages("==============Rich Header End ==================\n");
}
else
{
ShowMessages("=========== Rich Header Not Shown (%s) ===========\n", RichHeaderStatus);
}
//
// Offset of NT Header is found at 0x3c location in DOS header specified by
// e_lfanew
// Get the Base of NT Header(PE Header) = DosHeader + RVA address of PE
// header
//
if (Is32Bit)
{
NtHeader32 = (PIMAGE_NT_HEADERS32)((UINT64)(DosHeader) + (DosHeader->e_lfanew));
}
else
{
NtHeader64 = (PIMAGE_NT_HEADERS64)((UINT64)(DosHeader) + (DosHeader->e_lfanew));
}
//
// Identify for valid PE file
//
if (Is32Bit && NtHeader32->Signature == IMAGE_NT_SIGNATURE)
{
ShowMessages("\nValid PE32 file \n-------------\n");
}
else if (!Is32Bit && NtHeader64->Signature == IMAGE_NT_SIGNATURE)
{
ShowMessages("\nValid PE64 file \n-------------\n");
}
else
{
ShowMessages("err, the specified file is not a valid PE file");
Result = FALSE;
goto Finished;
}
//
// Dump NT Header Info....
//
ShowMessages("\nDumping COFF/PE Header "
"Info....\n--------------------------------");
ShowMessages("\n%-36s%s", "Signature :", "PE");
//
// Get the IMAGE FILE HEADER Structure
//
if (Is32Bit)
{
Header = NtHeader32->FileHeader;
}
else
{
Header = NtHeader64->FileHeader;
}
//
// Determine Machine Architecture
//
ShowMessages("\n%-36s", "Machine Architecture :");
//
// Only few are determined (for remaining refer
// to the above specification)
//
switch (Header.Machine)
{
case 0x0:
ShowMessages("All ");
break;
case 0x14d:
ShowMessages("Intel i860");
break;
case 0x14c:
ShowMessages("Intel i386, i486, i586");
break;
case 0x200:
ShowMessages("Intel Itanium processor");
break;
case 0x8664:
ShowMessages("AMD x64");
break;
case 0x162:
ShowMessages("MIPS R3000");
break;
case 0x166:
ShowMessages("MIPS R4000");
break;
case 0x183:
ShowMessages("DEC Alpha AXP");
break;
default:
ShowMessages("Not Found");
break;
}
//
// Determine the characteristics of the given file
//
ShowMessages("\n%-36s", "Characteristics : ");
if ((Header.Characteristics & 0x0002) == 0x0002)
ShowMessages("Executable Image, ");
if ((Header.Characteristics & 0x0020) == 0x0020)
ShowMessages("Application can address > 2GB, ");
if ((Header.Characteristics & 0x1000) == 0x1000)
ShowMessages("System file (Kernel Mode Driver(I think)), ");
if ((Header.Characteristics & 0x2000) == 0x2000)
ShowMessages("Dll file, ");
if ((Header.Characteristics & 0x4000) == 0x4000)
ShowMessages("Application runs only in Uniprocessor, ");
//
// Determine Time Stamp
//
TimeDateStamp = Header.TimeDateStamp;
ShowMessages("\n%-36s%s",
"Time Stamp :",
ctime(&TimeDateStamp));
//
// Determine number of sections
//
ShowMessages("%-36s%d", "No.sections(size) :", Header.NumberOfSections);
ShowMessages("\n%-36s%d", "No.entries in symbol table :", Header.NumberOfSymbols);
ShowMessages("\n%-36s%d",
"Size of optional header :",
Header.SizeOfOptionalHeader);
ShowMessages("\n%-36s%#x", "Raw machine value :", Header.Machine);
ShowMessages("\n%-36s%#x", "Pointer to symbol table :", Header.PointerToSymbolTable);
ShowMessages("\n%-36s%#x", "Raw characteristics value :", Header.Characteristics);
ShowMessages("\n\nDumping PE Optional Header "
"Info....\n-----------------------------------");
if (Is32Bit)
{
//
// Info about Optional Header
//
OpHeader32 = NtHeader32->OptionalHeader;
ShowMessages("\n\nInfo of optional Header\n-----------------------");
ShowMessages("\n%-36s%#x",
"Address of Entry Point : ",
OpHeader32.AddressOfEntryPoint);
ShowMessages("\n%-36s%#x", "Raw optional header magic :", OpHeader32.Magic);
PeShowEntrypointFileOffset(&Reader, OpHeader32.AddressOfEntryPoint);
ShowMessages("\n%-36s%#llx", "Base Address of the Image : ", OpHeader32.ImageBase);
ShowMessages("\n%-36s%s", "SubSystem type : ", PeGetSubsystemName(OpHeader32.Subsystem));
ShowMessages("\n%-36s%s", "Given file is a : ", OpHeader32.Magic == 0x20b ? "PE32+(64)" : "PE32");
ShowMessages("\n%-36s%#x", "File Alignment :", OpHeader32.FileAlignment);
ShowMessages("\n%-36s%#x", "Size of Image :", OpHeader32.SizeOfImage);
ShowMessages("\n%-36s%#x", "Size of Headers :", OpHeader32.SizeOfHeaders);
PeShowChecksum(&Reader,
OpHeader32.CheckSum,
(SIZE_T)(Reader.NtHeaders - Reader.ImageBase) + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER32, CheckSum));
ShowMessages("\n%-36s%#x", "Raw DLL characteristics value :", OpHeader32.DllCharacteristics);
ShowMessages("\n%-36s", "DLL characteristics flags :");
PeShowDllCharacteristics(OpHeader32.DllCharacteristics);
ShowMessages("\n%-36s%d", "Size of code segment(.text) : ", OpHeader32.SizeOfCode);
ShowMessages("\n%-36s%#x",
"Base address of code segment(RVA) :",
OpHeader32.BaseOfCode);
ShowMessages("\n%-36s%d",
"Size of Initialized data : ",
OpHeader32.SizeOfInitializedData);
ShowMessages("\n%-36s%#x",
"Base address of data segment(RVA) :",
OpHeader32.BaseOfData);
ShowMessages("\n%-36s%#x", "Section Alignment :", OpHeader32.SectionAlignment);
ShowMessages("\n%-36s%d", "Major Linker Version : ", OpHeader32.MajorLinkerVersion);
ShowMessages("\n%-36s%d", "Minor Linker Version : ", OpHeader32.MinorLinkerVersion);
PeShowDataDirectories(&Reader, OpHeader32.DataDirectory, OpHeader32.NumberOfRvaAndSizes);
PeShowCertificateTable(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_SECURITY ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY] : &EmptyDirectory);
PeShowBaseRelocations(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_BASERELOC ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] : &EmptyDirectory);
PeShowBoundImports(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT] : &EmptyDirectory);
PeShowResources(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE] : &EmptyDirectory);
PeShowExceptions(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXCEPTION ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION] : &EmptyDirectory,
Header.Machine,
TRUE);
PeShowDelayImports(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT] : &EmptyDirectory,
TRUE,
OpHeader32.ImageBase);
PeShowClrRuntime(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] : &EmptyDirectory);
PeShowTls(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_TLS ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS] : &EmptyDirectory,
TRUE,
OpHeader32.ImageBase);
PeShowDebug(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DEBUG ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] : &EmptyDirectory);
PeShowLoadConfig(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG] : &EmptyDirectory,
TRUE);
PeShowImports(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] : &EmptyDirectory,
TRUE);
PeShowExports(&Reader,
OpHeader32.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXPORT ? &OpHeader32.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] : &EmptyDirectory);
}
else
{
//
// Info about Optional Header
//
OpHeader64 = NtHeader64->OptionalHeader;
ShowMessages("\n\nInfo of optional Header\n-----------------------");
ShowMessages("\n%-36s%#x",
"Address of Entry Point : ",
OpHeader64.AddressOfEntryPoint);
ShowMessages("\n%-36s%#x", "Raw optional header magic :", OpHeader64.Magic);
PeShowEntrypointFileOffset(&Reader, OpHeader64.AddressOfEntryPoint);
ShowMessages("\n%-36s%#llx", "Base Address of the Image : ", OpHeader64.ImageBase);
ShowMessages("\n%-36s%s", "SubSystem type : ", PeGetSubsystemName(OpHeader64.Subsystem));
ShowMessages("\n%-36s%s", "Given file is a : ", OpHeader64.Magic == 0x20b ? "PE32+(64)" : "PE32");
ShowMessages("\n%-36s%#x", "File Alignment :", OpHeader64.FileAlignment);
ShowMessages("\n%-36s%#x", "Size of Image :", OpHeader64.SizeOfImage);
ShowMessages("\n%-36s%#x", "Size of Headers :", OpHeader64.SizeOfHeaders);
PeShowChecksum(&Reader,
OpHeader64.CheckSum,
(SIZE_T)(Reader.NtHeaders - Reader.ImageBase) + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER) + FIELD_OFFSET(IMAGE_OPTIONAL_HEADER64, CheckSum));
ShowMessages("\n%-36s%#x", "Raw DLL characteristics value :", OpHeader64.DllCharacteristics);
ShowMessages("\n%-36s", "DLL characteristics flags :");
PeShowDllCharacteristics(OpHeader64.DllCharacteristics);
ShowMessages("\n%-36s%d", "Size of code segment(.text) : ", OpHeader64.SizeOfCode);
ShowMessages("\n%-36s%#x",
"Base address of code segment(RVA) :",
OpHeader64.BaseOfCode);
ShowMessages("\n%-36s%d",
"Size of Initialized data : ",
OpHeader64.SizeOfInitializedData);
ShowMessages("\n%-36s%#x", "Section Alignment :", OpHeader64.SectionAlignment);
ShowMessages("\n%-36s%d", "Major Linker Version : ", OpHeader64.MajorLinkerVersion);
ShowMessages("\n%-36s%d", "Minor Linker Version : ", OpHeader64.MinorLinkerVersion);
PeShowDataDirectories(&Reader, OpHeader64.DataDirectory, OpHeader64.NumberOfRvaAndSizes);
PeShowCertificateTable(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_SECURITY ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY] : &EmptyDirectory);
PeShowBaseRelocations(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_BASERELOC ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC] : &EmptyDirectory);
PeShowBoundImports(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT] : &EmptyDirectory);
PeShowResources(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_RESOURCE ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE] : &EmptyDirectory);
PeShowExceptions(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXCEPTION ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION] : &EmptyDirectory,
Header.Machine,
FALSE);
PeShowDelayImports(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT] : &EmptyDirectory,
FALSE,
OpHeader64.ImageBase);
PeShowClrRuntime(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR] : &EmptyDirectory);
PeShowTls(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_TLS ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS] : &EmptyDirectory,
FALSE,
OpHeader64.ImageBase);
PeShowDebug(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_DEBUG ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG] : &EmptyDirectory);
PeShowLoadConfig(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG] : &EmptyDirectory,
FALSE);
PeShowImports(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_IMPORT ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] : &EmptyDirectory,
FALSE);
PeShowExports(&Reader,
OpHeader64.NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_EXPORT ? &OpHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] : &EmptyDirectory);
}
ShowMessages("\n\nDumping Sections Header "
"Info....\n--------------------------------");
//
// Retrieve a pointer to First Section Header(or Section Table Entry)
//
SecHeader = Reader.SectionHeaders;
NumberOfSections = Reader.FileHeader->NumberOfSections;
for (UINT32 i = 0; i < NumberOfSections; i++, SecHeader++)
{
CHAR SectionName[IMAGE_SIZEOF_SHORT_NAME + 1];
const BYTE * Pointer = NULL;
const CHAR * RawDataBounds = "empty";
PeImageReaderGetSectionName(SecHeader, SectionName, sizeof(SectionName));
if (SecHeader->SizeOfRawData != 0)
{
RawDataBounds = PeImageReaderGetPointerAtOffset(&Reader,
SecHeader->PointerToRawData,
SecHeader->SizeOfRawData,
&Pointer)
? "valid"
: "invalid";
}
ShowMessages("\n\nSection Info (%d of %d)", i + 1, NumberOfSections);
ShowMessages("\n---------------------");
ShowMessages("\n%-36s%s", "Section Header name : ", SectionName);
ShowMessages("\n%-36s%#x",
"ActualSize of code or data : ",
SecHeader->Misc.VirtualSize);
ShowMessages("\n%-36s%#x", "Virtual Address(RVA) :", SecHeader->VirtualAddress);
ShowMessages("\n%-36s%#x",
"Size of raw data (rounded to FA) : ",
SecHeader->SizeOfRawData);
ShowMessages("\n%-36s%#x",
"Pointer to Raw Data : ",
SecHeader->PointerToRawData);
ShowMessages("\n%-36s%s", "Raw data bounds :", RawDataBounds);
if (Pointer != NULL && SecHeader->SizeOfRawData != 0)
{
if (SecHeader->SizeOfRawData > MaxSectionScanBytes)
{
ShowMessages("\n%-36s%s", "Raw data entropy :", "skipped, section is larger than local cap");
ShowMessages("\n%-36s%s", "Raw data FNV-1a64 :", "skipped, section is larger than local cap");
}
else if ((ULONGLONG)SecHeader->SizeOfRawData > RemainingSectionScanBytes)
{
RemainingSectionScanBytes = 0;
ShowMessages("\n%-36s%s", "Raw data entropy :", "skipped, global scan budget exhausted");
ShowMessages("\n%-36s%s", "Raw data FNV-1a64 :", "skipped, global scan budget exhausted");
}
else
{
ShowMessages("\n%-36s%.4f", "Raw data entropy :", PeCalculateEntropy(Pointer, SecHeader->SizeOfRawData));
ShowMessages("\n%-36s%#llx", "Raw data FNV-1a64 :", PeFnv1a64(Pointer, SecHeader->SizeOfRawData));
RemainingSectionScanBytes -= SecHeader->SizeOfRawData;
}
}
if (SecHeader->SizeOfRawData != 0 && SecHeader->Misc.VirtualSize > SecHeader->SizeOfRawData)
{
ShowMessages("\n%-36s%s", "Warning :", "virtual size is larger than raw data");
}
ShowMessages("\n%-36s%#x",
"Pointer to Relocations : ",
SecHeader->PointerToRelocations);
ShowMessages("\n%-36s%#x",
"Pointer to Line numbers : ",
SecHeader->PointerToLinenumbers);
ShowMessages("\n%-36s%#x",
"Number of relocations : ",
SecHeader->NumberOfRelocations);
ShowMessages("\n%-36s%#x",
"Number of line numbers : ",
SecHeader->NumberOfLinenumbers);
ShowMessages("\n%-36s%s", "Characteristics : ", "Contains ");
if ((SecHeader->Characteristics & 0x20) == 0x20)
ShowMessages("executable code, ");
if ((SecHeader->Characteristics & 0x40) == 0x40)
ShowMessages("initialized data, ");
if ((SecHeader->Characteristics & 0x80) == 0x80)
ShowMessages("uninitialized data, ");
if ((SecHeader->Characteristics & 0x200) == 0x200)
ShowMessages("comments and linker commands, ");
if ((SecHeader->Characteristics & 0x10000000) == 0x10000000)
ShowMessages("shareable data(via DLLs), ");
if ((SecHeader->Characteristics & 0x40000000) == 0x40000000)
ShowMessages("Readable, ");
if ((SecHeader->Characteristics & 0x80000000) == 0x80000000)
ShowMessages("Writable, ");
//
// show the hex dump if the user needs it
//
if (SectionToShow != NULL)
{
if (!_strcmpi(SectionToShow, SectionName))
{
SectionFound = TRUE;
if (SecHeader->SizeOfRawData != 0)
{
SIZE_T DumpSize = SecHeader->SizeOfRawData;
BOOLEAN TruncatedBySectionCap = FALSE;
BOOLEAN TruncatedByGlobalCap = FALSE;
if (Pointer == NULL)
{
ShowMessages("\nerr, invalid section raw data\n");
continue;
}
if (RemainingSectionDumpBytes == 0)
{
ShowMessages("\n%-36s%s", "Warning, section dump skipped :", "global dump budget exhausted");
continue;
}
if (DumpSize > MaxSectionDumpBytes)
{
DumpSize = MaxSectionDumpBytes;
TruncatedBySectionCap = TRUE;
}
if ((ULONGLONG)DumpSize > RemainingSectionDumpBytes)
{
DumpSize = (SIZE_T)RemainingSectionDumpBytes;
TruncatedByGlobalCap = TRUE;
}
if (TruncatedBySectionCap)
{
ShowMessages("\n%-36s%#llx of %#x bytes",
"Warning, section dump truncated by section cap :",
(UINT64)DumpSize,
SecHeader->SizeOfRawData);
}
if (TruncatedByGlobalCap)
{
ShowMessages("\n%-36s%#llx of %#x bytes",
"Warning, section dump truncated by global cap :",
(UINT64)DumpSize,
SecHeader->SizeOfRawData);
}
if (Is32Bit)
{
PeHexDump((CHAR *)Pointer,
DumpSize,
(ULONGLONG)OpHeader32.ImageBase + SecHeader->VirtualAddress);
}
else
{
PeHexDump((CHAR *)Pointer,
DumpSize,
OpHeader64.ImageBase + SecHeader->VirtualAddress);
}
RemainingSectionDumpBytes -= DumpSize;
}
}
}
}
PeShowOverlayAndWarnings(&Reader,
Header.Machine,
Is32Bit ? OpHeader32.Magic : OpHeader64.Magic,
Is32Bit ? OpHeader32.AddressOfEntryPoint : OpHeader64.AddressOfEntryPoint,
Is32Bit ? OpHeader32.SizeOfImage : OpHeader64.SizeOfImage,
Is32Bit ? OpHeader32.SizeOfHeaders : OpHeader64.SizeOfHeaders,
Is32Bit ? OpHeader32.DataDirectory : OpHeader64.DataDirectory,
Is32Bit ? OpHeader32.NumberOfRvaAndSizes : OpHeader64.NumberOfRvaAndSizes);
if (SectionToShow != NULL && !SectionFound)
{
ShowMessages("\nerr, section '%s' was not found\n", SectionToShow);
}
ShowMessages("\n==============================================================="
"================\n");
//
// Set result to true
//
Result = TRUE;
Finished:
//
// Unmap and close the handles
//
if (PeFileRichHeaderInfo.PtrToBuffer != NULL)
{
delete[] PeFileRichHeaderInfo.PtrToBuffer;
}
if (PeFileRichHeader.Entries != NULL)
{
delete[] PeFileRichHeader.Entries;
}
PlatformUnmapFile(BaseAddr, MappedSize, FileHandle);
return Result;
}
/**
* @brief Detect whether PE is a 32-bit PE or 64-bit PE
* @param AddressOfFile
* @param Is32Bit
*
* @return BOOLEAN
*/
BOOLEAN
PeIsPE32BitOr64Bit(const WCHAR * AddressOfFile, PBOOLEAN Is32Bit)
{
BOOLEAN Result = FALSE;
HANDLE FileHandle = INVALID_HANDLE_VALUE; // Open file handle
SIZE_T MappedSize = 0; // Size of the mapped file in bytes
LPVOID BaseAddr; // Pointer to the base memory of mapped file
PIMAGE_DOS_HEADER DosHeader; // Pointer to DOS Header
PIMAGE_NT_HEADERS32 NtHeader32 = NULL; // Pointer to NT Header 32 bit
PE_IMAGE_READER Reader;
LARGE_INTEGER FileSize;
//
// Open and map the EXE file read-only
//
BaseAddr = PlatformMapFileReadOnly(AddressOfFile, &MappedSize, &FileHandle);
if (BaseAddr == NULL)
{
ShowMessages("err, unable to read or map the file (%x)\n", PlatformGetLastError());
return FALSE;
}
FileSize.QuadPart = (LONGLONG)MappedSize;
if (FileSize.QuadPart < (LONGLONG)sizeof(IMAGE_DOS_HEADER))
{
PlatformUnmapFile(BaseAddr, MappedSize, FileHandle);
return FALSE;
}
//
// Get the DOS Header Base
//
DosHeader = (PIMAGE_DOS_HEADER)BaseAddr; // 0x04000000
//
// Check for Valid DOS file
//
if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
Result = FALSE;
ShowMessages("err, the selected file is not in a valid PE format\n");
goto Finished;
}
if (DosHeader->e_lfanew < sizeof(IMAGE_DOS_HEADER) ||
(LONGLONG)DosHeader->e_lfanew + (LONGLONG)sizeof(IMAGE_NT_HEADERS32) > FileSize.QuadPart)
{
Result = FALSE;
ShowMessages("err, invalid image NT header offset\n");
goto Finished;
}
//
// Offset of NT Header is found at 0x3c location in DOS header specified by
// e_lfanew
// Get the Base of NT Header(PE Header) = DosHeader + RVA address of PE
// header
//
NtHeader32 = (PIMAGE_NT_HEADERS32)((UINT64)(DosHeader) + (DosHeader->e_lfanew));
//
// Identify for valid PE file
//
if (NtHeader32->Signature != IMAGE_NT_SIGNATURE)
{
Result = FALSE;
ShowMessages("err, invalid image NT signature\n");
goto Finished;
}
if (!PeImageReaderInitialize((const BYTE *)BaseAddr, (SIZE_T)FileSize.QuadPart, &Reader))
{
Result = FALSE;
ShowMessages("err, the selected file is not in a valid PE format\n");
goto Finished;
}
//
// Only few are determined (for remaining refer
// to the above specification)
//
switch (Reader.FileHeader->Machine)
{
case IMAGE_FILE_MACHINE_I386:
*Is32Bit = TRUE;
Result = TRUE;
goto Finished;
break;
case IMAGE_FILE_MACHINE_AMD64:
*Is32Bit = FALSE;
Result = TRUE;
goto Finished;
break;
default:
ShowMessages("err, PE file is not i386 or AMD64; thus, it's not supported "
"in HyperDbg\n");
Result = FALSE;
goto Finished;
break;
}
Finished:
//
// Unmap and close the handles
//
PlatformUnmapFile(BaseAddr, MappedSize, FileHandle);
return Result;
}
/**
* @brief Get the syscall number of a given Nt function
* @param NtFunctionName
*
* @return UINT32
*/
UINT32
PeGetSyscallNumber(LPCSTR NtFunctionName)
{
HMODULE DllHandle = NULL;
//
// Load ntdll.dll to get the address of the Nt function
//
DllHandle = LoadLibraryA("ntdll.dll");
if (!DllHandle)
{
ShowMessages("err, failed to load ntdll.dll\n");
return NULL;
}
//
// Choose any Nt function
//
VOID * TargetFunc = GetProcAddress(DllHandle, NtFunctionName);
if (!TargetFunc)
{
//
// If we failed to get the address of the Nt function,
// maybe it's from win32u.dll
//
DllHandle = LoadLibraryA("win32u.dll");
if (!DllHandle)
{
ShowMessages("err, failed to load win32u.dll\n");
return NULL;
}
TargetFunc = GetProcAddress(DllHandle, NtFunctionName);
if (!TargetFunc)
{
ShowMessages("err, failed to get address of %s\n", NtFunctionName);
return NULL;
}
}
//
// By default, we send 30 bytes to the disassembler,
// since usually the syscall handler is less than 30 bytes,
// and we want to avoid disassembling another function
//
return HyperDbgGetImmediateValueOnEaxForSyscallNumber((UCHAR *)TargetFunc, 30, TRUE);
}