PE-滴水逆向-手动实现
¶代码节空白节区添加代码
这里我们在notepad增加MessageBoxA函数,保证先调用我们的函数再运行notepad
¶思路
在代码区先加入对函数的调用,再将原来的OEP修改为我们插入代码在内存中的位置
¶ASLR
ASLR技术会使PE文件每次加载到内存的起始地址随机变化,并且进程的栈和堆的起始地址也会随机改变。 所以我们要先关闭
https://baijiahao.baidu.com/s?id=1681048472768096428&wfr=spider&for=pc&searchword=aslr
在标准PE头属性中的第一个值改为1即可
这样PE文件加载到内存中就不会随机化了
¶提取关键数据
¶可选PE头
DWORD AddressOfEntryPoint-10E5DBh
DWORD ImageBase-400000h
¶节表信息
MIsc.DWORD VirtualSize-17C51Eh
DWORD VirtualAddress-1000h
DWORD PointerToRawData-400h
DWORD SizeOfRawData-17C600h
¶判断能否添加
如果SizeOfRawData-MIsc.DWORD VirtualSize>=0x12,即可添加,这里是可以的
¶找到在文件中代码结束的位置
先找到在文件中的偏移PointerToRawData,再找到数据段大小MIsc.DWORD VirtualSize,两者相加就是真实数据段的结束位置
¶添加硬编码
MessageBoxA需要四个参数
call指令长度为5
jmp指令长度为5
真正要跳转的地址=E8/E9指令的下一条指令在内存中的地址+X(X就是E8后边跟着的四个字节)
所以我们需要添加的硬编码为6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
¶计算E8和E9的后四个字节
先找到E8对应的真实地址,也就是MessageBoxA的地址,在od里面command->输入bp MessageBoxA->状态栏b->找到MessageBoxA的地址
¶E8后四字节计算
真实地址为MessageBoxA的地址
X为MessageBoxA地址-VirtualAddress+ImageBase+8+5+Misc.VirtualSize
76BA5865
¶E9后四字节计算
真实地址为原来的OEP
X为ImageBase+AddressOfEntryPoint-VirtualAddress+ImageBase+8+5+5+Misc.VirtualSize
为FFF910AB
¶修改AddressOfPoint
让他先执行我们的代码,也就是修改其为插入硬编码的地址
VirtualAddress+Misc.VirtualSize=17D51Eh
¶最终效果
先执行MessageBoxA
再运行程序
¶新增节
¶大小判断
首先我们需要知道能否新增节SizeofHeaders-最后一个节表 的位置,如果大于或等于两个节表的大小(因为节表最后必须有长度为一个节表的00填充),则可以插入
¶修改节表数量
标准PE头->WORD NumberOfSections,+1即可
¶添加节表信息
这里直接复制.text段的信息,因为其可读可写可执行,后续可以不用再去修改节表属性
¶确定插入的大小
根据情况自行选择,这里假设我们插入0x1000字节
¶修改内存对齐后的大小
SizeOfImage+插入的大小(按照内存对齐)
¶修正节表信息
先看关键数据
DWORD VirtualAddress
DWORD SizeOfRawData
Misc.DWORD VirtualSize
DWORD PointerToRawData
这里为了方便将Misc.DWORD VirtualSize和DWORD SizeOfRawData都改为0x1000,注意如果插入的是其他字节数,需要计算按照文件和内存对齐来调整
¶计算VirtualAddress
前面一个节表的VirtualAddress+Max(Misc.DWORD VirtualSize,SizeOfRawData)-按内存对齐后的
这里SizeOfRawData大于前者,按内存对齐后是15000h,加上该节表的VirtualAddress就是我们插入节表在内存中的偏移地址
¶计算PointerToRawData
和前面类似SizeOfRawData(文件对齐)+PointerToRawData
¶最终修改
¶插入代码
在PointerToRawData插入0x1000字节
¶查看信息
在PE解析工具可以看到插入的节表
¶新增节-节表信息后不够位置
我们直到DOS和NT头之前有一段垃圾数据,当节表末尾没位置插入80个字节,我们需要将NT头和节表信息前移,这样就可以空出一段无用字节,长度为垃圾数据长度
注意要修改LONG AddressOfNewExeHeader-NT头的位置
直接复制到垃圾数据的起始地址
这就是空出来的节表,后续操作和前面新增节一样,就不赘述了
¶扩大节
当前面两种做法都不能满足,我们采取扩大节的办法,可以在任意节区末尾添加,但是如果不是在最后一个节添加,后面的节表偏移都要修改,所以我们扩大最后一个节
需要修改的数据
DWORD SizeOfRawData
Misc.DWORD VirtualSize
DWORD SizeOfImage
¶流程
- 将最后一个节的SizeOfRawData和VirtualSize改成N,N = 节内存对齐后的大小 + 要扩大的大小
- 修改 SizeOfImage大小=SizeOfImage大小+要扩大的大小
- 分配一块新的空间,大小为:节内存对齐增加的大小+要扩大的大小
¶修正节表信息
这里我们假设扩大0x1000个字节
要改为Max(DWORD VirtualSize,DWORD SizeOfRawData)内存对齐的大小+我们扩大的大小=16000h
¶修正SizeOfImage
直接加上0x1000即可
¶分配空间
节内存对齐增加的大小=N-DWORD SizeOfRawData,即C00
在节区尾部增加即可,有时候最后一个节区尾部之后还有别的程序,那么就需要计算最后一个节区的结束地址
求和得到2B6C00h
¶最终
¶合并节
¶修改节表个数
合并之后节数量-1,这里改为7
¶修改节表信息
要将节进行合并,就需要修改节表信息
¶DWORD SizeOfRawData和DWORD VirtualSize
将这两个值改为该节的Max(SizeOfRawData,VirtualSize)+下一个节的Max(SizeOfRawData,VirtualSize)
最后保存即可
那么剩下的节表信息没用了,我们可以再次新增节
¶最终效果
¶移动导出表
移动前,我们需要先开辟节区,先将后面三个表指向的数据复制到新的节区中,再复制那三个表的信息过去,最后将数据目录指向导出表开始的地方
¶定位导出表
在数据目录找到导出表的RVA
根据节的RVA和大小确定在哪个表
计算FOA=Export.RVA-rdata.RVA+rdata. PointerToRawData
计算得到F2270,跳转,根据Export.Size确定大小
¶新增节
前面说过,就不细🔒了
¶复制数据
前面四个是DWORD函数地址,DWORD函数名称表,WORD函数序号-这里比较少,就一个函数
这两个分别是dll名字和函数名称
直接复制剩下的导出表信息
¶修改RVA
这里我已经改好了,因为该节的RVA-PointOfRawData=1600,所以修改的时候将FOA+1600h=RVA
要修改的有
¶数据目录修改
将Export.RVA指向我们复制导出表的初始位置
¶移动重定位表
新增节->复制数据->数据目录修改重定位表位置
¶修改ImageBase
我们修改ImageBase时,重定位表也需要修改,其他不用改,因为其他节表信息映射到内存中时,都是按照ImageBase进行的
而重定位表是按照VirtualAddress+小表进行修复的,所以ImageBase修改了,重定位表的VirtualAddress也需要修改
假如ImageBase+1000,重定位表的VirtualAddress也需要+1000
¶导入表注入
每个DLL对应一个导入表,DLL存放着我们的函数
导入表后面还有一堆数据,我们不能直接添加,所以要新开辟节区,然后复制原来的导入表之后再在末尾添加导入表
¶新增节区
¶复制导入表信息
拷贝原来的导入表到新节中
¶新增导入表
¶新增INT表和IAT表
至少八字节
¶修改Name
存储要注入的dll的名称 ,并且将DLL名称的RVA赋值给新增导入表的Name
¶创建struct _IMAGE_IMPORT_BY_NAME
将函数名称赋值给结构体的第二个变量
¶修改INT和IAT指向的地址
将IMAGE_IMPORT_BY_NAME的RVA赋值给INT和IAT的第一项,因为INT和IAT都指向_IMAGE_IMPORT_BY_NAME
¶修改数据目录
指向我们的新增位置, 修改IMAGE_DATA_DIRECTORY结构的VirtualAddress和Size