I did not know there was an entire manual on the PE/COFF file-format, lol. That would've saved me tons of time and would've made writing this a million times easier. I wonder why it isn't on the MSDN websites. Is it offically supported by MS, or is it just something published in the name of one of their employers?
Thanks for the tip on the IMAGE_SCN_MEM_DISCARDABLE, I'll be sure to add that to the loader's code.
Also, I have simplified the manner in which I allocate sections by just using the SizeOfRawData as you had suggested. ALong with that, I have changed my relocations module to only relocate HIGHLOW relocations.
I did not need to translate RVAs to file-offsets because, by mapping the relocations section in the process, I would simply read them as mapped in memory using the virtualaddress member. However, now that I am no longer mapping in the relocations section (because it has the IMAGE_SCN_MEM_DISCARDABLE flag set) I have to read the relocations directory from the file and thus now need to calculate file-offsets from their respective rvas. So I have done that appropriately (only one place where it was really required)
And about loading older versions of the C++ runtime, no I haven't checked. However it probably will not work as you suggested. I will look in to fixing that.
I will be wrapping this entire module inside of an OOP interface, so it will be much easier to interface with.
About getting the page permissions, I'd rather just leave it the way it is, it works perfectly fine and gives the pages their respective permissions.
Thanks for all the feed-back, tbh I didn't expect such a descriptive and informative response and I really appreciate it.
*edit*
o.o I am not going to write an XML parser or statically link an XML parser to this. There would simply be too much code, it would be too easy to pickup with a signature scanner. (Granted, I haven't really designed this with that in mind :S)
Here is the latest revision:
Code:
// testttt.cpp : Defines the entry point for the console application.
//Author : [MPGH] Jetamay (mpgh.net)
#include "stdafx.h"
#include <string>
#include <Windows.h>
#include <iostream>
typedef BOOL (WINAPI* DllMain_t)(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpReserved);
class Exception
{
private:
std::string m_sReason;
public:
Exception(const std::string& sReason)
{
m_sReason = sReason;
}
~Exception()
{
}
std::string getReason()
{
return m_sReason;
}
};
inline void calculateRelocation(const long difference, const unsigned long ulBase, const WORD wOffset)
{
const unsigned long relocationType = wOffset>>12;
unsigned long ulDest = (wOffset & (0xFFF));
switch(relocationType)
{
case IMAGE_REL_BASED_HIGHLOW:
*reinterpret_cast<unsigned long*>(ulDest + ulBase) += difference;
break;
case IMAGE_REL_BASED_ABSOLUTE:
default:
break;
};
}
void setSectionPermissions(void* pAddress, unsigned long ulSize, unsigned long ulCharacteristics)
{
//Correct section permissions.
unsigned long ulPermissions = 0;
if(ulCharacteristics & IMAGE_SCN_MEM_EXECUTE)
ulPermissions = PAGE_EXECUTE;
if(ulCharacteristics & IMAGE_SCN_MEM_READ)
ulPermissions = PAGE_READONLY;
if(ulCharacteristics & IMAGE_SCN_MEM_WRITE)
ulPermissions = PAGE_READWRITE;
if((ulCharacteristics & IMAGE_SCN_MEM_EXECUTE) && ulPermissions == PAGE_READWRITE)
ulPermissions = PAGE_EXECUTE_READWRITE;
if((ulCharacteristics & IMAGE_SCN_MEM_EXECUTE) && ulPermissions == PAGE_READONLY)
ulPermissions = PAGE_EXECUTE_READ;
if(!VirtualProtect(pAddress, ulSize, ulPermissions, &ulPermissions))
throw Exception("Error applying page protection.");
}
const IMAGE_SECTION_HEADER* getRvaSection(const unsigned long ulRva, const IMAGE_NT_HEADERS* pNtHeaders)
{
IMAGE_SECTION_HEADER* pSections = IMAGE_FIRST_SECTION(const_cast<IMAGE_NT_HEADERS*>(pNtHeaders));
for(unsigned int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
if(ulRva >= pSections[i].VirtualAddress &&
ulRva < pSections[i].VirtualAddress + pSections[i].Misc.VirtualSize)
return &pSections[i];
}
throw Exception("Unable to resolve RVA to its parent section.");
}
long rvaToFileOffset(const unsigned long ulRva, const IMAGE_NT_HEADERS* pNtHeaders)
{
const IMAGE_SECTION_HEADER* pSection = getRvaSection(ulRva, pNtHeaders);
//Calculate differene in base of section data and base of section when mounted in to memory.
long lDelta = pSection->PointerToRawData - pSection->VirtualAddress;
return ulRva + lDelta;
}
void loadDll(const std::string& sLibrary)
{
//Map the file in to memory for quick IO and easy read access.
HANDLE hFile = CreateFileA(sLibrary.c_str(), GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if(hFile == INVALID_HANDLE_VALUE)
throw Exception("Unable to open file.");
HANDLE hFileMap = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
if(!hFileMap)
throw Exception("Error create file mapping.");
void* pViewBase = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if(!pViewBase)
throw Exception("Error mapping view of file in to memory.");
//Get the address of the NT header:
IMAGE_NT_HEADERS* pNtHeaders = reinterpret_cast<IMAGE_NT_HEADERS*>(reinterpret_cast<IMAGE_DOS_HEADER*>(pViewBase)->e_lfanew + reinterpret_cast<LONG>(pViewBase));
//Validate PE Image.
if(strcmp(reinterpret_cast<const char*>(&pNtHeaders->Signature), "PE\0\0") != 0)
throw Exception("Invalid PE Image.");
//Reserve enough memory to copy all the library's sections into. Not all of it will be needed, and thus it is more optimal to first reserve, then commit as needed.
void* pLibraryBase = VirtualAlloc(reinterpret_cast<void*>(pNtHeaders->OptionalHeader.ImageBase), pNtHeaders->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE);
if(!pLibraryBase)
throw Exception("Error allocating enough memory for library in process.");
IMAGE_SECTION_HEADER* pSectionHeaders = IMAGE_FIRST_SECTION(pNtHeaders);
for(unsigned int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
if(pSectionHeaders[i].Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
continue;
void* pFileSectionAddress = reinterpret_cast<void*>(pSectionHeaders[i].PointerToRawData + reinterpret_cast<unsigned long>(pViewBase));
void* pMemorySectionAddress = reinterpret_cast<void*>(pSectionHeaders[i].VirtualAddress + reinterpret_cast<unsigned long>(pLibraryBase));
unsigned long ulSectionSize = pSectionHeaders[i].SizeOfRawData;
//Commit the memory we previously reserved for this section.
if(VirtualAlloc(pMemorySectionAddress, pSectionHeaders[i].Misc.VirtualSize, MEM_COMMIT, PAGE_READWRITE) != pMemorySectionAddress)
throw Exception("Error commiting memory for section.");
if(ulSectionSize > 0)
memcpy_s(pMemorySectionAddress, ulSectionSize, pFileSectionAddress, ulSectionSize);
}
//Resolve image imports and setup the IAT.
IMAGE_IMPORT_DESCRIPTOR* pImportDescriptors = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR*>(pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + reinterpret_cast<unsigned long>(pLibraryBase));
for(unsigned int i = 0; pImportDescriptors[i].FirstThunk; i++)
{
IMAGE_THUNK_DATA* pInts = reinterpret_cast<IMAGE_THUNK_DATA*>(reinterpret_cast<unsigned long>(pLibraryBase) + pImportDescriptors[i].OriginalFirstThunk);
IMAGE_THUNK_DATA* pIat = reinterpret_cast<IMAGE_THUNK_DATA*>(reinterpret_cast<unsigned long>(pLibraryBase) + pImportDescriptors[i].FirstThunk);
//Technically, I could just use a recursive call to our version of load library, but loading the library via the windows PE loader is a lot less error prone. If you
//are writing a packer or something, obviously you want to avoid calls to LoadLibraryA, in which case you should just call recusivley, otherwise just call LoadLibraryA
HMODULE hImportLib = LoadLibraryA(reinterpret_cast<const char*>(reinterpret_cast<unsigned long>(pLibraryBase) + pImportDescriptors[i].Name));
if(!hImportLib)
throw Exception("Unable to find dependancy library.");
for(unsigned int x = 0; pInts[x].u1.Function != 0; x++)
{
unsigned long ulImportNameOrdinal = 0;
if(pInts[x].u1.Function & (1>>31))
{
//if MSB is set, it is an ordinal.
ulImportNameOrdinal = pInts[x].u1.Function & ~(1>>31);
}else
{
IMAGE_IMPORT_BY_NAME* pImport = reinterpret_cast<IMAGE_IMPORT_BY_NAME*>(reinterpret_cast<unsigned long>(pLibraryBase) + pInts[x].u1.Function);
ulImportNameOrdinal = reinterpret_cast<unsigned long>(pImport->Name);
}
if( !(pIat[x].u1.Function = reinterpret_cast<unsigned long>(GetProcAddress(hImportLib, reinterpret_cast<const char*>(ulImportNameOrdinal))) ))
throw Exception("Error finding import.");
}
}
//Do relocations described in the Relocations data directory
IMAGE_BASE_RELOCATION* pBaseRelocations = reinterpret_cast<IMAGE_BASE_RELOCATION*>(rvaToFileOffset(pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress, pNtHeaders) + reinterpret_cast<unsigned long>(pViewBase));
for(IMAGE_BASE_RELOCATION* pCurrentRelocation = pBaseRelocations;
reinterpret_cast<unsigned long>(pCurrentRelocation) - reinterpret_cast<unsigned long>(pBaseRelocations) < pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
pCurrentRelocation = reinterpret_cast<IMAGE_BASE_RELOCATION*>(reinterpret_cast<unsigned long>(pCurrentRelocation) + pCurrentRelocation->SizeOfBlock))
{
long difference = reinterpret_cast<unsigned long>(pLibraryBase) - pNtHeaders->OptionalHeader.ImageBase;
unsigned long ulBase = reinterpret_cast<unsigned long>(pLibraryBase) + pCurrentRelocation->VirtualAddress;
WORD* pRelocationOffsets = reinterpret_cast<WORD*>(reinterpret_cast<unsigned long>(pCurrentRelocation) + sizeof(IMAGE_BASE_RELOCATION));
for(unsigned int i = 0; i < pCurrentRelocation->SizeOfBlock / sizeof(WORD); i++)
calculateRelocation(difference, ulBase, pRelocationOffsets[i]);
}
//After code relocations, we can apply the proper page permissions.
for(unsigned int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
if(pSectionHeaders[i].Characteristics & IMAGE_SCN_MEM_DISCARDABLE)
continue;
void* pMemorySectionAddress = reinterpret_cast<void*>(pSectionHeaders[i].VirtualAddress + reinterpret_cast<unsigned long>(pLibraryBase));
setSectionPermissions(pMemorySectionAddress, pSectionHeaders[i].Misc.VirtualSize, pSectionHeaders[i].Characteristics);
}
//And call the library's entrypoint.
DllMain_t pEntry = reinterpret_cast<DllMain_t>(reinterpret_cast<unsigned long>(pLibraryBase) + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
pEntry(reinterpret_cast<HINSTANCE>(pLibraryBase), DLL_PROCESS_ATTACH, 0);
UnmapViewOfFile(hFileMap);
CloseHandle(hFile);
}
int _tmain(int argc, _TCHAR* argv[])
{
const char* const DLL_NAME = "testdll.dll";
try
{
loadDll(DLL_NAME);
}catch(Exception& e)
{
std::cout<<"Error occured: "<<e.getReason()<<std::endl;
}
}