PE文件動態加載執行過程

主要步驟:html

1.將要加載的文件讀取到內存中(簡稱爲文內),檢查文件格式無誤後,根據可選PE頭(簡稱op頭)的SizeOfImage,申請出一塊空間用於存儲該文件加載到內存後展開的數據(簡稱爲內內)。記得先所有初始化爲0,免去後續拷貝中對齊補0的步驟。ios

2.將文件數據拷貝到申請出來內存空間中(模仿PE加載器將文件裝載到虛擬內存中),先根據op頭的SizeOfHeaders,將文件的各類頭數據先拷貝過來(由於各類頭數據是線性存儲的,在靜態動態都是相同的存放順序),隨後複製節表數據,遍歷每一個節表,若是當前節表在文內長度時爲0,則說明該節在內存中僅作對齊用,並無實際數據,遍歷下一節,將數據從文起復制文長的數據到內起中,因爲前面申請空間時已初始化,因此無需在填0對齊。函數

3.進行重定位,若是當前加載到內存當中的基址與op的IB同樣,即在理想基址中展開了,或重定位表data[5]的長度爲0,則無須要重定位。不然獲取到重定位表的塊數據後,根據他的(塊長度-8)/2獲得該塊的地址數量,前8字節存放着該塊的偏移和大小,每一個佔4字節,一個重定位地址佔2字節,經過塊地址+8+(2*i)取出須要重定位的地址,與0x3000進行異或,若是首位爲3,則後12位爲地址偏移,則重定位地址=後12位(塊中偏移)+塊的起始位置+內存起始位置 重定位則爲*重定位地址=重定位地址+(理想基址和實際基址的偏移) 即*重定位地址+=(實際基址-理想基址)。若是首位爲0,則說明該偏移爲對齊使用,遍歷下一個(當前塊基址+當前塊長度)。將所有塊遍歷重定位完後,將op的IB也替換成當前加載到內存的基址。spa

4.構建導入表:經過偏移+內存基址,獲取導入表第一個dll的數據,按照導入的dll逐個遍歷,直到當前導入表的OriginalFirstThunk爲0,即遍歷完畢。 先經過GetModuleHandleA函數獲取當前DLL的句柄,若是返回爲NULL,則當前進程還未加載該DLL,loadlibary進來,得到句柄。經過內存基址+OriginalFirstThunk得到INT(輸入名字表),內存基址+FirstThunk得到IAT(輸入地址表)。檢查INT的Ordinal是否爲0,爲0則遍歷完當前DLL,若是不爲0,檢查第32位是否爲1(&0x80000000),爲1則爲序號導入,Ordinal的後16位爲序號取出(&0xFFFF),GetProcAddress獲得函數地址,並賦值給IAT表的Function。若是第32位爲0,則說明按名稱導入,此時先經過內存基址+AddressOfData得到函數名,再用GetProcAddress獲得函數地址,並賦值給IAT表的Function,循環遍歷各個DLL便可。3d

導出表在PE文件加載到內存時並不會使用到指針

5.經過VirtualProtect修改整個內存內容的保護屬性,修改成PAGE_EXECUTE_READWRITE(執行讀寫)。調試

6.定義一個指向DLL加載函數類型的函數指針,typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); 隨後聲明該函數指針的實例,並將(當前內存基址+op的AddressOfEntryPoint)的函數地址賦值給它,隨後調用該入口函數。orm

DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//定義函數入口地址htm

bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//調用入口函數blog

 

 

附上代碼:

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

char *g_pFileSrc = NULL;//文件內容
char *g_pFileBuffer = NULL;//虛擬內存空間
int g_iFileBufferLen = 0;//虛擬內存空間大小

//定義一個函數指針   指向DLL加載的入口函數類型的函數
typedef BOOL(WINAPI *DllProcEntry)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);

