原理介绍
通过修改PE来加载DLL,相比之前了解的远程线程注入,这种方法只用注入一次,以后每次启动都会加载
用到的程序介绍
书上用的DLL会发起网络请求,下载一个html到本地,这里学习的主要目的是修改PE加载DLL,所以就用简单的dll来代替进行
DLL源代码
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" __declspec(dllexport) VOID helloworld() {
MessageBox(NULL, L"Hello World", NULL, MB_OK);
}
目标程序
这里也简单选用一个普通的程序来进行演示:
#include<windows.h>
int main() {
MessageBoxW(NULL,L"Hello Excel!",L"ERROR",MB_OK);
return 0;
}
修改前准备工作
PE文件中导入的DLL信息以结构体数组的形式存在导入地址表中,只要将我们要导入的dll加到尾部即可,但是需要先判断一下导入地址表尾部是否有空间让我们来添加
判断导入表是否有足够的空间
通过 010editor 打开exe文件,查看扩展头里数据目录表的导入表:
大小是180字节,在文件中的位置是0x1764,跳转过去看看:
一个dll的信息占用14h个字节,一共有180字节,也就是有8个dll(剩下一个为NULL,来表示结束)
这180个字节之后,没有空的位置,所以导入表没有足够的空间去装下新的一个dll的信息
查找空白区域
这种情况下,要把整个导入表转移到更宽阔的地方,然后再添加新的dll信息
确定移动的目标位置可用以下三种方法:
- 查找文件中的空白区域
- 增加文件最后一个区段的大小
- 在文件末尾添加新的区段
关于区段的操作,之前的笔记里已经记录过了,这里以查找空白区域为例,来学习转移导入表的操作
经过大致浏览,发现rdata区段末尾存在挺多的空白区域:
一般区段结尾都会有空白区域
因为我们只需要14h*(9+1)=200个字节的空间,这里足够了,那就这里了
手动修改PE加载DLL
移动导入表需要做的操作有:
- 修改数据目录表中导入表的数据(RVA和Size)
- 删除绑定导入表
- 将原来的导入表复制到新的位置
- 添加新的导入表内容,Name,IAT,INT
- 修改区段属性
修改数据目录表中的导入表信息
根据空白区域,我们选择1CF8作为导入表数组的起始位置,1CF8的RVA是2AF8
所以修改导入表信息RVA为2AF8,修改大小为200:
删除绑定导入表
绑定导入表也在数据目录表里有,删除的操作就是把RVA和Size都改为0,这里我的绑定导入表默认就是0,所以就不用管:
移动导入表
将原本的导入表数组复制到我们新定义的导入表数组的位置:
添加新的导入表项
首先观察其他导入dll是怎么写的:
这里第一个DWORD写指向导入名称表的RVA,FirstThunk是指向导入地址表的RVA,这里两个表可以是同一个地方,也可以不同,在硬盘中,这两个表的内容是相同的:
- 前两个字节表示导出表的序号,不是必须的,可以填0
- 后面的字节是导出函数的名称
那么只要让他们指向同一个地方就行了
然后name则指向填写dll名称的RVA即可
填写完成之后如下图所示:
注意!!这里导入地址表和导入名称表RVA后面要空一个DWORD来表示结束,不然会出错(因为这个导入名称表和导入地址表都是数组,以空项结尾)
修改区段属性
因为PE装载器加载PE的时候,会向导入地址表写入重定位后的地址,所以这个区段得有写入权限才行
找到这个区段属性的位置:
找到IMAGE_SCN_MEM_WRITE项,然后改成1:
到这里修改就完成了,可以保存了
效果演示
双击打开我们刚刚修改过的文件:
程序正常运行,然后用Process Explorer进行查看:
我们手动写入的dll成功加载进来了