PE file dynamic loading execution process

The main steps:

1. Read the file to be loaded into the memory (referred to as in-text), check that the file format is correct, and apply for a space to store the file after loading it into the memory according to the SizeOfImage of the optional PE header (referred to as the op header). Expanded data (referred to as NINE for short). Remember to initialize all to 0 first to avoid the step of aligning and filling 0 in subsequent copies.

2. Copy the file data into the requested memory space (simulating the PE loader to load the file into the virtual memory), first copy the various header data of the file according to the SizeOfHeaders of the op header (because the various header data are Linear storage, the same storage order in static and dynamic), then copy the section table data, traverse each section table, if the current section table is 0 in the text length, it means that the section is only used for alignment in memory , there is no actual data, traverse the next section, and copy the data from the text to the internal data. Since the space has been initialized before, there is no need to fill in 0 alignment.

3. Perform relocation. If the base address currently loaded into the memory is the same as the IB of op, that is, it is expanded in the ideal base address, or the length of the relocation table data[5] is 0, then there is no need for relocation. Otherwise, after the block data of the relocation table is obtained, the number of addresses of the block is obtained according to his (block length-8)/2. The first 8 bytes store the offset and size of the block, each occupying 4 bytes, A relocation address occupies 2 bytes, and the address to be relocated is taken out through the block address +8+(2*i), and XOR is performed with 0x3000. If the first bit is 3, the last 12 bits are the address offset, then relocation address = the last 12 bits (offset in the block) + the starting position of the block + the starting position of the memory, relocation is * relocation address = relocation address + (ideal offset between base address and actual base address) i.e. location address + = (actual base address - ideal base address) If the first bit is 0, it means that the offset is used for alignment and traverses the next one (current bl ock base address + current block length). After all blocks are traversed and relocated, the IB of op is also replaced with the base address currently loaded into memory.

4. Build the import table: Obtain the data of the first dll of the import table through offset + memory base address, and traverse the imported dll one by one until the OriginalFirstThunk of the current import table is 0, that is, the traversal is completed. First obtain the handle of the current DLL through the GetModuleHandleA function. If the return is NULL, the current process has not loaded the DLL, and loadlibary comes in to obtain the handle. The INT (input name table) is obtained by the memory base address+OriginalFirstThunk, and the IAT (input address table) is obtained by the memory base address+FirstThunk. Check whether the Ordinal of INT is 0, if it is 0, the current DLL has been traversed. If it is not 0, check whether the 32nd bit is not 1 (&0x80000000), if it is 1, it is sequence number import, the last 16 bits of ordinal are sequence number extraction (&0xffff), GetProcAddress gets the function address and assigns it to the function of the IAT table If the 32nd bit is 0, it means importing by name. At this time, first obtain the function name through the memory base address + AddressOfData, then use GetProcAddress to obtain the function on address, and assign it to the Function of the IAT table, and loop through each DLL.

The export table is not used when the PE file is loaded into memory

5. Modify the protection attribute of the entire memory content through VirtualProtect, and modify it to PAGE_EXECUTE_READWRITE (perform read and write).

6. Define a function pointer to the DLL load function type, typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); Then declare an instance of the function pointer, and add (current memory base address + op's AddressOfEntryPoint ) of the function address is assigned to it, and then the entry function is called.

DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//Define function entry address

bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//Call the entry function

 

 

Attached code:

#include<iostream>
#include<Windows.h>
#include <winnt.h>
using namespace std;

char *g_pFileSrc = NULL;//document content
char *g_pFileBuffer = NULL;//virtual memory space
int g_iFileBufferLen = 0;//virtual memory size

//Define a function pointer that points to the function of the entry function type loaded by the DLL
typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

DWORD RVAtoFA(DWORD dwRVA)   //rva to file address
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;		//dos header
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT header
	PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;		//PE head
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead);		//section table
	int dwSectionCount = pFileHead->NumberOfSections;//Get the number of section tables
	for (int iNum = 0; iNum < dwSectionCount; iNum++)
	{
		if (dwRVA >= pSection->VirtualAddress && dwRVA < (pSection->VirtualAddress + pSection->Misc.VirtualSize))//If the value of RVA falls within the range of the current node
		{
			return (DWORD)g_pFileSrc + ((dwRVA - pSection->VirtualAddress) + pSection->PointerToRawData);
			/*Then file address = mapping base address + file offset address ( RVA- VirtualAddress + RawAddress )
			= Map base + RVA - VirtualAddress + RawAddress*/
		}
		pSection++;//point to the next section table
	}
	return 0;
}
bool LoadFile(char *pFileName)  //read file
{
	//read file content
	FILE* fp = fopen(pFileName, "rb");
	if (!fp)
	{
		cout << "fail to open the file" << endl;
		return false;
	}
	fseek(fp, 0, SEEK_END);
	int iFileSize = ftell(fp);
	g_pFileSrc = (char*)malloc(iFileSize);
	//DWORD dwBufferSize = *(int*)((DWORD)g_pFileSrc - 16);//This method can take out the length of this space
	if (!g_pFileSrc)
	{
		cout << "Failed to allocate memory" << endl;
		return false;
	}
	memset(g_pFileSrc, 0, iFileSize);
	rewind(fp);
	fread(g_pFileSrc, 1, iFileSize, fp);

	//Check file format
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;
	if (pDosHead->e_magic != 0x5A4D)
	{
		cout << "the file is not executable" << endl;
		return false;
	}
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
	if (pNtHead->Signature != 0x4550)
	{
		cout << "the file is not PE document" << endl;
		return false;
	}

	PIMAGE_OPTIONAL_HEADER pOptionalHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;

	g_pFileBuffer = (char*)malloc(pOptionalHead->SizeOfImage);
	if (!g_pFileBuffer)
	{
		cout << "Failed to allocate modulo virtual memory" << endl;
		return false;
	}
	memset(g_pFileBuffer, 0, pOptionalHead->SizeOfImage);
	cout << "Read the file successfully, by:A strange 2020.7.9" << endl;
	return true;
}

