前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PE文件和COFF文件格式分析——RVA和RA相互计算

PE文件和COFF文件格式分析——RVA和RA相互计算

作者头像
方亮
发布2019-01-16 10:30:03
8130
发布2019-01-16 10:30:03
举报
文章被收录于专栏:方亮方亮

        之前几节一直是理论性质的东西非常多。本文将会讲到利用之前的知识得出一个一个非常有用的一个应用。(转载请指明来源于breaksoftware的csdn博客

        首先我们说下磁盘上A.exe文件和正在内存中运行的A.xe之间的关系。当我们双击A.exe后,A.exe会运行起来。我们在任务管理器中可以看到A.exe。这个过程很简单,但是双击A.exe之后系统做了什么?这个过程你是否思考过?我不准备在此处详细说这个过程,因为这个过程比较复杂。我只讲一个过程——载入。

        磁盘上A.exe在硬盘上,运行时的A.exe的执行体是在内存中。从磁盘到内存,这是个载入的过程。我们查看下A.exe占用的内存和A.exe这个文件的大小,会发现,这两个大小之间不存在任何关系。一则是因为A.exe运行时会载入部分DLL导致内存变大,其次可能是A.exe运行时申请了大量的空间。还有个原因很容易被忽略掉,就是系统在加载这个A.exe时,它是分段读取该文件,分段加载的。比如文件中一个信息是1byte,系统读取该信息后出于内存对齐考虑,可能会给这个信息分配4bytes的空间。是不是感觉我们A.exe被系统加载后,数据的连续性等被破坏了?如图

        是的,原来的结构是被打乱的。是不是感觉恐慌?其实不用,这种映射必定存在一定的算法。我们本文就是讨论这种算法的。

        讨论这个算法前,我们先说个概念——位置。现在有GPS功能的设备越来越多,玩这个功能的人是否考虑过其中的简单的原理呢?我没有研究过它,但是我想这是天上的卫星和地上(或者空间)一些设备合作计算出来的。比如我们地上有些基站,卫星知道它们的坐标,然后通过一些数据,比如我们“相对‘A基站的距离,“相对‘B基站的距离,从而计算出我们GPS设备和这些坐标的关系,从而得出我们所在的坐标,从而知道我们的位置。可以见得计算我们位置的过程涉及到“相对”这个概念。

        文件中数据位置的描述也是使用”相对“来定位的,正在运行的程序中的数据的定位也是通过”相对“来定位的。一个数据位置相对于文件头(第一个字节)的偏移我们称为相对地址(RA),内存中一个数据相对于程序开始处的偏移我们称为相对虚拟地址(RVA)。这两个概念非常重要。

        那我们PE文件中对数据的表述是使用RA还是RVA呢?大体可以总结如下:如果要在内存中运行和使用的数据大部分是使用RVA描述的。如果只是文件信息,程序运行时不关心的数据使用RA描述的。我在《PE文件和COFF文件格式分析——签名、COFF文件头和可选文件头2》最后部分,说了一句话“DataDirectory保存了指向“块信息”的目录信息,其中包括偏移(除了IMAGE_DIRECTORY_ENTRY_SECURITY元素是相对文件偏移RA,其他都是相对虚拟首地址偏移RVA)和大小。”IMAGE_DIRECTORY_ENTRY_SECURITY区块保存的是文件的签名信息,在运行程序前,系统加载文件的过程是不会把这块信息加载到内存中的。还有《PE文件和COFF文件格式分析——签名、COFF文件头和可选文件头1》中介绍的IMAGE_FILE_HEADER::PointerToSymbolTable,它指向的数据是符号表,该信息也是程序运行时不关心的,所以它也是RA。程序运行时需要使用的信息的数据地址就要使用RVA来表示了,试想如果这些数据用RA表示,则程序在运行前要根据加载的情况把这些数据在内存中的RVA再算出来,这个工作量是非常大的,是非常不科学的。

        在我分析PE文件时,遇到的大部分信息是RVA。于是我想查看该位置的信息,就要通过RVA计算出RA。一般来说文件的结构是比较紧凑的,这样是为了方便文件传输(想想在那个网络非常慢,硬盘那么贵的年代)。而程序的内存中的结构则相对松散些,这个并不是因为内存不值钱,而是为了执行效率,而且一般没谁会把电脑上所有保存的程序都跑起来执行吧?

       一般来说,系统加载PE文件时,会先读取文件头信息,查看该文件是否可以重定向(通过判断IMAGE_FILE_HEADER::Characteristics是否包含IMAGE_FILE_RELOCS_STRIPPED)啊。如果不可以,则查看它想加载的位置(IMAGE_OPTIONAL_HEADER32(64)::ImageBase)是否被占用了,如果没有占用,或者即使被占用了但是其允许重定位,就继续读取”节信息“。关于节信息,我在《PE文件和COFF文件格式分析——节信息》中有说明。这儿我们再看下节头信息数据结构

代码语言:javascript
复制
#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

        VirtualAddress保存的是映像文件中该节被加载进内存时,第一个字节相对于映像基址的偏移量。VirtualSize保存的是内存中该节的大小。这组数据和RVA关系很大。

        PoiterToRawData保存的是该节第一个字节在文件中相对于文件第一个字节的偏移量。SizeOfRawData保存的是该节在文件中的大小。这组数据和RA关系很大。

        那么系统加载这些节可以用下图说明

        是不是还是感觉比较乱?但是注意一个现象,看线1、2是平行的,线3、4是平行的,线5、6是平行的。为什么是平行的?在《PE文件和COFF文件格式分析——节信息》一文中我介绍VirtualSize属性时这么说的“VirtualSize属性是节加载进入内存后,节在内存中的大小。如果它比SizeOfRawData大,则多余的部分是用0x00填充的。”

        那么这个平行这意味着什么?这意味着如果我们可以利用”相对“这个概念。因为平行就是相对的,如1和2平行而和3不平行。现在假如RVA落在内存的SectionBStart和SectionBEnd之间,则我们可以计算出RVA于SectionBStart的偏移OffsetBSection。然后我们通过节信息找到SectionBStart在文件中的相对地址RA,最后,我们让SectionBstart+OffsetBSection就得到RVA对应的RA了。是的!算法就是如此简单

代码语言:javascript
复制
        for ( VecSectionHeaderIter it = m_vecSectionHeaders.begin(); 
            it != m_vecSectionHeaders.end(); it++ )
        {
            if ( dwRVA >= it->VirtualAddress 
                && dwRVA < it->VirtualAddress + it->Misc.VirtualSize ) 
            { 
                if ( 0 == it->SizeOfRawData ) {
                    break;
                }

                dwRA = dwRVA - it->VirtualAddress + it->PointerToRawData;
                bSuc =  TRUE;
                break;
            }
        }

        这儿还要说一种场景:有些PE文件坏坏的让VirtualSize为0,而实际该大小确实存在的,这个时候我们就要修正VirtualSize之后再判断了。

代码语言:javascript
复制
            for ( VecSectionHeaderIter it = m_vecSectionHeaders.begin(); 
                it != m_vecSectionHeaders.end(); it++ )
            {
                if ( dwRVA >= it->VirtualAddress 
                    && 0 == it->Misc.VirtualSize ) 
                { 
                    // 校正虚拟大小
                    DWORD dwSectionAlignment = ( EOp32 == m_eFileOpType ) ? m_OptionalHeader32.SectionAlignment : m_OptionalHeader64.SectionAlignment;
                    DWORD dwVirtualSize = ( it->SizeOfRawData + (dwSectionAlignment - 1) ) &~(dwSectionAlignment - 1);
                    it->Misc.VirtualSize = dwVirtualSize;
                    if ( dwRVA < it->VirtualAddress + dwVirtualSize ) {
                        dwRA = dwRVA - it->VirtualAddress + it->PointerToRawData;
                        bSuc =  TRUE;
                        break;  
                    }
                }
            }

        以上算法是讨论了从RVA计算出RA的过程,而通过RA计算出RVA只是这样一个逆向过程。我想如果看懂了以上的原理,那么这个算法是很容易写出来的。

        刚接触这块的同学可能会存在一个误区:比如他知道RVA=0x00002100,还知道这个RVA对应的RA=0x00002200。于是可能会想当然的认为该文件的RA = RVA + 0x100。于是他下次遇到RVA=0x00008000时就认为其对应的RA=RVA+0x100=0x00008100!!这个是不对的,就像0x00002200落在上图的SectionAStart和SectionAEnd之间,而0x00008000落在上图的SectionBStart和SectionBEnd之间,线1和3不是平行的,即差值是不同的,不能这么算的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2012年09月06日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档