節表(Section Table)是Windows PE/COFF格式的可執行文件中一個非常重要的數據結構,它記錄了各個代碼段、數據段、資源段、重定向表等在文件中的位置和大小信息,是操作系統載入文件時根據節表來進行各個段的映射和初始化的重要依據。節表中的每個記錄則被稱為`IMAGE_SECTION_... ...
節表(Section Table)是Windows PE/COFF格式的可執行文件中一個非常重要的數據結構,它記錄了各個代碼段、數據段、資源段、重定向表等在文件中的位置和大小信息,是操作系統載入文件時根據節表來進行各個段的映射和初始化的重要依據。節表中的每個記錄則被稱為IMAGE_SECTION_HEADER
,它記錄了一個段的各種屬性信息和在文件中的位置和大小等信息,一個文件可以由多個IMAGE_SECTION_HEADER
構成。
在執行PE文件的時候,Windows 並不在一開始就將整個文件讀入記憶體,PE裝載器在裝載的時候僅僅建立好虛擬地址和PE文件之間的映射關係,只有真正執行到某個記憶體頁中的指令或者訪問頁中的數據時,這個頁面才會被從磁碟提交到記憶體中,這種機制極大的節約了記憶體資源,使文件的裝入速度和文件的大小沒有太多的關係。
Windows 裝載器在裝載DOS部分PE文件頭部分和節表部分時不進行任何處理,而在裝載節區的時候會根據節的不同屬性做不同的處理,一般需要處理以下幾個方面的內容:
節區的屬性: 節是相同屬性的數據的組合,當節被裝入記憶體的時候,同一個節對應的記憶體頁面將被賦予相同的頁屬性,Windows系統對記憶體屬性的設置是以頁為單位進行的,所以節在記憶體中的對其單位必須至少是一個頁的大小,對於X86來說這個值是4KB(1000h),而對於X64來說這個值是8KB(2000h),磁碟中存儲的程式並不會對齊4KB,而只有被PE載入器載入記憶體的時候,PE裝載器才會自動的補齊4KB對其的零頭數據。
節區的偏移: 節的起始地址在磁碟文件中是按照IMAGE_OPTIONAL_HEADER
結構的FileAhgnment欄位的值對齊的,而被載入到記憶體中時是按照同一結構中的SectionAlignment欄位的值對齊的,兩者的值可能不同,所以一個節被裝入記憶體後相對於文件頭的偏移和在磁碟文件中的偏移可能是不同的。
節區的尺寸: 由於磁碟映像和記憶體映像的對齊單位不同,磁碟中的映像在裝入記憶體後會自動的進行長度擴展,而對於未初始化的數據段(.data?)來說,則沒有必要為它在磁碟文件中預留空間,只要可執行文件裝入記憶體後動態的為其分配空間即可,所以包含未初始化數據的節在磁碟中長度被定義為0,只有在運行後PE載入器才會動態的為他們開闢空間。
不進行映射的節: 有些節中包含的數據僅僅是在裝入的時候用到,當文件裝載完畢時,他們不會被遞交到物理記憶體中,例如重定位節,該節的數據對於文件的執行代碼來說是透明的,他只供Windows裝載器使用,可執行代碼根本不會訪問他們,所以這些節存在於磁碟文件中,不會被映射到記憶體中。
一般來說,當一個PE文件被編譯生成時則預設會存在.text,.data
等基本節表,而每一個節表都是由一個IMAGE_SECTION_HEADER
結構排列而成,每個結構都用來描述一個節,節表總被存放在緊接在PE文件頭的地方,也即是從PE文件頭開始偏移為00f8h
的位置,針對每一個節中的定義可查看節表結構體的定義;
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize; // 節區尺寸
} Misc;
DWORD VirtualAddress; // 節區RVA
DWORD SizeOfRawData; // 在文件中對齊後的尺寸
DWORD PointerToRawData; // 在文件中的偏移
DWORD PointerToRelocations; // 在OBJ文件中使用
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 節區屬性欄位
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
針對IMAGE_SECTION_HEADER
中各個欄位的詳細解析:
-
Name:段名,是一個8位元組的
ASCII
字元串,不足8位元組用0補齊。 -
VirtualSize:虛擬大小,標識在記憶體中占用的大小,請勿與
PhysicalSize
(物理大小)混淆。 -
VirtualAddress:虛擬地址,標識在記憶體中對應段頭的地址,與實際載入的位置有關。
-
SizeOfRawData:物理大小,標識在PE文件中該段的占用大小,不足以文件對齊單位則會進行填充。
-
PointerToRawData:物理地址,標識該段在文件中的偏移位置。
-
PointerToRelocations:重定向表的偏移位置。
-
PointerToLinenumbers:行號表的偏移位置。
-
NumberOfRelocations:重定向表數量。
-
NumberOfLinenumbers:行號表數量。
-
Characteristics:標識該段的各種屬性信息,包括下列常用屬性:
- IMAGE_SCN_MEM_READ:可讀;
- IMAGE_SCN_MEM_WRITE:可寫;
- IMAGE_SCN_MEM_EXECUTE:可執行;
- IMAGE_SCN_CNT_CODE:代碼段;
- IMAGE_SCN_CNT_INITIALIZED_DATA:已初始化數據段;
- IMAGE_SCN_CNT_UNINITIALIZED_DATA:未初始化數據段;
- IMAGE_SCN_LNK_INFO:包含附加信息。
與數據目錄表的枚舉方式基本一致,數據目錄表的枚舉也不會太難,讀者只需要通過NtHeader->FileHeader.NumberOfSections
獲取到當前有多少個節,並通過迴圈的方式依次得到這些節中的指針,並將該指針轉換為PIMAGE_SECTION_HEADER
結構,依次迴圈輸出即可得到;
int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);
if (PE == TRUE)
{
printf("編號\t 節區名稱\t虛擬偏移\t虛擬大小\t實際偏移\t實際大小\t節區屬性\n");
for (DWORD each = 0; each < NtHeader->FileHeader.NumberOfSections; each++, pSection++)
{
printf("%d\t %-9s\t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X \t 0x%.8X \n",
each + 1, pSection->Name, pSection->VirtualAddress, pSection->Misc.VirtualSize,
pSection->PointerToRawData, pSection->SizeOfRawData, pSection->Characteristics);
}
}
else
{
printf("非標準程式 \n");
}
system("pause");
return 0;
}
運行上述程式,即可輸出當前程式中存在的節表信息,輸出效果如下圖所示;
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/17679067.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!