https://blog.csdn.net/cs2626242/article/details/79391599

pe官方文档:https://docs.microsoft.com/zh-cn/windows/win32/debug/pe-format

1.什么是文件

存储数据的实体
不同的文件是给不同的软件去使用的。不同的文件主要是格式不同。
格式就是数据的排列组织方式。软件读取文件,按照固定的形式去解析文件的。

2.什么是PE文件

(portable Executable)可执行文件的缩写。这种类型的文件是供windows系统解析,解析完之后,能够创建出进程去运行的文件。

按照一定的格式去解析文件中的数据,这种顺序就是数据的排列组织方式。

3.PE文件的头部信息(DOS头,NT头,区段表)

我们学习PE文件,就是学习PE文件的格式,学习格式就是在学习一堆结构体。这些东西有大量的需要记忆。为了便于我们记忆,需要一些辅助性的工具。
(1)PE文件格式的图

(2)010-Editor

(3)PE的16进制与结构体的对照图

4.LordPE是一个非常好用的PE解析器以及编辑器。

DOS头:
在windows系统中的可执行文件再设计的时候,考虑到了兼容性的问题。在正常的可执行文件的一开始的部分。嵌入了一个DOS可执行文件。作用是在MS-DOS系统下能够输出这一行这个程序不是运行在此系统下的。

这里有两个字段是有用的:
第一个e_magic永远都是小尾模式存储下是0x4D 0x5A 正常情况下是0x5A4D,我们需要知道大端和小端的知识。
最后一个e_lfanew它是真正的可执行文件的起始位置
NT头

Signature:永远都是0x50 0x45 0x00 0x00 0x00004550
IMAGE_FILE_HEADER:

重要的:
NumberOfSections区段的数量
SizeOfOptionalHeader:扩展头的大小。因为扩展头中数据目录表中的个数是不确定的(数组中的数据数量可能不一定)。所以这里需要一个大小
有用的:
machine:运行平台
TimeDateStamp:时间戳,表明是在什么时候编译的
IMAGE_OPTIONAL_HEADER(扩展头):

非常重要的:
ImageBase:程序的默认加载基址。
AddressOfEntryPoint:程序的入口点(EP)。
比较重要的:
SectionAlignment:内存对齐 0x1000(因为一页内存是4kb)
FileAlignment:文件对齐 0x200
一个程序加载到内存中的时候不单单是将文件中的信息复制到内存中,而是将文件中的每个区段的对齐扩展成1000(在文件中是200),多余的地方都填充0。
SizeOfImage:映像大小(我这个PE文件被加载到内存所占用的空间是多大)
SIzeOfHeader:头部大小 Dos头+NT头+区块表的大小。
DllCharacteristics:PE的一组属性
极为重要的:

DataDirectory:数据目录表(有很多的重要的数据表是通过它来索引到的),描述了PE文件中非常重要的数据块的大小和位置。

(1)导入表
(2)导出表
(3)重定位表
(4)资源表
(5)TLS表
....
区段表:结构体数组,数组的元素个数由文件头中的NumberOfSection决定。

区段表中的一个元素描述的就是一个区段的信息:

1.Name:区段名
2.PointerToRawData:在文件中的位置
3.SizeOfRawData:在文件中的大小
4.VirtualAddress:在内存中的位置
5.Misc.VirtualSize:在内存中的大小
6.Characteristics:区段的属性:可读、可写、可执行.....


文件偏移(FOA或者Offset):某一个数据距离文件开头的偏移
虚拟地址(VA):程序在运行的时候,是将PE加载到进程的内存空间中。进程的这块内存空间就称之为虚拟内存空间,32为程序虚拟内存空间是以字节为单位的,每一个字节都有一个编号从0x00000000到0xFFFFFFFF之间。这个编号就是虚拟地址。
相对虚拟地址(RVA):PE文件不会占满整个虚拟内存空间,而是会占用一部分。那么就会有一个起始位置,这个起始位置也称为加载基址。PE文件中的数据相对于加载基址的偏移就是相对虚拟地址
如果系统加载PE文件的时候,就是将PE文件原封不动的复制到了内存中,那么某一个数据的FOA和RVA就是相等的。
打开文件到内存并显示信息

char* ReadFileMemory(char* pFilePath)
{
    //1.获取文件句柄
    HANDLE hFile = CreateFileA(pFilePaht,
                              GENERIC_READ | GENERIC_WRITE,
                               FALSE,
                               NULL,
                               OPEN_EXISTING,
                               FILE_ATTRIBUTE_NORMAL,
                               NULL
                              )
    if(hFile == INVALID_HANDLE_VALUE)
    {
        printf("打开文件失败!\n");
        return 0;
    }
    //2.获取文件大小
    DWORD dwFileSize = GetFileSize(hFile,NULL);
    //3.申请内存空间
    char* pBuf = new char[dwFileSize]{};
    if(!pBuf)
    {
        CloseHandle(hFile);
        printf("内存申请失败\n");
        return 0;
    }
    //4.读取文件内容到内存空间
    DWORD dwRead;
    ReadFile(hFile,pBuf,dwFileSize,&dwRead,NULL);
    //5.返回内存地址
    return pBuf;
}

bool IsPeFile(char* pBuf)
{
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
    if(pDos->e_magic != IMAGE_DOS_SIGNATURE)
    {
        printf("不是PE文件\n");
        return false;
    }
    return ture;
}

int main()
{
    char *pBuf = ReadFileToMemory("123.exe");   //将文件读到内存
    if(IsPeFile(pBuf))  //判断是否是PE文件
    {
        ShowImportantHead(pBuf);    //显示文件信息
    }
    delete pBuf;
    return 0;
}
最后修改:2020 年 08 月 26 日
如果觉得我的文章对你有用,请随意赞赏