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就会转换成一个单个的LFCR:Carriage Return,对应ASCII中转义字符\r,表示回车 LF:Linefeed,对应ASCII中转义字符\n,表示换行

 参考:https://blog.csdn.net/liqing19850102/article/details/12040193

2、释放或分配内存提示已触发了一个断点

 

出现这个错误的原因是因为有内存溢出, 检查分配内存的大小够不够,是不是写入的数据大小超过了分配的大小。

 

posted on 2020-04-03 10:36  bzryk  阅读(738)  评论(0编辑  收藏  举报