PE檔案中所有節的屬性都被定義在節表中,節表由一系列的IMAGE_SECTION_HEADER結構排列而成,每個結構用來描述一個節,結構的排列順序和它們描述的節在檔案中的排列順序是一致的。全部有效結構的最後以一個空的IMAGE_SECTION_HEADER結構作為結束,所以節表中總的IMAGE_SECTION_HEADER結構數量等於節的數量加一。節表總是被存放在緊接在PE檔案頭的地方。
IMAGE_SECTION_HEADER結構的定義如下:
TISHMisc = packed record
case Integer of
0: (PhysicalAddress: DWORD);
1: (VirtualSize: DWORD);
end;
IMAGE_SIZEOF_SHORT_NAME= 8;
_IMAGE_SECTION_HEADER = packed record
Name: packed array[0..IMAGE_SIZEOF_SHORT_NAME-1] of Byte;
Misc: TISHMisc;
VirtualAddress: DWORD; //節區的RVA地址
SizeOfRawData: DWORD;
PointerToRawData: DWORD; //從檔案頭開始算起的位移量
PointerToRelocations: DWORD;
PointerToLinenumbers: DWORD;
NumberOfRelocations: Word;
NumberOfLinenumbers: Word;
Characteristics: DWORD;
end;
TImageSectionHeader = _IMAGE_SECTION_HEADER;
IMAGE_SECTION_HEADER = _IMAGE_SECTION_HEADER;
● Name欄位
Name欄位定義了節的名稱,欄位的長度為8個位元組。
PE檔案中的節的名稱是一個由ANSI字元組成的字串,但並沒有規定以0結束,如果節的名稱字串長度小於8個位元組的話,後面以0補齊,但是字串長度達到8個位元組的話,後面就沒有0字元了,所以在處理的時候要注意字串的結束方式。
每個節的名稱是惟一的,不能有同名的兩個節,但是節的名稱不代表任何含義,它僅僅是為了查看方便而設定的一個標記而已,可以選擇任何名稱甚至將它空著也可以,將包含代碼的節命名為“DATA”或者將包含資料的節命名為“CODE”都是合法的。
各種編譯器都以自己的方式對節進行命名,所以,在PE檔案中可以看到各式各樣的節名稱,比如,在MASM32產生的可執行檔中,代碼節被命名為“.text”;可讀寫的資料節被命名為“.data”;包含唯讀資料、匯入表以及匯出表的節被命名為“.rdata”;而資源節被命名為“.rsrc”等。但是在其他一些編譯器中,匯入表被單獨放在“.idata”中;而代碼節可能被命名為“.code”。
RVA和檔案位移的轉換
PE檔案中出現RVA的概念是因為PE的記憶體映像和磁碟檔案映像是不同的,為了提高效率,PE檔案頭中使用的都是記憶體映像中的位移量,也就是RVA。RVA僅僅是對於處於節中的資料而言的,對於檔案頭和節表來說無所謂RVA和檔案位移,因為它們在被映射到記憶體中後不管是大小還是位移都不會有任何改變。
當處理PE檔案時,任何的RVA必須經過到檔案位移的換算:
(1)迴圈掃描節表並得到每個節在記憶體中的起始RVA(根據VirtualAddress欄位),並根據節的大小(SizeOfRawData欄位)算出節的結束RVA,最後比較判斷目標RVA是否落在某個節之內。
(2)如果目標RVA處於某個節之內,那麼用目標RVA減去節的起始RVA,這樣就得到了目標RVA相對於節起始地址的位移量RVA'。
(3)在節表中擷取節在檔案中所處的位移(PointerToRawData欄位),將這個位移值加上上一步得到的RVA'值,這才是資料在檔案中的真正位移位置。
即:1 節的起始RVA - 節在檔案中所處的位移 = k
2 目標RVA - k = 檔案位移