mirror of
https://github.com/winterknife/SILVERPICK
synced 2026-06-08 18:21:15 +00:00
170 lines
7.4 KiB
C++
170 lines
7.4 KiB
C++
// ========================================================================
|
|
// File: PEParse.cpp
|
|
//
|
|
// Author: winterknife
|
|
//
|
|
// Description: This source file contains routine(s) for parsing Windows
|
|
// Portable Executable (PE) files mapped in UVAS without any external
|
|
// dependencies including CRT, Win32, or Native API routines
|
|
//
|
|
// Modifications:
|
|
// 2025-09-25 Created
|
|
// 2025-11-12 Updated
|
|
// ========================================================================
|
|
|
|
// ========================================================================
|
|
// Includes
|
|
// ========================================================================
|
|
|
|
#include "PEParse.h"
|
|
#include <algorithm>
|
|
|
|
// ========================================================================
|
|
// Routines
|
|
// ========================================================================
|
|
|
|
#pragma region ROUTINES
|
|
|
|
_Use_decl_annotations_
|
|
PVOID __stdcall get_exported_symbol_address_by_hash(
|
|
PVOID pImageBase,
|
|
QWORD qwTargetExportHash
|
|
) {
|
|
// Init local variables
|
|
PIMAGE_DOS_HEADER pImageDosHeader = nullptr;
|
|
DWORD dwElfanew = 0;
|
|
PIMAGE_NT_HEADERS pImageNtHeaders = nullptr;
|
|
PIMAGE_OPTIONAL_HEADER64 pImageOptionalHeader64 = nullptr;
|
|
PIMAGE_DATA_DIRECTORY pImageDataDirectory = nullptr;
|
|
DWORD dwExportDirectoryRva = 0;
|
|
DWORD dwExportDirectorySize = 0;
|
|
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = nullptr;
|
|
DWORD dwTotalExportCount = 0;
|
|
DWORD dwNamedExportCount = 0;
|
|
PDWORD pdwAddressTable = nullptr;
|
|
PDWORD pdwNameTable = nullptr;
|
|
PWORD pwoNameOrdinalTable = nullptr;
|
|
WORD woOrdinalBase = 0;
|
|
WORD woExportOrdinal = 0;
|
|
WORD woExportEatIndex = 0;
|
|
DWORD dwIndex = 0;
|
|
PCSTR strExportName = nullptr;
|
|
DWORD_PTR dwptrExportNameLength = 0;
|
|
QWORD qwExportHash = 0;
|
|
BOOLEAN bFlag = false;
|
|
PVOID pExportSymbol = nullptr;
|
|
|
|
// Sanity check the function parameters
|
|
if (pImageBase == nullptr || pImageBase == INVALID_HANDLE_VALUE)
|
|
return nullptr;
|
|
|
|
// Get the address of the DOS header - nt!_IMAGE_DOS_HEADER structure
|
|
pImageDosHeader = STATIC_CAST(PIMAGE_DOS_HEADER, pImageBase);
|
|
if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
|
|
return nullptr; // not a valid PE
|
|
|
|
// Get the offset to the NT headers
|
|
dwElfanew = pImageDosHeader->e_lfanew;
|
|
if (dwElfanew >= (256U * 1024U * 1024U))
|
|
return nullptr; // offset must not be larger than 256 MB
|
|
|
|
// Get the address of the NT headers - nt!_IMAGE_NT_HEADERS32/64 structure
|
|
pImageNtHeaders = STATIC_CAST(PIMAGE_NT_HEADERS, convert_rva_to_va(pImageBase, dwElfanew));
|
|
if (pImageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
|
|
return nullptr; // not a valid PE
|
|
|
|
// Check if the module is a 64-bit binary (PE32+)
|
|
if (pImageNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC)
|
|
return nullptr; // PE32
|
|
|
|
// Get the address of the Optional header - nt!_IMAGE_OPTIONAL_HEADER64 structure
|
|
pImageOptionalHeader64 = &(pImageNtHeaders->OptionalHeader);
|
|
|
|
// Get the address of the first data directory - nt!_IMAGE_DATA_DIRECTORY structure
|
|
pImageDataDirectory = pImageOptionalHeader64->DataDirectory;
|
|
|
|
// Get the RVA of the data for the export directory
|
|
dwExportDirectoryRva = pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
|
if (dwExportDirectoryRva == 0)
|
|
return nullptr;
|
|
|
|
// Get the size of the data for the export directory
|
|
dwExportDirectorySize = pImageDataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
|
|
|
|
// Get the AVA of the data for the export directory - nt!_IMAGE_EXPORT_DIRECTORY structure
|
|
pImageExportDirectory = STATIC_CAST(PIMAGE_EXPORT_DIRECTORY, convert_rva_to_va(pImageBase, dwExportDirectoryRva));
|
|
|
|
// Get the total number of exports
|
|
// WARNING: If EAF/EAF+ exploit mitigation is enabled, then the following line of code will crash the host process
|
|
dwTotalExportCount = pImageExportDirectory->NumberOfFunctions;
|
|
if (dwTotalExportCount == 0)
|
|
return nullptr; // no exports
|
|
|
|
// Get the number of named exports
|
|
dwNamedExportCount = pImageExportDirectory->NumberOfNames;
|
|
|
|
// Get the address of the array of RVAs of exported symbols - Export Address Table (EAT)
|
|
pdwAddressTable = STATIC_CAST(PDWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfFunctions));
|
|
|
|
// Get the address of the array of RVAs of exported symbol names as ANSI strings - Export Name Table (ENT)
|
|
pdwNameTable = STATIC_CAST(PDWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfNames));
|
|
|
|
// Get the address of the array of exported symbol name ordinals/zero-indexed indices for export names into the EAT - Export Name Ordinal Table (ENOT)
|
|
pwoNameOrdinalTable = STATIC_CAST(PWORD, convert_rva_to_va(pImageBase, pImageExportDirectory->AddressOfNameOrdinals));
|
|
|
|
// Get the starting ordinal number
|
|
woOrdinalBase = LOWORD(pImageExportDirectory->Base); // default = 1
|
|
|
|
// Check if we received an ordinal number
|
|
if (HIWORD(qwTargetExportHash) == 0) {
|
|
// Get the target ordinal value
|
|
woExportOrdinal = LOWORD(qwTargetExportHash);
|
|
if (woExportOrdinal < woOrdinalBase)
|
|
return nullptr;
|
|
|
|
// Get the zero-indexed index for the export ordinal into the EAT
|
|
woExportEatIndex = woExportOrdinal - woOrdinalBase;
|
|
}
|
|
// Else we received a hash of the export name
|
|
else if (qwTargetExportHash) {
|
|
// Lookup the desired name in the name table using linear search
|
|
for (dwIndex = 0; dwIndex < dwNamedExportCount; dwIndex++) {
|
|
// Get the name of the current exported symbol
|
|
strExportName = STATIC_CAST(PCSTR, convert_rva_to_va(pImageBase, pdwNameTable[dwIndex]));
|
|
|
|
// Get the length of the null-terminated ANSI string representing the name of the current exported symbol
|
|
dwptrExportNameLength = std::min(BIT_CAST(DWORD_PTR, scan_memory(strExportName, 0x00, MAX_EXPORTED_SYMBOL_NAME_LEN)) - BIT_CAST(DWORD_PTR, strExportName), MAX_EXPORTED_SYMBOL_NAME_LEN);
|
|
|
|
// Get the 64-bit FNV-1a hash of the current exported symbol name
|
|
qwExportHash = HASH_STRING_RUN_TIME(strExportName, dwptrExportNameLength);
|
|
|
|
// Check if the hash of the current exported symbol name matches the hash of the target exported symbol name
|
|
if (qwExportHash == qwTargetExportHash) {
|
|
bFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the flag is not set, then a matching table entry was not found
|
|
if (bFlag == false)
|
|
return nullptr;
|
|
|
|
// Get the zero-indexed index for the export name into the EAT
|
|
woExportEatIndex = pwoNameOrdinalTable[dwIndex];
|
|
}
|
|
|
|
// Validate the ordinal by checking if the index is within the bounds of the EAT
|
|
if (woExportEatIndex >= dwTotalExportCount)
|
|
return nullptr;
|
|
|
|
// Get the address of the exported symbol
|
|
pExportSymbol = convert_rva_to_va(pImageBase, pdwAddressTable[woExportEatIndex]);
|
|
|
|
// Check if the exported symbol RVA is a forwarder RVA by checking if the function pointer lies within the target module's export directory range
|
|
if ((pExportSymbol > pImageExportDirectory) && (pExportSymbol < convert_rva_to_va(pImageExportDirectory, dwExportDirectorySize)))
|
|
return nullptr;
|
|
|
|
return pExportSymbol;
|
|
}
|
|
|
|
#pragma endregion |