PE文件和COFF文件格式分析——RVA和RA相互计算
? ? ? ? 之前幾節一直是理論性質的東西非常多。本文將會講到利用之前的知識得出一個一個非常有用的一個應用。(轉載請指明來源于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文件格式分析——節信息》中有說明。這兒我們再看下節頭信息數據結構
#define IMAGE_SIZEOF_SHORT_NAME 8typedef 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了。是的!算法就是如此簡單
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之后再判斷了。
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不是平行的,即差值是不同的,不能這么算的。
總結
以上是生活随笔為你收集整理的PE文件和COFF文件格式分析——RVA和RA相互计算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PE文件和COFF文件格式分析——节信息
- 下一篇: PE文件和COFF文件格式分析——导出表