PE3-26 移动导出表-重定位表
为什么需要移动各种表?
表的重要性: 这些表由编译器生成,存储了程序运行过程中不可或缺的信息。
初始化的作用: 系统在程序启动时,会依据这些表完成初始化,例如将使用的 DLL 中函数的地址加载到 IAT 表中。
保护程序的需求: 为了保护程序,可以对 .exe 的二进制代码进行加密。但问题在于,各种表和用户代码及数据混合在一起。加密后,系统在初始化时会因无法正确解析表信息而出错。
总结:掌握表的移动技巧,是程序加密与反破解的基础。
移动导出表通常步骤:新增一个节(Section),并将表移动到新节。移动后需要重新计算 ROV。
具体操作步骤:
新增节: 在 DLL 中创建一个新的节,记录新增后的 FOA。
**复制 AddressOfFunctions**: 长度:4 × NumberOfFunctions。
**复制 AddressOfNameOrdinals**: 长度:2 × NumberOfNames。
**复制 AddressOfNames**: 长度:4 × NumberOfNames。
复制函数名字符串: 长度不固定,在复制时直接修复 AddressOfNames。
复 ...
PE3-25 重定位表
动态链接库的重定位表分析重定位表的作用重定位表用于在程序加载到内存时,修正程序的内存地址。通常,当程序在加载时,它的基地址可能与编译时的预定基地址不同,特别是对于动态链接库(DLL)来说,可能会出现多个程序或库文件冲突的情况。因此,必须修正程序中的地址引用,确保它们指向正确的位置。并不是所有的exe程序都有重定位表,但是DLL却是必须需要重定位信息。
为了提高搜索的速度,模块间地址也是要对齐的。模块地址对齐为10000H,也就是64K(64*1024)。
为什么DLL文件需要重定位表大多数EXE文件不会使用重定位表,因为它们通常按固定基址(如0x400000)加载,不会和其他程序冲突。但DLL文件由于可能被多个程序同时加载,且加载地址可能不同,因此必须有重定位表来修正各个地方的内存地址,避免内存冲突。
为什么要用重定位表
在程序加载到内存时,程序中的地址(如全局变量的地址或函数的地址)是相对于编译时的预定基地址(ImageBase)计算的。也就是说,如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表。假设编译时程序的基地址设为0x400000,那么所有的内存地址 ...
PE3-24 导出表
动态链接库(DLL)的导出表分析导出表(Export Table)是PE(Portable Executable)格式中一个非常重要的部分,主要用于记录DLL文件中的导出信息,系统通过它可以找到DLL中的函数、资源等,完成动态链接的过程。
在Windows操作系统中,动态链接库(DLL)文件并不是像静态链接库那样直接嵌入到程序中,而是通过导出表提供的入口点与调用程序进行动态连接。系统会根据导出表中的信息,知道如何加载函数和资源。
扩展名为.exe 的PE 文件中一般不存在导出表,而大部分的.dll 文件中都包含导出表。但这并不是绝对的。例如纯粹用作资源的.dll 文件就不需要导出函数,另外有些特殊功能的.exe 文件也会存在导出函数。
导出表(Export Table)中的主要成分是一个表格,内含函数名称、输出序数等。序数是指定DLL 中某个函数的16位数字,在所指向的DLL 文件中是独一无二的。在此我们不提倡仅仅通过序数来索引函数的方法,这样会给DLL 文件的维护带来问题。例如当DLL 文件一旦升级或修改就可能导致调用改DLL 的程序无法加载到需要的函数。
导出表的结构导出表通常是一 ...
PE3-23 静态链接库-动态链接库
静态链接库与动态链接库静态链接库(.lib)静态链接库(.lib)是编译时将库的代码直接链接到应用程序中的库。使用时,库的函数和符号在编译期间被解析并复制到最终的可执行文件中。
静态库使用方法:
隐式方式1:
将 .h 和 .lib 文件复制到项目目录中。
在需要使用的源文件中包含 #include "xxx.h"。
使用 #pragma comment(lib, "xxx.lib") 引用静态库。
隐式方式2:
将 .h 和 .lib 文件复制到项目目录中。
在源文件中包含 #include "xxx.h"。
在项目属性的连接器设置中,选择“输入”->“附加依赖项”,并将 .lib 文件添加到此处。
编译时的行为:
编译时,静态库的代码被直接打包进最终的可执行文件中,因此每次修改库文件或更新库时,都需要重新编译可执行文件。
这种方式的缺点是程序体积较大,且不易于更新。
平时包含一个头文件,就可以用里面的函数,且没有加#pragma comment(lib,"xxx.lib"),这 ...
PE3-20 扩大节-合并节-数据目录
扩大节-合并节-数据目录扩大节注意:只能扩大最后一个节。
操作步骤:
拉伸到内存
将节加载到内存后处理。
分配新的空间
增加Ex的空间,修改 SizeOfImage:
1SizeOfImage = SizeOfImage + Ex
修改最后一个节的大小
更新最后一个节的 SizeOfRawData和 VirtualSize:
12N = (max(SizeOfRawData, VirtualSize) 内存对齐后的值) + ExSizeOfRawData = VirtualSize = N
更新SizeOfImage
修改 PE 头中的 SizeOfImage值,增加新增空间的大小:
1SizeOfImage = SizeOfImage + Ex
合并节合并节的意义在于节省节表空间。合并操作需要先扩大节后再进行。
操作步骤:
拉伸到内存
将要合并的节加载到内存,便于处理。
计算合并后的大小
合并后的大小取决于所有节的大小:
12Max = max(VirtualSize, SizeOfRawData) 内存对齐后的大小合并节大小 = Virtual ...
PE3-19 新增节-添加代码
新增节-添加代码新增节需要满足的条件:确保新增节后,节表区域仍有剩余空间以容纳一个节表。计算公式如下:
1SizeOfHeader - DOS文件头 - 可选PE头 - 节表数量 * 0x28 > 80
**解释:**新增节后需留出一个节表的空间(遵循Windows规则)。若不遵守规则,可能导致程序异常。
修改的内容与步骤:
添加新的节表
在现有节表末尾新增一个节,可以直接复制已有节表数据进行初始化。
填充新增节后的空间
在新增节后,填充一个节大小的 0x00,确保数据完整。
修改PE头中的节表数量
找到PE头中的NumberOfSections字段,增加1,更新节表数量。
更新SizeOfImage字段
修改PE头中的SizeOfImage字段,增加内存对齐后的新增节大小。
在文件末尾新增节数据
在原有数据末尾,添加一个内存对齐后的新增节数据区域。
修正新增节的属性
确保新增节的属性字段设置正确(如只读、可执行等)。根据需要调整其Characteristics字段。
填充新增节数据
在新增节区域内写入有效数据,避免数据为空导致程序运行失败。
当节 ...
PE3-18 在任意代码空白区添加代码
计算NewBuffer的大小可以通过最后一个节表的信息知道最后一个节表的偏移地址+ImageBase+节的大小,就是NewBuffer的大小
Work参考代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 ...
PE3-17 代码节空白区添加代码
硬编码:E8 和 E9 指令E8 和 E9 是 call 和 jmp 指令的操作码,后面跟的并不是直接的小端序地址。关键是如何计算跳转的地址。
call 例子:
12call 0x77E5425FE8 5F 42 E5 77 (这是错的)
jmp 例子:
12jmp 0x2345678E9 2B 2B 00 00
从这个例子可以看出,E8 和 E9 后面的地址不是直接的小端序转换。要理解如何生成这些字节,我们需要用一个公式来计算。
跳转地址的计算方式真正要跳转的地址 = E8 或 E9 指令的下一条指令的地址 + X
其中:
X = 真正要跳转的地址 - E8 或 E9 指令的下一条指令的地址
具体例子12345678func();00A825E1 E8 62 ED FF FF call func (0A81348h) return 0;00A825E6 33 C0 xor eax,eax 00A825E8 5F pop edi ...
PE3_16 FileBuffer->ImageBuffer
前面提到过,文件从硬盘加载到内存运行时会经过“拉伸”操作,那么在经过简单拉伸之后,程序是否就可以运行呢?答案是否定的。在完成基本拉伸操作后,程序还不能立即运行,还需要进行一些额外的处理。不过此时已经非常接近能够运行了。
Misc.VirtualSize 和 SizeOfRawData 的大小对比在内存中的节有一个 Misc.VirtualSize 属性,表示在内存中拉伸后的实际大小;而 SizeOfRawData 则表示文件在磁盘上的大小。
这两者的大小通常不同,因为内存中可能包含一些未初始化的数据,而这些数据不会在文件中实际保存,但在加载到内存时会被计算并为其预留空间。因此,通常情况下,Misc.VirtualSize 会大于 SizeOfRawData。
总结:
Misc.VirtualSize 是加载到内存中时的实际大小,且它是加载之前未对齐的大小。
SizeOfRawData 是磁盘文件中已对齐的数据大小。
拷贝文件内容到内存时,应该以两者中较小的数值为准。
如何算出内存中节存储的某个地址在文件中对应的地址举个例子,假设在某个节中有一个内存地址是 0x501234。
首 ...
PE3_13 节表
联合体1234union TestUnion { char x; int y;};
特点:
共享空间:联合体的所有成员共享同一内存空间。
内存大小:联合体的大小由最大成员的大小决定。例如,以上例子中,联合体的大小为4字节(int类型)。
有效性:联合体中最多只有一个成员是有效的,但仍然可以访问所有成员。
123456789union TestUnion { char x; int y;};union { char x; int y;} TestUnion;
节表(Section Table)节表是Windows PE/COFF格式可执行文件中的一个重要数据结构,记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息。操作系统通过节表进行各个段的映射和初始化。
在执行PE文件时,Windows不会立即将整个文件读入内存,而是由PE装载器建立虚拟地址与PE文件之间的映射关系。只有当执行到某个内存页中的指令或访问页中的数据时,才会将该页面从磁盘加载到内存。这种机制极 ...