PE结构-节

首先,节的话有两部分,一部分是节表,另外一部分就是节区了,节表是用于描述节区信息的,而节区呢,简单点说就是数据块。

在上一篇博客中有提及过如何定位节表,重点就是需要根据选项头的大小来定位,这里的话就直接上一点代码吧,前两篇概念较多

    PIMAGE_DOS_HEADER pDosHeader = GetDosHeader(); //获取dos头
    if (pDosHeader == NULL)
        return NULL;
    PIMAGE_FILE_HEADER pFileHeader = GetFileHeader(); //获取文件头
    if (pFileHeader == NULL)
        return NULL;
    //这里为 sizeof Signature + sizeof IMAGE_FILE_HEADER 的大小
    DWORD offset = (DWORD)&(((PIMAGE_NT_HEADERS)0)->OptionalHeader);
    //首地址 + e_lfanew + offset + SizeOfOptionalHeader
    PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)(GetPeAddrBase() + pDosHeader->e_lfanew
                                        + offset + pFileHeader->SizeOfOptionalHeader);

OK,老规矩下面来探讨一下节表的字段,把PE宏观看成一块一块的数据,那么在创建进程时,操作系统需要将此可执行文件按某种方式搬运过去,也就是如下图所示

上面的图片可能分布不同均匀,画功有限,根据其数据地址来观察即可。观察完图片,那么该如何描述文件数据搬运到内存呢?

这里的话比较容易想到四个条件,文件块首地址,文件块大小,搬运到的内存首地址,内存块大小

对的,上面的四个也就是节表中的四个属性,下面来看一下结构

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; //节名称
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc; //内存实际字节数
    DWORD   VirtualAddress; //内存地址
    DWORD   SizeOfRawData; //文件中节区大小
    DWORD   PointerToRawData; //文件中节区地址
    DWORD   PointerToRelocations; //重定位信息
    DWORD   PointerToLinenumbers; //行信息
    WORD    NumberOfRelocations; //重定位数
    WORD    NumberOfLinenumbers; //行数
    DWORD   Characteristics; //属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

这里的话我们只需要关心上面说的四个描述搬运字段以及最后一个属性字段即可,剩余的字段是处于跨平台设计考虑的,忽略即可

首先来看节名称,这里的节名称类似于注释的效果

再来看后两个内存相关字段,首先Misc,这是个半说明性字段,在不影响分页的情况下,可理性修改。后面是VirtualAddress,这里是一个内存中的相对虚拟地址(RVA)

RVA:(Relative Virtual Address) , RVA只是内存中的一个相对于PE文件装入地址的偏移位置,或者称偏移量
目标地址 401000h - 装入地址 400000h = RVA 1000h

为什么需要使用RVA呢?因为ImageBase基址可能会变,所以使用RVA描述会好一些

然后下面就是文件相关的两个字段了,SizeOfRawData,表示文件中的节区大小,后面的PointerToRawData表示文件地址

好了,下面先看看一段二进制来解析一下

我们来看.text段,该段描述的是从文件偏移处200的位置,拷贝到虚拟内存地址401000(ImageBase+1000)的地址处,那么问题来了,拷贝多少呢?因为内存描述的是实际大小0x26,而文件这边是0x200。其实这里我的理解是拷贝哪个都行,最好是按照多的来拷贝,因为其实不管多还是少,有效数据都拷贝到内存了。

下面再来看一下.rdata段,该段描述的是从文件偏移400的位置,拷贝200字节到虚拟内存地址402000处,实际有效的字节数为0xA2。

下面来手工模拟添加一个节,首先我们来看一下我们头部是否够添加一个节数据

这程序很不巧,刚好,第一个节从0x200开始,然后头部数据刚刚好占满,也就是说连添加一个节信息的地方都不够。

那么我们来分析一下,因为头部占了0x200,如果扩充头部变成0x400,而内存对齐是0x1000,所以说假如文件扩充了头部,其实在内存中应该是变化不大的,相对于原先是200的大小拷贝到1000内,现在是400的大小拷贝到1000内,所以还是在一个分页内。如果超过了一个分页,此时就需要慎重了,毕竟后面的数据和代码处的地址都会跟随变化。

OK,下面先干扩充头部的活,先将头部扩充为0x400

可以发现,之前0x200部分的数据现在已经到了0x400处,下面需要修正一些值,首先就是选项头中的SizeOfHeaders大小了

下面还是就是修正节表,因为头部添加了0x200,所以所有的节表的文件起始位置都得加上0x200

OK修正完毕后就可以试试双击运行了,下面再来说添加节的事,添加一个节做如下四个步骤

1.添加节描述
2.添加节数据
3.修正 SizeOfImage - 选项头中
4.修正 NumberOfSections - 文件头中

先来添加节描述,这里我们需要注意的是节和节之间不能重叠,并且需要连续(操作系统有检查)

此时我们添加的新节的内存起始地址就是上一个节的内存页终点(0x2000+0x1000),而文件的起始地址也就是上一节的文件块终点(0x600+0x200)。

第二步,在末尾添加0x200的字节数据,并且将这200字节填充为0x90,方便后面验证,这里就不截图了

第三步修正SizeOfImage,因为多了一个节映射,所以对应的内存空间应该也会多一页(0x1000)

第四步,将NumberOfSections的数量+1

OK,修改完毕后就可跑跑看了,是否运行正确,我们可以使用OD打开程序来观察一下

验证数据是否加载正确

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章