对于控制台应用,想要使其跑起来,基础要求如下:
PE加载到内存
PE根据PE头对齐进行伸展开
修复导入表
修复重定位表
修改内存属性,赋予执行权限
具体代码如下:
PELoad.h文件内容,主要完成了对PE文件类对象的声明操作
#include <iostream>
#include <windows.h>
#ifndef _PE_LOAD_H_
#define _PE_LOAD_H_
class CPE{
private:
PBYTE pMemData;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeader;
PIMAGE_FILE_HEADER pFileHeader;
PIMAGE_OPTIONAL_HEADER pOperationHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
//获取文件在内存中的镜像大小
DWORD GetSizeOfImage(PBYTE pFileBuff);
//修复导入表
bool ImportTable();
//修复重定位表
bool RelocationTable();
//抹除PE头信息
bool ErasurePEHeader();
public:
//无参构造
CPE();
//有参构造
CPE(PCHAR pFilePathName);
//释放资源
~CPE();
//拉伸PE文件
bool MapFile(PCHAR pFilePathName);
//开始执行程序
bool CallEntry();
};
#endif
PELoad.cpp文件主要是对类内容的实现部分
#include "PELoad.h"
//无参构造
CPE::CPE(){
pMemData = NULL;
pDosHeader = NULL;
pNtHeader = NULL;
pFileHeader = NULL;
pOperationHeader = NULL;
pSectionHeader = NULL;
}
//有参构造
CPE::CPE(PCHAR pFilePathName){
CPE();
MapFile(pFilePathName);
}
//获取文件在内存中的镜像大小
DWORD CPE::GetSizeOfImage(PBYTE pFileBuff){
DWORD dwSizeOfImage = 0;
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileBuff;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pFileBuff + pDos->e_lfanew);
dwSizeOfImage = pNt->OptionalHeader.SizeOfImage;
return dwSizeOfImage;
}
//拉伸PE文件
bool CPE::MapFile(PCHAR pFilePathName){
//打开文件,设置属性可读可写
HANDLE hFile = CreateFileA(pFilePathName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL);
if (INVALID_HANDLE_VALUE == hFile){
return FALSE;
}
//获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
//申请空间
PBYTE pFileData = new BYTE[dwFileSize];
if (NULL == pFileData){
return FALSE;
}
//将文件读取到内存中
DWORD dwRet = 0;
ReadFile(hFile, pFileData, dwFileSize, &dwRet, NULL);
CloseHandle(hFile);
//==============================开始初始化PE头===============
//获取镜像大小
DWORD dwSizeOfImage = GetSizeOfImage(pFileData);
//根据镜像大小在进程中开辟一块内存空间
pMemData = (PBYTE)VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pMemData){
return FALSE;
}
//将申请的进程空间全部填0
RtlZeroMemory(pMemData, dwSizeOfImage);
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pFileData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pFileData + pDos->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
//所有头 + 结表头的大小
DWORD dwSizeOfHeaders = pNt->OptionalHeader.SizeOfHeaders;
//获取区段数量
int nNumerOfSections = pNt->FileHeader.NumberOfSections;
// 将前一部分都拷贝过去
RtlCopyMemory(pMemData, pFileData, dwSizeOfHeaders);
//初始化各个头
pDosHeader = (PIMAGE_DOS_HEADER)pMemData;
pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + pMemData);
pFileHeader = (PIMAGE_FILE_HEADER)&pNtHeader->FileHeader;
pOperationHeader = (PIMAGE_OPTIONAL_HEADER)&pNtHeader->OptionalHeader;
pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
PBYTE chSrcMem = NULL;
PBYTE chDestMem = NULL;
DWORD dwSizeOfRawData = 0;
for (int i = 0; i < nNumerOfSections; i++){
if ((0 == pSection->VirtualAddress) ||(0 == pSection->SizeOfRawData)){
pSection++;
continue;
}
// 拷贝节区
chSrcMem = (PBYTE)((DWORD)pFileData + pSection->PointerToRawData);
chDestMem = (PBYTE)((DWORD)pMemData + pSection->VirtualAddress);
dwSizeOfRawData = pSection->SizeOfRawData;
RtlCopyMemory(chDestMem, chSrcMem, dwSizeOfRawData);
//指向下一个节区
pSection++;
}
return TRUE;
}
//修复导入表
bool CPE::ImportTable(){
//获取导入表
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)(pMemData +
pOperationHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
// 循环遍历DLL导入表中的DLL及获取导入表中的函数地址
PBYTE lpDllName = NULL; //指向dll名称
HMODULE hDll = NULL; //保存dll基址
PIMAGE_THUNK_DATA lpImportNameArray = NULL; //导入名称表
PIMAGE_IMPORT_BY_NAME lpImportByName = NULL; //函数名称结构
PIMAGE_THUNK_DATA lpImportFuncAddrArray = NULL; //函数地址表
FARPROC lpFuncAddress = NULL; //函数地址
DWORD i = 0;
while (0 != pImportTable->OriginalFirstThunk){
// 获取导入表中DLL的名称并加载DLL
lpDllName = (PBYTE)(pMemData + pImportTable->Name);
//首先从当前内存中检索所需模块
hDll = GetModuleHandleA((LPCTSTR)lpDllName);
//如果内存中没有,则去加载所需模块
if (NULL == hDll){
hDll = LoadLibraryA((LPCTSTR)lpDllName);
if (NULL == hDll){
pImportTable++;
continue;
}
}
// 获取OriginalFirstThunk以及对应的导入函数名称表首地址
lpImportNameArray = (PIMAGE_THUNK_DATA)(pMemData + pImportTable->OriginalFirstThunk);
// 获取FirstThunk以及对应的导入函数地址表首地址
lpImportFuncAddrArray = (PIMAGE_THUNK_DATA)(pMemData + pImportTable->FirstThunk);
for (int i = 0; 0 != lpImportNameArray[i].u1.AddressOfData;i++){
// 获取IMAGE_IMPORT_BY_NAME结构
lpImportByName = (PIMAGE_IMPORT_BY_NAME)(pMemData + 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);
}
// 注意此处的函数地址表的赋值
lpImportFuncAddrArray[i].u1.Function = (DWORD)lpFuncAddress;
}
//指向下一个导入模块结构
pImportTable++;
}
return TRUE;
}
bool CPE::RelocationTable(){
//获取重定位表
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(pMemData + pOperationHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
//判断是否有重定位表
if (pLoc->SizeOfBlock == 0 || pLoc->VirtualAddress == 0){
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++){
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000) //这是一个需要修正的地址
{
DWORD* pAddress = (DWORD*)(pMemData + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF));
DWORD dwDelta = *pAddress + (DWORD)pMemData - pNtHeader->OptionalHeader.ImageBase;
*pAddress = dwDelta;
}
}
//转移到下一个节进行处理
pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}
return TRUE;
}
bool CPE::CallEntry(){
PBYTE ExeEntry = (pMemData + pNtHeader->OptionalHeader.AddressOfEntryPoint);
//修复导入表
if (!ImportTable()){
return FALSE;
}
//修复重定位表
if (!RelocationTable()){
return FALSE;
}
//擦除PE头信息
ErasurePEHeader();
// 跳转到入口点处执行
__asm
{
mov eax, ExeEntry;
jmp eax;
}
return TRUE;
}
bool CPE::ErasurePEHeader(){
RtlZeroMemory(pDosHeader, sizeof(IMAGE_DOS_HEADER));
RtlZeroMemory(pNtHeader, sizeof(IMAGE_NT_HEADERS));
RtlZeroMemory(pSectionHeader, sizeof(IMAGE_SECTION_HEADER));
pDosHeader = NULL;
pNtHeader = NULL;
pFileHeader = NULL;
pOperationHeader = NULL;
pSectionHeader = NULL;
return TRUE;
}
CPE::~CPE(){
if (pMemData){
delete[] pMemData;
}
}
main.cpp文件是对PE类的操作
#include <iostream>
#include "PELoad.h"
using namespace std;
int main(){
char arr[128];
cin >> arr;
CPE cpe(arr);
cpe.CallEntry();
return 0;
}
运行效果展示:z.exe程序直接运行
z.exe程序使用加载器运行
用加载器加载的好处是:
可以对文件进行加解密操作
抹除被加载程序的PE头信息,防止爆破进程号打开进程
为逆向工作操作添加阻碍