PE学习——PE空白区添加代码,制作感染型病毒基础
镇楼图,PE文件的内存映射图要熟悉,计算地址要以内存中的为准:
PE空白区添加代码
现在我们有一个任务,需要在空白区添加一段代码(你也可以称之为Shellcode),并且在程序运行之前执行这段代码;首先我们要知道PE空白区是什么,PE空白区表示PE文件按照对齐方式之后多出来的部分,可以是节与节之间的空白区也可以是节表与节之间的空白区。
基本概念了解之后,我们来看下插入的代码,就是如下这个弹窗功能:
::MessageBoxA(
0
,
0
,
0
,
0
);
那么如何让这段代码插入到空白区呢?直接插入代码很明显不可取,因为我们没有源文件,所以我们要插入的是之前所学习的硬编码,我们可以在VC6中下断点反编译查看这段代码对应的硬编码:
注意:
补充: 在我机器上测试看,messagebox的地址是用的FF15 跟间接call,也就是call导入表函数,E8是call自己.text代码段里的函数,注意区别。
首先我们可以看见有4个传参:6A 00,接着就是调用函数MessageBox了,但是在这里的调用是需要遵循导入表的(后面章节会了解导入表内容),是一个间接调用,我们插入的时候需要修改成直接调用的形式,也就是:E8 00 00 00 00,E8后面的4字节表示一个偏移值(上述代码是FF15 call的绝对地址),表示当前指令地址与需要调用指令地址之间的偏移,我们可以使用需要调用指令地址减去当前指令地址(包含本身的宽度)就得出这个偏移值。
我们可以先找到MessageBoxA这个函数地址,在Win32的时候也了解过这个函数是技术是Windows操作系统的USER32.DLL提供的,我们可以在DTDebug中打开一个文件按如下图所示,找到对应的DLL文件,双击进去按Ctrl+N快捷键找到MessageBoxA函数的地址:
如果你觉得这样麻烦也可以考虑在VC6下面下断点跟进这个函数地址即可:
得到了所需要调用的指令地址0x77D5050B,我们还需要知道要填充代码位置的地址,也就是ImageBase加上当前地址(由于现代编译器编译的文件不区分文件、内存对齐,所以不考虑这些),根据扩展PE头可以知道当前ImageBase为0x400000:
这个地址我选择为0xFE0,先填充8字节的6A 00,Call指令开始的位置就是0xFE8,加上ImageBase就是0x400FE8,0x77D5050B减去(0x400FE8+5)(包括指令本身的宽度)等于0x7794F51E,再拼接指令就是:E8 1E F5 94 77 (计算方式:0x77D5050B - 0x400FE8-5=0x7794f51e),这边注意我们是按小端存储模式去写入的。
至此,我们的工作还没有完成,还需要在插入的代码结束之后跳转回程序的入口,程序的入口地址可以根据扩展PE头的AddressOfEntryPoint成员(加上ImageBase)获得,如下图所示程序入口地址就是0x4010B0:
跳回可以使用JMP指令:E9 00 00 00 00,同样这后面的4字节也是偏移值,用程序入口地址减去当前地址即可:0x4010B0 - (0x400FE8+5+5),最终得出:0xBE,那么指令就变成了:E9 BE 00 00 00。
这样,我们插入的代码都已经准备完毕,填充到空白区即可:
接着,我们还需要修改入口函数的地址为当前地址,这样当前程序运行的时候才会先执行我们的代码,执行完成我们插入的代码后就会JMP回入口函数。
我们只需要在扩展PE头中修改AddressOfEntryPoint成员为0xFE0即可:
修改完成之后保存运行就会发现其先运行了我们插入的代码而后再进入程序真正的入口函数代码:
至此,我们就完成了在空白区添加代码并执行的任务了,最后需要注意的是我们这里所插入的代码只能在本机去运行,不能在其他机器上使用,因为这不是一个标准的Shellcode(后续章节会了解Shellcode)。
注意:上面的例子默认是文件对齐和内存对齐大小是一样的!!!
在我的机器实战下,我们使用飞鸽来进行实验,该工具的下载地址: https://dyiyang.jb51.net/201309/tools/IPMsg3(jb51.net).rar ,看下图其对齐大小都是1000
最终效果:
本质上就是在制作感染病毒了!具体做法:
首先,我将如下shellcode添加到.text空白区域:
上述shellcode翻译过来的含义就是调用messagebox弹窗以后,再转到飞鸽的入口:
为啥我选择45150h这个位置加入shellcode呢?因为.text这里有一大片00的空白区域,插入自己的代码再也合适不过了。
上面shellcode部分,最关键的是计算偏移地址:
先找messagebox的地址
msgbox地址:779CA7D0
E8(call指令)下一条指令的绝对地址:
4515D + 400000 = 44515D
E8(call指令)的偏移:
779CA7D0-44515D=7758 5673
好了,类似E9指令的偏移计算:
入口:
0x000441EC
E9(jmp指令)的偏移:
(400000+45162)-(400000+441EC)=fffff08a
最后我们修改PE入口为45150,也就是shellcode的地址:
至此,所有的操作完成!
接下来,我们试试文件对齐和内存对齐不一致的场景,看应该如何正确修改?找一个记事本(在我的win11上,按照下面的方法操作C:\Windows\SysWOW64\notepad.exe是无法成功的,点击之后什么也没有,但是其计算跳转地址的思路是没有任何问题的!):
判断空闲区域是否能放下shellcode
计算文件中代码区空闲空间
SizeOfRawData(0x7800) - VirtualSize(0x007748) > 0x12
空闲空间大于shellcode长度,可以放得下。
2.3将构造好的ShellCode写入空闲区:
PointerToRawData(0x0400)+SizeOfRawData(0x7800)=0x7C00,
在0x7B48与0x7C00之间写入shellcode:
图3
2.4计算E8后边的值
在计算相关值由文件映射到内存时,需要考虑内存对齐和文件对齐。
真正要跳转的地址:MessageBox地址:0x77D507EA
文件中,E8下一行地址相对PointerToRawData偏移量:0x7B5D - 0x400 = 0x775D
映射到内存中,E8下一行地址:ImageBase + VirtualAddress + 0x775D = 0x0100875D
E8后边的值:MessageBox - 0x0100875D = 0x76D4808D
2.5计算E9后边的值
要保证MessageBox关闭后,程序能够正常运行,需要jmp到原来的OEP
1
2
3
4
5
6
7
|
原来OEP(真正要跳转的地址):ImageBase + AddressOfEntryPoint = 0x0100739D 文件中,E9下一行地址相对PointerToRawData偏移量:0x7B62 - 0x400 = 7762 映射到内存中,E9下一行地址:ImageBase + VirtualAddress + 0x7762 = 0x01008762 E9后边的值:`0x0100739D - 0x01008762 = 0xFFFFEC3B` |
2.6修改OEP(AddressOfEntryPoint)
1
2
3
4
5
|
文件中shellcode起始地址相对PointerToRawData偏移量:0x7B50 - 0x400 = 0x7750 映射到内存中,相对ImageBase偏移:VirtualAddress + 0x7750 = 0x8750 将原来的OEP修改为,映射到内存后的shellcode起始地址(0x8750)。 |
另存文件,双击,成功弹出MessageBox!
按照上面的计算思路,我重新找了一个可执行文件,CFF explorer自带的Task Explorer.exe,为了修改方便,我是直接使用CFF explorer来操作,如下,因为可以快速定位段,并且偏移量还是从0开始,不需要再去减去400来计算:
首先看是否可以容纳下shellcode:
00064E00-00064C5E=1A2
我打算在64DDD这个地方加入shellcode:
填充内容如下:
里面地址具体的计算方式为
E8下一条的绝对地址:
00400000+00001000+64DDD=46 5DDD
E8跳转地址:
779CA7D0-465DDD=7756 49F3
E9下一条的绝对地址:
00400000+00001000+64DE2=46 5DE2
老的OEP绝对地址:
00400000+000300FC=43 00FC
E9跳转地址:
4300FC-465DE2=FFFC A31A
新的OEP相对地址:
64DD0+1000=6 5DD0
最后双击运行: