PE文件的间隙:
PE文件在磁盘上存放时其各个节是按页的倍数对齐的(磁盘一页为200h,内存一页为1000h),当一个节不是页的整倍数时其尾部用0填充,这就是PE的间隙。
本示例是在.text节加入了可执行代码,在.idata节加入了外部引用函数,在.reloc节加入了重定位地址。
本示例MyPE.exe是用VS2008自动生成的SingleWindow样式的程序。
为方便理解,附上部分源码:
本示例的目的:
当按下菜单栏的“关于”时,先弹出我们的 确定_取消消息框,点击确定程序继续执行,显示关于对话框,点击取消,不显示。
准备工具:
OllyDbg。用于调试、分析程序流程、获取机器码。
Hex workshop:修改PE文件。
Stud_PE:查看PE结构。
SoftICE:查看菜单标识、查看消息常量。
spy++:查看窗口句柄。辅助SoftICE用的。
可选工具:
IDA Pro : 分析程序流程更强悍。
操作步骤:
1:找到窗口回调函数中处理“关于”的地址处。
首先我们先分析程序流程,当按下菜单时,程序会调用SendMessage向窗口的回调函数发送WM_COMMAND消息。
SendMessage有4个参数:HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam
在这里msg == WM_COMMAND
最后一个参数在这里用不到,忽略。
第三个参数的低字是菜单的标识(ID)。这个ID是在编程时由程序员自己定义的值,我们无法得知。
我们可以想象到,在处理关于的地址处肯定有CMP ***, 111h(WM_COMMAND),如果是COMMAND消息,则继续比较看是否是“关于”的菜单ID。所以我们现在需要得到这个ID。
另外WM_COMMAND的常量可以用SoftICE的WMSG 命令得到:
我们也可用OllyDbg或IDA Pro查找常量111h,然后直接分析也能找到。但这里我们用SoftICE下消息断点来得到消息ID:
首先运行MyPE,再运行Spy++获得MyPE的窗口句柄。
这里得到的窗口句柄是3019E。
打开SoftICE输入BMSG 3019E WM_COMMAND,然后退出。再点击MyPE的“关于”,这时SoftICE捕捉到WM_COMMAND并中断:
这里我们就得到了“关于”的标识68。而且代码窗口也准备要跳到回调函数了。
然后用OllyDbg找常量111h再找到68h,这样就找打了目标地址。
这里的代码也很容易理解:
从上面分析下来可以确定这里就是“关于”的处理程序处了。
4118C4 --- 4118CB:是否等于 == 68,如果是就跳转到4118D8处。
4118D8处首先保存ESP,然后几个参数入栈,到了4118EE处调用DialogBoxParam打开“关于”对话框。
我们现在只要修改地址4118CB处的机器码,让它跳到我们的代码地址处。然后如果点击了确定就跳到4118DA处。即参数入栈,执行DialgoBoxParamW。点击取消,跳到4118F4处,即跳过了DialogBoxParam的调用。
2:插入代码
我们的可执行代码要掉用MessagBoxW这个函数,但MyPE的.idata节内没有引用这个函数,所以我们得自己把这个函数加进去。
这里先复习下DLL函数引入的原理:
PE可选首部最后的一个成员是一个数组,数组内装了16个IMAGE_DATA_DIRECTORY类型的数据结构。
数组中的第2项是描述引入表的。从这里得到引入表的起始RVA,和字节数。
用图来描述:
好了,复习完毕。现在就开始我们的引入工作。
基本原理是:把上面那个RVA指向我们的新地址,并改变大小。复制旧地址处dll表格到我们的新地址。并把原先为NULL的修改成我们的数据,最后再留5个为0的双字(结束标记)。
用Stud_PE打开MyPE,查看.idata的下一个节的RawOffset,这里是:6E00
然后用Hex Workshop 打开MyPE,Ctrl+G跳到这个地址处再往上翻,这些00的地方就可以写入我们的代码。
这里可以从6B47处开始写,但为了直观我打算隔一行。6B60处。
.idata节的相对偏移为:VAOffset - RawOffset = 18000 - 6200 = 11E00H
6B60的VA: 6B60 + 11E00 = 18960H
先修改我之前说到的那个IMAGE_DATA_DIRECTORY结构的内容,让RVA = 18960,SIZE = 原来的内容 + 14H
我这里改好后为:
60 89 01 00 64 00 00 00
复制旧内容到新地址处:
红色为复制过来的部分。
前3个短框:指向INT , 指向DLL名称, 指向IAT
长框是结束标记。
写入格式:
隔行, DLL名称+一个字节NULL IAT 间隔一个双字 INT 间隔一个双字 WORD函数名称两个字节NULL.
(函数名称前有个WORD是数据结构的一部分,是函数在DLL中的序号值,这里不重要;且我们存放WORD函数名称的地址高31为必须为0,)
IAT 和INT都指向函数名称地址RVA。
DLL名称起始RawOffset = 6BD0, RVA = 189D0
IAT起始RawOffset = 6BDB, RVA = 189DB ,注意这个RVA,下面汇编时要用它。
INT ..................... = 6BE3, RVA = 189E3
函数名称.................= 6BEB, RVA = 189EB,189EB的最高位为0,。
再把各项数据填入3个短框内,如图:
保存。用Stud_PE打开刚保存的PE文件,查看Functions就可以知道是否引入成功。
至此函数引入完毕。
3:写入可执行代码
先找到.text节的间隙。
我这里是4100处。RVA = 14D00。这里写入UNCODE字符串“继续执行?”
4110处写入可可执行代码,RVA = 14D10.
先把字符串写到4100处:
E7 7E ED 7E 67 62 4C 88 1F FF 00 00
保存。
再在OllyDbg中开始写汇编:
414D1D处已经被自动替换成了函数名称,原本输入的是4189DB
如果调试成功的话,就把机器码写入到对应的文件地址。
保存。基本上可以运行了,但还需把代码中涉及直接寻址的操作数的地址放入重定位表。
机器码的格式是:操作码 操作数
按照OllyDbg给出的机器码,从Hex workshop中查看,跳过操作码,找到操作数的RawOffset,再转换成RVA。
需重定位的地址:
4118CB处:RVA = 118CB, +操作码占的2个字节为:118CB,, 基址部分:11000
414D16处:..........14D16, ..................1............:14D17,.............: 14000,
414D1D处:..........14D1D,...................2............:14D1F, .........................
414D26处:..........14D26,....................2............:14D28, ........................
414D2C处:..........14D2C,....................1.............:14D2D, ..... .. .. . .. .......
重定位表有由很多块组成的。结构为:
virtualAddress DWORD -----基址部分
blockSize DWORD -----块占的字节数。减去8个字节,剩下的就是各个项目占的字节,再除以2 = 项目数。
typeOffset WORD ------高4位为属性0或者3,低12位为偏移。
NULL WORD -----结束标记
各项偏移地址去掉基址部分,再OR 3000H就可以得到TypeOffset了。
数据目录的第6个结构是描述重定位表的,这里需要修改的是大小。原内容再加上我们占用的字节数。
再把我们的地址加到重定位表中:
save。over。