DWORD RVAtoFA(DWORD dwRVA)   //rva轉文件地址
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;		//dos頭
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT頭
	PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;		//PE頭
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead);		//節表
	int dwSectionCount = pFileHead->NumberOfSections;//得到節表數量
	for (int iNum = 0; iNum < dwSectionCount; iNum++)
	{
		if (dwRVA >= pSection->VirtualAddress && dwRVA < (pSection->VirtualAddress + pSection->Misc.VirtualSize))//若是RVA的值落在當前節點的範圍內
		{
			return (DWORD)g_pFileSrc + ((dwRVA - pSection->VirtualAddress) + pSection->PointerToRawData);
			/*則文件地址=映射基址 + 文件偏移地址( RVA- VirtualAddress + RawAddress)
			= 映射基址 + RVA - VirtualAddress + RawAddress*/
		}
		pSection++;//指向下一個節表
	}
	return 0;
}
bool LoadFile(char *pFileName)  //讀取文件
{
	//讀取文件內容
	FILE* fp = fopen(pFileName, "rb");
	if (!fp)
	{
		cout << "打開文件失敗" << endl;
		return false;
	}
	fseek(fp, 0, SEEK_END);
	int iFileSize = ftell(fp);
	g_pFileSrc = (char*)malloc(iFileSize);
	//DWORD dwBufferSize = *(int*)((DWORD)g_pFileSrc - 16);//這種方法能夠取出這段空間的長度
	if (!g_pFileSrc)
	{
		cout << "分配內存失敗" << endl;
		return false;
	}
	memset(g_pFileSrc, 0, iFileSize);
	rewind(fp);
	fread(g_pFileSrc, 1, iFileSize, fp);

	//檢查文件格式
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;
	if (pDosHead->e_magic != 0x5A4D)
	{
		cout << "該文件不是可執行文件" << endl;
		return false;
	}
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
	if (pNtHead->Signature != 0x4550)
	{
		cout << "該文件不是PE文件" << endl;
		return false;
	}

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

	g_pFileBuffer = (char*)malloc(pOptionalHead->SizeOfImage);
	if (!g_pFileBuffer)
	{
		cout << "分配模虛擬內存失敗" << endl;
		return false;
	}
	memset(g_pFileBuffer, 0, pOptionalHead->SizeOfImage);
	cout << "讀取文件成功,by:阿怪   2020.7.9" << endl;
	return true;
}

bool CopyContent()//拷貝數據
{

	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileSrc;		//dos頭
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT頭
	PIMAGE_FILE_HEADER pFileHead = (PIMAGE_FILE_HEADER)&pNtHead->FileHeader;		//PE頭
	PIMAGE_OPTIONAL_HEADER pOptionalHead = (PIMAGE_OPTIONAL_HEADER)&pNtHead->OptionalHeader;		//可選PE頭
	PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHead);		//節表
	int iSection = pFileHead->NumberOfSections;//節數量

	memcpy(g_pFileBuffer, g_pFileSrc, pOptionalHead->SizeOfHeaders);//複製各類頭數據
	//	//pSection->PointerToRawData;//文件中節的起始地址			pSection->SizeOfRawData;//文件中節的長度
	//	//pSection->VirtualAddress;//虛擬內存中節的起始地址		pSection->Misc.VirtualSize;//虛擬內存中節的長度
	for (int num = 0; num < iSection; num++)
	{
		if (pSection->SizeOfRawData == 0) //若是在文件中這個節的長度爲0,證實該節爲未被初始化的靜態內存區
		{
			pSection++;
			continue;
		}
		memcpy(g_pFileBuffer + pSection->VirtualAddress, 
			g_pFileSrc + pSection->PointerToRawData, 
			pSection->SizeOfRawData
			);
		pSection++;
	}
	cout << "從文件拷貝數據到內存完畢,by:阿怪   2020.7.9" << endl;
	return true;
}

