1. 程式人生 > >PE檔案格式學習(五):資源表

PE檔案格式學習(五):資源表

1.概述

程式內部和外部的介面等元素的二進位制資料統稱為資源,程式把它們放在一個特定的表中,符合資料和程式分離的設計原則。

Windows程式中的資源大致分為六類:選單、對話方塊、點陣圖、游標、圖示、自定義資源

資源表是資料目錄表中的第三個元素,排在匯入表的後面。

2.資源表解析

資源表的解析比較複雜,可能是所有資料目錄表中最複雜的一個。我將結合例子說明,這樣會比較好理解。

我們的檔案在十六進位制工具010editor的幫助下,將PE中的結構全部標記了出來。資源表是資料目錄表的第三個,查詢到它的RVA是0x4000,轉換成offset還是0x4000,找到它的位置如下圖所示:

從0x4000開始就是資源表的結構體了,這個結構體叫IMAGE_RESOURCE_DIRECTORY,有如下欄位:

struct IMAGE_RESOURCE_DIRECTORY
{
    DWORD Characteristics;
    DWORD  TimeDateStamp;
    WORD MajorVersion;
    WORD  MinorVersion;
    WORD  NumberOfNamedEntries;
    WORD  NumberOfIdEntries;
}

Characteristics:屬性,一般為0,對應上圖中的0x00000000

TimeDateStamp:時間戳,一般為0(前面說過檔案頭也有這個欄位,匯出表、匯入表都有這個欄位,都有值,在這裡很多時間為0),對應上圖中的0x00000000

MajorVersion:資源的版本,一般為0,對應上圖中的0x0000

MinorVersion:一般為0,對應上圖中的0x0000

NumberOfNamedEntries:以字串命名的資源數量,對應上圖中的0x0000

NumberOfIdEntries:以整型數字命名的資源數量,對應上圖中的0x0003

上圖中NumberOfNamedEntries和NumberOfIdEntries的和是0x3,表明這個程式有3個資源專案,也就是說緊跟著IMAGE_RESOURCE_DIRECTORY結構體的有3個IMAGE_RESOURCE_DIRECTORY_ENTRY結構,這裡展示一下IMAGE_RESOURCE_DIRECTORY_ENTRY結構體:

struct IMAGE_RESOURCE_DIRECTORY_ENTRY
{
     DWORD Name;
     DWORD OffsetToData;
}

Name:對應上圖中的0x00000003(第一個IMAGE_RESOURCE_DIRECTORY_ENTRY),當它的最高位是1的時候,表示低位作為一個指標使用,指向IMAGE_RESOURCE_DIR_STRING_U結構;當它的最高位是0的時候,分為三種情況:當結構用於第1層目錄時定義的是資源型別,用於第2層目錄時定義的是資源ID,用於第3層目錄時定義的是資源語言型別(0x409表示是英語)。第1層-第3層是什麼意思呢?簡單的理解就是,資源表的解析分為三層,每一層都是IMAGE_RESOURCE_DIRECTORY+IMAGE_RESOURCE_DIRECTORY_ENTRY的形式,第二個欄位OffsetToData的最高位是1的時候就會指向下一層,每一層都是一樣的IMAGE_RESOURCE_DIRECTORY和IMAGE_RESOURCE_DIRECTORY_ENTRY的結構,大概是這個樣子,後面會更詳細的介紹,現在有個印象就好。IMAGE_RESOURCE_DIR_STRING_U的結構體如下:

struct IMAGE_RESOURCE_DIR_STRING_U
{
    WORD Length;
    WCHAR NameString;
}

這個資源的Name是0x00000003,最高位是0,並且它在第一層,也就是說0x00000003代表的是資源型別,那麼資源型別又有哪些呢?下表列出了一些型別,所以這個資源是圖示。

offsetToData:對應上圖的0x80000028,當它的最高位是1的時候,低位資料指向下一層的IMAGE_RESOURCE_DIRECTORY的起始地址,當它的最高位是0的時候,指標指向IMAGE_RESOURCE_DATA_ENTRY結構,這個結構後面會說,它是真正的資源資料的地址,一般情況下,分析到第三層的時候,offsetToData的最高位會是0。值得注意的是offsetToData作為指標的時候(0x80000028),28是從資源塊最開始的地方開始計算偏移的,它不是RVA。IMAGE_RESOURCE_DATA_ENTRY的結構體是:

IMAGE_RESOURCE_DATA_ENTRY STRUCT

    OffsetToData DWORD ? ; 資源資料的RVA
    Size1 DWORD ? ; 資源資料的長度
    CodePage DWORD ? ; 內碼表, 一般為0
    Reserved DWORD ? ; 保留欄位

IMAGE_RESOURCE_DATA_ENTRY ENDS

這個結構體,暫時先不用管它,等分析到第三層的時候再回來看。

先總結一下,我已經分析了第一層的IMAGE_RESOURCE_DIRECTORY,它從0x4000開始,佔據16個位元組,它的NumberOfNameEntries和NumberOfNameIds的總和是0x3,因此從0x4010開始有3個IMAGE_RESOURCE_DIRECTORY_ENTRY結構體,也就是3個資源,每個結構體佔8個位元組,所以從0x4010-0x4027是它的內容,這三個結構體的Name欄位的最高位都是0,所以它們代表的是型別,分別是0x03圖示、0x04選單、0x0E圖示組,它們的OffsetToData欄位的最高位是1,所以指向的位置是下一層。

接下來我們找到位置開始分析第二層的內容。

根據第一個資源的OffsetToData的低位位元組0x28,我們從0x4000往後數0x28個位元組,找到如下圖的內容,我們知道OffsetToData這時指向的是第二層的IMAGE_RESOURCE_DIRECTORY結構體。所以它同樣是16個位元組。

最後的NumberOfIdEntries為0x01,所以它的後面緊跟著一個IMAGE_RESOURCE_DIRECTORY_ENTRY,也就是如下圖中所示:

Name欄位的最高位是0,我們知道最高位是0,並且Name在第二層代表的是資源ID,所以這個資源的ID是0x01,OffsetToData最高位是1,所以它的最低位指向的是最後一層,也就是第三層。通過0x4000+0x70,我們找到0x4070,這裡就是第三層的IMAGE_RESOURCE_DIRECTORY。如下圖

我們看到NumberOfIdEntries為0x01,所以第三層也只有一個IMAGE_RESOURCE_DIRECTORY_ENTRY。如下圖:

這時我們看到它的第一個元素Name的最高位是0,在第三層Name最高位為0就代表資源語言型別,這時表示的資源語言型別就是0x804,OffsetToData的最高位是0,所以0xB8+0x4000處就是IMAGE_RESOURCE_DATA_ENTRY,如下圖:

我們再來回顧一下這個結構體:

IMAGE_RESOURCE_DATA_ENTRY STRUCT

    OffsetToData DWORD ? ; 資源資料的RVA
    Size1 DWORD ? ; 資源資料的長度
    CodePage DWORD ? ; 內碼表, 一般為0
    Reserved DWORD ? ; 保留欄位

IMAGE_RESOURCE_DATA_ENTRY ENDS

所以資源的RVA是0x4100,轉為offset仍然是0x4100,這個資源的起始位置就是這個0x4100,長度是0x2e8。

3.總結

我們還沒有碰到IMAGE_RESOURCE_DIRECTORY_ENTRY裡面的Name為1的情況,以後可能會補充。資源表的理論說起來很複雜,其實沒那麼複雜,只要寫一遍PE分析工具,在程式碼中可以更好地理解資源表。