PE文件结构分析及应用三

本文讲述了如何在PE文件中添加新的节,涉及段对齐原理、IMAGE_SECTION_HEADER中的关键字段,以及如何处理内存和文件中的对齐问题。作者通过示例展示了创建、调整大小和写入新节的过程,同时介绍了相关结构的使用方法。
摘要由CSDN通过智能技术生成

在PE文件中新增一个节

自从被PE文件段对齐问题搞得头大以及前面失败读取图标之后,就停留过一段时间,没怎么研究PE文件,今天重整旗鼓,再来研究一下,

不过这次,有了一些准备,临时做了个读取PE文件结构数据的小东西,仅可以简单读取PE文件里的几个结构,不包括段具体数据,很菜的东西,如果有谁正需要这方面的东西,又找不到的话。就拿这个吧!

地址:_下载 - 爱问文库

一边看不怎么详细的资料一边对比PE文件各结构成员变量的值,总算对那段对齐的问题有点了解了,了解了才知没好资料的痛苦。

先来说说段在文件中对齐的问题,段起始位置在文件中对齐值由IMAGE_OPTIONAL_HEADER32(可选头)结构里的变量FileAlignment给出,一般这个值是512,也就是段的起始位置必须是512的倍数。

举个例子吧,假设文件对齐值(FileAlignment)为512,PE文件第四部分在1000处正好结束,那么后面直接就是具体段了吗?不用猜也知道,肯定不是的,由于段对齐问题,第一个段的起始地址是在1024处,同理如果第一段在1033处结束,那么下一个段的起始位置就在1536处。所以当PE文件第四部分结束后,后面可能不会直接是具体段数据,会有一段没用的数据。

而PE文件在内存中也有对齐方式,对齐值由IMAGE_OPTIONAL_HEADER32结构里的变量SectionAlignment给出,一般为4096。

PE文件添加新节需要了解的东西:IMAGE_SECTION_HEADER里的VirtualSize、VirtualAddress、SizeOfRawData、PointerRawToData、

Characteristics。

VirtualSize的值是节的真实大小,即不经过对齐的节大小,VirtualAddress是节在内存的位置(按照内存中对齐方式),是SectionAlignment的整数倍,SizeofRawData是节在文件中对齐后的节大小,如果一个节位置在1000,它的大小为234,那么经过对齐后节大小就是536(假设对齐值为512)PointerRawToData是节在文件中的位置。

Characteristics指明节的属性,关于Characteristics的解释,我直接从网上找了,如下:

/**************************************************************************************************************************************************************************/

如果位5 IAGE_SCN_CNT_CODE(含有代码的节)被置1,表示节中包含可执行代码。
    如果位6 IMAGE_SCN_CNT_INITIALIZED_DATA(含有初始化数据的节)被置1,表示节中包含执行开始前即取得已定义值的数据。换言之:文件中节的数据就是有意义的。
    如果位7 IMAGE_SCN_CNT_UNINITIALIZED_DATA(含有未初始化数据的节)被置1, 表示节中包含未初始化数据,并需于执行开始前被初始化为全0。这通常是BSS节。
    如果位9 IMAGE_SCN_LNK_INFO(链接器信息节)被置1, 表示节中不包含映象数据,只有一些注释、描述或者其他的文档。这些信息是目标文件的一部分,并有可能是提供给链接器的信息,比如需要哪些库文件。
    如果位11 IMAGE_SCN_LNK_REMOVE(链接可删除节)被置1,表示数据是目标文件的、被预定于可执行文件被链接后丢弃掉的节的一部分。常和位9连用。
    如果位12 IMAGE_SCN_LNK_COMDAT(链接通用块节)被置1, 表示节中包含“common block data”(通用块数据),也即某种形式的打包函数。
    如果位15 IMAGE_SCN_MEM_FARDATA(内存远程数据节)被置1,表示我们拥有远程数据----意味着什么。此位的含义不明。
    如果位17 IMAGE_SCN_MEM_PURGEABLE(内存可清除节)被置1, 表示节中的数据可清除----但我认为它和“可丢弃”不是一回事,可丢弃拥有自己的标志位,参见后面。同样,它也明显的不是用来指示16位信息的,因为它也有一个IMAGE_SCN_MEM_16BIT定义。此位的含义不明。
    如果位18 IMAGE_SCN_MEM_LOCKED(内存被锁节)被置1, 表示节不应该被从内存中移除?亦或表明没有重定位信息?此位的含义不明。
    如果位19 IMAGE_SCN_MEM_PRELOAD(内存预载入节)被置1,表示节在执行开始前应该被页载入?此位的含义不明。
    位20至23 指定我没有找到信息的对齐。诸如#defines IMAGE_SCN_ALIGN_16BYTES之类。我曾经见过的唯一值为0,是16位的缺省对齐。 我怀疑它们是库之类文件的目标对齐。
    如果位24 IMAGE_SCN_LNK_NRELOC_OVFL(链接扩展重定位节)被置1,表示节中包含一些我不知道的扩展重定位。
    如果位25 IMAGE_SCN_MEM_DISCARDABLE(内存可丢弃节)被置1,表示节中的数据在进程启动后就不需要了。它是,举例来说,含有重定位信息的情况。我曾经见过它也用于只执行一次的驱动和服务程序的启动例程,还用于输入目录。
    如果位26 IMAGE_SCN_MEM_NOT_CACHED(内存不缓存节)被置1,表示节中的数据不应该被缓存。不要问我为什么不。这是不是意味着关掉2级缓存?
    如果位27 IMAGE_SCN_MEM_NOT_PAGED(内存不可页换出节)被置1,表示节中的数据不应该页换出。它对驱动程序有意义。
    如果位28 IMAGE_SCN_MEM_SHARED(内存共享节)被置1,表示节中的数据在映象文件的所有正在运行的实例中共享。如果它是,例如DLL文件的未初始化数据,那么DLL的所有正在运行的实例程序在任何时候都将拥有相同的变量内容。
