节操作
以下操作中用的PE文件建议自行寻找一个再去实验。
扩大节
在上一章节中我们可以在任意空白区添加自己的代码,但如果添加的代码比较多,空白区不够怎么办?这时候就需要扩大节,节有很多个,我们应该扩大哪一个节呢?想象一下如果你现在扩大第一个节,那么其他节的偏移量之类的属性都需要修改,这样很麻烦,所以我们可以选择扩大最后一个节,这样就不用修改其他节的属性了。
如下是节表成员的数据结构:
#define IMAGE_SIZEOF_SHORT_NAME
8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
// ASCII字符串(节名),可自定义,只截取8个字节,可以8个字节都是名字
union {
// Misc,双字,是该节在没有对齐前的真实尺寸,该值可以不准确
DWORD PhysicalAddress;
// 真实宽度,这两个值是一个联合结构,可以使用其中的任何一个
DWORD VirtualSize;
// 一般是取后一个
} Misc;
DWORD VirtualAddress;
// 在内存中的偏移地址,加上ImageBase才是在内存中的真正地址
DWORD SizeOfRawData;
// 节在文件中对齐后的尺寸
DWORD PointerToRawData;
// 节区在文件中的偏移
DWORD PointerToRelocations;
// 调试相关
DWORD PointerToLinenumbers;
// 调试相关
WORD NumberOfRelocations;
// 调试相关
WORD NumberOfLinenumbers;
// 调试相关
DWORD Characteristics;
// 节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
我们想要扩大节就需要修改这几个部分:SizeOfRawData、VirtualSize。接下来我们就来扩大节,首先需要分配一块新的空间,这块空间的大小取决你所需要的代码大小,在这里就定位0x1000,也就是4096字节,如下图所示使用010 Editor在文件末尾插入字节(这样也就不会打乱其他结构的偏移):
然后找到最后一个节表成员,修改SizeOfRawData和VirtualSize成员的值,这个值是要取SizeOfRawData和VirtualSize成员当前值的最大值进行内存对齐之后的值加上我们插入宽度0x1000:
如上图所示两个成员中值最大的为SizeOfRawData,SizeOfRawData是按照文件对齐的,在这里我们还需要将SizeOfRawData的值按内存对齐,首先来看下内存对齐(SectionAlignment)和文件对齐(FileAlignment)的值:
可以看见,在这里两个值不一样,所以当前SizeOfRawData是按文件对齐的值直接添加是不可取的,我们还需要按内存对齐,可以使用如下公式进行计算:
⌈⌉(SizeOfRawData(
0x8000
) / SectionAlignment(
0x1000
)) = ResultA(
0x8
)
// 0x8000除以0x1000向上取整(符号⌈⌉)
ResultA(
0x8
) * SectionAlignment(
0x1000
) = ResultB(
0x8000
)
最终我们得出按内存对齐的值为0x8000,然后我们要将SizeOfRawData和VirtualSize成员的值修改为0x9000(0x8000+0x1000)。
接着,如果你要让这个节中存放代码并且需要执行的话,就需要修改节的属性;然后我们需要去修改扩展PE头中的SizeOfImage成员,该成员表示在内存中整个PE文件映射的大小,如下图是当前的值:
在之前的章节里一家了解过了SizeOfImage可以比实际值要大并且这个值默认情况下本身就是与内存对齐之后的结果,所以你在原基础上加0x1000即可,这样我们就完成了扩大节的操作。
插入代码执行
那么,在扩大节之后又该如何去插入自己的代码调用呢?如果按照之前插入空白区的方法,根据当前的指令地址再加上ImageBase去调用,很明显这是不可取的,因为当前文件与内存对齐是不一致的,我们想要去掉用就要知道在内存中当前指令的地址。
而这个,我们可以使用原SizeOfImage的值获得,因为原SizeOfImage的值表示PE文件在内存中展开的大小,我们单独添加的空间就在PE文件的末尾,所以可以根据SizeOfImage来知道在添加空间中的指令地址;例如如下图所示众标红部分在内存中的地址范围应为ImageBase加原SizeOfImage与指令的宽度,最终的地址范围就是:0x1013000 - 0x1013003
接着,我们只需要添加自己的指令进去即可,这边还是套用在空白区时所使用的指令MessageBoxA(0x77D5050B),并计算出偏移量:
0x77D5050B
(MessageBoxA) -
0x1013000
(指令开始地址) -
13
(指令本身长度) =
0x76D3D4F8
再拼接上指令(别忘了小端存储模式)插入即可(这里的指令进行了简化,省略了JMP跳回原AddressOfEntryPoint地址部分):
6A
00
6A
00
6A
00
6A
00
E8 F8 D4 D3
76
最后我们需要修改一下OEP(这里的OEP是Original EntryPoint,也就是原始入口点,没有加壳以及其他修改的情况下,AddressOfEntryPoint就是OEP,但是如果存在修改/加壳的情况,AddressOfEntryPoint只能称为是EP,因为不是原始入口点,你需要自己去寻找了)这里修改也按照之前得出的指令地址去除ImageBase的值进行修改:
接着保存运行即可:
新增节
上一章节中我们知道当想插入的代码过多的时候,空白区不够用的情况下,我们可以使用扩大节的方法扩大最后一个节,然后在里面插入自己的代码;这样的方法虽然有效,但还是有一些弊端,比如最后一个节的属性会被修改,插入的代码会与原节的数据混合在一块。所以,我们可以新增节,在新增的节里添加自己的数据。
新增节,首先判断是否有足够的空间可以添加一个节表成员(40字节),我们可以找到一个PE文件来看一下它的节表部分是否有足够多的空白区,首先我们根据标准PE头的第二个成员(NumberOfSections)知道有4个节:
接着我们在扩展PE头之后找到节表,我们可以看见在这个节表之后有40字节的空白区让我们添加新的节表成员,我们可以选择复制一份".text"节表成员作为新增的成员,这是因为我们要在节数据中添加代码,而".text"就是存放代码的,所以我们直接复制过来就不需要修改节属性了:
因为我们增加了一个节,所以需要在标准PE头的第二个成员(NumberOfSections)中加1:
为了方便添加节我们还需要修改下最后一个节表成员的属性,将其真实大小(VirtualSize)修改成文件对齐之后的大小(SizeOfRawData):
接着修改添加的节表成员的属性:名字、真实大小(0x1000)、文件对齐之后的大小(0x1000)、内存中的偏移(第4个节的偏移地址0x23000+其数据大小0x4000)、文件中的偏移:
然后需要修改一下SizeOfImage的大小,加上0x1000即可:
最后在文件末尾添加对应节的数据(0x1000 → 4096个字节):
这样我们就完成了新增节的所有步骤,你可以基于这个基础再去插入代码执行。
我自己实践下,使用cff explorer来新增节:
由于PEview
不能编辑,故使用功能更强大的CFF explorer
。
1. 改变address of entry point
为新插入节所在的位置。
- 记录原先的
address of entry point
- 添加新节
empty space
,软件会自动改变文件的相应信息如numberofsections
、sizeofimage
- 复制新节的地址,将
addressofentry
改为它。
- 保存后,将该节的属性改为可执行。
好了有了上面的步骤说明,我自己实际操作下:
各个地址的计算:
E8下一条的绝对地址:
00400000+000C5000+D=4C500D
E8跳转地址:
779CA7D0-4C500D=7750 57C3
E9下一条的绝对地址:
00400000+000C5000+12=4C 5012
老的OEP绝对地址:
00400000+000300FC=43 00FC
E9跳转地址:
4300FC-4C5012=FFF6 B0EA
新的OEP相对地址:
000C5000
修改入口点:
最后效果:
合并节(todo,回头再实践吧,来不及解释了)
上一章中了解到新增节需要在节表之后至少有40个字节的空白区给我们去新增,但并不是所有的程序都可以满足这个条件,如下图所示的程序在节表之后的数据是编译器填充的,这些数据我们并不能覆盖:
按内存对齐展开
那么这样我们如何新增节呢?如果PE的DOS块没有被占用的情况下,我们完全可以将PE头向上提升,替换DOS块的部分,这样就可以多出一块空间出来,但是如果当前PE文件的DOS块被占用,这种方法显然就不可取了。所以,我们想要实现新增节可以采用合并其他节的方法,给我们新增的节表成员留出空间。
我们想要合并节,首先要考虑到当前PE文件的文件对齐和内存对齐是否一致,在当前PE文件来看是不一致的,所以直接合并肯定是不行的,我们需要先将节进行内存对齐展开。首先,需要知道当前PE文件的内存对齐的值:
如上图所示内存对齐就是0x1000,接着找到节表成员,将其对应成员属性按如下公式代入计算:
⌈⌉(max(SizeOfRawData, Misc) / SectionAlignment) = ResultA
// 取SizeOfRawData和Misc之间的最大值除以内存对齐的值,最后的结果向上取整(符号⌈⌉)
ResultA * SectionAlignment = ResultB
// ResultA乘以内存对齐的值
// 代入公式计算
max(
0x7800
,
0x7748
) =
0x7800
⌈⌉(
0x7800
/
0x1000
) = ⌈⌉(
0x7
余
0.8
) =
0x8
0x8
x
0x1000
=
0x8000
按公式得出的结果替换原先的SizeOfRawData、Misc:
接着我们需要将内存对齐后的值减去原SizeOfRawData的值,得出一个差值:
0x8000
-
0x7800
=
0x800
这个值则用于增加节的空间,找到该节的末尾,也就是PointerToRawData的值加上原SizeOfRawData的值:
0x400
+
0x7800
=
0x7C00
在这开始添加0x800字节的空间:
由于我们这里修改了第一个节的大小并添加了空间,所以之后的节的文件偏移(PointerToRawData)要对应添加上增加的差值:
按照这样的步骤以此类推将所有的节全部按照内存对齐的方式进行修正(由于SizeOfImage本身就是按内存对齐之后的大小,所以其的值会比实际值大,我们这里添加的空间从理论上来说也不会超出这个值,甚至我们所添加之后的大小是等于这个值的,也就无需更改)。
手动合并节
接下来我们需要计算出所有节的大小,这里我们可以使用SizeOfImage减去SizeOfHeaders内存对齐之后的大小,得出的结果0x12000就是所有节的大小(你也可以选择计算所有节展开后的大小和,这时候你就会发现这里的大小是一模一样的):
那么这个值我们就对应给到第一个节表成员中的SizeOfRawData、Misc:
然后由于我们合并了其他的节,但是其他节的Characteristics(属性)是不一样的,我们合并了也要将其他节的属性添加进来,这里可以选择使用或运算,如下图所示就是进行或运算之后的结果0xE0000060:
将这个结果给到第一个节表成员的Characteristics即可。最后因为所有节合并为一了,所以我们要修改NumberOfSections的值为1:
其他的节表成员可以用00填充,这一块空间就可以给我们添加自己的节表成员:
标签:explorer,CFF,节表,SizeOfRawData,内存,PE,对齐,我们 From: https://www.cnblogs.com/bonelee/p/17399885.html