PE的加载从文件到内存有一个拉伸的过程,拉伸的原因是因为PE在文件中的对齐字节和在内存中的对齐字节可能不一样(文件对齐字节<=内存对齐字节,为了节省磁盘空间,目前的pe文件大部分文件和内存对齐字节都是一样的)。文件对齐字节在可选PE头里:
_IMAGE_OPTIONAL_HEADER:
32 | 4 | SectionAlignment | 内存对齐 | 当加载进内存时节的对齐值(以字节计)。它必须≥FileAlignment。默认是相应系统的页面大小。 |
36 | 4 | FileAlignment | 文件对齐 |
用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)。它应该是界于512和64K之间的2的幂(包括这两个边界值)。默认是512。如果SectionAlignment小于相应系统的页面大小,那么FileAlignment必须与SectionAlignment相等。 |
下面模拟PE文件加载时内存拉伸的过程:
1、读取文件,判断文件大小,分配一段与文件大小相同的内存缓冲区,filebuffer
2、读取DOS头结构,根据DOS头,判断第一个WORD e_magic,这是dos头标记‘MZ’,用来判断是否是pe文件格式,读取最后一个DWORD(e_lfanew)指向NT头
3、读取NT头结构,NT头包括PE签名、标准PE头和可选PE头。根PE头中的SizeOfImage是在内存中拉伸后的大小
4、分配内存空间imagebuffer,填充空数据0,大小为内存中拉伸后的大小
5、将头信息(dosheader+ntheader)复制到imagebuffer,dosheader+ntheader类似于首个节,拉伸也是在其后面补充0,所以其实位置和真实数据位置不变,可以直接复制。
6、遍历节表,把每个节依次复制到imagebuffer中。
以下是实现的代码
一、头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS//高版的VS默认不让使用scanf,fopen等函数,说是scanf,fopen等函数不安全,而代替其函数的是scanf_s,fopen_s等函数,后边有个"_s"的形式。想要使用, 可以在源文件开头加个:
#include <Windows.h>
//函数声明
//**************************************************************************
//ReadPEFile:将文件读取到缓冲区
//参数说明:
//lpszFile 文件路径 //pFileBuffer 缓冲区指针
//返回值说明:
//读取失败返回0 否则返回实际读取的大小
//******************************************************
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer);
//******************************************************
//CopyFileBufferToImageBuffer:将文件从FileBuffer复制到ImageBuffer
//参数说明:
//pFileBuffer FileBuffer指针
//pImageBuffer ImageBuffer指针
//返回值说明: //读取失败返回0 否则返回复制的大小
//******************************************************
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer);
//******************************************************
//CopyImageBufferToNewBuffer:将ImageBuffer中的数据复制到新的缓冲区
//参数说明:
//pImageBuffer ImageBuffer指针
//pNewBuffer NewBuffer指针
//返回值说明:
//读取失败返回0 否则返回复制的大小
//******************************************************
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer);
//******************************************************
//MemeryTOFile:将内存中的数据复制到文件
//参数说明:
//pMemBuffer 内存中数据的指针
//size 要复制的大小
//lpszFile 要存储的文件路径
//返回值说明:
//读取失败返回0 否则返回复制的大小
//******************************************************
BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile);
二、函数实现
#include "pestudy.h"
#include<iostream>
DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
FILE* file = fopen(lpszFile, "r+b");
if (file == NULL)
return 0;
int flength;
fseek(file, 0, SEEK_END);
flength = ftell(file);
fseek(file, 0, SEEK_SET);
*pFileBuffer = malloc(flength);
if (pFileBuffer == NULL)
return 0;
size_t readSize= fread(*pFileBuffer, 1, flength, file);
fclose(file);
return flength;
}
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer, OUT LPVOID* pImageBuffer)
{
//dos头
_IMAGE_DOS_HEADER* pDosHeader;//dos头
_IMAGE_NT_HEADERS* pNtHeader;//nt头
DWORD dSectionPosition;
//dos头
pDosHeader = (_IMAGE_DOS_HEADER*)pFileBuffer;
if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("读取的文件不是exe文件");
return 0;
}
//NT头
pNtHeader = (_IMAGE_NT_HEADERS*)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("读取的文件不是exe文件");
return 0;
}
//节表位置
dSectionPosition = (DWORD)pFileBuffer + pDosHeader->e_lfanew + sizeof(IMAGE_FILE_HEADER) + sizeof(DWORD) + pNtHeader->FileHeader.SizeOfOptionalHeader;
//节表
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)dSectionPosition;
//分配内存
*pImageBuffer = malloc(pNtHeader->OptionalHeader.SizeOfImage);//SizeOfImage:当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数; 一般文件大小与加载到内存中的大小是不同的。 -> 00 00 50 00'
if (*pImageBuffer == NULL)
return 0;
memset(*pImageBuffer, 0, pNtHeader->OptionalHeader.SizeOfImage);
//写入header
memcpy(*pImageBuffer, pFileBuffer, pNtHeader->OptionalHeader.SizeOfHeaders);
//遍历节表
while (pNtHeader->FileHeader.NumberOfSections--)
{
memcpy(PVOID((DWORD)*pImageBuffer + (pSectionHeader)->VirtualAddress) , PVOID((DWORD)pFileBuffer+(pSectionHeader)->PointerToRawData), pSectionHeader->SizeOfRawData);
pSectionHeader++;
}
return pNtHeader->OptionalHeader.SizeOfImage;
}
DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer, OUT LPVOID* pNewBuffer)
{
_IMAGE_DOS_HEADER* dosHeader;
_IMAGE_NT_HEADERS* ntHeader;
DWORD setionPosition;
dosHeader = (_IMAGE_DOS_HEADER*)pImageBuffer;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("内存缓冲区的文件格式有问题");
return 0;
}
ntHeader = (_IMAGE_NT_HEADERS*)((DWORD)pImageBuffer+dosHeader->e_lfanew);
if (ntHeader->Signature != IMAGE_NT_SIGNATURE)
{
printf("内存缓冲区中的文件格式有问题");
return 0;
}
setionPosition = (DWORD)pImageBuffer + dosHeader->e_lfanew + 4 + sizeof(_IMAGE_FILE_HEADER) + ntHeader->FileHeader.SizeOfOptionalHeader;
//节表
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)setionPosition;
//找到最后一个节表
PIMAGE_SECTION_HEADER pLastSetion =(PIMAGE_SECTION_HEADER)(pSectionHeader + ntHeader->FileHeader.NumberOfSections - 1);
size_t fileSize = pLastSetion->PointerToRawData + pLastSetion->SizeOfRawData;
*pNewBuffer = malloc(fileSize);
memset(*pNewBuffer, 0, fileSize);
//复制头信息
memcpy(*pNewBuffer, pImageBuffer, ntHeader->OptionalHeader.SizeOfHeaders);
//遍历节表
while (ntHeader->FileHeader.NumberOfSections--)
{
memcpy(PVOID((DWORD)*pNewBuffer + (pSectionHeader)->PointerToRawData), PVOID((DWORD)pImageBuffer + (pSectionHeader)->VirtualAddress), pSectionHeader->SizeOfRawData);
pSectionHeader++;
}
return fileSize;
}
BOOL MemeryTOFile(IN LPVOID pMemBuffer, IN size_t size, OUT LPSTR lpszFile)
{
FILE* file= fopen(lpszFile, "wb");
if (file == NULL)
{
printf("打开文件失败");
return 0;
}
fwrite(pMemBuffer, size, 1, file);
fclose(file);
return 1;
}
三、调用
#include <iostream>
#include <algorithm>
#include <vector>
#include "student.h"
#include "pestudy.h"
using namespace std;
int main()
{
//MessageBoxA(NULL, "", "", 0);
LPVOID pFileBuffer=NULL;
LPSTR pePath = LPSTR("C:\MFCApplication1.exe");
LPSTR toPath = LPSTR("C:\newnotepad.exe");
if (size_t fsize=ReadPEFile(pePath, &pFileBuffer))
{
LPVOID pImageBuffer=NULL;
if (size_t imageSize=CopyFileBufferToImageBuffer(pFileBuffer, &pImageBuffer))
{
free(pFileBuffer);
pFileBuffer = NULL;
LPVOID pNewBuffer=NULL;
if (size_t nsize = CopyImageBufferToNewBuffer(pImageBuffer, &pNewBuffer))
{
MemeryTOFile(pNewBuffer, nsize, toPath);
}
if (pNewBuffer != NULL)
free(pNewBuffer);
}
if (pImageBuffer != NULL)
free(pImageBuffer);
}
if (pFileBuffer != NULL)
free(pFileBuffer);
return 0;
}
四、容易遇到的问题
1、我读取exe到内存缓冲区,少一个字节,导致后面结构错了一位
原因是因为读取PE的时候用的是函数 fopen(lpszFile, "r"),第二个参数是读取模式,r表示可读,w表示可写。b表示二进制读取方式,Windows如果没有指定二进制读取的话,默认是按文本模式打开,Windows默认的换行符CR+LF就会转换成一个单个的LF。CR:Carriage Return,对应ASCII中转义字符 ,表示回车 LF:Linefeed,对应ASCII中转义字符 ,表示换行
参考:https://blog.csdn.net/liqing19850102/article/details/12040193
2、释放或分配内存提示已触发了一个断点
出现这个错误的原因是因为有内存溢出, 检查分配内存的大小够不够,是不是写入的数据大小超过了分配的大小。