手工構造一個超微型的 PE 文件

作者: 一塊三毛錢
郵箱: [email protected]
日期: 2003.12.18

最近構造了一個微型的 PE 文件,下面把構造的方法和一點心得寫出來和大家交流,也算是對 PE 格式的一個複習吧。

最終構造好的文件大小是 180 字節,可以在 Win2k 下運行,運行後會彈出一個消息框。下載

來看看最後生成的文件的內容:

00000000 4D 5A 00 00 50 45 00 00 4C 01 01 00 75 73 65 72 MZ..PE..L...user
00000010 33 32 2E 64 6C 6C 00 00 70 00 0F 01 0B 01 6A 00 32.dll..p.....j.
00000020 B8 8C 00 40 00 50 50 6A 00 EB 05 00 1E 00 00 00 [email protected]........
00000030 FF 15 78 00 40 00 C3 00 00 00 40 00 04 00 00 00 ..x.@.....@.....
00000040 04 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 ................
00000050 00 00 00 00 B4 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 02 00 00 00 00 00 10 00 00 00 00 00 00 00 10 00 ................
00000070 00 10 00 00 00 00 00 00 C4 01 00 80 00 00 00 00 ................
00000080 00 00 00 00 9C 00 00 00 28 00 00 00 5A 54 53 B1 ........(...ZTS.
00000090 E0 D0 B4 00 B4 00 00 00 00 00 00 00 B4 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 0C 00 00 00 78 00 00 00 ............x...
000000B0 E0 00 00 E0 ....

用 dumpbin 顯示文件結構如下:

FILE HEADER VALUES
             14C machine (i386)
               1 number of sections
        72657375 time date stamp Sat Oct 26 21:21:57 2030
        642E3233 file pointer to symbol table
            6C6C number of symbols
              70 size of optional header
             10F characteristics
                   Relocations stripped
                   Executable
                   Line numbers stripped
                   Symbols stripped
                   32 bit word machine

OPTIONAL HEADER VALUES
             10B magic #
          106.00 linker version
        40008CB8 size of code
        6A505000 size of initialized data
           5EB00 size of uninitialized data
              1E RVA of entry point             <----
          7815FF base of code
          C30040 base of data
          400000 image base
               4 section alignment
               4 file alignment
            4.00 operating system version
            0.00 image version
            4.00 subsystem version
               0 Win32 version
              B4 size of image
               0 size of headers
               0 checksum
               2 subsystem (Windows GUI)
               0 DLL characteristics
          100000 size of stack reserve
               0 size of stack commit
          100000 size of heap reserve
            1000 size of heap commit
               0 loader flags
        800001C4 number of directories
               0 [       0] RVA [size] of Export Directory
              9C [      28] RVA [size] of Import Directory      <----
               0 [       0] RVA [size] of Resource Directory
               0 [       0] RVA [size] of Exception Directory
               0 [       0] RVA [size] of Certificates Directory
               0 [       0] RVA [size] of Base Relocation Directory
               0 [       0] RVA [size] of Debug Directory
               0 [       0] RVA [size] of Architecture Directory
               0 [       0] RVA [size] of Special Directory
               0 [       0] RVA [size] of Thread Storage Directory
               0 [       0] RVA [size] of Load Configuration Directory
               0 [       0] RVA [size] of Bound Import Directory
               0 [       0] RVA [size] of Import Address Table Directory
               0 [       0] RVA [size] of Delay Import Directory
               0 [       0] RVA [size] of Reserved Directory
               0 [       0] RVA [size] of Reserved Directory

現在開始具體的步驟

1. Dos Header

IMAGE_DOS_HEADER STRUCT
        e_magic                 <-- 4D 5A
        ...                     <-- 其他的都填 0
        e_lfanew                <-- 04 00 00 00
IMAGE_DOS_HEADER ENDS
爲了把文件做得儘可能的小,所以 PE Header 準備放在文件偏移 4 的地方,本來還可以往前放,由於 Dos Header 的 e_lfanew 必須指向 PE Header 的偏移位置。當放在偏移4 的地方,Dos Header 的 e_lfanew 正好對應着 PE Header 的 SectionAlignment,我們只需要把 SectionAlignment 設爲 4 就可以達到兩個目的。
2. PE Header

IMAGE_NT_HEADERS STRUCT
        Signature                       <-- 50 45 00 00
        FileHeader
        OptionalHeader
IMAGE_NT_HEADERS ENDS
下面打了 * 標誌的意味着不能隨便填數據,具體的數據可以參考上面 dumpbin 顯示的數據。凡是沒有打 * 標誌的可以填入任意數據,我們的代碼就準備塞在這些結構裏面。
IMAGE_FILE_HEADER STRUCT
        Machine                         *
        NumberOfSections                *
        TimeDateStamp
        PointerToSymbolTable
        NumberOfSymbols
        SizeOfOptionalHeader            *
        Characteristics                 *
IMAGE_FILE_HEADER ENDS

IMAGE_OPTIONAL_HEADER32 STRUCT
        Magic                           *
        MajorLinkerVersion
        MinorLinkerVersion
        SizeOfCode
        SizeOfInitializedData
        SizeOfUninitializedData
        AddressOfEntryPoint             *
        BaseOfCode
        BaseOfData
        ImageBase                       *
        SectionAlignment                *
        FileAlignment                   *
        MajorOperatingSystemVersion     *
        MinorOperatingSystemVersion     *
        MajorImageVersion               *
        MinorImageVersion               *
        MajorSubsystemVersion           *
        MinorSubsystemVersion           *
        Win32VersionValue               *
        SizeOfImage                     *
        SizeOfHeaders                   *
        CheckSum
        Subsystem                       *
        DllCharacteristics              *
        SizeOfStackReserve              *
        SizeOfStackCommit               *
        SizeOfHeapReserve               *
        SizeOfHeapCommit                *
        LoaderFlags
        NumberOfRvaAndSizes             *
        DataDirectory
IMAGE_OPTIONAL_HEADER32 ENDS

對於 DataDirectory 中不需要的成員可以不要,只留下 Export Directory 和 Import Directory。

整個 PE Header 的大小爲 88h 字節,其中 Optional Header 的大小爲 70h 字節。

3. Section Table

IMAGE_SECTION_HEADER STRUCT
    Name1                               <-- ZTS 編寫
    union Misc
        PhysicalAddress
        VirtualSize                     <-- B4 00 00 00
    ends
    VirtualAddress                      <-- 00 00 00 00
    SizeOfRawData                       <-- B4 00 00 00
    PointerToRawData                    <-- 00 00 00 00
    PointerToRelocations                <-- 00 00 00 00
    PointerToLinenumbers                <-- 00 00 00 00
    NumberOfRelocations                 <-- 00 00
    NumberOfLinenumbers                 <-- 00 00
    Characteristics                     <-- E0 00 00 E0
IMAGE_SECTION_HEADER ENDS

整個文件的內容就是節的內容,最後文件的全部內容會被完整的映射到 400000h 的地址處。

因爲映射到內存中後文件的內容後面都是 0,所以相當於節表以一個全 0 元素結束。

4. Import

文件只需要從 user32.dll 中輸入一個函數 MessageBoxA,所以輸入表中有一個非 0 成員和一個結束的全 0 成員。就因爲要保證有一個全 0 成員來結束輸入表,所以也把輸入表放在文件的末尾,和節的情況一樣,當文件被映射到內存中後,文件後面的內容都是 0,就相當於有一個全 0 成員。

一個輸入表成員的大小是 20 字節,在節表當中找出沒有被利用的域用來放輸入表,找到了從 SizeOfRawData 開始的位置。輸入表中的 OriginalFirstThunk ,TimeDateStamp 和 ForwarderChain 都是沒用的域,不用管他們是什麼值,所以不會因爲在節表中插入輸入表而改變節表中有用的域:SizeOfRawData 和 PointerToRawData 。

還有的就是 Name 和 FirstThunk 啦,在文件中找到偏移 0Ch 的地方寫入 user32.dll,然後把 Name 指向偏移 0Ch,這個偏移就是文件頭中 TimeDateStamp 的偏移位置。在文件中再找到一個偏移位置 78h 來放 IAT,然後把 FirstThunk 指向偏移 78h,這個偏移是文件頭中NumberOfRvaAndSizes 的偏移位置。在上面雖然說了 NumberOfRvaAndSizes 域不能隨便填數據(打了 * 標誌),但這個域只要不填 2 以下的值就可以,所以我們可以利用。

填好的樣子如下:

00000070                         C4 01 00 80 00 00 00 00 ................
00000080
00000090                                     B4 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 0C 00 00 00 78 00 00 00 ............x...
爲了減少文件的大小,輸入 MessageBoxA 函數是通過序號的方式引入的。

手工寫好輸入表之後把輸入表的偏移和大小填到 DataDirectory 數組的 Import Directory 成員中去,偏移爲 9Ch,大小爲 28h。

5. 代碼

所有準備工作做完就開始寫代碼,代碼也需要從文件頭中間找沒用的域來存放。找找文件頭髮現還有兩個地方沒有被使用,一個是 MajorLinkerVersion 開始的 14 個字節,偏移爲 1Eh,另一個是 BaseOfCode 開始的 8 個字節,偏移爲 30h。

需要的代碼寫好就是下面的樣子:

0000001E: 6A00            push        0
00000020: B88C004000      mov         eax,40008C
00000025: 50              push        eax
00000026: 50              push        eax
00000027: 6A00            push        0
00000029: EB05            jmp         000000030

00000030: FF1578004000    call        dword ptr [00400078]
00000036: C3              ret
把代碼對應的 16 進制值填到偏移 1Eh 和 30h 處就行了。

保存文件,所有的工作就結束了。最後把注意事項再總結一下:

1. 如果 FileAlignment 小於 200h,則要求 FileAlignment == SectionAlignment >= 2

2. 如果 FileAlignment 小於 200h,則要求 VirtualAddress == PointerToRawData

3. VirtualSize <= SizeOfRawData

4. SizeOfHeaders < SizeOfImage

5. NumberOfRvaAndSizes >= 2 數據目錄結構的數量要求不小於 2

6. 節表和輸入表都要求有一個結束的全 0 成員

胡亂寫了一點,希望不會浪費大家太多時間,如果有錯誤還望各位大俠指點指點,也好讓象我這樣的菜鳥能多學一些東西。

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