PE結構-節

首先,節的話有兩部分,一部分是節表,另外一部分就是節區了,節表是用於描述節區信息的,而節區呢,簡單點說就是數據塊。

在上一篇博客中有提及過如何定位節表,重點就是需要根據選項頭的大小來定位,這裏的話就直接上一點代碼吧,前兩篇概念較多

    PIMAGE_DOS_HEADER pDosHeader = GetDosHeader(); //獲取dos頭
    if (pDosHeader == NULL)
        return NULL;
    PIMAGE_FILE_HEADER pFileHeader = GetFileHeader(); //獲取文件頭
    if (pFileHeader == NULL)
        return NULL;
    //這裏爲 sizeof Signature + sizeof IMAGE_FILE_HEADER 的大小
    DWORD offset = (DWORD)&(((PIMAGE_NT_HEADERS)0)->OptionalHeader);
    //首地址 + e_lfanew + offset + SizeOfOptionalHeader
    PIMAGE_SECTION_HEADER section = (PIMAGE_SECTION_HEADER)(GetPeAddrBase() + pDosHeader->e_lfanew
                                        + offset + pFileHeader->SizeOfOptionalHeader);

OK,老規矩下面來探討一下節表的字段,把PE宏觀看成一塊一塊的數據,那麼在創建進程時,操作系統需要將此可執行文件按某種方式搬運過去,也就是如下圖所示

上面的圖片可能分佈不同均勻,畫功有限,根據其數據地址來觀察即可。觀察完圖片,那麼該如何描述文件數據搬運到內存呢?

這裏的話比較容易想到四個條件,文件塊首地址,文件塊大小,搬運到的內存首地址,內存塊大小

對的,上面的四個也就是節表中的四個屬性,下面來看一下結構

typedef 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;

這裏的話我們只需要關心上面說的四個描述搬運字段以及最後一個屬性字段即可,剩餘的字段是處於跨平臺設計考慮的,忽略即可

首先來看節名稱,這裏的節名稱類似於註釋的效果

再來看後兩個內存相關字段,首先Misc,這是個半說明性字段,在不影響分頁的情況下,可理性修改。後面是VirtualAddress,這裏是一個內存中的相對虛擬地址(RVA)

RVA:(Relative Virtual Address) , RVA只是內存中的一個相對於PE文件裝入地址的偏移位置,或者稱偏移量
目標地址 401000h - 裝入地址 400000h = RVA 1000h

爲什麼需要使用RVA呢?因爲ImageBase基址可能會變,所以使用RVA描述會好一些

然後下面就是文件相關的兩個字段了,SizeOfRawData,表示文件中的節區大小,後面的PointerToRawData表示文件地址

好了,下面先看看一段二進制來解析一下

我們來看.text段,該段描述的是從文件偏移處200的位置,拷貝到虛擬內存地址401000(ImageBase+1000)的地址處,那麼問題來了,拷貝多少呢?因爲內存描述的是實際大小0x26,而文件這邊是0x200。其實這裏我的理解是拷貝哪個都行,最好是按照多的來拷貝,因爲其實不管多還是少,有效數據都拷貝到內存了。

下面再來看一下.rdata段,該段描述的是從文件偏移400的位置,拷貝200字節到虛擬內存地址402000處,實際有效的字節數爲0xA2。

下面來手工模擬添加一個節,首先我們來看一下我們頭部是否夠添加一個節數據

這程序很不巧,剛好,第一個節從0x200開始,然後頭部數據剛剛好佔滿,也就是說連添加一個節信息的地方都不夠。

那麼我們來分析一下,因爲頭部佔了0x200,如果擴充頭部變成0x400,而內存對齊是0x1000,所以說假如文件擴充了頭部,其實在內存中應該是變化不大的,相對於原先是200的大小拷貝到1000內,現在是400的大小拷貝到1000內,所以還是在一個分頁內。如果超過了一個分頁,此時就需要慎重了,畢竟後面的數據和代碼處的地址都會跟隨變化。

OK,下面先幹擴充頭部的活,先將頭部擴充爲0x400

可以發現,之前0x200部分的數據現在已經到了0x400處,下面需要修正一些值,首先就是選項頭中的SizeOfHeaders大小了

下面還是就是修正節表,因爲頭部添加了0x200,所以所有的節表的文件起始位置都得加上0x200

OK修正完畢後就可以試試雙擊運行了,下面再來說添加節的事,添加一個節做如下四個步驟

1.添加節描述
2.添加節數據
3.修正 SizeOfImage - 選項頭中
4.修正 NumberOfSections - 文件頭中

先來添加節描述,這裏我們需要注意的是節和節之間不能重疊,並且需要連續(操作系統有檢查)

此時我們添加的新節的內存起始地址就是上一個節的內存頁終點(0x2000+0x1000),而文件的起始地址也就是上一節的文件塊終點(0x600+0x200)。

第二步,在末尾添加0x200的字節數據,並且將這200字節填充爲0x90,方便後面驗證,這裏就不截圖了

第三步修正SizeOfImage,因爲多了一個節映射,所以對應的內存空間應該也會多一頁(0x1000)

第四步,將NumberOfSections的數量+1

OK,修改完畢後就可跑跑看了,是否運行正確,我們可以使用OD打開程序來觀察一下

驗證數據是否加載正確

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章