bool CopyContent()//copy data
{

	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;		//dos header
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT header
	PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;		//PE head
	PIMAGE_OPTIONAL_HEADER pOptionalHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;		//Optional PE header
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead);		//section table
	int iSection = pFileHead->NumberOfSections;//number of sections

	memcpy(g_pFileBuffer, g_pFileSrc, pOptionalHead->SizeOfHeaders);//Copy various header data
	//	//pSection->PointerToRawData;// The starting address of the section in the file psection->sizeofrawdata// Length of section in file
	//	//pSection->VirtualAddress;// The starting address of the section in the virtual memory psection->misc.virtualsize// Length of section in virtual memory
	for (int num = 0; num < iSection; num++)
	{
		if (pSection->SizeOfRawData == 0) //If the length of this section in the file is 0, it proves that the section is an uninitialized static memory area
		{
			pSection++;
			continue;
		}
		memcpy(g_pFileBuffer + pSection->VirtualAddress, 
			g_pFileSrc + pSection->PointerToRawData, 
			pSection->SizeOfRawData
			);
		pSection++;
	}
	cout << "After copying data from file to memory, by:A strange 2020.7.9" << endl;
	return true;
}

bool Relocation()  //Relocate and modify base address
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;		//dos header
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT header
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
	DWORD dwRelocationRVA = pOptional->DataDirectory[5].VirtualAddress;//Relocation table RVA
	int iRelocationSize = pOptional->DataDirectory[5].Size;//Relocation table length
	DWORD dwImageBaseGap = (DWORD)g_pFileBuffer - pOptional->ImageBase;  //Calculate the distance between the loaded base address and the originally expected base address
	if ((dwImageBaseGap == 0) || (iRelocationSize == 0))
	{
		cout << "The program does not require relocation" << endl;
		return false;
	}
	PIMAGE_BASE_RELOCATION pBaseRelocation = (PIMAGE_BASE_RELOCATION)RVAtoFA(dwRelocationRVA);//Relocation table current block
	while ((pBaseRelocation->VirtualAddress != 0) && (pBaseRelocation->SizeOfBlock != 0))  //Traverse to the end of the relocation table
	{
		for (int i = 0; i < ((pBaseRelocation->SizeOfBlock - 8) / 2); i++)   //Block start address -8 (the first 4 are the block offset, the last 4 are the block length)/2=the number of addresses to be relocated in the block
		{
			WORD pRelocationAddr = *(WORD*)((DWORD)pBaseRelocation + 8 + (2 * i));//In the block, each relocation address occupies 2 bytes (WORD type)
			if (pRelocationAddr != 0)  //When it is 0, it means that the position data is filled for alignment
			{
				DWORD dwRVA = (pRelocationAddr ^ 0x3000) + pBaseRelocation->VirtualAddress;//Offset that needs to be relocated
				PDWORD dwFileAddr = (DWORD*)(dwRVA + g_pFileBuffer);//Relocation address = current program base address + current block base address + current target offset
				*dwFileAddr += dwImageBaseGap;
			}
		}
		pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock);  //Next block file address = current block file address + current block length
	}
	pOptional->ImageBase = (DWORD)g_pFileBuffer;  //Change the base address of the current file to the base address after loading into memory

	cout << "The program relocates and modifies the base address successfully, by:A strange 2020.7.9" << endl;
	return true;

}

bool ImportList()  //import table
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;		//dos header
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT header
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
	DWORD dwImportListRVA = pOptional->DataDirectory[1].VirtualAddress;//Import table RVA
	int dwImportListSize = pOptional->DataDirectory[1].Size;//Import table length
	PIMAGE_IMPORT_DESCRIPTOR pImpotrList = (PIMAGE_IMPORT_DESCRIPTOR)(dwImportListRVA + g_pFileBuffer); //Get the first imported dll in the import table

	while (pImpotrList->OriginalFirstThunk != 0)  //Only one member of the structure has a content of 0, that is, the import table has been traversed.
	{
		char* szDllName = (char*)(g_pFileBuffer + pImpotrList->Name);//Get the name of the current DLL
		HMODULE hDllImageBase = LoadLibrary(szDllName);//First load the current dll into the program
		if (!hDllImageBase)
		{
			int iError = GetLastError();
			cout << "load current dll failed with error code:" << iError << endl;
			return false;
		}

		PIMAGE_THUNK_DATA pDllINT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->OriginalFirstThunk);//Get the input name table (INT) of the functions imported by the current DLL
		PIMAGE_THUNK_DATA pDllIAT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->FirstThunk);//Get the input address table (IAT) of the functions imported by the current DLL The current IAT and INT point to the same content

		for (int i = 0; pDllINT->u1.Ordinal; i++)  //When the function Ordinal imported by the current DLL is 0, all functions of the current DLL are traversed
		{
			if (pDllINT->u1.Ordinal & 0x80000000)  //If the 32nd bit of the Ordinal of the current structure information is 1, the function of the current dll is imported by the Ordinal number
			{
				DWORD dwFuncNum = pDllINT->u1.AddressOfData & 0xFFFF;//The last 16 digits are the import sequence number
				//dwDllIAT->u1.Function = (DWORD)hDllImageBase + dwFuncNum;
				pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, (LPCSTR)dwFuncNum);
			}
			else   //If not 1, import by function name
			{
				PIMAGE_IMPORT_BY_NAME szFuncName = (PIMAGE_IMPORT_BY_NAME)(g_pFileBuffer +pDllINT->u1.AddressOfData);  //get function name
				pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, szFuncName->Name);	//Get the address of the current function in memory through dll and function name
			}
			pDllINT++;
			pDllIAT++;
		}
		pImpotrList++;
	}
	cout << "Construct IAT success, by:A strange 2020.7.9" << endl;
	return true;
	}


bool DynamicLoad(char *pDllName)
{
	if (!LoadFile(pDllName)) //Get file content, check file format, allocate 2 blocks of memory (store file data and virtual memory space)
	{
		return false;
	}
	if (!CopyContent()) //Load file data into virtual content
	{
		return false;
	}

	Relocation(); //Relocate and modify the base address (not all PE structures require relocation)

	if (!ImportList()) //Build IAT by importing tables
	{
		return false;
	}


	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);

	DWORD dwOldProtect = 0;
	if (FALSE == VirtualProtect(g_pFileBuffer, pNtHead->OptionalHeader.SizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
	{
		printf("Failed to set page properties\n");
		return NULL;
	}
	
	DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//Define function entry address
	MessageBoxA(0, 0, 0, 0);
	bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//Call the entry function

	cout << "Loading completed"<<endl<<"by:A strange 2020.7.9" << endl;

	return true;
}

operation result:

 

 

 

 

Call messagebox at the location that needs to be debugged and then locate key points in the debugger (OD,MDbug) for easy debugging.

Step on the pit:

1. Alignment when assigning section data, if you do not follow the above steps, you can also obtain the aligned size of the current section in virtual memory by using the alignment value. Using this value - the size of the current section in the file, you can get Length padded with 0s. And how to copy various header files (various header files are linear and can be copied directly), and how to deal with the first section when the file size is 0.

2. The relocation address of the relocation is the current memory base address + the current relocation block base address + the current relocation offset, and the relocated value should be assigned to * relocation, that is, * relocation address = relocation address + ( The distance between the pre-ideal base address and the current memory base address)

3. When the static file is dynamically loaded into the memory, the import table is through the INT table, and the address of the imported function is loaded into the IAT table. After all the loading is completed, the INT table is useless, and the corresponding IAT table can be found. function address. And when taking the corresponding address, it should be the RVA+ memory base address of the member of the import table, not the value through RVAtoFA.

 

Import table related articles: https://www.sohu.com/a/278971010_653604

Export table related articles: https://www.write-bug.com/article/1926.html   https://www.cnblogs.com/Madridspark/p/WinPEFile.html

Simulate how the PE parser works: https://www.cnblogs.com/onetrainee/p/12938085.html 

Thanks for the inspiration and reference brought by the author of the above information, and here is also to share my feelings when doing this dynamic loading. thanks.

Any questions or opinions welcome comments

 

Posted by orangehairedboy on Sat, 30 Jul 2022 23:02:17 +0530