PE结构:所有的可执行文件都要满足PE文件建构
我这里就以内存图的形式展示一下pe结构
上图为pe的简单内存结构图
1.DosHeader 结构 这里我仅说明我常用到的成员。
struct _IMAGE_DOS_HEADER {
0x00 WORD e_magic; // 记录是否是可执行文件
0x02 WORD e_cblp;
0x04 WORD e_cp;
0x06 WORD e_crlc;
0x08 WORD e_cparhdr;
0x0a WORD e_minalloc;
0x0c WORD e_maxalloc;
0x0e WORD e_ss;
0x10 WORD e_sp;
0x12 WORD e_csum;
0x14 WORD e_ip;
0x16 WORD e_cs;
0x18 WORD e_lfarlc;
0x1a WORD e_ovno;
0x1c WORD e_res[4];
0x24 WORD e_oemid;
0x26 WORD e_oeminfo;
0x28 WORD e_res2[10];
0x3c DWORD e_lfanew; // 记录PE标准头开始位置的偏移
};
2.NTHeader结构
struct _IMAGE_NT_HEADERS {
0x00 DWORD Signature; // pe签名
0x04 _IMAGE_FILE_HEADER FileHeader; // 标准pe头
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader; // 可选PE头
};
2.1 FileHeader 标准PE头
struct _IMAGE_FILE_HEADER {
0x00 WORD Machine; // 机器型号
0x02 WORD NumberOfSections; // 节表数量
0x04 DWORD TimeDateStamp; // 时间戳
0x08 DWORD PointerToSymbolTable;
0x0c DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader; // 可选pe头大小
0x12 WORD Characteristics; // 特征码,每个位代表一个含义
};
2.2 OptionalHeader 可选PE头
struct _IMAGE_OPTIONAL_HEADER {
0x00 WORD Magic;
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; // 所有代码节的和 FileAlignment对齐后的大小
0x08 DWORD SizeOfInitializedData; // 已经初始化数据大小的和 FileAlignment对齐后的大小
0x0c DWORD SizeOfUninitializedData; // 未初始化数据大小的和 FileAlignment对齐后的大小
0x10 DWORD AddressOfEntryPoint; // 程序入口地址偏移量
0x14 DWORD BaseOfCode; // 代码开始的基址
0x18 DWORD BaseOfData; // 数据开始的基址
0x1c DWORD ImageBase; // 内存镜像的基址
0x20 DWORD SectionAlignment; // 内存对齐大小
0x24 DWORD FileAlignment; // 文件对齐大小
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; // 内存中整个pe的小小
0x3c DWORD SizeOfHeaders; // 所有头+节表的大小, 按照文件对齐都的大小
0x40 DWORD CheckSum;
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve;
0x4c DWORD SizeOfStackCommit;
0x50 DWORD SizeOfHeapReserve;
0x54 DWORD SizeOfHeapCommit;
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes;
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16];
};
3.节表 一个PE文件有多个节表每个节表都是内存连续的 节表结构如下
typedef struct _IMAGE_SECTION_HEADER {
0x00 BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节的名字
union {
0x08 DWORD PhysicalAddress;
0x08 DWORD VirtualSize;
} Misc; // 联合 一般都是真实数据的大小
0x0c DWORD VirtualAddress; // 节在内存中的地址偏移
0x10 DWORD SizeOfRawData; // 节在文件中对齐后的大小
0x14 DWORD PointerToRawData; // 节在文件中的地址偏移
0x18 DWORD PointerToRelocations;
0x1c DWORD PointerToLinenumbers;
0x20 WORD NumberOfRelocations;
0x22 WORD NumberOfLinenumbers;
0x24 DWORD Characteristics; // 特征码 具体代表意思看下图
};
节表的特征码代表含义看下图
除了 DataDirectory 结构大概就是这样。然后下图是在 内存中 的关系图
下面代码实践。
1.一个从执行文件把二进制写入到内存,
2.然后将读取到的信息拉伸后写入新的一块内存
3.将MessageBox的地址写入代码区
4.将拉伸后的内存 去掉拉伸后的内存形式写入 一块新内存
5.将新的内存写入一个新的 exe文件
PeStructPractise.h
#pragma once DWORD ReadPEFile(const char* lpszFile, PVOID* pFileBuffer); // 将filebuff 拉伸复制到 imagebuff DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer); // 将imagebuff复制到 newbuff DWORD CopyImageBufferToNewBuffer(PVOID pImageBuffer, PVOID* pNewBuffer); // 在.txt添加代码 BOOL WriteNewCodeInImageBuffCode(LPVOID* pImageBuff); BOOL MemeryTOFile(PVOID pMemBuffer, DWORD size);
PeStructPractise.cpp
#include#include #include #include "PeStructPractise.h" DWORD ReadPEFile(const char* lpszFile, PVOID* pFileBuffer) { FILE* pFile = NULL; DWORD fileSize = 0; //打开文件 DWORD ret = fopen_s(&pFile, lpszFile, "rb"); if (ret) { printf(" 无法打开 EXE 文件! "); return 0; } //读取文件大小 fseek(pFile, 0, SEEK_END); fileSize = ftell(pFile); fseek(pFile, 0, SEEK_SET); //分配缓冲区 *pFileBuffer = malloc(fileSize); if (!*pFileBuffer) { printf(" 分配空间失败! "); fclose(pFile); return 0; } //将文件数据读取到缓冲区 size_t n = fread(*pFileBuffer, fileSize, 1, pFile); if (!n) { printf(" 读取数据失败! "); free(*pFileBuffer); fclose(pFile); return 0; } //关闭文件 fclose(pFile); return fileSize; } DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer, PVOID* pImageBuffer) { PIMAGE_DOS_HEADER pDocHead = (PIMAGE_DOS_HEADER)pFileBuffer; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((PBYTE)pFileBuffer + pDocHead->e_lfanew); PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((PBYTE)pNtHead + 4); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pFileHeader + sizeof(IMAGE_FILE_HEADER)); // 复制 sizeofheader 到 imgebuff *pImageBuffer = malloc(pOptionalHeader->SizeOfImage); DWORD nSizeOfImage = pOptionalHeader->SizeOfImage; // pe内存映射的总大小 if (!*pImageBuffer) { printf(" 分配空间失败! "); return 0; } DWORD nSizeOfHeader = pOptionalHeader->SizeOfHeaders; // pe头 + 所有节表 文件对齐后的大小 memset(*pImageBuffer, 0, nSizeOfImage); memcpy_s(*pImageBuffer, nSizeOfHeader, pFileBuffer, nSizeOfHeader); // 复制节表 DWORD nCopySize = 0; PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((PBYTE)pOptionalHeader + pFileHeader->SizeOfOptionalHeader); for (DWORD i = 0; i < pFileHeader->NumberOfSections; i++) { DWORD nPointToRawData = pSectionHeader->PointerToRawData; // 节表在文件中的地址偏移 DWORD nVirtualAddress = pSectionHeader->VirtualAddress; // 节表在内存中的地址偏移 DWORD nSizeOfRawData = pSectionHeader->SizeOfRawData; // 节表对齐后的大小 memcpy_s((PBYTE)*pImageBuffer + nVirtualAddress, nSizeOfRawData, (PBYTE)pDocHead + nPointToRawData, nSizeOfRawData); pSectionHeader += 1; nCopySize = nVirtualAddress + nSizeOfRawData; } return nCopySize; } DWORD CopyImageBufferToNewBuffer(PVOID pImageBuffer, PVOID* pNewBuffer) { PIMAGE_DOS_HEADER pDocHead = (PIMAGE_DOS_HEADER)pImageBuffer; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((PBYTE)pImageBuffer + pDocHead->e_lfanew); PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((PBYTE)pNtHead + 4); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pFileHeader + sizeof(IMAGE_FILE_HEADER)); // 复制 sizeofheader 到 imgebuff DWORD nSizeOfHeader = pOptionalHeader->SizeOfHeaders; // pe头 + 所有节表 文件对齐后的大小 DWORD nSizeOfImage = pOptionalHeader->SizeOfImage; // pe内存映射的总大小 *pNewBuffer = malloc(nSizeOfImage); if (!*pNewBuffer) { printf(" 分配空间失败! "); return 0; } memset(*pNewBuffer, 0, nSizeOfImage); memcpy_s(*pNewBuffer, nSizeOfHeader, pImageBuffer, nSizeOfHeader); // 复制节表 节表开始的位置 PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((PBYTE)pOptionalHeader + pFileHeader->SizeOfOptionalHeader); DWORD nCopySize = 0; for (DWORD i = 0; i < pFileHeader->NumberOfSections; i++) { DWORD nPointToRawData = pSectionHeader->PointerToRawData; // 节表在文件中的地址偏移 DWORD nVirtualAddress = pSectionHeader->VirtualAddress; // 节表在内存中的地址偏移 DWORD nSizeOfRawData = pSectionHeader->SizeOfRawData; // 节表对齐后的数据大小 memcpy_s((PBYTE)*pNewBuffer + nPointToRawData, nSizeOfRawData, (PBYTE)pDocHead + nVirtualAddress, nSizeOfRawData); pSectionHeader += 1; nCopySize = nPointToRawData + nSizeOfRawData; } return nCopySize; } char CodeMessageBox[] = { 0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00 }; // 加入代码 BOOL WriteNewCodeInImageBuffCode(LPVOID* pImageBuff) { PIMAGE_DOS_HEADER pDocHead = (PIMAGE_DOS_HEADER)*pImageBuff; PIMAGE_NT_HEADERS pNtHead = (PIMAGE_NT_HEADERS)((PBYTE)*pImageBuff + pDocHead->e_lfanew); PIMAGE_FILE_HEADER pFileHeader = (PIMAGE_FILE_HEADER)((PBYTE)pNtHead + 4); PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pFileHeader + sizeof(IMAGE_FILE_HEADER)); PIMAGE_SECTION_HEADER pSectionHead = (PIMAGE_SECTION_HEADER)((PBYTE)pOptionalHeader + pFileHeader->SizeOfOptionalHeader); // 节表开始的位置 WORD nNumberOfSections = pFileHeader->NumberOfSections; DWORD nImageBase = pOptionalHeader->ImageBase; DWORD nPointToRawData = pSectionHead->PointerToRawData; // 节表在文件中的地址偏移 DWORD nVirtualAddress = pSectionHead->VirtualAddress; // 节表在内存中的地址偏移 DWORD nMisc = pSectionHead->Misc.VirtualSize; // 节表中真实数据的大小 DWORD nSizeOfRawData = pSectionHead->SizeOfRawData; // 节表对齐后的数据大小 DWORD nEntryOfPoint = pOptionalHeader->AddressOfEntryPoint; // 入口偏移 DWORD nNewCodeSize = sizeof(CodeMessageBox); if ((nSizeOfRawData - nMisc) > nNewCodeSize) { DWORD nBeginAddress = nVirtualAddress + nMisc; //0x75E90D60 是MessageBoxA的地址,电脑重启和其他电脑都会变,可用x64dlg查找。使用方法看后面的 DWORD nDealNewCodeAdress = 0x75E90D60 - (nImageBase + nBeginAddress + 8 + 5); memcpy_s(CodeMessageBox + 9, 4, &nDealNewCodeAdress, 4); DWORD nMainCodeAddress = (nImageBase + nEntryOfPoint) - (nImageBase + nBeginAddress + 13 + 5); memcpy_s(CodeMessageBox + 14, 4, &nMainCodeAddress, 4); memcpy_s((PBYTE)pDocHead + nBeginAddress, nNewCodeSize, CodeMessageBox, nNewCodeSize); pOptionalHeader->AddressOfEntryPoint = nBeginAddress; return true; } return false; } BOOL MemeryTOFile(PVOID pMemBuffer, DWORD size) { FILE* stream; fopen_s(&stream, ".\test.exe", "wb+"); fwrite(pMemBuffer, size, 1, stream); fclose(stream); return true; }
main.cpp
#include#include #include "PeStructPractise.h" int main() { std::string szExePath = "D:\PETool 1.0.0.5.exe"; // 找一个只有exe就可以正常执行的文件 PVOID pFileBuff = nullptr; int ret = ReadPEFile(szExePath.c_str(), &pFileBuff); if (ret == 0) { printf("ReadPEFile failed, szExePath:%sn", szExePath.c_str()); return 0; } else { printf("ReadPEFile sucess, szExePath:%s, nLen:%dn", szExePath.c_str(), ret); } PVOID pImageBuff = nullptr; ret = CopyFileBufferToImageBuffer(pFileBuff, &pImageBuff); free(pFileBuff); pFileBuff = nullptr; // 写入.text节 ret = WriteNewCodeInImageBuffCode(&pImageBuff); PVOID pNewBuff = nullptr; ret = CopyImageBufferToNewBuffer(pImageBuff, &pNewBuff); MemeryTOFile(pNewBuff, ret); free(pImageBuff); free(pNewBuff); pNewBuff = nullptr; pImageBuff = nullptr; return 0; }
注意:
1.exe你要弄一个可单独执行的exe
2.添加进去的代码 是要你自己电脑上MessageBoxA的地址加上去。 可通过Ob 或者x64dlg查看如下图
写入代码前效果打开后直接是这样
写入代码后效果打开后先弹窗 再打开 petool如下图



