PE節表詳細分析

HackLee發表於2021-11-06

PE節表詳細分析

0x00 前言

上一篇文章我們學習了PE結構中的PE頭,我之前說過PE檔案結構是PE頭->節表->每個節,所以我們掌握了節表後也就知道怎麼去獲取每個節了。(當然後面還有輸入表,輸出表這些比較重要的東西。這些知識在後面的文章詳細介紹。)

0x01 PE節表分析

節表結構

PE節表位於PE頭的下面,PE節表中記錄了各個節表的起始位置、大小,以及在記憶體中偏移位置和屬性。

其結構體如下:

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];//*[1]:節名
    union {
            DWORD   PhysicalAddress; 
            DWORD   VirtualSize;          //*[2]:對其前節大小,記憶體中節大小
    } Misc;//聯合體
    DWORD   VirtualAddress;               //*[3]:記憶體中偏移
    DWORD   SizeOfRawData;                //*[4]:檔案對其後節大小
    DWORD   PointerToRawData;             //*[5]:檔案中節位置
    DWORD   PointerToRelocations;//
    DWORD   PointerToLinenumbers;//
    WORD    NumberOfRelocations; //
    WORD    NumberOfLinenumbers; //
    DWORD   Characteristics;              //*[6]:節屬性的標誌
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

image-20211106161212671

節表數量

節表數量不在節表中,而是在上一篇文章中已經介紹過,在標準PE頭中。標準PE頭中有一個成員是NumberOfSections他是節表的數量。

image-20211106161056869

節表名字

節表名字是各個節表的第一個成員,首先我們根據PE頭大小來確定節表所在位置即DOS頭+垃圾資料+NT頭,下面在010Editor演示具體資料。

節表中第一個資料是節名,節名一共佔用的資料大小是8個位元組, 演示中第一個節名是.text

image-20211106162142952

節表大小

節表中真實的大小在VirtualAddress中,也就是節名後的4個位元組。他所代表的意思是對其前的大小也是在記憶體中的大小。

而在他後4四節之後的4位元組資料,代表的是對其後的大小也是檔案中的大小SizeOfRawData

image-20211106183020009

節位置

節位置都是在節大小之後,佔用大小也是為4位元組。VirualAddress後的4位元組代表的是記憶體中節偏移SizeOfRawData後的4位元組代表的是檔案中節位置

image-20211106183953593

在記憶體中鎖定具體的節位置需要記憶體地址+節偏移得到的地址才是真實的記憶體節所在的位置,而在檔案中的節位置則可以根據地址直接索引到,不需要+檔案地址因為檔案地址開始就是0。

image-20211106185117155

記憶體地址在可選PE頭的ImageBase中,所以我們需要先獲取到ImageBase的值這裡是0x00400000,所以加上偏移位置就是0x00401000

image-20211106184450035

image-20211106184804699

一般程式在沒有被修改的情況下,預設.text段就是程式用來放程式碼的地方。所以我們也可以用OD直接載入程式,然後跳到.text段的位置可以看到程式的彙編程式碼。

(小知識:OD載入程式就是模擬了程式拉伸的過程,程式在記憶體中的樣子。)

image-20211106185730814

節表屬性

節表中的最後一個資料也是最重要的,他代表了這個節是否可讀可寫可執行或者是否包含可執行程式碼、初始化、未初始化的資料。所以一般我們判斷一個段是否是程式碼段就是根據這個屬性的值來判斷的,因為節表名是可以改的,比如我把隨便一個節表名改成.text、.code。那你就覺得他是程式碼段了嗎?

程式碼段的屬性一般是0x60000020,其中這4位元組的資料,他每一位都對應下面表格中的資料。下面我們把0x60000020來拆分一下,首先最後2位對應下面的包含可執行程式碼的資料,然後最高位的6對應下面的(該塊可執行)+(該塊可讀)的值。所以程式碼段的屬性就是:(包括可執行程式碼)、(可執行)、(可讀)

--> 標誌(屬性塊) 常用特徵值對照表:<--
[值:00000020h](*包含可執行程式碼)
[值:00000040h](*該塊包含已初始化的資料)
[值:00000080h](*該塊包含未初始化的資料)
[值:00000200h][Section contains comments or some other type of information.]
[值:00000800h][Section contents will not become part of image.]
[值:00001000h][Section contents comdat.]
[值:00004000h][Reset speculative exceptions handling bits in the TLB entries for this section.]
[值:00008000h][Section content can be accessed relative to GP.]
[值:00500000h][Default alignment if no others are specified.]
[值:01000000h][Section contains extended relocations.]
[值:02000000h][Section can be discarded.]
[值:04000000h][Section is not cachable.]
[值:08000000h][Section is not pageable.]
[值:10000000h](*該塊為共享塊).
[值:20000000h](*該塊可執行)
[值:40000000h](*該塊可讀)
[值:80000000h](*該塊可寫)

0x02 程式碼編寫

在最後我們還是用程式碼來寫個工具,之前寫過解析PE頭的資料了,所以繼續解析就是節表的資料了。

節表解析整體思路是:

  • (1)、先得到節數量 NumberOfSections
  • (2)、迴圈次數=節數量
  • (3)、安裝節的結構體來解析每個節的資料
  • (4)、輸出相應的資料顯示到控制檯中
