需求拆解:
RVA:内存相对地址(相对模块基址VA - BASE)
VA :内存里的地址
FOA:硬盘里的地址(文件偏移)
内存制作工艺和硬盘制作工艺不一样的,内存有这自己的对齐规范多出的就补零
硬盘也有自己的对齐方式(有的程序也有一样的)对齐的字节不一样,这样的对齐方式可以实现快速访问。内存通常是比硬盘对齐的字节打一些,PE文件格式里就指明了硬盘和内存对齐的大小
PE是一种组织文件,组织数据的一种格式,本身不是什么技术,就是微软规定好的一种格式,这种格式主要有exe可执行文件,动态链接库dll,src,sys...都是PE文件,都遵守PE的文件规范
PE里有一个文件叫做PE的头,在PE头文件里就会给我们提供PE组织文件的的基本数据。
一个文件一开始就是它的PE头,头后面就是它的表,节... 我们的代码,资源,常量等就放在节里。节还有名字
我们把EXE文件双击加载到内存里,应该来讲直接把这个数据拷贝到内存里就可以了,但因为对齐粒度不同,他就会放大,都得补到1000的倍数,就会有拉伸放大的效果。
根据是怎么拉伸的,最后求出对应的地址在硬盘里是什么,这就是RVA=>FOA的换算
一打开就这样的,这个叫做DOS头,它是为了兼容DOS程序 ,因为windows有一个发展历程,会向下兼容。当时这个东西已经不用了。
DOS头结构体:
只要范访问e_lfanew就能指向NT头(+DOS头)
唯一重要的就是这个偏移数据。因为在DOS头后面紧跟了一个结构体,这个结构体名字叫NT头,NT头就在DOS头加这个偏移就是NT头。所以用780000+118就到了NT头
NT头定义:
这儿有一个ascii的PE,就是所谓PE头(无模块化技术抹掉PE头就是这个PE头)
文件头定义:
最重要的就是这个:表示有多少个节(里面放的是核心的数据和代码等等)
我们的VA是通过某一个节的FOA映射过来的
选项头定义:
重要的有入口点:
模块默认加载基址: (加载到了指定位置就不需要重定位)
能精确加载到这个位置也就应用程序可以,我们的模块不见得可以精确的加载到这个位置,因为模块有可能会冲突
内存和文件里的对齐粒度
不够的填充为0,有些程序也会填充成CC
NT头结束的第一个位置就是节
根据DOS头加偏移得到NT头,NT头大小加上NT头位置得到第一个节的地方
节:
用之前写的登录器启动,打开OD,看我们自己的模块 ,这就是之前多开检测自己定义的一个节
当时我们是放了一个int变量4字节,但这个数据有时候不准,不采纳了已经
节在硬盘里大小是200(对齐粒度200)
有了这些数据,就可以做换算了。
FOA - 》 ROA
先求RVA与节开始的差距 RVA - virtualAddess[2]可以得到偏移 再加上文件中第二个节的起始地址就得到了在硬盘中的地址
基本定义:
class PEProtecter { unsigned Base; unsigned SecCount;//节数量 PIMAGE_SECTION_HEADER SecArays; private: char* _data{}; public: bool LoadFile(wchar_t* _file); unsigned VAtofoa(unsigned va, unsigned _base=0); char* ReadDataByVA(unsigned va, unsigned _base =0); ~PEProtecter(); };