PE文件和COFF文件格式分析——RVA和RA相互计算
? ? ? ? 之前幾節(jié)一直是理論性質(zhì)的東西非常多。本文將會講到利用之前的知識得出一個一個非常有用的一個應(yīng)用。(轉(zhuǎn)載請指明來源于breaksoftware的csdn博客)
? ? ? ? 首先我們說下磁盤上A.exe文件和正在內(nèi)存中運(yùn)行的A.xe之間的關(guān)系。當(dāng)我們雙擊A.exe后,A.exe會運(yùn)行起來。我們在任務(wù)管理器中可以看到A.exe。這個過程很簡單,但是雙擊A.exe之后系統(tǒng)做了什么?這個過程你是否思考過?我不準(zhǔn)備在此處詳細(xì)說這個過程,因為這個過程比較復(fù)雜。我只講一個過程——載入。
? ? ? ? 磁盤上A.exe在硬盤上,運(yùn)行時的A.exe的執(zhí)行體是在內(nèi)存中。從磁盤到內(nèi)存,這是個載入的過程。我們查看下A.exe占用的內(nèi)存和A.exe這個文件的大小,會發(fā)現(xiàn),這兩個大小之間不存在任何關(guān)系。一則是因為A.exe運(yùn)行時會載入部分DLL導(dǎo)致內(nèi)存變大,其次可能是A.exe運(yùn)行時申請了大量的空間。還有個原因很容易被忽略掉,就是系統(tǒng)在加載這個A.exe時,它是分段讀取該文件,分段加載的。比如文件中一個信息是1byte,系統(tǒng)讀取該信息后出于內(nèi)存對齊考慮,可能會給這個信息分配4bytes的空間。是不是感覺我們A.exe被系統(tǒng)加載后,數(shù)據(jù)的連續(xù)性等被破壞了?如圖
? ? ? ? 是的,原來的結(jié)構(gòu)是被打亂的。是不是感覺恐慌?其實(shí)不用,這種映射必定存在一定的算法。我們本文就是討論這種算法的。
? ? ? ? 討論這個算法前,我們先說個概念——位置?,F(xiàn)在有GPS功能的設(shè)備越來越多,玩這個功能的人是否考慮過其中的簡單的原理呢?我沒有研究過它,但是我想這是天上的衛(wèi)星和地上(或者空間)一些設(shè)備合作計算出來的。比如我們地上有些基站,衛(wèi)星知道它們的坐標(biāo),然后通過一些數(shù)據(jù),比如我們“相對‘A基站的距離,“相對‘B基站的距離,從而計算出我們GPS設(shè)備和這些坐標(biāo)的關(guān)系,從而得出我們所在的坐標(biāo),從而知道我們的位置。可以見得計算我們位置的過程涉及到“相對”這個概念。
? ? ? ? 文件中數(shù)據(jù)位置的描述也是使用”相對“來定位的,正在運(yùn)行的程序中的數(shù)據(jù)的定位也是通過”相對“來定位的。一個數(shù)據(jù)位置相對于文件頭(第一個字節(jié))的偏移我們稱為相對地址(RA),內(nèi)存中一個數(shù)據(jù)相對于程序開始處的偏移我們稱為相對虛擬地址(RVA)。這兩個概念非常重要。
? ? ? ? 那我們PE文件中對數(shù)據(jù)的表述是使用RA還是RVA呢?大體可以總結(jié)如下:如果要在內(nèi)存中運(yùn)行和使用的數(shù)據(jù)大部分是使用RVA描述的。如果只是文件信息,程序運(yùn)行時不關(guān)心的數(shù)據(jù)使用RA描述的。我在《PE文件和COFF文件格式分析——簽名、COFF文件頭和可選文件頭2》最后部分,說了一句話“DataDirectory保存了指向“塊信息”的目錄信息,其中包括偏移(除了IMAGE_DIRECTORY_ENTRY_SECURITY元素是相對文件偏移RA,其他都是相對虛擬首地址偏移RVA)和大小?!盜MAGE_DIRECTORY_ENTRY_SECURITY區(qū)塊保存的是文件的簽名信息,在運(yùn)行程序前,系統(tǒng)加載文件的過程是不會把這塊信息加載到內(nèi)存中的。還有《PE文件和COFF文件格式分析——簽名、COFF文件頭和可選文件頭1》中介紹的IMAGE_FILE_HEADER::PointerToSymbolTable,它指向的數(shù)據(jù)是符號表,該信息也是程序運(yùn)行時不關(guān)心的,所以它也是RA。程序運(yùn)行時需要使用的信息的數(shù)據(jù)地址就要使用RVA來表示了,試想如果這些數(shù)據(jù)用RA表示,則程序在運(yùn)行前要根據(jù)加載的情況把這些數(shù)據(jù)在內(nèi)存中的RVA再算出來,這個工作量是非常大的,是非常不科學(xué)的。
? ? ? ? 在我分析PE文件時,遇到的大部分信息是RVA。于是我想查看該位置的信息,就要通過RVA計算出RA。一般來說文件的結(jié)構(gòu)是比較緊湊的,這樣是為了方便文件傳輸(想想在那個網(wǎng)絡(luò)非常慢,硬盤那么貴的年代)。而程序的內(nèi)存中的結(jié)構(gòu)則相對松散些,這個并不是因為內(nèi)存不值錢,而是為了執(zhí)行效率,而且一般沒誰會把電腦上所有保存的程序都跑起來執(zhí)行吧?
? ? ? ?一般來說,系統(tǒng)加載PE文件時,會先讀取文件頭信息,查看該文件是否可以重定向(通過判斷IMAGE_FILE_HEADER::Characteristics是否包含IMAGE_FILE_RELOCS_STRIPPED)啊。如果不可以,則查看它想加載的位置(IMAGE_OPTIONAL_HEADER32(64)::ImageBase)是否被占用了,如果沒有占用,或者即使被占用了但是其允許重定位,就繼續(xù)讀取”節(jié)信息“。關(guān)于節(jié)信息,我在《PE文件和COFF文件格式分析——節(jié)信息》中有說明。這兒我們再看下節(jié)頭信息數(shù)據(jù)結(jié)構(gòu)
#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保存的是映像文件中該節(jié)被加載進(jìn)內(nèi)存時,第一個字節(jié)相對于映像基址的偏移量。VirtualSize保存的是內(nèi)存中該節(jié)的大小。這組數(shù)據(jù)和RVA關(guān)系很大。
? ? ? ? PoiterToRawData保存的是該節(jié)第一個字節(jié)在文件中相對于文件第一個字節(jié)的偏移量。SizeOfRawData保存的是該節(jié)在文件中的大小。這組數(shù)據(jù)和RA關(guān)系很大。
? ? ? ? 那么系統(tǒng)加載這些節(jié)可以用下圖說明
? ? ? ? 是不是還是感覺比較亂?但是注意一個現(xiàn)象,看線1、2是平行的,線3、4是平行的,線5、6是平行的。為什么是平行的?在《PE文件和COFF文件格式分析——節(jié)信息》一文中我介紹VirtualSize屬性時這么說的“VirtualSize屬性是節(jié)加載進(jìn)入內(nèi)存后,節(jié)在內(nèi)存中的大小。如果它比SizeOfRawData大,則多余的部分是用0x00填充的?!?/p>
? ? ? ? 那么這個平行這意味著什么?這意味著如果我們可以利用”相對“這個概念。因為平行就是相對的,如1和2平行而和3不平行。現(xiàn)在假如RVA落在內(nèi)存的SectionBStart和SectionBEnd之間,則我們可以計算出RVA于SectionBStart的偏移OffsetBSection。然后我們通過節(jié)信息找到SectionBStart在文件中的相對地址RA,最后,我們讓SectionBstart+OffsetBSection就得到RVA對應(yīng)的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,而實(shí)際該大小確實(shí)存在的,這個時候我們就要修正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只是這樣一個逆向過程。我想如果看懂了以上的原理,那么這個算法是很容易寫出來的。
? ? ? ? 剛接觸這塊的同學(xué)可能會存在一個誤區(qū):比如他知道RVA=0x00002100,還知道這個RVA對應(yīng)的RA=0x00002200。于是可能會想當(dāng)然的認(rèn)為該文件的RA = RVA + 0x100。于是他下次遇到RVA=0x00008000時就認(rèn)為其對應(yīng)的RA=RVA+0x100=0x00008100!!這個是不對的,就像0x00002200落在上圖的SectionAStart和SectionAEnd之間,而0x00008000落在上圖的SectionBStart和SectionBEnd之間,線1和3不是平行的,即差值是不同的,不能這么算的。
總結(jié)
以上是生活随笔為你收集整理的PE文件和COFF文件格式分析——RVA和RA相互计算的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PE文件和COFF文件格式分析——节信息
- 下一篇: PE文件和COFF文件格式分析——导出表