bool Relocation()  //進行重定位並修改基址
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;		//dos頭
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT頭
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
	DWORD dwRelocationRVA = pOptional->DataDirectory[5].VirtualAddress;//重定位表RVA
	int iRelocationSize = pOptional->DataDirectory[5].Size;//重定位表長度
	DWORD dwImageBaseGap = (DWORD)g_pFileBuffer - pOptional->ImageBase;  //計算加載後的基址與原先預想的基址的距離
	if ((dwImageBaseGap == 0) || (iRelocationSize == 0))
	{
		cout << "該程序無需重定位" << endl;
		return false;
	}
	PIMAGE_BASE_RELOCATION pBaseRelocation = (PIMAGE_BASE_RELOCATION)RVAtoFA(dwRelocationRVA);//重定位表當前塊
	while ((pBaseRelocation->VirtualAddress != 0) && (pBaseRelocation->SizeOfBlock != 0))  //遍歷到重定位表末尾爲止
	{
		for (int i = 0; i < ((pBaseRelocation->SizeOfBlock - 8) / 2); i++)   //塊首地址-8(前4爲塊偏移,後4爲塊長度)/2=塊中需重定位地址數量
		{
			WORD pRelocationAddr = *(WORD*)((DWORD)pBaseRelocation + 8 + (2 * i));//在塊中,每一個重定位地址佔2字節(WORD類型)
			if (pRelocationAddr != 0)  //爲0時,說明該位置數據爲對齊而填充
			{
				DWORD dwRVA = (pRelocationAddr ^ 0x3000) + pBaseRelocation->VirtualAddress;//須要重定位的偏移
				PDWORD dwFileAddr = (DWORD*)(dwRVA + g_pFileBuffer);//重定位地址=當前程序基址+當前塊基址+當前目標偏移
				*dwFileAddr += dwImageBaseGap;
			}
		}
		pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock);  //下個塊文件地址=當前塊文件地址+當前塊長度
	}
	pOptional->ImageBase = (DWORD)g_pFileBuffer;  //將當前文件的基址改成加載到內存後的基址

	cout << "程序重定位並修改基址成功,by:阿怪   2020.7.9" << endl;
	return true;

}

bool ImportList()  //導入表
{
	PIMAGE_DOS_HEADER pDosHead = (PIMAGE_DOS_HEADER)g_pFileBuffer;		//dos頭
	PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);		//NT頭
	PIMAGE_OPTIONAL_HEADER pOptional = (PIMAGE_OPTIONAL_HEADER)&(pNtHead->OptionalHeader);
	DWORD dwImportListRVA = pOptional->DataDirectory[1].VirtualAddress;//導入表RVA
	int dwImportListSize = pOptional->DataDirectory[1].Size;//導入表長度
	PIMAGE_IMPORT_DESCRIPTOR pImpotrList = (PIMAGE_IMPORT_DESCRIPTOR)(dwImportListRVA + g_pFileBuffer); //獲取導入表第一個導入的dll

	while (pImpotrList->OriginalFirstThunk != 0)  //只有該結構中有一個成員內容爲0,即遍歷完了導入表了
	{
		char* szDllName = (char*)(g_pFileBuffer + pImpotrList->Name);//獲取當前DLL的名字
		HMODULE hDllImageBase = LoadLibrary(szDllName);//先將當前dll加載到程序中
		if (!hDllImageBase)
		{
			int iError = GetLastError();
			cout << "加載當前dll失敗,錯誤代碼:" << iError << endl;
			return false;
		}

		PIMAGE_THUNK_DATA pDllINT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->OriginalFirstThunk);//獲取當前DLL導入的函數的輸入名稱表(INT)
		PIMAGE_THUNK_DATA pDllIAT = (PIMAGE_THUNK_DATA)(g_pFileBuffer + pImpotrList->FirstThunk);//獲取當前DLL導入的函數的輸入地址表(IAT) 當前IAT和INT指向同一內容

		for (int i = 0; pDllINT->u1.Ordinal; i++)  //噹噹前DLL導入的函數Ordinal爲0時,即遍歷完當前DLL全部函數
		{
			if (pDllINT->u1.Ordinal & 0x80000000)  //若是當前結構信息的序數Ordinal的第32位爲1  則當前dll的函數由序號導入
			{
				DWORD dwFuncNum = pDllINT->u1.AddressOfData & 0xFFFF;//後16位爲導入序號
				//dwDllIAT->u1.Function = (DWORD)hDllImageBase + dwFuncNum;
				pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, (LPCSTR)dwFuncNum);
			}
			else   //不爲1則由函數名字導入
			{
				PIMAGE_IMPORT_BY_NAME szFuncName = (PIMAGE_IMPORT_BY_NAME)(g_pFileBuffer +pDllINT->u1.AddressOfData);  //得到函數名
				pDllIAT->u1.Function = (DWORD)GetProcAddress(hDllImageBase, szFuncName->Name);	//經過dll和函數名  得到當前函數在內存中的地址
			}
			pDllINT++;
			pDllIAT++;
		}
		pImpotrList++;
	}
	cout << "構建IAT成功,by:阿怪   2020.7.9" << endl;
	return true;
	}


