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

流程

  1. 将最后一个节的SizeOfRawData和VirtualSize改成N,N = 节内存对齐后的大小 + 要扩大的大小
  2. 修改 SizeOfImage大小=SizeOfImage大小+要扩大的大小
  3. 分配一块新的空间,大小为:节内存对齐增加的大小+要扩大的大小

修正节表信息

这里我们假设扩大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