1
0
Fork 0

[DBG] Work on modinfo improvements:

- Add ImageNtHeaders() (clone of RtlImageNtHeaderEx which doesn't exist on XP) to obtain PE headers given a VA
- Add HEADER_FIELD() and THUNK_VAL() macros to module.h to allow accessing header fields independent of process and file bitness
- Add IMAGE_NT_HEADERS pointer to MODINFO, since anything related to parsing PEs needs this struct
- Read PE headers in GetModuleInfo(). Currently the headers are being parsed every time a TitanEngine helper function is called, the goal is to reduce this to once per module load
- GetModuleInfo(): eliminate all TitanEngine calls now that we have the headers
- Add RvaToVa() for SEC_COMMIT mappings. This can simultaneously serve as replacement for rva2offset helpers (pass base = 0). Preferably SEC_IMAGE should be used though as that way neither of these would be needed
- ReadExportDirectory(): use RtlImageDirectoryEntryToData() to obtain a PIMAGE_EXPORT_DIRECTORY and its size in one go to eliminate TitanEngine helper calls and RVA to offset conversions
- Answer burning questions re: Windows loader behaviour when parsing exports in comments
- (Minor) fix '>= 0' comparison against unsigned as this will always evaluate to true
- Add comment re: PDB search path order since it's wrong atm but I'm too scared of breaking something if I change this code myself
This commit is contained in:
Mattiwatti 2018-03-18 07:01:23 +01:00 committed by Duncan Ogilvie
parent 013cd1e5f7
commit fc9285ed2e
No known key found for this signature in database
GPG Key ID: FC89E0AAA0C1AAD8
2 changed files with 115 additions and 29 deletions

View File

@ -1,5 +1,6 @@
#include "module.h"
#include "TitanEngine/TitanEngine.h"
#include "ntdll/ntdll.h"
#include "threading.h"
#include "symbolinfo.h"
#include "murmurhash.h"
@ -15,28 +16,81 @@
std::map<Range, std::unique_ptr<MODINFO>, RangeCompare> modinfo;
std::unordered_map<duint, std::string> hashNameMap;
// RtlImageNtHeaderEx is much better than the non-Ex version due to stricter validation, but isn't available on XP x86.
// This is essentially a fallback replacement that does the same thing
static NTSTATUS ImageNtHeaders(duint base, duint size, PIMAGE_NT_HEADERS* outHeaders)
{
PIMAGE_NT_HEADERS ntHeaders;
__try
{
if(base == 0 || outHeaders == nullptr)
return STATUS_INVALID_PARAMETER;
if(size < sizeof(IMAGE_DOS_HEADER))
return STATUS_INVALID_IMAGE_FORMAT;
const PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)base;
if(dosHeaders->e_magic != IMAGE_DOS_SIGNATURE)
return STATUS_INVALID_IMAGE_FORMAT;
const ULONG e_lfanew = dosHeaders->e_lfanew;
const ULONG sizeOfPeSignature = sizeof('PE00');
if(e_lfanew >= size ||
e_lfanew >= (ULONG_MAX - sizeOfPeSignature - sizeof(IMAGE_FILE_HEADER)) ||
(e_lfanew + sizeOfPeSignature + sizeof(IMAGE_FILE_HEADER)) >= size)
return STATUS_INVALID_IMAGE_FORMAT;
ntHeaders = (PIMAGE_NT_HEADERS)((PCHAR)base + e_lfanew);
// RtlImageNtHeaderEx verifies that the range does not cross the UM <-> KM boundary here,
// but it would cost a syscall to query this address as it varies between OS versions // TODO: or do we already have this info somewhere?
if(!MemIsCanonicalAddress((duint)ntHeaders + sizeof(IMAGE_NT_HEADERS)))
return STATUS_INVALID_IMAGE_FORMAT;
if(ntHeaders->Signature != IMAGE_NT_SIGNATURE)
return STATUS_INVALID_IMAGE_FORMAT;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return GetExceptionCode();
}
*outHeaders = ntHeaders;
return STATUS_SUCCESS;
}
// Use only with SEC_COMMIT mappings, not SEC_IMAGE! (in that case, just do VA = base + rva...)
static duint RvaToVa(duint base, PIMAGE_NT_HEADERS ntHeaders, ULONG_PTR rva)
{
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
const WORD numSections = ntHeaders->FileHeader.NumberOfSections;
for(WORD i = 0; i < numSections; ++i)
{
if(section->VirtualAddress <= rva &&
section->VirtualAddress + section->Misc.VirtualSize > rva)
{
return base + (rva - section->VirtualAddress) + section->PointerToRawData;
}
section++;
}
return 0;
}
static void ReadExportDirectory(MODINFO & Info, ULONG_PTR FileMapVA)
{
// TODO: proper bounds checking
duint exportDirRva = GetPE32DataFromMappedFile(FileMapVA, 0, UE_EXPORTTABLEADDRESS);
duint exportDirSize = GetPE32DataFromMappedFile(FileMapVA, 0, UE_EXPORTTABLESIZE);
if(!exportDirRva || !exportDirSize)
// Get the export directory and its size
ULONG exportDirSize;
auto exportDir = (PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData((PVOID)FileMapVA,
FALSE,
IMAGE_DIRECTORY_ENTRY_EXPORT,
&exportDirSize);
if(exportDirSize == 0 || exportDir == nullptr || exportDir->NumberOfFunctions == 0)
return;
auto rva2offset = [&Info, FileMapVA](duint rva)
auto rva2offset = [&Info](duint rva)
{
return ConvertVAtoFileOffsetEx(FileMapVA, Info.loadedSize, 0, rva, true, false);
return RvaToVa(0, Info.headers, rva);
};
auto exportDirOffset = rva2offset(exportDirRva);
if(!exportDirOffset)
return;
auto exportDir = PIMAGE_EXPORT_DIRECTORY(exportDirOffset + FileMapVA);
if(!exportDir->NumberOfFunctions)
return;
auto addressOfFunctionsOffset = rva2offset(exportDir->AddressOfFunctions);
if(!addressOfFunctionsOffset)
return;
@ -58,21 +112,23 @@ static void ReadExportDirectory(MODINFO & Info, ULONG_PTR FileMapVA)
auto & entry = Info.exports.back();
entry.ordinal = i + exportDir->Base;
entry.rva = addressOfFunctions[i];
if(entry.forwarded = entry.rva >= exportDirRva && entry.rva < exportDirRva + exportDirSize)
const auto entryVa = RvaToVa(FileMapVA, Info.headers, entry.rva);
entry.forwarded = entryVa >= (duint)exportDir;
if(entry.forwarded && entryVa < (duint)exportDir + exportDirSize)
{
auto forwardNameOffset = rva2offset(entry.rva);
if(forwardNameOffset) // TODO: what does the Windows loader do if this fails?
if(forwardNameOffset) // Silent ignore (1) by ntdll loader: invalid forward names or addresses of forward names
entry.forwardName = String((const char*)(forwardNameOffset + FileMapVA));
}
}
for(DWORD i = 0; i < exportDir->NumberOfNames; i++)
{
auto index = addressOfNameOrdinals[i];
if(index >= 0 && index < Info.exports.size()) // TODO: what does the Windows loader do if this fails?
DWORD index = addressOfNameOrdinals[i];
if(index < Info.exports.size()) // Silent ignore (2) by ntdll loader: bogus AddressOfNameOrdinals indices
{
auto nameOffset = rva2offset(addressOfNames[i]);
if(nameOffset) // TODO: what does the Windows loader do if this fails?
if(nameOffset) // Silent ignore (3) by ntdll loader: invalid names or addresses of names
Info.exports[index].name = String((const char*)(nameOffset + FileMapVA));
}
}
@ -411,6 +467,11 @@ void ReadDebugDirectory(MODINFO & Info, ULONG_PTR FileMapVA)
file = Info.pdbFile.substr(lastIdx + 1);
}
// TODO: this order is exactly the wrong way around :P
// It should be: symbol cache (by far the most likely location, also why it exists) -> PDB path in PE -> program directory.
// (this is also the search order used by WinDbg/symchk/dumpbin and anything that uses symsrv)
// WinDbg even tries HTTP servers before the path in the PE, but that might be taking it a bit too far
// Program directory
char pdbPath[MAX_PATH];
strcpy_s(pdbPath, Info.path);
@ -438,8 +499,15 @@ void ReadDebugDirectory(MODINFO & Info, ULONG_PTR FileMapVA)
void GetModuleInfo(MODINFO & Info, ULONG_PTR FileMapVA)
{
// Get the PE headers
if(!NT_SUCCESS(ImageNtHeaders(FileMapVA, Info.loadedSize, &Info.headers)))
{
dprintf(QT_TRANSLATE_NOOP("DBG", "Module %s%s: invalid PE file!\n"), Info.name, Info.extension);
return;
}
// Get the entry point
duint moduleOEP = GetPE32DataFromMappedFile(FileMapVA, 0, UE_OEP);
duint moduleOEP = HEADER_FIELD(Info.headers, AddressOfEntryPoint);
// Fix a problem where the OEP is set to zero (non-existent).
// OEP can't start at the PE header/offset 0 -- except if module is an EXE.
@ -447,31 +515,34 @@ void GetModuleInfo(MODINFO & Info, ULONG_PTR FileMapVA)
if(!moduleOEP)
{
WORD characteristics = (WORD)GetPE32DataFromMappedFile(FileMapVA, 0, UE_CHARACTERISTICS);
// If this wasn't an exe, invalidate the entry point
if((characteristics & IMAGE_FILE_DLL) == IMAGE_FILE_DLL)
if((Info.headers->FileHeader.Characteristics & IMAGE_FILE_DLL) == IMAGE_FILE_DLL)
Info.entry = 0;
}
// Enumerate all PE sections
Info.sections.clear();
int sectionCount = (int)GetPE32DataFromMappedFile(FileMapVA, 0, UE_SECTIONNUMBER);
WORD sectionCount = Info.headers->FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER ntSection = IMAGE_FIRST_SECTION(Info.headers);
for(int i = 0; i < sectionCount; i++)
for(WORD i = 0; i < sectionCount; i++)
{
MODSECTIONINFO curSection;
memset(&curSection, 0, sizeof(MODSECTIONINFO));
curSection.addr = GetPE32DataFromMappedFile(FileMapVA, i, UE_SECTIONVIRTUALOFFSET) + Info.base;
curSection.size = GetPE32DataFromMappedFile(FileMapVA, i, UE_SECTIONVIRTUALSIZE);
const char* sectionName = (const char*)GetPE32DataFromMappedFile(FileMapVA, i, UE_SECTIONNAME);
curSection.addr = ntSection->VirtualAddress + Info.base;
curSection.size = ntSection->Misc.VirtualSize;
// Null-terminate section name
char sectionName[IMAGE_SIZEOF_SHORT_NAME + 1];
strncpy_s(sectionName, (const char*)ntSection->Name, IMAGE_SIZEOF_SHORT_NAME);
// Escape section name when needed
strcpy_s(curSection.name, StringUtils::Escape(sectionName).c_str());
// Add entry to the vector
Info.sections.push_back(curSection);
ntSection++;
}
ReadExportDirectory(Info, FileMapVA);

View File

@ -6,6 +6,19 @@
#include "symbolsourcebase.h"
// Macros to safely access IMAGE_NT_HEADERS fields since the compile-time typedef of this struct may not match the actual file bitness.
// Never access OptionalHeader.xx values directly unless they have the same size and offset on 32 and 64 bit. IMAGE_FILE_HEADER fields are safe to use
#define IMAGE32(NtHeaders) ((NtHeaders) != nullptr && (NtHeaders)->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)
#define IMAGE64(NtHeaders) ((NtHeaders) != nullptr && (NtHeaders)->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
#define HEADER_FIELD(NtHeaders, Field) (IMAGE64(NtHeaders) \
? ((PIMAGE_NT_HEADERS64)(NtHeaders))->OptionalHeader.Field : (IMAGE32(NtHeaders) \
? ((PIMAGE_NT_HEADERS32)(NtHeaders))->OptionalHeader.Field \
: 0))
#define THUNK_VAL(NtHeaders, Ptr, Val) (IMAGE64(NtHeaders) \
? ((PIMAGE_THUNK_DATA64)(Ptr))->Val : (IMAGE32(NtHeaders) \
? ((PIMAGE_THUNK_DATA32)(Ptr))->Val \
: 0))
struct MODSECTIONINFO
{
duint addr; // Virtual address
@ -69,6 +82,8 @@ struct MODINFO
char extension[MAX_MODULE_SIZE]; // File extension (including the dot)
char path[MAX_PATH]; // File path (in UTF8)
PIMAGE_NT_HEADERS headers = nullptr; // Image headers. Always use HEADER_FIELD() to access OptionalHeader values
std::vector<MODSECTIONINFO> sections;
std::vector<MODRELOCATIONINFO> relocations;
std::vector<duint> tlsCallbacks;