DLL
注入指的是向运行中的其他进程强制插入特定的DLL
文件。
可以通过修改静态的PE
文件,修改输入表结构,使得程序执行时载入特定的DLL
文件。
通常可执行文件需要使用其他DLL
文件中的代码或数据,这些DLL
文件相关的信息会保存在输入表中,因此我们通过修改PE
文件中输入表相应的信息,即可实现PE
文件在运行时自动载入特定的DLL
文件。
pe结构分析之手工段表构建--该实验,明确段结构在windows PE程序中的作用,掌握段在文件和内存中加载的过程。实操请复制下方链接
https://www.hetianlab.com/expc.do?ce=d5bc0c11-a182-474b-8ac1-251194174436?pk_campaign=weixin-wemedia#stu
使用PEview
工具可以清晰的看到程序输入表的信息
由于需要修改输入表信息,因此这里简单介绍一下输入表的结构。
在PE
文件的可选头中存在这数据目录项, 里面记载了输出表、输入表等关键信息的偏移及大小。那么理所当然的PE
文件在执行时也会通过数据目录项里的信息去找寻输入表。
输入表是由IMAGE_IMPORT_DESCRIPTOR
结构的数组组成,简称IID
,没有特定的成员指出IID
项数,但是会由全为0的IID
结构作为结束。
上图可以看出输入表的起始地址为0x1B1C4
,这是RVA
(相对偏移地址)地址,我们需要转化为文件的偏移地址才能够在文件中找到相应的内容。工具中提供了RVA
与文件偏移地址的转换或者自行计算。
从上图可以看出IID
结构确实是由全为0的IID
结构作为结束。
输入表结构
IID
结构的字段成员如下,其中OriginalFirstThunk
、Name
以及FirstThunk
成员是我们添加DLL
文件的关键。
IMAGE_IMPORT_DESCRIPTOR
union
characteristics DWORD
OriginalFirstThunk DWORD //指向IMAGE_THUNK_DATA结构的数组
ends
TimeDateStamp DWORD //时间标志
ForwarderChain DWORD //一般为0
Name DWORD //指向DLL名称的指针
FirstThunk DWORD//指向IMAGE_THUNK_DATA结构的数组
IMAGE_IMPORT_DESCRIPTOR
在PE
文件尚未执行过时,OriginalFirstThunk
与FirstThunk
字段指向相同的结构,区别在于OriginalFirstThunk
不可以重写,而FirstThunk
可以被重写,当PE
文件执行后FirstThunk
指向的结构会用于存放输入函数的真实地址。因此我们修改时将OriginalFirstThunk
与FirstThunk
字段指向同个地址即可。而Name
字段存放的是指向DLL
文件名称的指针。
OriginalFirstThunk | FirstThunk | Name |
---|---|---|
指向IMAGE_THUNK_DATA结构的数组 | 指向IMAGE_THUNK_DATA结构的数组 | 指向DLL名称的指针 |
IMAGE_THUNK_DATA
union u1
ForwarderString DWORD //指向一个转向者字符串的RVA
Function DWORD //被输入的函数的内存地址
Oridinal DWORD //被输入的API的序数值
AddressOfData DWORD //指向IMAGE_IMPORT_BY_NAME
IMAGE_THUNK_DATA
IMAGE_IMPORT_BY_NAMEIMAGE_THUNK_DATA
结构在不同情况下的成员不同,但是重点关注AddresOfData
字段,该字段指向IMAGE_IMPORT_BY_NAME
结构,该结构记录的输入函数的名称。当IMAGE_THUNK_DATA
值的双字的最高位为0时,表示函数以字符串类型的函数名方式输入。因此构造时高两个字节为0,低两个字节为IMAGE_IMPORT_BY_NAME
结构地址即可。
IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD //忽略设置为0
Name BYTE //输入函数名称
IMAGE_IMPORT_BY_NAME
IMAGE_IMPORT_BY_NAME
结构的高两字节的值忽略,后门跟着的数据直接填入DLL
文件中输出的函数名称,即PE
文件运行时会使用到DLL
文件中函数的名称。这里准备两个文件
HelloWorld.exe
,该文件仅仅是简单在屏幕输出HelloWorld!!!
的字符DLL
文件,show.dll
,该DLL
文件的功能可以根据实际情况而定,这里我准备的DLL
文件可以简单的弹出一个对话框。HelloWorld.exe
文件拖入PEview
工具中,查看输入表内容。可以看到并没有载入show.dll
文件。运行HelloWorld.exe
文件
开始修改HelloWorld.exe
文件的思路
需要在输入表中添加额外的IID
结构,该IID
成员的信息为show.dll
文件的信息
由于需要添加IID
成员,需要观察原始输入表是否由额外的空间可以容纳新的IID
结构,若没有则可以选择
文件中的空白区域
文件末尾添加新节区
现在观察HelloWorld.exe
文件的输入表,可以看见在输入表的结尾处紧跟着的是一串数据,并且大概率不是无用的数据,若我们直接在输入表结尾处添加新的IID
结构必定会破坏原文件的结构,导致程序无法正常运行。
因此选择在找空白处,因为PE
文件需要对齐,因此会使用大量的空字符进行填充。空白区域可以任取,但是需要记住选取的地址因为后续需要用到。并且我们需要观察该空白区域是否会被载入到内存中去。我们这里选择的是idata
段末尾位置,因此需要去查询idata
段信息。
如下图所示,文件中idata
段的大小比映射到内存中的大小更大,因此我们可以利用这段差值填充伪造输入表。这里选择文件偏移0x8960
作为输入表的起始地址。不能将0x8950
作为其实地址,这样KERNEL32.DLL
字符串会缺失截断符,运行时会提示找不到该DLL
文件。
首先将原输入表的数据复制下来,写入文件偏移0x8960
处,新增一个IID
结构,Name
字段填入DLL
文件的名字,即show.dll
,而OriginalFirstThunk
与FirstThunk
字段填入填入IMAGE_IMPORT_BY_NAME
结构体的地址,IMAGE_IMPORT_BY_NAME
的内容填入输入函数的名称,并且高两个字节需要为0。这里所有填入的地址都为RVA
地址,因此需要将文件地址转化为RVA
地址填入。
接着需要改写idata
的权限,前面说到FirstThunk
在PE文件运行后是会被改写的,因此输入表所在的区段需要具有写权限。可以看到idata
不具备写权限,因此需要将写权限加上。
0x80000000
为写权限的标志位,因此将原来的数据或上0x80000000
即可
0xC0000000
最后由于修改了输入表结构以及所在地址并且新增了一个IID
结构因此需要去数据目录项的位置修改输入表的地址及大小。
使用PEView
工具查看修改后的文件,能够发现修改后的文件使用工具依然能够识别出来,证明没有把文件修坏。
最后执行程序,发现show.dll
文件成功注入