內存加載Exe原理,PELoder
一丶原理
原理是模擬Window 雙擊 Exe進行操作.
而對於我們來說.其實就是 針對PE文件的 NT頭 節表數據 重定位表 導入表 等進行操作.
步驟如下:
總共分為幾大步驟
-
文件數據轉內存數據
1.拷貝文件中NT頭數據大小. 按照SizeofHeder大小拷貝
2.申請SizeofImage大小內存. 用於存儲文件中的PE數據
-
數據操作階段
1.拷貝節表數據到內存中.按照內存對齊. 其實就是拷貝節表中記錄數據大小 到內存起始位置
2.修復重定位表,有的情況下. 根據重定位表的結構. 按照每一頁進行重定位表的遍歷. 來進行修復即可.
3.修復導入表. 根據導入表的雙橋結構. INT IAT Name 根據name加載PE所需要的DLL 根據INT 判斷是序號導入還是名字導入並且使用 GetProcAddress 加載對應的函數. 加載的函數填寫到IAT表中
-
調用階段
調用階段就是獲取PE的OEP入口點的RVA 然后修改內存中的ImageBase 入口點RVA與內存的起始點相加得到OEP的VA. 然后內聯匯編進行調用即可.
二丶代碼
1.代碼分布講解
代碼很簡單.如下:
第一步.讀取PE數據.用於后續操作
//第一步 文件操作.獲取PE中文件的數據,返回PE讀取的PE數據
char* SepOne_RetPeFileData()
{
/*
第一步 文件以及內存操作
1.只讀打開文件
2.獲取文件大小
3.返回文件數據
*/
HANDLE hFile = INVALID_HANDLE_VALUE;
LARGE_INTEGER laFileSize = { 0 };
hFile =
CreateFile(
FILE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (NULL == INVALID_HANDLE_VALUE)
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
if (0 == GetFileSizeEx(hFile, &laFileSize))
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
}
//獲取文件大小
char* pszFileData = NULL;
pszFileData = new char[laFileSize.QuadPart]();
if (pszFileData == NULL)
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
DWORD dwReadOrWriteByes = 0;
if (0 == ReadFile(
hFile,
pszFileData,
laFileSize.QuadPart,
&dwReadOrWriteByes,
NULL))
{
if (pszFileData != NULL)
{
delete[] pszFileData;
pszFileData = NULL;
}
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
if (pszFileData != NULL)
{
return pszFileData;
}
return NULL;
}
第二步,PE文件數據映射,並且進行修復
//傳入對齊值,以及大小進行對其拷貝
DWORD GetAgenSizeOfRawData(DWORD Agine,DWORD Value)
{
while ((Value % Agine) != 0)
{
Value++;
}
return Value;
}
BOOL CopyPeFileSectionDataToPeMmSectionData(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress)
{
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSecHeder = NULL;
DWORD SectionCount = GetSectionNumber(PeFileStatusAddress);
if (SectionCount == 0)
{
return FALSE;
}
//賦值頭
pDosHeder = (PIMAGE_DOS_HEADER)PeFileStatusAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PeFileStatusAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSecHeder = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
for (DWORD index = 0; index < SectionCount; index++)
{
if (pSecHeder == NULL)
{
return FALSE;
}
//開始遍歷文件中節表中的數據. 然后拷貝到內存中的數據
char* pSrcData = (char*)PeFileStatusAddress + pSecHeder[index].PointerToRawData; //文件中節表開始的位置
//這里需要內存對齊,而且 VirtualAddress還可能為0 不過一般不會為0 除非畸形PE
char* pDestDataAddress = (char*)PEMmAddress + pSecHeder[index].VirtualAddress;
//DWORD dwSizeOfRawData = GetAgenSizeOfRawData(pOptHeder->SectionAlignment,pSecHeder[index].SizeOfRawData); //獲取文件大小.按照內存對齊來對其來進行拷貝
//DWORD TempValue = (DWORD)((char*)pDestDataAddress + dwSizeOfRawData);
RtlCopyMemory(pDestDataAddress, pSrcData, pSecHeder[index].SizeOfRawData); //拷貝文件中記錄的節數據的大小
//pSecHeder++;
}
return TRUE;
}
//重定位數組表記錄的所有偏移
typedef struct _RELOC_ARRAY
{
WORD ArrayOffset : 12;
WORD mark : 3;
}RELOC_ARRAY, * PRELOC_ARRAY;
//修復重定位表
BOOL RepairReloc(LPVOID PeFileStatusAddress, LPVOID PEMmAddress)
{
/*
文件以及內存中都已經有了數據了.所以這里直接以內存為起始點 來獲取重定位表.並且修復自身內存的重定位表
當然也可以使用文件來便利文件中的重定位表來修復內存中的重定位表
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_BASE_RELOCATION pRelocTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//獲取RVA以及內存中真正重定位表的位置
DWORD dwRelocTalbeRva = pOptHeder->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
pRelocTable = (PIMAGE_BASE_RELOCATION)((char*)PEMmAddress + dwRelocTalbeRva);
if (pRelocTable == PEMmAddress) //代表沒有重定位表
return FALSE;
//遍歷重定位表進行內存中重定位表的修復
while (true)
{
if (pRelocTable->SizeOfBlock == 0)
{
break;
}
//獲取每一個重定位表中重定位表數組. 繼續遍歷這個數組 來獲取offset 來獲取那個地方需要重定位. 然后再把里面的內容修改掉
//獲取地n項 重定位數組表 以及計算出它的大小
PRELOC_ARRAY pRelocArray = (PRELOC_ARRAY)((PBYTE)(pRelocTable)+8);
int nRelocArrayCount = (pRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(RELOC_ARRAY);
for (int index = 0; index < nRelocArrayCount; index++)
{
//是需要進行重定位的一項
if (pRelocArray[index].mark == 3)
{
//計算出需要重定位的位置
PVOID pRelocaLocation = (char*)PEMmAddress + pRelocTable->VirtualAddress + pRelocArray[index].ArrayOffset;
//修改里面重定位的數值為 新的Imagebase + 偏移 偏移計算方式 偏移 = 當前地址 - 原ImageBase = 偏移
DWORD RVA = *(DWORD*)pRelocaLocation - pOptHeder->ImageBase;
DWORD Offset = (DWORD)(char*)PEMmAddress + RVA;
*(DWORD*)pRelocaLocation = Offset;
continue;
}
//否則重定位表移動到下一頁進行操作
}
pRelocTable = (PIMAGE_BASE_RELOCATION)((char*)pRelocTable + pRelocTable->SizeOfBlock);
}
return TRUE;
// PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PEMmAddress;
// PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
// PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
//
// // 判斷是否有 重定位表
// if ((PVOID)pLoc == (PVOID)pDosHeader)
// {
// // 重定位表 為空
// return TRUE;
// }
//
// while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //開始掃描重定位表
// {
// WORD* pLocData = (WORD*)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
// //計算本節需要修正的重定位項(地址)的數目
// int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
//
// for (int i = 0; i < nNumberOfReloc; i++)
// {
// // 每個WORD由兩部分組成。高4位指出了重定位的類型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定義了重定位類型的取值。
// // 低12位是相對於VirtualAddress域的偏移,指出了必須進行重定位的位置。
///*
// #ifdef _WIN64
// if ((DWORD)(pLocData[i] & 0x0000F000) == 0x0000A000)
// {
// // 64位dll重定位,IMAGE_REL_BASED_DIR64
// // 對於IA-64的可執行文件,重定位似乎總是IMAGE_REL_BASED_DIR64類型的。
//
// ULONGLONG* pAddress = (ULONGLONG *)((PBYTE)pNewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
// ULONGLONG ullDelta = (ULONGLONG)pNewBase - m_pNTHeader->OptionalHeader.ImageBase;
// *pAddress += ullDelta;
//
// }
// #endif
//*/
// if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //這是一個需要修正的地址
// {
// // 32位dll重定位,IMAGE_REL_BASED_HIGHLOW
// // 對於x86的可執行文件,所有的基址重定位都是IMAGE_REL_BASED_HIGHLOW類型的。
// DWORD* pAddress = (DWORD*)((PBYTE)pDosHeader + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
// DWORD dwDelta = (DWORD)pDosHeader - pNtHeaders->OptionalHeader.ImageBase;
// *pAddress += dwDelta;
//
// }
// }
//
// //轉移到下一個節進行處理
// pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
// }
return TRUE;
}
//將wstring轉換成string
string wstring2string(wstring wstr)
{
string result;
//獲取緩沖區大小,並申請空間,緩沖區大小事按字節計算的
int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL);
char* buffer = new char[len + 1];
//寬字節編碼轉換成多字節編碼
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, NULL);
buffer[len] = '\0';
//刪除緩沖區並返回值
result.append(buffer);
delete[] buffer;
return result;
}
BOOL RepairExport(LPVOID PeFileStatusAddress, LPVOID PEMmAddress)
{
/*
根據雙橋結構來進行導入表的修復
1.根據所需要的模塊,加載其模塊
2.根據BY_NAME結構. 解析是否是序號到處還是名字導入. 並調用GetProcaddress 來進行 函數地址獲取
3.將IAT表指向的函數地址 修改為獲取的函數地址
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImprtTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//獲取導入表
DWORD dwImportRva = pOptHeder->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
pImprtTable = (PIMAGE_IMPORT_DESCRIPTOR)((char*)PEMmAddress + dwImportRva);
if (pImprtTable == PEMmAddress) //說明沒有導入表
return FALSE;
HMODULE hModule = NULL;
//遍歷導入表的雙橋結構
while (true)
{
//校驗退出條件. 退出條件是當 導入表最后一項為0的時候進行退出
if (pImprtTable->OriginalFirstThunk == 0
&& pImprtTable->Name == 0
&& pImprtTable->FirstThunk == 0)
{
break;
}
//獲取模塊名字.根據名字進行加載模塊. 這里需要注入. 你的PEloader是放在他的目錄下才能加載DLL成功. 否則你就要做一個全局路徑來拼接處DLL路徑來進行加載
//這里拼接模塊名字獲取.這樣就不會限制在只能放在它目錄下才能運行
wstring ModuleName = FILE_NAME;
ModuleName = ModuleName.substr(0, ModuleName.find_last_of(L"\\") + 1);
//獲取指向的名字,拷貝名字
char * pDllName = (char*)((char*)PEMmAddress + pImprtTable->Name);
char szDllNameBuffer[MAX_PATH] = { 0 };
strcpy_s(szDllNameBuffer, sizeof(szDllNameBuffer) / sizeof(szDllNameBuffer[0]), (char *)pDllName);
std::string AscDllName = wstring2string(ModuleName);
SetCurrentDirectoryA(AscDllName.c_str()); //切換到DLL所在目錄進行加載
//AscDllName = AscDllName + szDllNameBuffer;
//
hModule = LoadLibraryA(szDllNameBuffer);
if (hModule == NULL)
{
pImprtTable++;
continue;
}
//獲取INT 以及IAT. 進行雙橋結構遍歷
PIMAGE_THUNK_DATA pIntTable;
PIMAGE_THUNK_DATA pIAtTable;
//INT IAT 中記錄的偏移都是RVA 所以我們要加上首地址來真正定位到導入表導出表所在的位置
DWORD dwIntTableRva = pImprtTable->OriginalFirstThunk;
DWORD dwIatTableRva = pImprtTable->FirstThunk;
pIntTable = (PIMAGE_THUNK_DATA)((char *)PEMmAddress + dwIntTableRva);
pIAtTable = (PIMAGE_THUNK_DATA)((char *)PEMmAddress + dwIatTableRva);
//IAT INT 都是一個偏移. INT 表指向了By_Name表.且是一個數組. 說一要用數組索引
int index = 0;
DWORD dwFunctionAddress = 0;
while (true)
{
if (pIntTable[index].u1.AddressOfData == 0)
{
break;
}
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((char *)PEMmAddress + pIntTable[index].u1.AddressOfData);
//判斷IAT表是否是序號導入
if (pIntTable[index].u1.Ordinal & 0x80000000)
{
//序號導入,加載序號導入 序號的話高位為0 那么 低位就會被看做是一個函數序號
dwFunctionAddress =(DWORD)GetProcAddress(hModule, (LPCSTR)(pIntTable[index].u1.Ordinal & 0x0000FFFF));
}
else
{
//名字導入.加載名字
dwFunctionAddress = (DWORD)GetProcAddress(hModule, (LPCSTR)pByName->Name);
}
//修復到IAT表中
pIAtTable[index].u1.Function = (DWORD)dwFunctionAddress;
index++;
}
//遍歷下一個導入表
//pImprtTable = (PIMAGE_IMPORT_DESCRIPTOR)((char*)pImprtTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));
pImprtTable++;
}
//PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PEMmAddress;
//PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
//PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader +
// pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
//// 循環遍歷DLL導入表中的DLL及獲取導入表中的函數地址
//char* lpDllName = NULL;
//HMODULE hDll = NULL;
//PIMAGE_THUNK_DATA lpImportNameArray = NULL;
//PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
//PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
//FARPROC lpFuncAddress = NULL;
//DWORD i = 0;
//while (TRUE)
//{
// if (0 == pImportTable->OriginalFirstThunk)
// {
// break;
// }
// // 獲取導入表中DLL的名稱並加載DLL
//
// wstring ModuleName = FILE_NAME;
// ModuleName = ModuleName.substr(0, ModuleName.find_last_of(L"\\") + 1);
// //獲取指向的名字,拷貝名
// std::string AscDllName = wstring2string(ModuleName);
// SetCurrentDirectoryA(AscDllName.c_str()); //切換到DLL所在目錄進行加載
// //AscDllName = AscDllName + szDllNameBuffer;
// lpDllName = (char*)((DWORD)pDosHeader + pImportTable->Name);
// hDll = ::GetModuleHandleA(lpDllName);
// if (NULL == hDll)
// {
// hDll = ::LoadLibraryA(lpDllName);
// if (NULL == hDll)
// {
// pImportTable++;
// continue;
// }
// }
// i = 0;
// // 獲取OriginalFirstThunk以及對應的導入函數名稱表首地址
// lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->OriginalFirstThunk);
// // 獲取FirstThunk以及對應的導入函數地址表首地址
// lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->FirstThunk);
// while (TRUE)
// {
// if (0 == lpImportNameArray[i].u1.AddressOfData)
// {
// break;
// }
// // 獲取IMAGE_IMPORT_BY_NAME結構
// lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + lpImportNameArray[i].u1.AddressOfData);
// // 判斷導出函數是序號導出還是函數名稱導出
// if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
// {
// // 序號導出
// // 當IMAGE_THUNK_DATA值的最高位為1時,表示函數以序號方式輸入,這時,低位被看做是一個函數序號
// lpFuncAddress = GetProcAddress(hDll, (LPCSTR)(lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
// }
// else
// {
// // 名稱導出
// lpFuncAddress = GetProcAddress(hDll, (LPCSTR)lpImportByName->Name);
// }
// // 注意此處的函數地址表的賦值,要對照PE格式進行裝載,不要理解錯了!!!
// lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
// i++;
// }
// pImportTable++;
//}
return TRUE;
}
void SepTwo_PEFileStatus2MmStatus(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress) //內存中PE的地址.內存開始
{
/*
PE 文件狀態 轉為 PE內存狀態
1.拷貝整個NT頭到內存
2.拷貝節表數據到內存
3.修復 重定位表 導入表
*/
//setp1 拷貝整個NT頭到內存 核心思想就是獲取Opt頭中的SizeOfNtHeder進行拷貝
DWORD dwSizeOfNtHeder = GetSizeOfNtHeder(PeFileStatusAddress);
RtlCopyMemory(PEMmAddress, PeFileStatusAddress, dwSizeOfNtHeder);
//setp2 遍歷節表根據文件中節表數據 拷貝到內存中節表數據. 按照內存對齊拷貝.
CopyPeFileSectionDataToPeMmSectionData(PeFileStatusAddress, PEMmAddress);
//sep3 查看是否有重定位表.遍歷重定位表.修復重定位表
RepairReloc(PeFileStatusAddress, PEMmAddress);
//修復導入表
RepairExport(PeFileStatusAddress, PEMmAddress);
}
第三步直接調用入口點
BOOL CallEntry(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress)
{
/*
1.解析PE 獲取OEP .跳轉OEP執行,但是跳轉之前需要設置其ImageBase
2.解析PE 設置其ImageBase
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImprtTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//設置ImageBase
pOptHeder->ImageBase = (DWORD)PEMmAddress;
//跳轉到OEP執行
LPVOID Entry = (LPVOID)((ULONG32)PEMmAddress + pOptHeder->AddressOfEntryPoint);
__asm
{
//int 3;
mov eax, Entry;
jmp eax;
}
return TRUE;
}
主函數調用
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//Set1 PE文件狀態操作.
//申請內存,內存大小是整個PE鏡像的大小.也就是SizeOfImage
char* pszFileData = NULL;
pszFileData = SepOne_RetPeFileData();
if (pszFileData == NULL)
{
return FALSE;
}
DWORD dwSizeOfImage = GetPeSizeOfImage(pszFileData);
LPVOID lpMemoryStart = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//SETP2 PE文件轉內存
SepTwo_PEFileStatus2MmStatus(pszFileData, lpMemoryStart);
//第三步,設置頁屬性.
DWORD dwOldProtect = 0;
if (FALSE == ::VirtualProtect(lpMemoryStart, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
return NULL;
}
//第四步,跳轉執行
//獲取入口點跳轉到入口點執行代碼
CallEntry(pszFileData, lpMemoryStart);
MessageBoxA(NULL, "", "", 0);
}
2.所有代碼
// WindowsProject1.cpp : 定義應用程序的入口點。
//
#include "framework.h"
#include "WindowsProject1.h"
#include <string>
using namespace std;
/*
1.讀取文件
2.申請內存. 將PE文件的NT頭拷貝到內存中
3.根據文件中的 節表數據 將數據按照內存對齊拷貝到 申請的內存中
4.判斷有沒有重定位表:
有: 遍歷重定位表.修復重定位表
無: 不進行修復
5.修復導入表
根據雙橋結構
1.遍歷導入表.從導入表中找到 ModuleName選項.根據所需要的DLL 加載DLL
2.遍歷文件中的INT 找到指向INT By_Name的結構 判斷高位是否是序號還是名字
序號: GetProcAddress 獲取序號導出的函數
名字: GetProcAddress 獲取名字導出的函數
3.將獲取的地址 修復到 IAT表中的指向位置.
6.獲取OEP 跳轉到OEP執行.
PELoader 完美執行, 有資源的沒有嘗試.
*/
#define FILE_NAME LR"(C:\Users\Administrator\Desktop\MyPeLoder\WindowsProject1\Release\Test1.exe)"
//#define FILE_NAME LR"(E:\OtherTools\baidu\BaiduNetdisk\BaiduNetdisk.exe)"
//獲取內存鏡像大小
DWORD GetPeSizeOfImage(char* pFileData)
{
/*
根據文件讀取到內存中的數據.進行頭展開.來獲取內存鏡像大小
代碼寫的啰嗦了.但是為了養成良好的解析習慣還是都列出來
*/
IMAGE_DOS_HEADER DosHeader = { 0 };
IMAGE_NT_HEADERS NtHeder = { 0 };
IMAGE_FILE_HEADER FileHeader = { 0 };
IMAGE_OPTIONAL_HEADER OptHeder = { 0 };
RtlCopyMemory(&DosHeader, pFileData, sizeof(IMAGE_DOS_HEADER));
RtlCopyMemory(&NtHeder, pFileData + DosHeader.e_lfanew, sizeof(IMAGE_NT_HEADERS));
RtlCopyMemory(&FileHeader, &NtHeder.FileHeader, sizeof(IMAGE_FILE_HEADER));
RtlCopyMemory(&OptHeder, &NtHeder.OptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER));
if (OptHeder.SizeOfImage != 0)
{
return OptHeder.SizeOfImage;
}
return 0;
}
DWORD GetSizeOfNtHeder(LPVOID PEFileDataAddress)
{
IMAGE_DOS_HEADER DosHeader = { 0 };
IMAGE_NT_HEADERS NtHeder = { 0 };
IMAGE_FILE_HEADER FileHeader = { 0 };
IMAGE_OPTIONAL_HEADER OptHeder = { 0 };
RtlCopyMemory(&DosHeader, PEFileDataAddress, sizeof(IMAGE_DOS_HEADER));
RtlCopyMemory(&NtHeder, (char*)PEFileDataAddress + DosHeader.e_lfanew, sizeof(IMAGE_NT_HEADERS));
RtlCopyMemory(&FileHeader, &NtHeder.FileHeader, sizeof(IMAGE_FILE_HEADER));
RtlCopyMemory(&OptHeder, &NtHeder.OptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER));
if (OptHeder.SizeOfHeaders != 0)
{
return OptHeder.SizeOfHeaders;
}
return 0;
}
DWORD GetSectionNumber(LPVOID PEFileDataAddress)
{
IMAGE_DOS_HEADER DosHeader = { 0 };
IMAGE_NT_HEADERS NtHeder = { 0 };
IMAGE_FILE_HEADER FileHeader = { 0 };
IMAGE_OPTIONAL_HEADER OptHeder = { 0 };
RtlCopyMemory(&DosHeader, PEFileDataAddress, sizeof(IMAGE_DOS_HEADER));
RtlCopyMemory(&NtHeder, (char*)PEFileDataAddress + DosHeader.e_lfanew, sizeof(IMAGE_NT_HEADERS));
RtlCopyMemory(&FileHeader, &NtHeder.FileHeader, sizeof(IMAGE_FILE_HEADER));
RtlCopyMemory(&OptHeder, &NtHeder.OptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER));
if (FileHeader.NumberOfSections != 0)
{
return FileHeader.NumberOfSections;
}
return 0;
}
//第一步 文件操作.獲取PE中文件的數據,返回PE讀取的PE數據
char* SepOne_RetPeFileData()
{
/*
第一步 文件以及內存操作
1.只讀打開文件
2.獲取文件大小
3.映射內存
4.獲取PE鏡像的大小
5.申請內存
*/
HANDLE hFile = INVALID_HANDLE_VALUE;
LARGE_INTEGER laFileSize = { 0 };
hFile =
CreateFile(
FILE_NAME,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (NULL == INVALID_HANDLE_VALUE)
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
if (0 == GetFileSizeEx(hFile, &laFileSize))
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
}
//獲取文件大小
char* pszFileData = NULL;
pszFileData = new char[laFileSize.QuadPart]();
if (pszFileData == NULL)
{
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
DWORD dwReadOrWriteByes = 0;
if (0 == ReadFile(
hFile,
pszFileData,
laFileSize.QuadPart,
&dwReadOrWriteByes,
NULL))
{
if (pszFileData != NULL)
{
delete[] pszFileData;
pszFileData = NULL;
}
if (hFile != INVALID_HANDLE_VALUE)
{
CloseHandle(hFile);
return FALSE;
}
return FALSE;
}
CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
if (pszFileData != NULL)
{
return pszFileData;
}
return NULL;
}
//傳入對齊值,以及大小進行對其拷貝
DWORD GetAgenSizeOfRawData(DWORD Agine,DWORD Value)
{
while ((Value % Agine) != 0)
{
Value++;
}
return Value;
}
BOOL CopyPeFileSectionDataToPeMmSectionData(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress)
{
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSecHeder = NULL;
DWORD SectionCount = GetSectionNumber(PeFileStatusAddress);
if (SectionCount == 0)
{
return FALSE;
}
//賦值頭
pDosHeder = (PIMAGE_DOS_HEADER)PeFileStatusAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PeFileStatusAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSecHeder = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
for (DWORD index = 0; index < SectionCount; index++)
{
if (pSecHeder == NULL)
{
return FALSE;
}
//開始遍歷文件中節表中的數據. 然后拷貝到內存中的數據
char* pSrcData = (char*)PeFileStatusAddress + pSecHeder[index].PointerToRawData; //文件中節表開始的位置
//這里需要內存對齊,而且 VirtualAddress還可能為0 不過一般不會為0 除非畸形PE
char* pDestDataAddress = (char*)PEMmAddress + pSecHeder[index].VirtualAddress;
//DWORD dwSizeOfRawData = GetAgenSizeOfRawData(pOptHeder->SectionAlignment,pSecHeder[index].SizeOfRawData); //獲取文件大小.按照內存對齊來對其來進行拷貝
//DWORD TempValue = (DWORD)((char*)pDestDataAddress + dwSizeOfRawData);
RtlCopyMemory(pDestDataAddress, pSrcData, pSecHeder[index].SizeOfRawData); //拷貝文件中記錄的節數據的大小
//pSecHeder++;
}
return TRUE;
}
//重定位數組表記錄的所有偏移
typedef struct _RELOC_ARRAY
{
WORD ArrayOffset : 12;
WORD mark : 3;
}RELOC_ARRAY, * PRELOC_ARRAY;
//修復重定位表
BOOL RepairReloc(LPVOID PeFileStatusAddress, LPVOID PEMmAddress)
{
/*
文件以及內存中都已經有了數據了.所以這里直接以內存為起始點 來獲取重定位表.並且修復自身內存的重定位表
當然也可以使用文件來便利文件中的重定位表來修復內存中的重定位表
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_BASE_RELOCATION pRelocTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//獲取RVA以及內存中真正重定位表的位置
DWORD dwRelocTalbeRva = pOptHeder->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
pRelocTable = (PIMAGE_BASE_RELOCATION)((char*)PEMmAddress + dwRelocTalbeRva);
if (pRelocTable == PEMmAddress) //代表沒有重定位表
return FALSE;
//遍歷重定位表進行內存中重定位表的修復
while (true)
{
if (pRelocTable->SizeOfBlock == 0)
{
break;
}
//獲取每一個重定位表中重定位表數組. 繼續遍歷這個數組 來獲取offset 來獲取那個地方需要重定位. 然后再把里面的內容修改掉
//獲取地n項 重定位數組表 以及計算出它的大小
PRELOC_ARRAY pRelocArray = (PRELOC_ARRAY)((PBYTE)(pRelocTable)+8);
int nRelocArrayCount = (pRelocTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(RELOC_ARRAY);
for (int index = 0; index < nRelocArrayCount; index++)
{
//是需要進行重定位的一項
if (pRelocArray[index].mark == 3)
{
//計算出需要重定位的位置
PVOID pRelocaLocation = (char*)PEMmAddress + pRelocTable->VirtualAddress + pRelocArray[index].ArrayOffset;
//修改里面重定位的數值為 新的Imagebase + 偏移 偏移計算方式 偏移 = 當前地址 - 原ImageBase = 偏移
DWORD RVA = *(DWORD*)pRelocaLocation - pOptHeder->ImageBase;
DWORD Offset = (DWORD)(char*)PEMmAddress + RVA;
*(DWORD*)pRelocaLocation = Offset;
continue;
}
//否則重定位表移動到下一頁進行操作
}
pRelocTable = (PIMAGE_BASE_RELOCATION)((char*)pRelocTable + pRelocTable->SizeOfBlock);
}
return TRUE;
// PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PEMmAddress;
// PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
// PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)pDosHeader + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
//
// // 判斷是否有 重定位表
// if ((PVOID)pLoc == (PVOID)pDosHeader)
// {
// // 重定位表 為空
// return TRUE;
// }
//
// while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //開始掃描重定位表
// {
// WORD* pLocData = (WORD*)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
// //計算本節需要修正的重定位項(地址)的數目
// int nNumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
//
// for (int i = 0; i < nNumberOfReloc; i++)
// {
// // 每個WORD由兩部分組成。高4位指出了重定位的類型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定義了重定位類型的取值。
// // 低12位是相對於VirtualAddress域的偏移,指出了必須進行重定位的位置。
///*
// #ifdef _WIN64
// if ((DWORD)(pLocData[i] & 0x0000F000) == 0x0000A000)
// {
// // 64位dll重定位,IMAGE_REL_BASED_DIR64
// // 對於IA-64的可執行文件,重定位似乎總是IMAGE_REL_BASED_DIR64類型的。
//
// ULONGLONG* pAddress = (ULONGLONG *)((PBYTE)pNewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
// ULONGLONG ullDelta = (ULONGLONG)pNewBase - m_pNTHeader->OptionalHeader.ImageBase;
// *pAddress += ullDelta;
//
// }
// #endif
//*/
// if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //這是一個需要修正的地址
// {
// // 32位dll重定位,IMAGE_REL_BASED_HIGHLOW
// // 對於x86的可執行文件,所有的基址重定位都是IMAGE_REL_BASED_HIGHLOW類型的。
// DWORD* pAddress = (DWORD*)((PBYTE)pDosHeader + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
// DWORD dwDelta = (DWORD)pDosHeader - pNtHeaders->OptionalHeader.ImageBase;
// *pAddress += dwDelta;
//
// }
// }
//
// //轉移到下一個節進行處理
// pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
// }
return TRUE;
}
//將wstring轉換成string
string wstring2string(wstring wstr)
{
string result;
//獲取緩沖區大小,並申請空間,緩沖區大小事按字節計算的
int len = WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), NULL, 0, NULL, NULL);
char* buffer = new char[len + 1];
//寬字節編碼轉換成多字節編碼
WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), wstr.size(), buffer, len, NULL, NULL);
buffer[len] = '\0';
//刪除緩沖區並返回值
result.append(buffer);
delete[] buffer;
return result;
}
BOOL RepairExport(LPVOID PeFileStatusAddress, LPVOID PEMmAddress)
{
/*
根據雙橋結構來進行導入表的修復
1.根據所需要的模塊,加載其模塊
2.根據BY_NAME結構. 解析是否是序號到處還是名字導入. 並調用GetProcaddress 來進行 函數地址獲取
3.將IAT表指向的函數地址 修改為獲取的函數地址
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImprtTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//獲取導入表
DWORD dwImportRva = pOptHeder->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
pImprtTable = (PIMAGE_IMPORT_DESCRIPTOR)((char*)PEMmAddress + dwImportRva);
if (pImprtTable == PEMmAddress) //說明沒有導入表
return FALSE;
HMODULE hModule = NULL;
//遍歷導入表的雙橋結構
while (true)
{
//校驗退出條件. 退出條件是當 導入表最后一項為0的時候進行退出
if (pImprtTable->OriginalFirstThunk == 0
&& pImprtTable->Name == 0
&& pImprtTable->FirstThunk == 0)
{
break;
}
//獲取模塊名字.根據名字進行加載模塊. 這里需要注入. 你的PEloader是放在他的目錄下才能加載DLL成功. 否則你就要做一個全局路徑來拼接處DLL路徑來進行加載
//這里拼接模塊名字獲取.這樣就不會限制在只能放在它目錄下才能運行
wstring ModuleName = FILE_NAME;
ModuleName = ModuleName.substr(0, ModuleName.find_last_of(L"\\") + 1);
//獲取指向的名字,拷貝名字
char * pDllName = (char*)((char*)PEMmAddress + pImprtTable->Name);
char szDllNameBuffer[MAX_PATH] = { 0 };
strcpy_s(szDllNameBuffer, sizeof(szDllNameBuffer) / sizeof(szDllNameBuffer[0]), (char *)pDllName);
std::string AscDllName = wstring2string(ModuleName);
SetCurrentDirectoryA(AscDllName.c_str()); //切換到DLL所在目錄進行加載
//AscDllName = AscDllName + szDllNameBuffer;
//
hModule = LoadLibraryA(szDllNameBuffer);
if (hModule == NULL)
{
pImprtTable++;
continue;
}
//獲取INT 以及IAT. 進行雙橋結構遍歷
PIMAGE_THUNK_DATA pIntTable;
PIMAGE_THUNK_DATA pIAtTable;
//INT IAT 中記錄的偏移都是RVA 所以我們要加上首地址來真正定位到導入表導出表所在的位置
DWORD dwIntTableRva = pImprtTable->OriginalFirstThunk;
DWORD dwIatTableRva = pImprtTable->FirstThunk;
pIntTable = (PIMAGE_THUNK_DATA)((char *)PEMmAddress + dwIntTableRva);
pIAtTable = (PIMAGE_THUNK_DATA)((char *)PEMmAddress + dwIatTableRva);
//IAT INT 都是一個偏移. INT 表指向了By_Name表.且是一個數組. 說一要用數組索引
int index = 0;
DWORD dwFunctionAddress = 0;
while (true)
{
if (pIntTable[index].u1.AddressOfData == 0)
{
break;
}
PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((char *)PEMmAddress + pIntTable[index].u1.AddressOfData);
//判斷IAT表是否是序號導入
if (pIntTable[index].u1.Ordinal & 0x80000000)
{
//序號導入,加載序號導入 序號的話高位為0 那么 低位就會被看做是一個函數序號
dwFunctionAddress =(DWORD)GetProcAddress(hModule, (LPCSTR)(pIntTable[index].u1.Ordinal & 0x0000FFFF));
}
else
{
//名字導入.加載名字
dwFunctionAddress = (DWORD)GetProcAddress(hModule, (LPCSTR)pByName->Name);
}
//修復到IAT表中
pIAtTable[index].u1.Function = (DWORD)dwFunctionAddress;
index++;
}
//遍歷下一個導入表
//pImprtTable = (PIMAGE_IMPORT_DESCRIPTOR)((char*)pImprtTable + sizeof(IMAGE_IMPORT_DESCRIPTOR));
pImprtTable++;
}
//PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)PEMmAddress;
//PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((ULONG32)pDosHeader + pDosHeader->e_lfanew);
//PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader +
// pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
//// 循環遍歷DLL導入表中的DLL及獲取導入表中的函數地址
//char* lpDllName = NULL;
//HMODULE hDll = NULL;
//PIMAGE_THUNK_DATA lpImportNameArray = NULL;
//PIMAGE_IMPORT_BY_NAME lpImportByName = NULL;
//PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL;
//FARPROC lpFuncAddress = NULL;
//DWORD i = 0;
//while (TRUE)
//{
// if (0 == pImportTable->OriginalFirstThunk)
// {
// break;
// }
// // 獲取導入表中DLL的名稱並加載DLL
//
// wstring ModuleName = FILE_NAME;
// ModuleName = ModuleName.substr(0, ModuleName.find_last_of(L"\\") + 1);
// //獲取指向的名字,拷貝名
// std::string AscDllName = wstring2string(ModuleName);
// SetCurrentDirectoryA(AscDllName.c_str()); //切換到DLL所在目錄進行加載
// //AscDllName = AscDllName + szDllNameBuffer;
// lpDllName = (char*)((DWORD)pDosHeader + pImportTable->Name);
// hDll = ::GetModuleHandleA(lpDllName);
// if (NULL == hDll)
// {
// hDll = ::LoadLibraryA(lpDllName);
// if (NULL == hDll)
// {
// pImportTable++;
// continue;
// }
// }
// i = 0;
// // 獲取OriginalFirstThunk以及對應的導入函數名稱表首地址
// lpImportNameArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->OriginalFirstThunk);
// // 獲取FirstThunk以及對應的導入函數地址表首地址
// lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImportTable->FirstThunk);
// while (TRUE)
// {
// if (0 == lpImportNameArray[i].u1.AddressOfData)
// {
// break;
// }
// // 獲取IMAGE_IMPORT_BY_NAME結構
// lpImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + lpImportNameArray[i].u1.AddressOfData);
// // 判斷導出函數是序號導出還是函數名稱導出
// if (0x80000000 & lpImportNameArray[i].u1.Ordinal)
// {
// // 序號導出
// // 當IMAGE_THUNK_DATA值的最高位為1時,表示函數以序號方式輸入,這時,低位被看做是一個函數序號
// lpFuncAddress = GetProcAddress(hDll, (LPCSTR)(lpImportNameArray[i].u1.Ordinal & 0x0000FFFF));
// }
// else
// {
// // 名稱導出
// lpFuncAddress = GetProcAddress(hDll, (LPCSTR)lpImportByName->Name);
// }
// // 注意此處的函數地址表的賦值,要對照PE格式進行裝載,不要理解錯了!!!
// lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
// i++;
// }
// pImportTable++;
//}
return TRUE;
}
void SepTwo_PEFileStatus2MmStatus(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress) //內存中PE的地址.內存開始
{
/*
PE 文件狀態 轉為 PE內存狀態
1.拷貝整個NT頭到內存
2.拷貝節表數據到內存
3.修復 重定位表 導入表
*/
//setp1 拷貝整個NT頭到內存 核心思想就是獲取Opt頭中的SizeOfNtHeder進行拷貝
DWORD dwSizeOfNtHeder = GetSizeOfNtHeder(PeFileStatusAddress);
RtlCopyMemory(PEMmAddress, PeFileStatusAddress, dwSizeOfNtHeder);
//setp2 遍歷節表根據文件中節表數據 拷貝到內存中節表數據. 按照內存對齊拷貝.
CopyPeFileSectionDataToPeMmSectionData(PeFileStatusAddress, PEMmAddress);
//sep3 查看是否有重定位表.遍歷重定位表.修復重定位表
RepairReloc(PeFileStatusAddress, PEMmAddress);
//修復導入表
RepairExport(PeFileStatusAddress, PEMmAddress);
}
BOOL CallEntry(
LPVOID PeFileStatusAddress, //文件的內存地址保存了文件中PE的數據
LPVOID PEMmAddress)
{
/*
1.解析PE 獲取OEP .跳轉OEP執行,但是跳轉之前需要設置其ImageBase
2.解析PE 設置其ImageBase
*/
PIMAGE_DOS_HEADER pDosHeder = NULL;
PIMAGE_NT_HEADERS pNtHeder = NULL;
PIMAGE_FILE_HEADER pFileHeder = NULL;
PIMAGE_OPTIONAL_HEADER pOptHeder = NULL;
PIMAGE_SECTION_HEADER pSec = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImprtTable = NULL;
//賦值各個相關字段
pDosHeder = (PIMAGE_DOS_HEADER)PEMmAddress;
pNtHeder = (PIMAGE_NT_HEADERS)((char*)PEMmAddress + pDosHeder->e_lfanew);
pFileHeder = (PIMAGE_FILE_HEADER)&pNtHeder->FileHeader;
pOptHeder = (PIMAGE_OPTIONAL_HEADER)&pNtHeder->OptionalHeader;
pSec = (PIMAGE_SECTION_HEADER)&pNtHeder[1];
//設置ImageBase
pOptHeder->ImageBase = (DWORD)PEMmAddress;
//跳轉到OEP執行
LPVOID Entry = (LPVOID)((ULONG32)PEMmAddress + pOptHeder->AddressOfEntryPoint);
__asm
{
//int 3;
mov eax, Entry;
jmp eax;
}
return TRUE;
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//Set1 PE文件狀態操作.
//申請內存,內存大小是整個PE鏡像的大小.也就是SizeOfImage
char* pszFileData = NULL;
pszFileData = SepOne_RetPeFileData();
if (pszFileData == NULL)
{
return FALSE;
}
DWORD dwSizeOfImage = GetPeSizeOfImage(pszFileData);
LPVOID lpMemoryStart = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
//SETP2 PE文件轉內存
SepTwo_PEFileStatus2MmStatus(pszFileData, lpMemoryStart);
//第三步,設置頁屬性.
DWORD dwOldProtect = 0;
if (FALSE == ::VirtualProtect(lpMemoryStart, dwSizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
return NULL;
}
//第四步,跳轉執行
//獲取入口點跳轉到入口點執行代碼
CallEntry(pszFileData, lpMemoryStart);
MessageBoxA(NULL, "", "", 0);
}
這里模擬加載的是我自己創建的一個 窗口程序
代碼如下:
// Test1.cpp : 定義應用程序的入口點。
//
#include "framework.h"
#include "Test1.h"
#define MAX_LOADSTRING 100
// 全局變量:
HINSTANCE hInst; // 當前實例
WCHAR szTitle[MAX_LOADSTRING]; // 標題欄文本
WCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口類名
// 此代碼模塊中包含的函數的前向聲明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int g_Address = 10;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此處放置代碼。
// 初始化全局字符串
g_Address = 11;
MessageBoxA(NULL, "1", "1", 0);
}
三丶運行結果
四丶存在的問題
經過測試自己編寫的測試程序可以加載. 而加載百度雲盤 以及 等帶有資源的程序會加載失敗.
還是要找問題.這份代碼算是簡單的學習吧.