PE3-31 导入表注入
导入表注入在动态链接库一章中提到 DllMain,这里再回顾一次:
当 DLL 被加载进 4GB 空间时,会调用一次 DllMain(入口方法)。
当程序执行完毕,将 DLL 从 4GB 空间卸载时,也会调用一次 DllMain。
注入的本质是想方设法将自己的 DLL 投放到其他进程的 4GB 空间中。而在前面讲解的导入表一章中提到:
只有隐式调用(静态使用)时,DLL 的名字和相关函数才会出现在调用者 PE 文件的导入表中。
显式调用(动态使用)时,被调用的 DLL 名和相关函数不会出现在调用者的导入表中。
换句话说:
写入导入表中的 DLL 必定是被隐式调用的(静态使用)。
如果将毫不相关的 DLL 写入某个 PE 文件的完整导入表中,那么打开这个 PE 文件时,该 DLL 会被直接加载(操作系统的规则)。
导入表注入的原理与过程原理当 .exe 文件被加载时,系统会根据其导入表信息加载需要的 DLL。导入表注入的原理就是修改 .exe 的导入表,将自己的 DLL 添加进去。这样,运行 .exe 时,系统会加载该 DLL 并执行其 DllMain 中的代码,从而实现注入 ...
PE3-30 绑定导入表
绑定导入表绑定导入的基本概念在程序加载前,IAT(Import Address Table)和INT(Import Name Table)中的内容通常相同,表示程序引用的 DLL 中函数的名称或序号。加载完成后,IAT 中的内容会被替换为函数的实际地址。
绑定导入的意义
如果 DLL 文件更新,绑定导入表的时间戳和 DLL 文件的时间戳将不同,此时需要修正函数序号、函数名称和函数地址表。
加载前直接写绝对地址:在 IAT 表中保存绝对地址,可以加快程序启动速度,因为省去了修复 IAT 表的步骤。
优点
加快程序启动速度:在启动程序时需要申请4gb内存空间、贴exe、贴dll、将IAT表修复为地址等等,如果直接用绝对地址,则省去了修复IAT表的操作;
缺点
DLL 重定位时,如果 DLL 没有占据其自身的 ImageBase 地址,需要修复绝对地址。
DLL 修改时,IAT 表中的函数地址可能不正确,需要重新修复。
Windows 提供的一些程序(如记事本)使用了这种绑定导入方式。
判断绑定导入的方法在导入表的结构中有一个属性:TimeDateStamp,其值可以反映是否 ...
PE3-27 IAT表&&导入表
IAT:Import Address Table 导入地址表间接寻址调用
调用 DLL 函数时,汇编代码通过 间接寻址 实现,不直接 call 函数地址,而是通过一个中间地址跳转。
示例:调用 MessageBox系统函数(属于 DLL)时,汇编代码为:
1call dword ptr [004322d4]
004322d4 中存储地址 X,程序通过跳转到 X 执行函数。
004322d4 属于 .exe 的内存区域,而 X 可以指向 DLL 的内存区域(如 77d5050b)。
为什么需要间接寻址?
DLL 在加载时可能因重定位占用不同的内存地址。
因此,调用 DLL 函数不能写死地址,而需要动态更新。
程序先将 .exe 中某个位置(如 004322d4)写入代码,然后由操作系统在运行时填充实际的函数地址。
文件状态与加载状态的区别
未运行(文件状态):
004322d4 指向的地址仅存储一个字符串,例如 MessageBox.USER32.dll。
需要根据 RVA(相对虚拟地址)转换为 FOA(文件偏移地址),再减去 ImageBase 才能找到位置。
运行中 ...
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 ...