手动展开PE文件 | 二进制安全

admin 2023年1月11日01:14:06评论15 views字数 6203阅读20分40秒阅读模式

对于控制台应用,想要使其跑起来,基础要求如下:

  • 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程序直接运行

手动展开PE文件 | 二进制安全

z.exe程序使用加载器运行

手动展开PE文件 | 二进制安全

用加载器加载的好处是:

  • 可以对文件进行加解密操作

  • 抹除被加载程序的PE头信息,防止爆破进程号打开进程

  • 为逆向工作操作添加阻碍

原文始发于微信公众号(0x00实验室):手动展开PE文件 | 二进制安全

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月11日01:14:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   手动展开PE文件 | 二进制安全https://cn-sec.com/archives/1510341.html

发表评论

匿名网友 填写信息