主要步驟: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
感謝以上資料做者帶來的啓發與借鑑,在這也是分享本身在作這個動態加載時的感覺,如有不足之處也但願你們不吝賜教,指點出來。謝謝。
有什麼問題或見解歡迎評論