二进制安全-PE文件基础-手动添加ShellCode
目录
前言
前置知识
CALL与JMP的地址计算
OEP地址
ShellCode生成
结语
前言
在之前几节中,我们讲解了PE的一些基本知识,理论知识是很枯燥的,只有实际操作才能对PE文件的认知更加深刻。
前置知识
节表属性
被添加的节表属性必须有可执行属性,一般添加到.text节中。
硬编码
在PE文件中存储的都是硬编码,既是汇编指令对应的字节,硬编码是程序编译后的数据是写死的。所以ShellCode插入PE文件中也要遵守PE文件的格式,必须是硬编码不然无法执行。
直接调用(E8)和间接调用(FF)
直接调用:CALL 函数地址
直接调用后的函数地址是该函数真正的地址,在编译后这个地址可能不会发生改变。
间接调用:MOV EAX,EBX;CALL [EAX];
间接调用则不同,间接调用的函数函数地址虽然不会发生改变,但是会有一个地址保存该函数的地址。
比如这个,EAX的值是根据EBX的值得来的,那么当CALL [EAX]的时候真正调用的不是EAX函数,而是EAX保存的数据,这个数据就是该函数的地址。
1 |
|

直接调用了sum函数,此时CALL的硬编码是E8。
间接调用,把sum函数的地址存放到了004303F8h地址处,call的是004303F8h中保存的数据,此时call是FF。
CALL和JMP
CALL通常用来调用某个函数,而JMP是无条件跳转到指定地址后开始执行代码。
在硬编码中CALL的硬编码的E8,JMP的硬编码是E9。
OEP地址
OEP地址是程序在内存中真正执行的地址。该值是由该程序的AddressOfEntryPoint+ImageBase得到的。
ShellCode
这里就用最简单的ShellCode演示,使用MessageBox函数,我们在程序中写一个没有任何参数的MessageBox用来生成他的硬编码。
所以我们的硬编码就是:0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00,
同时呢这里也可以看到,我们调用MessageBox也是间接调用,因为MessageBox是在dll中的,程序每次运行后MessageBox的值可能发生改变。
这里手动添加ShellCode限制比较多,我们直接使用E8 CALL去调用MessageBox。
但是这样我们的硬编码是不完整的,因为我们让程序先执行我们的ShellCode后要在跳回程序原本的OEP让程序继续执行,所以就需要一个JMP,那么完整的硬编码就是:0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0x6A, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00
手动添加ShellCode
在本案例中我将ShellCode插入到了.text节的PointerToRawData + Misc.VirtualSize处。
1 |
|
我们就以这个程序作为例子添加我们的ShellCode。
找到MessageBox函数地址
我们使用x96dbg调式该程序,在”符号”中找到user32.dll中MessageBox函数的地址:
MessageBox地址是:76770F40
计算E8 CALL的地址
这里要延伸出一个问题了,CALL 地址在转为硬编码的时候并不是E8 地址
看这里,我要CALL的是00411604地址,而硬编码却是:E8 CE CF FE FF
那么这个函数地址与E8的地址之间的计算关系:
函数地址 = CALL当前指令的下一条指令的地址 + X
X就是E8后的的4个硬编码,那么X就等于:
X = 函数地址 - CALL当前指令的下一条指令的地址。
那么问题又来了,如何计算CALL当前指令的下一条指令的地址呢?
那就需要确定ShellCode插入的位置了,在本示例中我把ShellCode插入到了.text节表中的VirtualSize位置处。
所以E8 下一条指令的地址就是:PointerToRawData + VirtualSize + 0x8 + 0x5
其中 0x8 是 ShellCode 的前 8 个字节,0x5 是 E8 本身,这样就计算出了 E8 下一条指令的地址了。
所以我们这里E8 后4个字节的值就是:400h + 199A6h + 8h + 5h = 19DB3
`76770F40 - ((((19DB3 - PointerToRawData) + VirtualAddress) + ImageBase(400000h)) = 7634658D
这里之所要这样计算是因为我们要模拟ShellCode在内存中的状态,也就是ShellCode在内存中距离内存地址的距离。
所以硬编码就是 E8 8D 65 34 76
计算E9 JMP的地址
经过E8的计算,我们很快可以计算出来E9的硬编码,E9要跳回程序原来的OEP400h + 199A6h + 8h + 5h + 5h = 19DB811028h + 400000h - ((((19DB8 - PointerToRawData) + VirtualAddress) + 400000)) = FFFE6670 E9 70 66 FE FF`
修改原有OEP
新的OEP计算,ShellCode内存地址 - ImageBase
ShellCode内存地址:199A6 + 11000h + 400000h = 42A9A6
OEP42A9A6 - 400000 = 2A9A6 
验证
使用X96dbg调试程序



结语
通过手动添加ShellCode使得对PE文件格式有更加深刻的认识,特别是文件状态和内存状态。这两种状态特别容易混乱。手动添加ShellCode成功后再编写代码添加ShellCode是很快的,