bool DynamicLoad(char *pDllName)
{
	if (!LoadFile(pDllName)) //獲取文件內容、檢查文件格式、分配2塊內存(存放文件數據和虛擬內存空間)
	{
		return false;
	}
	if (!CopyContent()) //將文件數據裝載到虛擬內容中
	{
		return false;
	}

	Relocation(); //重定位並修改基址(並不是全部PE結構都須要重定位)

	if (!ImportList()) //經過導入表構建IAT
	{
		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("設置頁屬性失敗\n");
		return NULL;
	}
	
	DllProcEntry ProcAddr = (DllProcEntry)(g_pFileBuffer + pNtHead->OptionalHeader.AddressOfEntryPoint);//定義函數入口地址
	MessageBoxA(0, 0, 0, 0);
	bool bRetval = (*ProcAddr)((HINSTANCE)g_pFileBuffer, DLL_PROCESS_ATTACH, 0);//調用入口函數

	cout << "加載完成"<<endl<<"by:阿怪   2020.7.9" << endl;

	return true;
}

運行結果:

 

 

 

 

在須要調試的位置調用messagebox 隨後可在調試器(OD,MDbug)中定位到關鍵點,方便調試。

踩坑點:

1.節數據賦值時的對齊,若是不按照上面的步驟,也可經過對齊值,求得當前節在虛擬內存中的對齊後的大小,用該值-當前節在文件中的大小,可得用0補齊的長度。以及如何去複製各類頭文件(各類頭文件爲線性的,可直接複製),當第一個節在文件大小爲0時該怎麼處理。

2.重定位的重定位地址爲當前內存基址+當前重定位塊基址+當前重定位偏移,重定位後的值應該賦值給*重定位,即*重定位地址=重定位地址+(預先理想的基址與當前內存基址的距離)

3.在靜態文件到動態加載到內存時,導入表是經過INT表,把所導入的函數的地址裝載到IAT表,所有加載完後,INT表就沒用了,經過IAT表就能夠找到相應的函數地址。且在取相應的地址時,應該是由導入表的成員的RVA+內存基址,而不是經過RVAtoFA去取值。

 

 導入表相關文章: https://www.sohu.com/a/278971010_653604

導出表相關文章:https://www.write-bug.com/article/1926.html   https://www.cnblogs.com/Madridspark/p/WinPEFile.html

模擬PE解析器工做原理:http://www.javashuo.com/article/p-ppapohlk-mr.html 

 感謝以上資料做者帶來的啓發與借鑑,在這也是分享本身在作這個動態加載時的感覺,如不足之處也但願你們不吝賜教,指點出來。謝謝。

有什麼問題或見解歡迎評論

相關文章
相關標籤/搜索