注意:只有第一个实例的节被初始化。
含有代码的节总是被共享写时拷贝(copy-on-write)(亦即:如果重定位必不可少,那么共享就不工作)。(译注:“写时拷贝”的译法也许根本就是错误的,但我一时找不到更准确的翻译,也不清楚其具体含义,只能以此充数了。希望知情着指点。)
    如果位29 IMAGE_SCN_MEM_EXECUTE(内存可执行节)被置1,表示进程对节的内存有“执行”的存取权限。
    如果位30 IMAGE_SCN_MEM_READ(内存可读节)被置1,表示进程对节的内存有“读”的存取权限。
    如果位31 IMAGE_SCN_MEM_WRITE(内存可写节)被置1,表示进程对节的内存有“写”的存取权限。

/*************************************************************************************************************************************************************************/

上面的不需要全了解,现在稍微理解一下就行了,等用到的时候再来查看。。

 还有一个就是IMAGE_OPTIONAL_HEADER32结构里的SizeOfImage,这个变量的值是PE文件在内存中占多少字节(从某一方面可以说是PE文件的大小)也就是装载器为这个映象文件分配多少内存,这个值可以由IMAGE_SECTION_HEADER结构数组最后一个元素提供的数据算出,计算方法是最后一个元素的VirtualAddress+VirtualSize,此时这个VirtualAddress是最后一个节在内在中的位置(相对偏移),那么这个值再加上节原始数据大小,但这并不是最终结果,由于节在内存中的对齐方式,所以如果VirtualAddress+VirtualSize的结果不是SectionAlignment的整数倍,则还要增加大小。如:

DWORD SizeOfImage=VirtualAddress+VirtualSize;

while(SizeOfImage%SectionAlignment)  SizeOfImage++;

这样得到的结果是正确的。

最后一个是节数量,IMAGE_FILE_HEADER里的NumberOfSections

好了接下来个例子吧,这里我添加的节不是在原有的PE文件上,因为刚开始感觉在原有上修改太麻烦了,后来才发现不是这样,在网上也看了一个C语言添加PE节的例子,代码很少,但我就感到特别奇怪,在原有的PE文件上修改,怎么代码如此之少,所有我就仔细研究了一下,才发现,有一定的取巧性,因为大部分PE文件第四部分到第五部分之间会有间隔,所以把新加的节表直接写在这里面,也不会有错误,也就是说如果PE文件的这部分间隔是大于或等于sizeof(IMAGE_SECTION_HEADER),则不会有错,而我这个例子也是按照这样的方式。

等我写完才发现,按照我这样的方式,完全不需要重新再建一个PE文件,直接在原有的PE文件上修改会方便很多。

例子:

