Custom PE Loader - Sample
I have quickly whipped up a custom PE Loader. Honestly, I didn't spend that much time on it, so I haven't bothered to wrap it in an OOP design or clean it up, or anything of the sort. I'm in the process of doing that right now actually.
MSDN doesn't document A LOT of relocation types, and I really had to dig around to find information on some PE structures. So if you can constribute to this with your knowledge of the windows PE Loader, I would appreciate that.
I threw it all in one source file and threw in some comments. If you have any questions just ask (I will be posting a more revised version of it later, when I get more time.)
Right now, it just loads the library in to the local process. Making it do otherwise, is a simple manner of replacing memcpy with writeprocessmemory, virtualalloc with VirtualAllocEx, and the call with CreateRemoteThread, etc...
Also, sorry for all the reinterpret_casts... Its a side-effect of doing a lot of C pointer arithmatic in C++.
This can easily be modified for stealing another process's address space. I.e, unload the executable in the remote address space, and setup your executable inside of the remote process from memory (popular with malware.)
MSDN doesn't document A LOT of relocation types, and I really had to dig around to find information on some PE structures. So if you can constribute to this with your knowledge of the windows PE Loader, I would appreciate that.
I threw it all in one source file and threw in some comments. If you have any questions just ask (I will be posting a more revised version of it later, when I get more time.)
Right now, it just loads the library in to the local process. Making it do otherwise, is a simple manner of replacing memcpy with writeprocessmemory, virtualalloc with VirtualAllocEx, and the call with CreateRemoteThread, etc...
Also, sorry for all the reinterpret_casts... Its a side-effect of doing a lot of C pointer arithmatic in C++.
This can easily be modified for stealing another process's address space. I.e, unload the executable in the remote address space, and setup your executable inside of the remote process from memory (popular with malware.)
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)
{
//There are a lot of relocations that aren't documented (or I haven't found any sort of documentation for them.
//... however, not relocating them doesn't seem to cause much harm... Here are just some guesses as to what should be relocated (and how...)
case IMAGE_REL_BASED_MIPS_JMPADDR:
case IMAGE_REL_BASED_HIGH:
case IMAGE_REL_BASED_LOW:
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.");
}
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 = reinterpret_cast<IMAGE_SECTION_HEADER*>(reinterpret_cast<unsigned long>(pNtHeaders) + sizeof(IMAGE_NT_HEADERS));
for(unsigned int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
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;
//If the section size is zero(i.e no data), the standard says to use the sizes defined in the optional headers...
if(!ulSectionSize)
{
if(pSectionHeaders[i].Characteristics & IMAGE_SCN_CNT_CODE)
ulSectionSize = pNtHeaders->OptionalHeader.SizeOfCode;
else if(pSectionHeaders[i].Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA)
ulSectionSize = pNtHeaders->OptionalHeader.SizeOfInitializedData;
else if(pSectionHeaders[i].Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA)
ulSectionSize = pNtHeaders->OptionalHeader.SizeOfUninitializedData;
}
//Commit the memory we previously reserved for this section.
if(VirtualAlloc(pMemorySectionAddress, max(pSectionHeaders[i].Misc.VirtualSize, ulSectionSize), 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*>(pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress + reinterpret_cast<unsigned long>(pLibraryBase));
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++)
{
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;
}
}



