内存加载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] = ' ';
//删除缓冲区并返回值
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:UsersAdministratorDesktopMyPeLoderWindowsProject1ReleaseTest1.exe)"
//#define FILE_NAME LR"(E:OtherToolsaiduBaiduNetdiskBaiduNetdisk.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] = ' ';
//删除缓冲区并返回值
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);
}
三丶运行结果
四丶存在的问题
经过测试自己编写的测试程序可以加载. 而加载百度云盘 以及 等带有资源的程序会加载失败.
还是要找问题.这份代码算是简单的学习吧.