#include<windows.h>
int main()
{
DWORD dwSize;
HANDLE hFile = CreateFile("e:\\Second.exe", GENERIC_READ, 
FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);//以读写方式打开文件
HANDLE hNewFile=CreateFile("e:\\newSecond.exe",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,
         FILE_ATTRIBUTE_NORMAL,NULL);
IMAGE_DOS_HEADER dosHeader;
ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//读取DOS头
DWORD dosStubSize=dosHeader.e_lfanew-sizeof(IMAGE_DOS_HEADER);
BYTE *pDosStub=new BYTE[dosStubSize];
ReadFile(hFile,(void *)pDosStub,dosStubSize,&dwSize,NULL);//读取DOS STUB
IMAGE_NT_HEADERS NtHeader;
ReadFile(hFile,(void *)&NtHeader,sizeof(IMAGE_NT_HEADERS),&dwSize,NULL);//读取NT头
DWORD FileAlignment=NtHeader.OptionalHeader.FileAlignment;
DWORD SectionAlignment=NtHeader.OptionalHeader.SectionAlignment;
所有节表大小总和//
DWORD secHeaderSize=sizeof(IMAGE_SECTION_HEADER)*NtHeader.FileHeader.NumberOfSections;
IMAGE_SECTION_HEADER *pSecHeader=new IMAGE_SECTION_HEADER[NtHeader.FileHeader.NumberOfSections];
ReadFile(hFile,(void *)pSecHeader,secHeaderSize,&dwSize,NULL);//读取所有节表
DWORD spSize;
//计算PE文件第四部分到第五部分(段数据)的间隔大小
spSize=pSecHeader[0].PointerToRawData-(dosHeader.e_lfanew+sizeof(IMAGE_NT_HEADERS)+secHeaderSize);
BYTE *pSpSize=new BYTE[spSize];
ReadFile(hFile,(void *)pSpSize,spSize,&dwSize,NULL);
//设置各数据/
NtHeader.FileHeader.NumberOfSections+=1;//节数量增1
DWORD secSize=1000;//新增加的节大小为1000
NtHeader.OptionalHeader.SizeOfImage+=1000;//PE文件加载到内存中大小
while(NtHeader.OptionalHeader.SizeOfImage%NtHeader.OptionalHeader.SectionAlignment!=0)
NtHeader.OptionalHeader.SizeOfImage++;//节在内存中对齐
WriteFile(hNewFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//写入DOS头
WriteFile(hNewFile,(void *)pDosStub,dosStubSize,&dwSize,NULL);//写入DOS STUB
delete pDosStub;
WriteFile(hNewFile,(void *)&NtHeader,sizeof(IMAGE_NT_HEADERS),&dwSize,NULL);//写入NT头
IMAGE_SECTION_HEADER newSecHeader;//新增节的表头
strcpy((char *)newSecHeader.Name,".new");//节名
newSecHeader.Misc.VirtualSize=1000;//真实大小
newSecHeader.VirtualAddress=pSecHeader[NtHeader.FileHeader.NumberOfSections-2].VirtualAddress+
                     pSecHeader[NtHeader.FileHeader.NumberOfSections-2].Misc.VirtualSize;
while(newSecHeader.VirtualAddress%SectionAlignment!=0) newSecHeader.VirtualAddress++;//节在内存中的对齐地址
newSecHeader.SizeOfRawData=1000;
while(newSecHeader.SizeOfRawData%FileAlignment!=0) newSecHeader.SizeOfRawData++;//节在文件中的对齐大小

newSecHeader.PointerToRawData=pSecHeader[NtHeader.FileHeader.NumberOfSections-2].PointerToRawData
 +pSecHeader[NtHeader.FileHeader.NumberOfSections-2].SizeOfRawData;//节在文件中的位置

newSecHeader.PointerToLinenumbers=0;
newSecHeader.PointerToRelocations=0;
newSecHeader.NumberOfLinenumbers=0;
newSecHeader.NumberOfRelocations=0;
newSecHeader.Characteristics=IMAGE_SCN_CNT_INITIALIZED_DATA|IMAGE_SCN_MEM_READ
                             |IMAGE_SCN_MEM_WRITE;//节属性
WriteFile(hNewFile,(void *)pSecHeader,secHeaderSize,&dwSize,NULL);//写入原所有节表
//写入新增的节表
WriteFile(hNewFile,(void *)&newSecHeader,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL);
WriteFile(hNewFile,(void *)pSpSize,spSize-sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL);//写入间隔数据
delete pSpSize;
for(int i=0;i<NtHeader.FileHeader.NumberOfSections-1;i++)//减1是原节数量
{
 BYTE *pSecData=new BYTE[pSecHeader[i].SizeOfRawData];
 ReadFile(hFile,(void *)pSecData,pSecHeader[i].SizeOfRawData,&dwSize,NULL);//读取节数据
 WriteFile(hNewFile,(void *)pSecData,pSecHeader[i].SizeOfRawData,&dwSize,NULL);//写入节数据
 delete pSecData;
}
BYTE *pNewSecData=new BYTE[newSecHeader.PointerToRawData];
WriteFile(hNewFile,(void *)pNewSecData,newSecHeader.PointerToRawData,&dwSize,NULL);
delete pSecHeader;
CloseHandle(hFile);
CloseHandle(hNewFile);
return 0;
}

下面给出直接修改原PE文件的代码:

/*********未完******

待写*****************/

本文参考了网上若干资料。。。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bczheng1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值