修改PE文件实现静态DLL注入

向PE文件注入DLL一般来说分为两种,一种是动态的,就是在程序运行中注入。还有一种是静态的,也就是修改PE文件达到DLL注入的效果。直接上实验步骤

实验(经过处理的NotePad,输入表下面有大量0000空余,无需进行移位操作):
1.LoadPE查看输入表
1
可以看到输入表的RVA是00007604,转化成文件偏移是6A04(用LoadPE中的位置计算器计算),c32找到6A04处
2.C32去6A04处查看,选中了180个字节,rva这里是一个接一个的IMAGE_IMPORT_DESCRIPTOR 结构体,最后以一个NULL IID结构体结束。一个IID结构体20个字节,所以这里是180/20=9个IID结构体。从LoadPE中查看确实有9个导入DLL。我们的目的是在这之后添加一个IID结构体,实现运行时自动加载我们的DLL
补充:IID结构体信息:
IMAGE_IMPORT_DESCRIPTOR {
union {
Characteristics
OriginalFirstThunk //该成员项含有指向一个 IMAGE_THUNK_DATA 结构数组的RVA
}
TimeDateStamp
ForwarderChain
Name //指向DLL名字的RVA,即指向DLL名字的指针,也是一个ASCIIZ字符串
FirstThunk//指向一个 IMAGE_THUNK_DATA 结构数组的RVA,同OriginalFirstThunk一样
}
这里union是二选一的意思OriginalFirstThunk 和 FirstThunk都是指向指针数组的指针,Name是指向DLL名字的的指针(rva)
3.填写DLL信息
3
因为是文件偏移6B00处是DLL的名字,等下把这个地址转换成rva填入name成员处即可,文件偏移006b10处是函数序号(2字节+函数名字),006b20处是指向006b10的指针
4.添加IID结构体(注意rva/offset)
4
5.删除绑定表,给段可写权限,运行,注入成功

几个比较有价值的问题的解答:
1.INT和IAT的作用到底是什么
阅读PE装载器的模拟代码可以知道,INT并不是必须得有的,INT和IAT都是指针,指向的是结构体数组,结构体名字叫IMAGE_THUNK_DATA,是个4选1的union,4个对象是ForwarderString/Function/Ordinal/AddressOfData,IAT选择其中的Function(地址),INT是多选,一般来说是选择AddressOfData(指针),即指向IMAGE_IMPORT_BY_NAME结构体的指针,IMAGE_IMPORT_BY_NAME这个结构体就是上面的那个稳健偏移为006b10处的东西,IMAGE_IMPORT_BY_NAME的结构是:
IMAGE_IMPORT_BY_NAME{
Hint//指示本函数在其所驻留DLL的引出表中的索引号该值不是必须的,一些连接器将此值设为0。
Name1//引入函数的函数名。函数名是一个ASCIIZ字符串
}
但是INT不是必须的,INT可以是0,就像我这个程序一样,但是当INT为NULL的时候IAT就替代了INT的作用,IAT和INT本质上都是一样的结构体,只是这个结构体是个4选1的而已,IAT一般是Function地址,但是当INT为空时IAT就只能被解释为AddressOfData,不然程序肯定运行不了,也就是说PE装载器有个判断条件while(INT存在且指向AddressOfData || IAT存在且指向AddressOfData){……..加载dll,获取函数地址,复写IAT…….},那么这个时候又有一个疑问了就是,既然IAT完全可以取代INT的功能为什么还要再设计一个INT呢,这个问题某大神给出的解答:IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的 IMAGE_THUNK_DATA 数组里的元素值。由OriginalFirstThunk 指向的RVA数组始终不会改变,所以若还反过头来查找引入函数名,PE装载器还能找寻到。
2.添加一个IID结构体不用去PE可选头中修改输入表的大小吗?不用,因为根据PE装载器的原理,PE装载器会从PE可选头中找到输入表的RVA然后得到第一个IID结构体,之后编译加载dll获取本机函数地址写入IAT操作,然后IID++,再通过判断此时的IID!=NULL就继续上面的步骤,所以IID什么时候加载结束和可选头中输入表size无关,只和全0的IID什么时候被读到有关
3.IAT在PE装载的时候是会被复写为真实的函数地址的,但是我们查看IAT这个指针所指向的IMAGE_THUNK_DATA结构数组,发现他在的区段没有可写属性,那么为什么没有出错呢。因为PE可选头中有一个目录表叫IAT,他表示IAT指向的IMAGE_THUNK_DATA结构体数组在哪,范围有多大,然后这个范围就是特殊化,即使他的区段不可写,他们本身还是可以被PE装载器修改的,所以这里就存在一个问题,你添加一个IID结构体后,你的IAT指向的地方不在PE可选头中IAT指定的地方的,所以你的这个函数不会受到这个特殊照顾,所以你需要给你的区段添加可写属性
4.删除绑定导入表,这个属于优化问题,删了就行。
5.我没给text段可写属性,添加了IID后果然无法运行,但是这里进行下思考会发现一个非常细致的问题。既然PE装载器是while(INT存在且指向AddressOfData || IAT存在且指向AddressOfData)这样的条件判断要不要进行dll加载然后再GetProcAddress,那么也就是说IAT指向的地方不可写触发问题一定是在GetProcAddress拿到函数地址之后才会发生,但是GetProcAddress一定是在LoadLibrary之后才会发生,但是一旦LoadLibrary我们的dll的dllmain函数就应该运行了,那么MessageBox就应该已经弹出来了,但是事实上除了个报错提示框其他什么都没有。参考一些资料和自己的理解感觉原因应该是在PE装载器是在Windows内核层的代码,位于0环的装载器装载PE文件,修复重定位信息,复写IAT地址全部工作完成之后才会从0环回到3环,然后DllMain函数得以执行,所以0环的PE装载器出现写入错误会直接导致错误,而此时还没从0环回到3环所以DllMain根本没执行。这个问题是猜测,真正能确定只有进行内核调试才知道了

猜你喜欢

转载自blog.csdn.net/joliph/article/details/80230361