PE知识复习之PE的RVA与FOA的转换
一丶简介PE的两种状态
首先我们知道PE有两种状态.一种是内存展开.一种是在文件中的状态.那么此时我们有一个需求.
我们想改变一个全局变量的初始值.此时应该怎么做.你知道虚拟地址.或者文件位置了.那么你怎么自己进行转换.
也就是说通过文件中的节数据找到在内存中这块数据的位置.或者反之.
寻找之前我们要先弄前几个概念.
ImageBase: 模块基址.程序一开始的地址.
VA: 全名virtualAddress 虚拟地址. 就是内存中虚拟地址. 例如 0x00401000
RVA: RVA就是相对虚拟偏移. 就是偏移地址. 例如 0x1000. 虚拟地址0x00401000的RVA就是 0x1000. RVA = 虚拟地址-ImageBase
FOA: 文件偏移. 就是文件中所在的地址.
二丶因为PE的两种状态.所以需要转换.
上面简介了一下什么是VA RVA 以及FOA 那么我们为什么要转换.
原因是这样的. 我们程序的数据.在PE文件中的地址假设是0x400, 那么在内存中展开的时候就是0x1000位置处.
那么我们如何通过内存位置.找到文件中这个数据的位置. 或者反之. 如果找到就可以进行修改了.
原因就是PE有两种状态.有内存对齐跟文件对齐. 如果内存对齐跟文件对齐一样.那么不管在内存中还是在文件中.数据的位置都是一样的.
例如文件对齐是0x1000,内存也是一样. 那么文件中0x1000位置存放的值.跟PE在内存中展开的时候存放的值是一样的.所以就不需要转换了.直接在文件中更改或者在内存中更改就行了.
因为对齐值不一样.所以我们才需要进行转换.
例如下图:
文件对齐值是0x200,内存对齐是0x1000
三丶转换方法
既然上方了解了PE的内存状态.以及文件状态形式. 那么转换就很好理解了.
1.内存转文件偏移计算
1.1.计算RVA
这一个讲的就是内存转文件偏移.就是知道一个内存地址.我们要看看在文件中是哪里存储的.
第一步: 我们知道PE在内存中展开.是在ImageBase位置展开的.头跟文件是一样的.只不过节数据展开位置不一样.
所以首先就是 我们的内存地址-Image得出RVA
下方我们的内存地址我就设为x了.
x - ImageBase == RVA 得出了我们的x位置在内存中的相对偏移.相对偏移就是我们计算的这个地址在开始位置的什么地方.
ImageBase是在扩展头中存放的.我们可以查看一下.具体可以看看前几讲.属性解析.
注意都是16进制进行加算的.
根据上方我们得出的RVA.然后我们就在文件中从开头数RVA个字节,去寻找我们的这个数据.这样是不行的.因为文件对齐跟内存对齐是不一样的.所以我们要考虑对齐方式. 如果文件对齐跟内存对齐一样.那么这样就可以去找.
2.寻址FOA
既然找到了RVA了.那么就找一下FOA在哪里.也就是文件偏移在哪里.寻找这个值很简单.需要几个步骤.
2.1.判断RVA属于哪个节/头.
如果RVA属于头(DOS NT)那么不需要进行计算了.因为头在文件中根内存中都是一样展开的.直接从开始位置寻找到RVA个字节即可.
如果不在头,就要判断在那个节里面. 判断节开始位置.跟结束位置. 我们的RVA在这个值里面.
其中节虚拟地址结束位置 就是用节数据对齐后的大小 虚拟地址大小. 具体可以参考上一讲节表解析.
公式: RVA >= 节.VirtualAddress && RVA <= (节.VirtualAddress 节.SizeofRawData)
2.2 计算差值偏移. 虚拟地址距离节数据的开始位置的偏移.
然后计算差值偏移:
差值 = RVA - 节.VirtuallAddress
差值偏移:
为什么要计算差值.因为我们计算的差值偏移就是我们的 RVA距离我们节数据开始位置 的偏移是多少. 因为这个位置是不会改变的.
例如: 节数据开始位置是 0x1000 我们的RVA = 0x1024 那么差值是0x24. 如果文件中节数据开始的位置是0x400. 那么我们的差值偏移是不会变的. 那么文件偏移 差值偏移. 那么就是在文件中的位置. 例如 0x424
2.3 计算FOA
FOA就很好计算了. 差值偏移已经得出来了. 就知道我们的RVA距离节数据开始位置的偏移. 那么我们加上文件偏移就是FOA
公式: FOA = 差值偏移 节.PointToRawData
内存转文件偏移总结:
1.计算RVA 公式: x - ImageBase == RVA
2.计算差值偏移. RVA - 节.VirtualAddress == 差值偏移.
3.计算FOA 差值偏移 节.PointerToRawData == FOA
2.文件偏移转内存虚拟地址
上面讲解了我们根据虚拟地址可以定位到在文件中的那个位置.那么反之.我们也可以通过文件位置.定位到虚拟地址.
需要理解的还是差值偏移. 只不过角色互换了. .
设x 为节数据的任意一位置
1.计算差值偏移: x - 节.PointerToRawData(节数据在文件中开始的位置) == 差值偏移.
2.计算RVA 差值偏移 节.VirtuallAddress(节数据在内存中展开的位置) == RVA
3.计算虚拟地址: RvA ImageBase == VA
需要注意的就是我们的 x在哪一个节中. x <= 节.PointerToRawData 节.SizeofRawData
四丶实战演练
我们写一个程序.其代码如下:
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
int g_TestValue = 0x12345678;
int main(int argc, char *argv[])
{
printf("全局变量地址 = %p rn", &g_TestValue);
printf("全局变量值 = %X rn", g_TestValue);
getchar();
}
PS: 如果是VS系列编译器,请在属性 -> 连接 中去掉随机基址. 不然你需要计算一下.或者自己在PE中将文件头的文件属性更改. 更改为. 0x0103
程序截图:
此时我们已经知道了全局变量地址.那么我们要转换到文件中.将这个全局变量地址进行修改.也就是说.我们通过修改文件.达到修改我们的全局变量值的一种手段.
思路:
1.计算出RVA. RVA怎么计算我们也知道了.我们需要查看PE中扩展头的ImageBase成员的值. 这里我已经查看好了.值为0x400000. 那么我们的RVA = 19000
2.判断属于哪个节,计算出差值偏移
在我们的.data节中.差值偏移计算出结果为0.
3.计算FOA位置.
因为现在编译器的文件对齐以及内存对齐都是一样了.所以我们不许要进行计算了. 直接就是文件偏移就是FOA位置.
否则我们差值偏移加文件偏移 = = FOA. 现在我们的差值偏移是 0 0 节偏移 就是全局变量在文件中的位置.
7400 就是我们的FOA
4.跳转到FOA修改全局变量的值
跳转到我们的FOA位置,可以看到我们全局变量的初始值为小端模式的 0x12345678,那么我们进行修改.进行文件保存即可.
5.修改文件重新打开程序
修改为0x55555555了,重新打开程序观看结果.
这就是内存转文件偏移的实战. 如果学过逆向的人应该接触过OD.或者x64DBG. 如果我们在内存中修改后.要保存到文件.那么计算公式就是这个.