本节的目的是在所有节的空白区都不够存放我们要添加的代码时,教会我们新增一个足够大的节来添加代码
添加节
一.判断是否有足够的空间可以添加一个节表:新增节需要新增一个节表来记录此节信息
判断方法:SizeOfHeader - (DOS + 垃圾数据 + PE标记 + 标准PE头 + 可选PE头 + 已存在节表) >= 2个节表的大小(80个字节大小)
注意:windows会根据一个结构体后面是否有至少一个同结构体大小以上的0x00来判断这个结构体是否结束,所以最后一个节表后面通常需要填补同节表宽度的0以防系统误判
二.当满足添加节表的条件时,我们可以将已有的一个节表的40字节信息复制一份紧挨着最后一个节表的末尾(节表与节表之间时连续的),然后再定义之后的40字节都是0,之后再根据新增节的信息,去修改对应节表的字段值
三.修改数据:
1.修改PE头中NumberOfSections字段(节的数量)为我们添加节以后节的总数
2.修改SizeOfImage的大小:原有值 + 新增节的大小(注意内存对齐)
3.修正新增节表的属性:
Name:按照ASCII码自定义名字,最好长度限制在8字节内
Misc.VirtualSize:内存起始偏移地址,我们通过上一个节表的VirtualAddress + VirtualSize的值再进行内存对齐后得到上一个节的结尾位置,作为新增节的内存起始偏移地址。但我们在实际操作中选择VirtualSize和SizeOfRawData中大者取值
举例:
我们有时候发现文件内存中未对齐的大小确实比文件中对齐的大小大,这是由于节中包含初始化数据的缘故。比如notepad.exe文件的第二个节.data中,内存中未对齐的大小为0x1BA8,文件中对齐后的大小为0x600。因此我们可以发现,第三个节在文件起始中应该按照上一个节在文件中对齐后的大小往后接着存放,即0x7200 + 0x600 = 0x7800,所以第三个节的PointerToRawData为0x7800。而第三个节在内存起始中应该按照上一个节在内存中对齐后的大小往后接着存放而不是再按照上一个节的文件中对齐大小挨着存放了,这是因为此时VirtualSize比SizeOfRawData大,即0x8000 + 0x1BA8 = 0x9BA8,对齐后为0xA000,因此第三个节内存偏移为0xA000
SizeOfRawData:根据文件对齐粒度来修正这个值。如果上面设定了VirtualSize的值为0x1000,说明这个节的这0x1000字节都是有用数据,那么此节在硬盘上时也应该有这0x1000字节的数据,此时需要根据文件对齐粒度来修正这个值。如果为0x200或者0x1000,就不用修改0x1000,因为已经满足是文件对齐的整数倍了
PointerToRawData:文件对齐后的文件地址,我们可以通过上一个节表的PointerToRawData + SizeOfRawData计算得到此值
Characteristics:按照我们想要的属性来修改即可(可读、可写、可执行等)
如图所示:
注意:修改SizeOfHeaders的值代价很高,所以不能随便改变。因为如果这个值变大或变小了,后面的节都要跟着往后或者往前跟着改变,其中的数据地址也会随之改变,比如这些节当中涉及到相关计算地址的数值就需要重新计算了。其他诸多类似的修改都是一个繁琐的过程,会加重我们的工作量。比如说我们上节课作业中E8和E9后面的值,是通过其他的地址计算出来的。如果SizeOfHeaders改变了,节地址也会随之改变,因此这些值也要改变。
特殊情况
当节表后面有编译器添加的数据:
在众多程序中,有一些程序会在节表到节的空白区之间添加一些信息,这些信息是否有用我们尚且未知,所以我们不可轻易修改这些信息。但是我们要添加的新节表一定要与原来的节表连续紧挨着,所以在这种情况下如果我们要添加新节表就需要使用另一种办法:已知程序的DOS头到PE签名之间有一处区域叫做DOS Stub。该区域存储的是程序的一些说明信息相关的数据,这些数据不会影响程序的运行,并且对我们来说是垃圾数据,所以我们可以将NT头到节表末尾这一部分整体上移,把Dos Stub这块数据覆盖了,但原来节表末尾的数据不进行修改。这时我们修改DOS头中的e_lfanew字段的值为上移后PE签名的地址。此时节表末尾和原节表末尾的信息之间就会空出来一部分,我们将这部分全部修改成0x00后,就可以往这片区域新增节表了
当整体上移后空白出来的空间还不够新增节表的大小时,我们可以采用扩大最后一个节的方式,将添加的代码加到最后一个节扩大的空间中,由于内存对齐的原因,我们会有很大一部分的空间供我们去添加代码。这样可以保证不影响上面的所有地址
注意事项:
文件加载到内存中,最后一个节的VirtualAddress + VirtualSize对齐后的结尾一定就是一个文件在内存中的结束位置,即SizeOfImage。文件在硬盘上时,最后一个节的PointerToRawData + SizeOfRawData的值一定就是整个文件的结尾。
所以不管是往节的空白区中添加代码还是新增节、扩大节,使用编程来实现功能时,应该在ImageBuffer中操作更方便,即拉伸以后的情况下操作,这样不管是一些值的计算还是位置的选择上都要更方便一些
注意:如果在内存中,要注意节与节之间的排列是根据VirtualAddress和VirtualSize以及内存对齐进行判断。如果在硬盘上,节与节之间的排列,是根据PointerToRawData和SizeOfRawData以及文件对齐来判断