vector<IMAGE_SECTION_HEADER_2> vsection_header;
//解析節表
for (size_t i = 0; i < IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections; i++)
{
    IMAGE_SECTION_HEADER_2 aa;
    fread(&aa, sizeof(aa), 1, fp);
    vsection_header.push_back(aa);
}
//列印節中的資料
printf("---------------節表資料----------------\n");
cout << "節數量:" << IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections << endl;
for (size_t i = 0; i < IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections; i++)
{
    printf("--> %s段資訊 <--\n", vsection_header[i].Name);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
    printf("*[記憶體中段大小]:0x%x\n", vsection_header[i].Misc.VirtualSize);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
    printf("*[記憶體中偏移]:0x%x\n", vsection_header[i].VirtualAddress);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
    printf("*[檔案中段大小]:0x%x\n", vsection_header[i].SizeOfRawData);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_BLUE);
    printf("*[檔案中偏移]:0x%x\n", vsection_header[i].PointerToRawData);
    SetConsoleTextAttribute(handle, 0x07);
    printf("[OBJ重定位偏移]:0x%x\n", vsection_header[i].PointerToRelocations);
    printf("[OBJ重定位項數目]:0x%x\n", vsection_header[i].NumberOfRelocations);
    printf("[行號表偏移]:0x%x\n", vsection_header[i].PointerToLinenumbers);
    printf("[行號表中的數目]:0x%x\n", vsection_header[i].NumberOfLinenumbers);
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
    printf("*[標誌|屬性]:0x%x ", vsection_header[i].Characteristics);
    //區段的屬性
    DWORD l_Charctieristics = (BYTE)((DWORD)(vsection_header[i].Characteristics) & 0xFF);
    DWORD h_Charctieristics = (BYTE)(((DWORD)(vsection_header[i].Characteristics) >> 24) & 0xFF);

    vector<byte> l_flag;
    vector<byte> h_flag;
    //低位
    l_flag.push_back((l_Charctieristics >> 7) ? 3 : 0);
    l_flag.push_back((l_Charctieristics >> 6) & 1 ? 2 : 0);
    l_flag.push_back((l_Charctieristics >> 5) & 1 ? 1 : 0);
    //高位
    h_flag.push_back((h_Charctieristics >> 7) ? 7 : 0);
    h_flag.push_back((h_Charctieristics >> 6) & 1 ? 6 : 0);
    h_flag.push_back((h_Charctieristics >> 5) & 1 ? 5 : 0);
    h_flag.push_back((h_Charctieristics >> 4) & 1 ? 4 : 0);

    //包含資料情況
    SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
    for (vector<byte>::iterator iter = l_flag.begin(); iter != l_flag.end(); iter++)
    {
        switch (*iter)
        {
            case 1:
                cout << "(包含可執行程式碼),";
                break;
            case 2:
                cout << "(包含已初始化資料),";
                break;
            case 3:
                cout << "(包含未初始化資料),";
                break;
            default:
                break;
        }
    }
    //可讀寫執行情況
    for (vector<byte>::iterator iter = h_flag.begin(); iter != h_flag.end(); iter++)
    {
        switch (*iter)
        {
            case 4:
                cout << "(共享),";
                break;
            case 5:
                cout << "(可執行),";
                break;
            case 6:
                cout << "(可讀),";
                break;
            case 7:
                cout << "(可寫),";
                break;
            default:
                break;
        }
    }
    cout << endl << endl;;

    SetConsoleTextAttribute(handle, 0x07);
}
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
printf("--> 標誌(屬性塊) 常用特徵值對照表:<--\n");
printf("[值:00000020h](*包含可執行程式碼)\n");//IMAGE_SCN_CNT_CODE
printf("[值:00000040h](*該塊包含已初始化的資料)\n");//IMAGE_SCN_CNT_INITIALIZED_DATA
printf("[值:00000080h](*該塊包含未初始化的資料)\n");//IMAGE_SCN_CNT_UNINITIALIZED_DATA
printf("[值:00000200h][Section contains comments or some other type of information.]\n");//IMAGE_SCN_LNK_INFO
printf("[值:00000800h][Section contents will not become part of image.]\n");//IMAGE_SCN_LNK_REMOVE
printf("[值:00001000h][Section contents comdat.]\n");//IMAGE_SCN_LNK_COMDAT
printf("[值:00004000h][Reset speculative exceptions handling bits in the TLB entries for this section.]\n");//IMAGE_SCN_NO_DEFER_SPEC_EXC
printf("[值:00008000h][Section content can be accessed relative to GP.]\n");// IMAGE_SCN_GPREL
printf("[值:00500000h][Default alignment if no others are specified.]\n");//IMAGE_SCN_ALIGN_16BYTES  
printf("[值:01000000h][Section contains extended relocations.]\n");//IMAGE_SCN_LNK_NRELOC_OVFL
printf("[值:02000000h][Section can be discarded.]\n");//IMAGE_SCN_MEM_DISCARDABLE
printf("[值:04000000h][Section is not cachable.]\n");//IMAGE_SCN_MEM_NOT_CACHED
printf("[值:08000000h][Section is not pageable.]\n");//IMAGE_SCN_MEM_NOT_PAGED
printf("[值:10000000h](*該塊為共享塊).\n");//IMAGE_SCN_MEM_SHARED
printf("[值:20000000h](*該塊可執行)\n");//IMAGE_SCN_MEM_EXECUTE
printf("[值:40000000h](*該塊可讀)\n");//IMAGE_SCN_MEM_READ
printf("[值:80000000h](*該塊可寫)\n\n");// IMAGE_SCN_MEM_WRITE
SetConsoleTextAttribute(handle, 0x07);//IMAGE_SCN_MEM_WRITE

執行結果:

image-20211106193525571

最後歡迎加群:

Pwn菜雞學習小分隊群聊二維碼

相關文章