免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書(shū)等14項(xiàng)超值服

開(kāi)通VIP
手工打造可執(zhí)行程序
   我們這里將不依賴(lài)任何編譯器,僅僅使用一個(gè)十六進(jìn)制編輯器逐個(gè)字節(jié)的手工編寫(xiě)一個(gè)可執(zhí)行程序。以這種方式講解PE結(jié)構(gòu),通過(guò)這個(gè)過(guò)程讀者可以學(xué)習(xí)PE結(jié)構(gòu)中的PE頭、節(jié)表以及導(dǎo)入表相關(guān)方面的知識(shí)。
    為了簡(jiǎn)單而又令所有學(xué)習(xí)程序開(kāi)發(fā)的人感到親切,我們將完成一個(gè)Hello World! 程序。功能僅僅是運(yùn)行后彈出一個(gè)消息框,消息框的內(nèi)容是Hello World!。
首先了解一下Win32可執(zhí)行程序的大體結(jié)構(gòu),就是通常所說(shuō)的PE結(jié)構(gòu)。
如圖1所示PE結(jié)構(gòu)示意圖:


                             圖1 標(biāo)準(zhǔn)PE結(jié)構(gòu)圖

    由圖中可以看出PE結(jié)構(gòu)分為幾個(gè)部分:
   MS-DOS MZ 頭部:所有PE文件必須以一個(gè)簡(jiǎn)單的DOS MZ 頭開(kāi)始。有了它,一旦程序在DOS下執(zhí)行,DOS就能識(shí)別出這是有效的執(zhí)行體,然后運(yùn)行緊隨MZ header 之后的DOS程序。以此達(dá)到對(duì)Dos系統(tǒng)的兼容。(通常情況DOS MZ header總共占用64byte)。
  MS-DOS 實(shí)模式殘余程序:實(shí)際上是個(gè)有效的EXE,在不支持PE文件格式的操作系統(tǒng)中,它將簡(jiǎn)單顯示一個(gè)錯(cuò)誤提示,大多數(shù)情況下它是由匯編編譯器自動(dòng)生成。通常,它簡(jiǎn)單調(diào)用中斷21h,服務(wù)9來(lái)顯示字符串"This program cannot run in DOS mode"。(在我們寫(xiě)的程序中,他不是必須的,可以不予以實(shí)現(xiàn),但是要保留其大小,大小為112byte,為了簡(jiǎn)潔,可以使用00來(lái)填充。)
  PE文件標(biāo)志:是PE文件結(jié)構(gòu)的起始標(biāo)志。(長(zhǎng)度4byte, Windows程序此值必須為0x50450000)
  PE文件頭:是PE相關(guān)結(jié)構(gòu) IMAGE_NT_HEADERS 的簡(jiǎn)稱(chēng),其中包含了許多PE裝載器用到的重要域。執(zhí)行體在支持PE文件結(jié)構(gòu)的操作系統(tǒng)中執(zhí)行時(shí),PE裝載器將從DOS MZ header中找到PE header的起始偏移量,跳過(guò)了MS-DOS 實(shí)模式殘余程序 ,直接定位到真正的文件頭PE header,長(zhǎng)度20byte。
  PE文件可選頭:雖然它的名字是“可選頭部”,但是請(qǐng)確信:這個(gè)頭部并非“可選”,而是“必需”的。(長(zhǎng)度 224byte )。
  各段頭部:又稱(chēng)節(jié)頭部,一個(gè)Windows NT的應(yīng)用程序典型地?fù)碛?個(gè)預(yù)定義段(節(jié)),它們是“.text”、“.bss”、“.rdata”、“.data”、“.rsrc”、“.edata”、“.idata”、“.pdata”和“.debug”。一些應(yīng)用程序不需要所有的這些段,同樣還有些應(yīng)用程序?yàn)榱俗约禾厥獾男枰x了更多的段。(每個(gè)段頭部占40byte,我們這里也不需要所有的段,僅需3個(gè)段。)
    通常我們是將PE整個(gè)結(jié)構(gòu)分成四個(gè)部分,把MS-DOS MZ 頭部和MS-DOS 實(shí)模式殘余程序作為第一部分,可以稱(chēng)他為DOS部分,而PE文件標(biāo)志、PE文件頭、PE文件可選頭三個(gè)部分作為第二部分,稱(chēng)之為PE頭部分,因?yàn)檫@部分才是Windows下真正需要的部分,所以從PE文件標(biāo)志開(kāi)始才是真正的PE部分。各段頭部是第三部分,稱(chēng)之為節(jié)表。它詳細(xì)描述了PE文件中各個(gè)節(jié)的詳細(xì)信息。最后就是各個(gè)節(jié)的實(shí)體部分了,稱(chēng)為節(jié)數(shù)據(jù)。
    以上僅僅是對(duì)PE結(jié)構(gòu)各部分的大體講解。接下來(lái)再手寫(xiě)這個(gè)Hello World!程序過(guò)程中,我將詳細(xì)介紹每個(gè)部分的含義。
    首先準(zhǔn)備一下工具,一個(gè)十六進(jìn)制編輯器足以。我們這里使用VC++ 6.0所攜帶的十六進(jìn)制編輯器,您也可以使用如WinHex等十六進(jìn)制編輯工具。
    打開(kāi)VC,選擇文件,新建菜單項(xiàng),然后選擇一個(gè)二進(jìn)制文件,單擊確定。一切就緒了,下面就開(kāi)始手寫(xiě)可執(zhí)行程序,如圖2所示:

 

                               圖2 VC6.0下的十六進(jìn)制編輯器

    首先來(lái)完成“DOS MZ header”部分?!癉OS MZ header”的功能前面已經(jīng)講過(guò),在這里不再重述,直接實(shí)現(xiàn)他?!癉OS MZ header”總共64byte,他對(duì)應(yīng)的結(jié)構(gòu)是IMAGE_DOS_HEADER ,在WINNT.H文件中有定義。通過(guò)這個(gè)結(jié)構(gòu)我們可以看到,這64字節(jié)被分成19個(gè)成員,每個(gè)成員都有特殊的含義,與其說(shuō)我們是在逐字節(jié)的手寫(xiě)可執(zhí)行程序,倒不如說(shuō)我們是在逐個(gè)成員的寫(xiě)。因?yàn)閱为?dú)的一個(gè)字節(jié)并不一定具有什么意義。我們?cè)趯W(xué)習(xí)過(guò)程中,就是要按照官方的定義,將整個(gè)部分拆分成若干個(gè)成員,然后逐個(gè)成員的去學(xué)習(xí)。
 (提示: 如果安裝有VC開(kāi)發(fā)環(huán)境,那么在其安裝目錄下有一個(gè)頭文件WINNT.H,在這個(gè)頭文件中定義了所有PE結(jié)構(gòu)相關(guān)的各部分結(jié)構(gòu)體。如圖3所示:)  


             圖3 VC安裝目錄下的WINNT.H頭文件


使用VC開(kāi)發(fā)環(huán)境打開(kāi)此文件,然后按快捷鍵Ctrl+F輸入IMAGE_DOS_HEADER進(jìn)行搜索,如圖4所示:

  

                               圖4 VC下查找文件

單擊Find Next按鈕即可得到如下搜索結(jié)果,如圖5所示:  
  
   

                              圖5 

可以看出IMAGE_DOS_HEADER,結(jié)構(gòu)體的定義如下:
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
      WORD   e_magic;                     // Magic number
      WORD   e_cblp;                      // Bytes on last page of file
      WORD   e_cp;                        // Pages in file
      WORD   e_crlc;                      // Relocations
      WORD   e_cparhdr;                   // Size of header in paragraphs
      WORD   e_minalloc;                  // Minimum extra paragraphs needed
      WORD   e_maxalloc;                  // Maximum extra paragraphs needed
      WORD   e_ss;                        // Initial (relative) SS value
      WORD   e_sp;                        // Initial SP value
      WORD   e_csum;                      // Checksum
      WORD   e_ip;                        // Initial IP value
      WORD   e_cs;                        // Initial (relative) CS value
      WORD   e_lfarlc;                    // File address of relocation table
      WORD   e_ovno;                      // Overlay number
      WORD   e_res[4];                    // Reserved words
      WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
      WORD   e_oeminfo;                   // OEM information; e_oemid specific
      WORD   e_res2[10];                  // Reserved words
      LONG   e_lfanew;                    // File address of new exe header
    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    按照它的定義,我們分別完成各個(gè)成員。
   第一個(gè)成員(e_magic)是個(gè)WORD類(lèi)型,占2個(gè)字節(jié),它被用于表示一個(gè)MS-DOS兼容的文件類(lèi)型,他的值是固定的0x5A4D,所以在十六進(jìn)制編輯器中輸入“4D5A”。
 (注意:
因?yàn)槲覀兪窃谑M(jìn)制編輯器下寫(xiě)數(shù)據(jù),所以所有的數(shù)據(jù)格式都是十六進(jìn)制式的。但是我們?cè)陂_(kāi)發(fā)環(huán)境中通常在數(shù)據(jù)前添加“0x”用來(lái)表示十六進(jìn)制數(shù) ,即:0x5A4D。而在十六進(jìn)制編輯器中,直接寫(xiě)成“4D5A”即可。后面內(nèi)容都照此規(guī)定書(shū)寫(xiě)。有一點(diǎn)需要說(shuō)明,為什么十六進(jìn)制值0x5A4D輸入到十六進(jìn)制編輯器中是4D5A呢?這是因?yàn)橐粋€(gè)內(nèi)存值,無(wú)論是占兩個(gè)字節(jié)的WORD類(lèi)型,還是占四個(gè)字節(jié)的DWORD類(lèi)型等,如同我們學(xué)習(xí)數(shù)學(xué)中的十進(jìn)制數(shù)值一樣,都是有高低位之分的,從右向左位越來(lái)越高。然而在十六進(jìn)制編輯器中,十六進(jìn)制位是自左向右依次增高。因此按照高低位對(duì)齊的原則,值0x5A4D中,低位0x4D應(yīng)該應(yīng)該放到左邊,0x5A應(yīng)該放到右邊。也就得到了編輯器中的4D5A。)

     第2個(gè)成員到第18個(gè)成員總共58個(gè)字節(jié),是對(duì)DOS程序環(huán)境的初始化等操作,對(duì)于我們這個(gè)程序來(lái)說(shuō),沒(méi)什么影響,我們通通用“00”來(lái)填充。(如果您想對(duì)其進(jìn)行詳細(xì)了解,請(qǐng)查閱相關(guān)書(shū)籍。)
 (提示:
我們?cè)诖瞬豢赡馨裀E結(jié)構(gòu)所有的知識(shí)點(diǎn)都面面俱到,因?yàn)樗值凝嫶?。?dāng)然也沒(méi)有必要對(duì)他作完全徹底的掌握,只需掌握關(guān)鍵的地方就可以了。以后我們都將把不影響程序執(zhí)行的成員填充為零,這樣做,一方面使程序看起來(lái)簡(jiǎn)潔,另一方面可以使您快速定位PE結(jié)構(gòu)中要重點(diǎn)掌握的地方。)

    第19(e_lfanew)個(gè)成員非常重要,他是一個(gè)LONG類(lèi)型,占4個(gè)字節(jié),用來(lái)表示“PE文件標(biāo)志”在文件中的偏移,單位是byte。而從圖5-1中可以看到“PE文件標(biāo)志”緊隨“MS-DOS 實(shí)模式殘余程序”其后。知道這一點(diǎn),我們就可以計(jì)算一下,我們的“DOS MZ header”總共64 byte,后面的“MS-DOS 實(shí)模式殘余程序”占112 byte, 64 + 112 = 176 byte。但是要注意,我們這里的176是十進(jìn)制的,轉(zhuǎn)化成十六進(jìn)制是0xB0。因?yàn)槭?個(gè)字節(jié),其余三位字節(jié)應(yīng)該以00補(bǔ)齊,所以最終的值為0x000000B0。所以在我們的十六進(jìn)制編輯器中按照高低對(duì)齊的原則應(yīng)該填寫(xiě)“B0000000”。
    接下來(lái)完成“MS-DOS 實(shí)模式殘余程序”,筆者已經(jīng)介紹,他是用在DOS下執(zhí)行的,而我們所完成的HelloWorld程序是在win32下執(zhí)行的。所以這里的內(nèi)容并不影響我們程序的執(zhí)行。因此這里直接用“00”來(lái)填充,注意總共112 byte。 這兩部分完成之后代碼如圖6所示:  
  
 

              圖6 完成PE結(jié)構(gòu)中Dos部分的編寫(xiě)

    接下來(lái)便進(jìn)入真正主題,開(kāi)始寫(xiě)真正的PE結(jié)構(gòu)部分:微軟將“PE文件標(biāo)志”,“PE文件頭 ”,“PE文件可選頭 ”這三個(gè)部分用一個(gè)結(jié)構(gòu)來(lái)定義,即:IMAGE_NT_HEADERS32在WINNT.H中可以搜索其定義,定義如下:
  typedef struct _IMAGE_NT_HEADERS {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER32 OptionalHeader;
  } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
      可以看出這個(gè)結(jié)構(gòu)含有3個(gè)成員:
    第一個(gè)成員(Signature)表示“PE文件標(biāo)志”,是一個(gè)DWORD類(lèi)型,占4個(gè)字節(jié),它是PE開(kāi)始的標(biāo)記,對(duì)于Windows程序這個(gè)值必須為0x00004550,所以編輯器中填寫(xiě)“50450000”。
    第二個(gè)成員(FileHeader)表示“PE文件頭 ”,他的類(lèi)型是一個(gè)IMAGE_FILE_HEADER的結(jié)構(gòu)。也就是說(shuō)“PE文件頭”的20個(gè)字節(jié)被定義為IMAGE_FILE_HEADER結(jié)構(gòu),定義如下:
  typedef struct _IMAGE_FILE_HEADER {
      WORD    Machine;
      WORD    NumberOfSections;
      DWORD   TimeDateStamp;
      DWORD   PointerToSymbolTable;
      DWORD   NumberOfSymbols;
      WORD    SizeOfOptionalHeader;
      WORD    Characteristics;
  } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
這個(gè)結(jié)構(gòu)具有7個(gè)成員:
        成員1(Machine),占2個(gè)字節(jié),表示PE文件運(yùn)行所要求的CPU。對(duì)于Intel平臺(tái),該值是0x014C,所以編輯器中應(yīng)該填寫(xiě)“4C01”。
        成員2(NumberOfSections),占2個(gè)字節(jié),表示PE文件中段(節(jié))的總數(shù),在我們這個(gè)程序中,計(jì)劃完成3個(gè)段,(.text(代碼段)、.rdata(只讀數(shù)據(jù)段)、.data(全局變量數(shù)據(jù)段))。所以此處值是0x0003,因此填寫(xiě)“0300”。
        成員3(TimeDateStamp),占4個(gè)字節(jié),表示文件創(chuàng)建日期和時(shí)間,從1970.1.1 00:00:00以來(lái)的秒數(shù),我們這里填“0000”即可。
        成員4(PointerToSymbolTable),占4個(gè)字節(jié),表示符號(hào)表的指針,主要用于調(diào)試,在這里填“0000”。
        成員5(NumberOfSymbols),占4個(gè)字節(jié),表示符號(hào)的數(shù)目,主要用于調(diào)試,在這里填“0000”。
        成員6(SizeOfOptionalHeader),占2個(gè)字節(jié),表示后面的“PE文件可選頭 ”部分所占空間大小,我們已經(jīng)知道“PE文件可選頭 ”的大小是224 byte,轉(zhuǎn)換成十六進(jìn)制就是0xE0,此成員占兩個(gè)字節(jié),所以需要補(bǔ)齊一位00,即0x00E0。在編輯器中應(yīng)該填寫(xiě)“E000”。
        成員7(Characteristics),占2個(gè)字節(jié),表示關(guān)于文件信息的標(biāo)記,比如文件是exe還是dll。這個(gè)值實(shí)際上是二進(jìn)制位進(jìn)行或運(yùn)算得到的值。各二進(jìn)制位表示的意義如下:
    Bit 0 :置1表示文件中沒(méi)有重定向信息。每個(gè)段都有它們自己的重定向信息。這個(gè)標(biāo)志在可執(zhí)行文件中沒(méi)有使用,在可執(zhí)行文件中是用一個(gè)叫做基址重定向目錄表來(lái)表示重定向信息的。
    Bit 1 :置1表示該文件是可執(zhí)行文件。
    Bit 2 :置1表示沒(méi)有行數(shù)信息;在可執(zhí)行文件中沒(méi)有使用。
    Bit 3 :置1表示沒(méi)有局部符號(hào)信息;在可執(zhí)行文件中沒(méi)有使用。
    Bit 4 :未公開(kāi)
    Bit 7 :未公開(kāi)
    Bit 8 :表示希望機(jī)器為32位機(jī)。這個(gè)值永遠(yuǎn)為1。
    Bit 9 :表示沒(méi)有調(diào)試信息,在可執(zhí)行文件中沒(méi)有使用。
    Bit 10:置1表示該程序不能運(yùn)行于可移動(dòng)介質(zhì)中(如軟驅(qū)或CD-ROM)。在這種情況下,OS必須把文件拷貝到交換文件中執(zhí)行。
   Bit 11:置1表示程序不能在網(wǎng)上運(yùn)行。在這種情況下,OS必須把文件拷貝到交換文件中執(zhí)行。
   Bit 12:置1表示文件是一個(gè)系統(tǒng)文件例如驅(qū)動(dòng)程序。在可執(zhí)行文件中沒(méi)有使用。
   Bit 13:置1表示文件是一個(gè)動(dòng)態(tài)鏈接庫(kù)(DLL)。
   Bit 14:表示文件被設(shè)計(jì)成不能運(yùn)行于多處理器系統(tǒng)中。
   Bit 15:表示文件的字節(jié)順序如果不是機(jī)器所期望的,那么在讀出之前要進(jìn)行交換。在可執(zhí)行文件中它們是不可信的(操作系統(tǒng)期望按正確的字節(jié)順序執(zhí)行程序)。
對(duì)于我們的程序,因?yàn)樗强蓤?zhí)行程序,所以Bit 1必須置為1,其他位按照需要置位即可。在我們的程序中只需將第二位置1表示是可執(zhí)行程序。也就得到二進(jìn)制值“0000000000000010”,將其轉(zhuǎn)換為十六進(jìn)制形式為0x02,而該成員占兩個(gè)字節(jié),補(bǔ)齊一位00由此得到成員7的值為0x0002。因此在編輯器中填寫(xiě)“0200”。如果是dll,那么得到的二進(jìn)制值應(yīng)該是“0010000000000000”,轉(zhuǎn)換成十六進(jìn)制為0x2000。如果填寫(xiě)編輯器中應(yīng)該填寫(xiě)“0020”。
     第三個(gè)成員(OptionalHeader),表示“PE文件可選頭”,他的類(lèi)型是一個(gè)IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)。也就是說(shuō)PE文件頭的224個(gè)字節(jié)被定義為IMAGE_OPTIONAL_HEADER32結(jié)構(gòu),其結(jié)構(gòu)定義如下:
  typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //
    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
    //
    // NT additional fields.
    //
    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
  } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
該結(jié)構(gòu)總共具有31個(gè)成員,我們分別實(shí)現(xiàn)它:
        成員1(Magic),占2個(gè)字節(jié),表示文件的格式,值為0x010B表示.EXE文件,為0x0107表示ROM映像,因?yàn)槲覀儗?xiě)的是一個(gè)可執(zhí)行程序,所以此處應(yīng)該填寫(xiě)“0B01”。
        成員2(MajorLinkerVersion),占1個(gè)字節(jié),表示鏈接器的主版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00”。
        成員3(MinorLinkerVersion),占1個(gè)字節(jié),表示鏈接器的幅版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00”。
        成員4(SizeOfCode),占4個(gè)字節(jié),表示可執(zhí)行代碼的長(zhǎng)度,此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00000000”。
        成員5(SizeOfInitializedData),占4個(gè)字節(jié),表示初始化數(shù)據(jù)的長(zhǎng)度(數(shù)據(jù)段)。此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00000000”。
        成員6(SizeOfUninitializedData),占4個(gè)字節(jié),表示未初始化數(shù)據(jù)的長(zhǎng)度(bss段)。此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00000000”。
 (說(shuō)明:
在介紹成員7之前,有必要了解一個(gè)很重要的知識(shí)------文件映射到內(nèi)存。在可執(zhí)行程序運(yùn)行之前,PE加載器將把PE文件加載到進(jìn)程空間的內(nèi)存中去,并且初始化每個(gè)段實(shí)體。那么加載到內(nèi)存中的哪個(gè)地址去呢?這將由IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)的成員10的值指出加載的起始地址(又叫基地址)。這個(gè)值通常是“00400000”, 那么PE文件的首地址“00000”就被映射到內(nèi)存地址“00400000”處,那么相對(duì)于文件偏移10個(gè)字節(jié)的地址為“00010”,被映射到內(nèi)存后的偏移也應(yīng)該是10個(gè)字節(jié),映射后的地址應(yīng)該為“00400010”。PE加載器就是按照此種方法將文件映射到內(nèi)存中的。)

        成員7(AddressOfEntryPoint),4個(gè)字節(jié),表示代碼入口的RVA地址。
 (說(shuō)明:
RVA是指PE加載器將文件映射到內(nèi)存后,某個(gè)物理地址距離加載基址的偏移地址。)

    所謂代碼的入口是指程序從這兒開(kāi)始執(zhí)行。成員7實(shí)際上是PE裝載器準(zhǔn)備運(yùn)行的PE文件中的第一條指令的RVA值。若您要改變整個(gè)程序執(zhí)行的流程,可以將該值指定到新的RVA,這樣新RVA處的指令首先被執(zhí)行。
知道成員7的含義后,我們又如何來(lái)填充它呢?如何得知我們的程序?qū)⑹褂媚膫€(gè)地址作為入口呢?前面已經(jīng)提到,一般在PE文件中總會(huì)有個(gè).text段,這個(gè)段通常是用來(lái)填寫(xiě)代碼的。按照一般規(guī)律,我們也將實(shí)現(xiàn)這么一個(gè)段,將我們這個(gè)程序中的所有代碼指令寫(xiě)到此段中。我們?cè)谕瓿纱顺绦虻拇a時(shí),是從.text段起始地址開(kāi)始寫(xiě)起。所以.text段的起始地址就將是我們程序的入口地址。那么又出現(xiàn)另外一個(gè)問(wèn)題,如何得到.text段的起始地址呢?在PE結(jié)構(gòu)中,所有段都對(duì)應(yīng)有一個(gè)段頭部,而在段頭部中將指定該段的起始地址。那么這個(gè)值要等待我們完成.text頭部后才能夠得到,所以此處首先用“aaaaaaaa”填寫(xiě),待完成.text段頭部后再計(jì)算填寫(xiě)它。
        成員8(BaseOfCode),4個(gè)字節(jié),表示可執(zhí)行代碼起始位置。當(dāng)然就是.text段的首地址,此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00000000”。
        成員9(BaseOfData),4個(gè)字節(jié),表示初始化數(shù)據(jù)的起始位置,此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“00000000”。
        成員10(ImageBase),4個(gè)字節(jié),就是上面所講的文件映射到內(nèi)存后的基地址。PE文件的優(yōu)先裝載地址。通常為0x00400000。因?yàn)镻E裝載器默認(rèn)情況下優(yōu)先將嘗試把文件裝到虛擬地址空間的0x00400000處。字眼“優(yōu)先”表示若該地址區(qū)域已被其他模塊占用,那PE裝載器會(huì)選用其他空閑地址。我們這里的值設(shè)為“00004000”。
        成員11(SectionAlignment),4個(gè)字節(jié),表示段加載后在內(nèi)存中的對(duì)齊方式,即內(nèi)存中節(jié)對(duì)齊的粒度。例如,如果該值是4096 (1000h),那么每節(jié)的起始地址必須是4096的倍數(shù)。若第一節(jié)從401000h開(kāi)始,大小是10個(gè)字節(jié),下一個(gè)節(jié)并不是從401011開(kāi)始,因?yàn)橐?jīng)過(guò)節(jié)對(duì)齊,那么下一節(jié)必定從402000h開(kāi)始,即使401000h和402000h之間還有很多空間沒(méi)被使用。因?yàn)閃indows管理內(nèi)存采用分頁(yè)管理的方式,而每頁(yè)的大小為4k,也就是1000h。一般情況下程序的內(nèi)存節(jié)對(duì)齊粒度都為0x00001000,我們這個(gè)值也填充為“00100000”。
        成員12(FileAlignment),4個(gè)字節(jié),表示段在文件中的對(duì)齊方式。文件中節(jié)對(duì)齊的粒度。例如,如果該值是(200h),,那么每節(jié)的起始地址必須是512(十六進(jìn)制為200h)的倍數(shù)。若第一節(jié)從文件偏移量200h開(kāi)始且大小是10個(gè)字節(jié),則下一節(jié)必定位于偏移量400h處。即使偏移量512和1024之間還有很多空間沒(méi)被使用。一般情況下程序的文件節(jié)對(duì)齊粒度都為200h,所以我們?cè)诖藢⒋酥翟O(shè)為“00020000”。
        成員13(MajorOperatingSystemVersion),2個(gè)字節(jié),表示操作系統(tǒng)主版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“0000”。
        成員14(MinorOperatingSystemVersion),2個(gè)字節(jié),表示操作系統(tǒng)副版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“0000”。
        成員15(MajorImageVersion),2個(gè)字節(jié),表示程序主版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“0000”。
        成員16(MinorImageVersion),2個(gè)字節(jié),表示程序副版本號(hào),此值不會(huì)影響程序的執(zhí)行,我們這里填充零,此值為“0000”。
        成員17(MajorSubsystemVersion),2個(gè)字節(jié),表示子系統(tǒng)主版本號(hào)。win32子系統(tǒng)版本。PE文件是專(zhuān)門(mén)為Win32設(shè)計(jì)的,該子系統(tǒng)版本必定是4.0,那么此處值為“04”。
        成員18(MinorSubsystemVersion),2個(gè)字節(jié),表示子系統(tǒng)副版本號(hào),根據(jù)上面所說(shuō),此值應(yīng)為“00”。
        成員19(Win32VersionValue),2個(gè)字節(jié),此值一般為“00”。
        成員20(SizeOfImage),4個(gè)字節(jié),表示程序載入內(nèi)存后占用內(nèi)存的大?。▎挝蛔止?jié)),即等于所有段的長(zhǎng)度之和---------所有頭和節(jié)經(jīng)過(guò)節(jié)對(duì)齊處理后的大小。我們知道,我們文件PE結(jié)構(gòu)總長(zhǎng)小于1000h,但是內(nèi)存中的對(duì)齊粒度是1000h,所以PE結(jié)構(gòu)被映射后要占1000h,盡管很多空間沒(méi)有使用,另外我們有3個(gè)段,每個(gè)段的長(zhǎng)度小于1000h,但是被映射后同樣要占1000h,所以總共占用內(nèi)存的大小為1000h + 3 * 1000h = 4000h,因此此值為“00400000”。
        成員21(SizeOfHeaders),4個(gè)字節(jié),表示所有文件頭的長(zhǎng)度之和(從文件開(kāi)始到第一個(gè)段之間的大小)。所有頭即PE頭加所有節(jié)表頭的大小,也就等于文件尺寸減去文件中所有節(jié)的尺寸??梢砸源酥底鳛镻E文件第一節(jié)的文件偏移量。那么我們?cè)趺吹玫竭@個(gè)值呢?我們的PE文件頭總大小為:64 + 112 + 4 + 20 + 224 = 424,3個(gè)節(jié)表頭的總大小 3 * 40 =120。424 + 120 = 544 byte 轉(zhuǎn)化成十六進(jìn)制為220h,那么此值就填寫(xiě)220h嗎?不是的,因?yàn)槲覀兾募械膶?duì)齊粒度是200h,那么220h經(jīng)過(guò)文件對(duì)齊后實(shí)際上要占用400h的空間,所以此值為“00040000”。
        成員22(CheckSum),4個(gè)字節(jié),表示校驗(yàn)和。它僅用在驅(qū)動(dòng)程序中,在可執(zhí)行文件中可能為0。它的計(jì)算方法Microsoft沒(méi)有公開(kāi),在imagehelp.dll中的CheckSumMappedFile()函數(shù)可以計(jì)算它,此處我們?cè)O(shè)為填充零,此值為“00000000”。
        成員23(Subsystem),2個(gè)字節(jié),表示NT子系統(tǒng),可能是以下的值:
      IMAGE_SUBSYSTEM_NATIVE (1) 不需要子系統(tǒng)。用在驅(qū)動(dòng)程序中。
      IMAGE_SUBSYSTEM_WINDOWS_GUI(2) WIN32 graphical程序(它可用AllocConsole()來(lái)打開(kāi)一個(gè)控制臺(tái),但是不能在一開(kāi)始自動(dòng)得到)。
      IMAGE_SUBSYSTEM_WINDOWS_CUI(3) WIN32 console程序(它可以一開(kāi)始自動(dòng)建立)。
      IMAGE_SUBSYSTEM_OS2_CUI(5) OS/2 console程序(因?yàn)槌绦蚴荗S/2格式,所以它很少用在PE)。
      IMAGE_SUBSYSTEM_POSIX_CUI(7) POSIX console程序。
Windows程序總是用WIN32子系統(tǒng),所以只有2和3是合法的值。也就是說(shuō)此值必須為2或3,如果是3,那么程序運(yùn)行后會(huì)自動(dòng)打開(kāi)一個(gè)控制臺(tái),我們?yōu)榱丝匆幌滦Ч?,這里設(shè)為3,此值為“0300”。
        成員24(DllCharacteristics),2個(gè)字節(jié),表示Dll屬性,我們這里填充零,此值為“0000”。
        成員25(SizeOfStackReserve),4個(gè)字節(jié),保留堆棧大小,我們這里填充零,此值為“00000000”。
        成員26(SizeOfStackCommit),4個(gè)字節(jié),啟動(dòng)后實(shí)際申請(qǐng)的堆棧數(shù),可隨實(shí)際情況變大,我們這里填充零,此值為“00000000”。
        成員27(SizeOfHeapReserve),4個(gè)字節(jié),保留堆大小,我們這里填充零,此值為“00000000”。
        成員28(SizeOfHeapCommit),4個(gè)字節(jié),實(shí)際堆大小,我們這里填充零,此值為“00000000”。
        成員29(LoaderFlags),4個(gè)字節(jié),裝載標(biāo)志,我們這里填充零,此值為“00000000”。
        成員30(NumberOfRvaAndSizes),4個(gè)字節(jié),在講這個(gè)成員之前,我們應(yīng)該先了解        
        成員31,成員31實(shí)際上是一個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)的數(shù)組,成員30的值就是表示該數(shù)組的大小。通常有16個(gè)元素,也就是十六進(jìn)制的0x00000010,所以此值填為:“10000000”。 IMAGE_DATA_DIRECTORY結(jié)構(gòu)定義如下: 
typedef struct _IMAGE_DATA_DIRECTORY {
      DWORD   VirtualAddress;
      DWORD   Size;
  } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
成員31(DataDirectory),128個(gè)字節(jié),上面說(shuō)過(guò)他是一個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)的數(shù)組,通常具有16個(gè)元素。
  IMAGE_DATA_DIRECTORY結(jié)構(gòu)有兩個(gè)成員,各占4個(gè)字節(jié),也就得到成員31的總大小:2 * 4 * 16 = 128byte。16個(gè)元素中每個(gè)元素代表一個(gè)目錄表,每個(gè)目錄表表示的目錄如下:
  IMAGE_DIRECTORY_ENTRY_EXPORT (0)         導(dǎo)出目錄,用于DLL    
  IMAGE_DIRECTORY_ENTRY_IMPORT (1)         導(dǎo)入目錄    
  IMAGE_DIRECTORY_ENTRY_RESOURCE (2)      資源目錄    
  IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)     異常目錄    
  IMAGE_DIRECTORY_ENTRY_SECURITY (4)      安全目錄    
  IMAGE_DIRECTORY_ENTRY_BASERELOC (5)    重定位表    
  IMAGE_DIRECTORY_ENTRY_DEBUG (6)         調(diào)試目錄
  IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)      描述版權(quán)串    
  IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)      機(jī)器值    
  IMAGE_DIRECTORY_ENTRY_TLS (9)             本地線程存儲(chǔ)目錄
  IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)  載入配置目錄    
  IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11) 綁定導(dǎo)入表目錄    
  IMAGE_DIRECTORY_ENTRY_IAT (12)              輸入地址表目錄
  IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT(13) 延遲加載導(dǎo)入描述目錄
  IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR(14) COM 運(yùn)行時(shí)描述目錄
  是不是所有的目錄表都要關(guān)心呢?其實(shí)要把這些目錄表都研究清楚是個(gè)很大的課題,對(duì)于我們這個(gè)程序,只需關(guān)心第2個(gè)元素,導(dǎo)入目錄,它標(biāo)識(shí)了我們的程序從其他模塊導(dǎo)入的函數(shù)信息。因?yàn)槲覀円@示一個(gè)消息框,所以要導(dǎo)入user32.dll庫(kù)中的MessageBoxA函數(shù)。程序要正常退出,又要導(dǎo)入kernel32.dll庫(kù)中的ExitProcess函數(shù)。因此需要構(gòu)造這個(gè)目錄表。然而上面已說(shuō)明每個(gè)目錄是一個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu),該結(jié)構(gòu)具有兩個(gè)成員,第一個(gè)成員表示目錄表的起始RVA地址,第二個(gè)成員表示目錄表的長(zhǎng)度。我們將把這個(gè)目錄表構(gòu)造到.rdata段中,所以暫時(shí)先不填寫(xiě)。但是要留出空位來(lái),為了記住該位置,我們先都填寫(xiě)為a,即:“aaaaaaaa”,“aaaaaaaa”。注意因?yàn)橐募?duì)齊,所以其余的統(tǒng)統(tǒng)添零直到地址1a7h處。此時(shí)完成的代碼如圖7所示:

 

                    圖7 完成PE結(jié)構(gòu)中的PE頭部分

    接下來(lái)完成各段頭部,又稱(chēng)為節(jié)表。一個(gè)程序中用到的所有代碼、資源、全局?jǐn)?shù)據(jù)等信息分布在各個(gè)節(jié)中,而各個(gè)節(jié)的信息,如節(jié)加載位置,節(jié)大小,節(jié)屬性等信息都由緊跟PE頭之后的節(jié)表所指出。它實(shí)際上就是緊挨著 PE 頭的一個(gè)結(jié)構(gòu)數(shù)組,該數(shù)組成員的數(shù)目由 file header (IMAGE_FILE_HEADER) 結(jié)構(gòu)中 NumberOfSections 域的域值來(lái)決定。節(jié)表結(jié)構(gòu)又命名為 IMAGE_SECTION_HEADER。
我們這里有3個(gè)段,.text(代碼段), .rdata(只讀數(shù)據(jù)段),data(全局變量數(shù)據(jù)段)。每段是一個(gè)IMAGE_SECTION_HEADER 結(jié)構(gòu),具有10個(gè)成員。IMAGE_SECTION_HEADER結(jié)構(gòu)定義如下:
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;
首先我們來(lái)完成.text段。
        成員1(Name),8個(gè)字節(jié),表示該節(jié)的名稱(chēng),我們這里該節(jié)的名字為.text,那么此值應(yīng)該是他的ASKII碼值應(yīng)該為“2E74657874000000”。
        成員2(VirtualSize),4個(gè)字節(jié),表示該節(jié)數(shù)據(jù)映射到內(nèi)存后所占字節(jié)數(shù)。在這里是指有效代碼所占的字節(jié)數(shù)。稍后我們將把程序的執(zhí)行代碼指令寫(xiě)入到文件中,總共有多少字節(jié)的指令需要那時(shí)計(jì)算,我們也可以提前將代碼準(zhǔn)備好并計(jì)算出長(zhǎng)度將其填寫(xiě)。筆者已經(jīng)計(jì)算完畢,將寫(xiě)入26h個(gè)字節(jié)的代碼。所以此時(shí)仍然將此值設(shè)為“26000000”。我們也可以填寫(xiě)26h經(jīng)過(guò)內(nèi)存對(duì)齊后的值,即“00100000”。
        成員3(VirtualAddress),4個(gè)字節(jié),表示在.text段映射到內(nèi)存中的起始地址,那么這個(gè)值如何得來(lái)呢?我們知道.text是緊跟PE結(jié)構(gòu)后的,然后整個(gè)PE頭結(jié)構(gòu)映射到內(nèi)存后占的大小為1000h(因?yàn)镻E頭本身結(jié)構(gòu)小于1000h個(gè)字節(jié),而經(jīng)過(guò)內(nèi)存對(duì)齊后便為1000h),那么此值便得到了,為0x00001000,因此此處填寫(xiě)“00100000”。這個(gè)時(shí)候我們已經(jīng)可以完成前面遺留的一個(gè)問(wèn)題,再填寫(xiě)IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)的第七個(gè)成員的時(shí)候,它實(shí)際上是程序的入口地址,當(dāng)時(shí)已經(jīng)講解此入口地址實(shí)際就是.text段的起始地址,所以可以將此處的“aaaaaaaa”更改為“00100000”。
 (提示
程序的入口地址并不一定就代碼段.text的起始位置。讀者只需知道IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)的第7個(gè)成員所表示的含義是程序的入口地址。它通常是由編譯器生成的。因?yàn)槲覀儧](méi)有使用編譯器,而是要手工打造一個(gè)可執(zhí)行程序,所以所有的成員值都要自己來(lái)安排。只要按照PE結(jié)構(gòu)的要求,安排合理即可。因此我們可以把代碼的起始地址隨意安排什么地方,只要安排的那個(gè)地址剛好又是我們保存的程序執(zhí)行代碼的入口即可。我們?yōu)榱朔奖銓⑵浞旁?text段的起始地址處。)

        成員4(SizeOfRawData),4個(gè)字節(jié),表示.text段在文件中所占的大小。因?yàn)槲覀兊膶?shí)際代碼只有26h個(gè)字節(jié),那么這個(gè)值可以填寫(xiě)“26000000”,也可以填寫(xiě)此值經(jīng)過(guò)文件對(duì)齊后的值即200h,所以也可以填寫(xiě)此值為“00020000”。
        成員5(PointerToRawData),4個(gè)字節(jié),表示.text段在文件中的起始地址,上面已經(jīng)計(jì)算過(guò)PE文件的總長(zhǎng)度為400h,他實(shí)際上也就是.text的起始偏移地址,此值為“00040000”。
        成員6(PointerToRelocations),7(PointerToLinenumbers),8(NumberOfRelocations),9(NumberOfLinenumbers),均占4個(gè)字節(jié),都僅用于目標(biāo)文件,我們這里用零來(lái)填充。
成員10(Characteristics),4個(gè)字節(jié)。包含標(biāo)記以指示節(jié)屬性,比如節(jié)是否含有可執(zhí)行代碼、初始化數(shù)據(jù)、未初始數(shù)據(jù),是否可寫(xiě)、可讀等。這個(gè)值實(shí)際上是二進(jìn)制位進(jìn)行或運(yùn)算得到的值。各二進(jìn)制位表示的意義如下:
  bit 5 (IMAGE_SCN_CNT_CODE),置1,節(jié)內(nèi)包含可執(zhí)行代碼。    
  bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA)置1,節(jié)內(nèi)包含的數(shù)據(jù)在執(zhí)行前是確定的。    
  bit 7 (IMAGE_SCN_CNT_UNINITIALIZED_DATA) 置1,本節(jié)包含未初始化的數(shù)據(jù),執(zhí)行前即將被初始化為0。一般是BSS.
  bit 9 (IMAGE_SCN_LNK_INFO) 置1,節(jié)內(nèi)不包含映象數(shù)據(jù)除了注釋?zhuān)枋龌蛘咂渌臋n外,是一個(gè)目標(biāo)文件的一部分,可能是針對(duì)鏈接器的信息。比如哪個(gè)庫(kù)被需要。
  bit 11 (IMAGE_SCN_LNK_REMOVE) 置1,在可執(zhí)行文件鏈接后,作為文件一部分的數(shù)據(jù)被清除。
  bit 12 (IMAGE_SCN_LNK_COMDAT) 置1,節(jié)包含公共塊數(shù)據(jù),是某個(gè)順序的打包的函數(shù)。
  bit 15 (IMAGE_SCN_MEM_FARDATA) 置1,不確定。
  bit 17 (IMAGE_SCN_MEM_PURGEABLE) 置1,節(jié)的數(shù)據(jù)是可清除的。
  bit 18 (IMAGE_SCN_MEM_LOCKED) 置1,節(jié)不可以在內(nèi)存內(nèi)移動(dòng)。
  bit 19 (IMAGE_SCN_MEM_PRELOAD)置1, 節(jié)必須在執(zhí)行開(kāi)始前調(diào)入。
  bits 20 to 23指定對(duì)齊。一般是庫(kù)文件的對(duì)象對(duì)齊。
  bit 24 (IMAGE_SCN_LNK_NRELOC_OVFL) 置1, 節(jié)包含擴(kuò)展的重定位。
  bit 25 (IMAGE_SCN_MEM_DISCARDABLE) 置1,進(jìn)程開(kāi)始后節(jié)的數(shù)據(jù)不再需要。
  bit 26 (IMAGE_SCN_MEM_NOT_CACHED) 置1,節(jié)的 數(shù)據(jù)不得緩存。
  bit 27 (IMAGE_SCN_MEM_NOT_PAGED) 置1,節(jié)的 數(shù)據(jù)不得交換出去。
  bit 28 (IMAGE_SCN_MEM_SHARED) 置1,節(jié)的數(shù)據(jù)在所有映象例程內(nèi)共享,如DLL的初始化數(shù)據(jù)。
  bit 29 (IMAGE_SCN_MEM_EXECUTE) 置1,進(jìn)程得到“執(zhí)行”訪問(wèn)節(jié)內(nèi)存。
  bit 30 (IMAGE_SCN_MEM_READ) 置1,進(jìn)程得到“讀出”訪問(wèn)節(jié)內(nèi)存。
  bit 31 (IMAGE_SCN_MEM_WRITE)置1,進(jìn)程得到“寫(xiě)入”訪問(wèn)節(jié)內(nèi)存。
  在我們這里,因?yàn)檫@是代碼段,所以bit 5 (IMAGE_SCN_CNT_CODE)位要置1,一般代碼段都含有初始化數(shù)據(jù),那么bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA)位要置1,又因?yàn)榇a段的代碼可以執(zhí)行的,所以bit 29 (IMAGE_SCN_MEM_EXECUTE) 位要置1,那么這3個(gè)二進(jìn)制位進(jìn)行或運(yùn)算最終得到的二進(jìn)制值為
“00100000000000000000000001100000”,將其轉(zhuǎn)換為十六進(jìn)制值為0x20000060,所以此處應(yīng)該填寫(xiě)“60000020”。
到此整個(gè).text頭編寫(xiě)完畢,按照上面的方法,分別填寫(xiě).rdata段和.data段。因?yàn)橐募?duì)齊,所以后面的代碼用零補(bǔ)齊,直到3ffh。
PE加載器根據(jù)節(jié)表加載程序的過(guò)程是這樣的:讀取 IMAGE_FILE_HEADER 的 NumberOfSections域,得到文件中節(jié)的數(shù)目。 讀取IMAGE_OPTIONAL_HEADER32的SizeOfHeaders 域值,將其作為節(jié)表的文件偏移,并以此定位節(jié)表。 遍歷整個(gè)結(jié)構(gòu)數(shù)組檢查各成員值。 對(duì)于每個(gè)結(jié)構(gòu),讀取PointerToRawData域值并定位到該文件偏移量。然后再讀取SizeOfRawData域值來(lái)決定映射內(nèi)存的字節(jié)數(shù)。將VirtualAddress域值加上ImageBase域值等于節(jié)起始的虛擬地址。然后把節(jié)映射進(jìn)內(nèi)存,并根據(jù)Characteristics域值設(shè)置屬性。 遍歷整個(gè)數(shù)組,直至所有節(jié)都已處理完畢。
 (提示
感染型病毒經(jīng)常通過(guò)增加節(jié)來(lái)達(dá)到感染正常文件的目的。因?yàn)楦腥菊N募枰砑硬《敬a,病毒最常用的方法是新建一個(gè)節(jié),然后將病毒代碼放置在新建的節(jié)中,然后修改程序入口地址使其指向病毒代碼。這樣程序運(yùn)行以后首先運(yùn)行的是病毒代碼,等病毒代碼運(yùn)行完畢才會(huì)跳轉(zhuǎn)到被感染程序的原始入口地址處執(zhí)行。)

  最后的編寫(xiě)結(jié)果如圖8所示:       

     

                               圖8 完成各個(gè)節(jié)表

至此,我們已經(jīng)完成了PE頭結(jié)構(gòu)的編寫(xiě)。為了讓我們寫(xiě)的程序可以運(yùn)行,我們還要完成.text(代碼段), .rdata(只讀數(shù)據(jù)段),data(全局變量數(shù)據(jù)段)三個(gè)段的實(shí)體部分。
首先編寫(xiě).text段,他緊接著PE結(jié)構(gòu)后面。前面已經(jīng)說(shuō)過(guò),.text段中存放所有可執(zhí)行的指令代碼(機(jī)器碼)。我們可以通過(guò)先編寫(xiě)匯編指令(調(diào)用MessageBoxA和ExitProcess兩個(gè)函數(shù)),然后反匯編出機(jī)器代碼抄到這里就可以了。我們的程序功能是彈出一個(gè)消息框,這需要用到MessageBoxA函數(shù),當(dāng)用戶(hù)單擊確定以后程序要退出,這又需要用到ExitProcess函數(shù)。這兩個(gè)函數(shù)調(diào)用的匯編代碼如下:
push    0          ; MessageBoxA的第四個(gè)參數(shù),即消息框的風(fēng)格,這里傳入0。                      
push    ????   ;第三個(gè)參數(shù),消息框的標(biāo)題字符串所在的地址,需要計(jì)算。                   
push    ????   ;第二個(gè)參數(shù),消息框的內(nèi)容字符串所在的地址,需要計(jì)算。                     
push    0          ;第一個(gè)參數(shù),消息框所屬窗口句柄,這里填0。                      
call    ???? ;調(diào)用MessageBoxA,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址        
push    0       ;ExitProcess函數(shù)的參數(shù),程序退出碼,傳入0.                     
call    ???? ;調(diào)用ExitProcess,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址 
jmp    ???? ;跳轉(zhuǎn)到MessageBoxA的真正地址處。
jmp    ???? ;跳轉(zhuǎn)到ExitProcess的真正地址處。
首先計(jì)算MessageBoxA兩個(gè)字符串參數(shù)的地址,實(shí)際上是兩個(gè)字符串,“消息框”和“HelloWorld !”。這兩個(gè)串需要保存到文件中,我們?cè)O(shè)計(jì)將其存放在.data(全局變量數(shù)據(jù)段)。這個(gè)段位于.rdata段之后,那么可以計(jì)算它的起始內(nèi)存地址,PE頭1000h,.text段只有26h字節(jié),內(nèi)存對(duì)齊后為1000h,.data(只讀數(shù)據(jù)段)是準(zhǔn)備用來(lái)完成導(dǎo)入表的段,它肯定也不會(huì)超過(guò)1000h,所以對(duì)齊后應(yīng)為1000h,因此緊隨其后的.rdata的起始內(nèi)存地址應(yīng)該在偏移為:
1000h+1000h+1000h=3000h處,程序的基址為400000h,故此得到.rdata的絕對(duì)內(nèi)存地址為:
0x00400000+0x00003000=0x00403000。我們將“消息框”字符串放于此處,該字符串占7個(gè)字符,那么緊隨其后的“Hello World !”字符串的地址應(yīng)該為0x00403000+7=0x00403007。
所以修正以上匯編代碼為:
push    0          ; MessageBoxA的第四個(gè)參數(shù),即消息框的風(fēng)格,這里傳入0。                      
push    0x403000   ;第三個(gè)參數(shù),消息框的標(biāo)題字符串所在的地址。                   
push    0x403007   ;第二個(gè)參數(shù),消息框的內(nèi)容字符串所在的地址。                     
push    0          ;第一個(gè)參數(shù),消息框所屬窗口句柄,這里填0。                      
call    ???? ;調(diào)用MessageBoxA,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。     
push    0       ;ExitProcess函數(shù)的參數(shù),程序退出碼,傳入0.                         
call    ???? ;調(diào)用ExitProcess,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。
jmp    ???? ;跳轉(zhuǎn)到MessageBoxA的真正地址處。
jmp    ???? ;跳轉(zhuǎn)到ExitProcess的真正地址處。
其次需要計(jì)算MessageBoxA和ExitProcess兩個(gè)函數(shù)所在地址,這個(gè)需要完成.rdata段的導(dǎo)入表才可以得到。所以首先用200h個(gè)00將.text段填充,待完成.rdata段后再返過(guò)來(lái)完成它。
接下來(lái)完成.rdata段,這個(gè)段非常重要,也非常繁瑣。因?yàn)槲覀円止ご蛟鞂?dǎo)入表。通常導(dǎo)入表是由編譯器生成的,其生成規(guī)則遵循IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)。IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)的定義如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
前面曾介紹這個(gè)程序我們只用完成數(shù)據(jù)目錄數(shù)組的第二個(gè)元素-------導(dǎo)入表目錄,然而此目錄值我們當(dāng)時(shí)沒(méi)有填寫(xiě),當(dāng)時(shí)添充的是“aaaaaaaa”作為標(biāo)記,現(xiàn)在我們要一并解決這個(gè)問(wèn)題。前面已經(jīng)說(shuō)過(guò),每個(gè)數(shù)據(jù)目錄具有兩個(gè)成員,第一個(gè)成員表示目錄表的起始RVA地址,第二個(gè)成員表示目錄表的長(zhǎng)度。對(duì)于我們這個(gè)導(dǎo)入表目錄來(lái)說(shuō),他指的就是導(dǎo)入表了,導(dǎo)入表實(shí)際上是一個(gè)IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)數(shù)組,每個(gè)結(jié)構(gòu)包含PE文件從某一個(gè)DLL庫(kù)引入函數(shù)的相關(guān)信息。例如,我們這個(gè)程序?qū)?個(gè)DLL庫(kù)中導(dǎo)入函數(shù),那么這個(gè)數(shù)組就有2個(gè)成員,同時(shí)該數(shù)組以一個(gè)全零的成員結(jié)尾。每一個(gè)IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)具有5個(gè)成員,都是DOWRD類(lèi)型,因此每個(gè)IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)的大小為4*5=20byte。因此整個(gè)導(dǎo)入表的大小應(yīng)該為(2+1)*20=60byte。轉(zhuǎn)換成十六進(jìn)制也就是0x3C。這樣也就得到了導(dǎo)入表的大小,現(xiàn)在可以將導(dǎo)入表目錄的第二個(gè)成員修改過(guò)來(lái),將“aaaaaaaa”替換為0x0000003c,也就是在編輯器中輸入“3c000000”。接下來(lái)看導(dǎo)入目錄的第一個(gè)成員如何計(jì)算,它是導(dǎo)入表的起始地址的RVA值,我們計(jì)劃將導(dǎo)入表放到.rdata段,并且自.rdata段起始地址處開(kāi)始。那么導(dǎo)入表的起始地址也就是.rdata段的起始地址。.rdata緊隨.text段之后,那么它的起始地址偏移應(yīng)該為PE頭的大小1000h+.text的大小1000h即2000h。現(xiàn)在可以將導(dǎo)入表目錄的第一個(gè)成員修改過(guò)來(lái),將“aaaaaaaa”替換為“00200000”。
之后的工作就是手工打造一個(gè)導(dǎo)入表。導(dǎo)入表在文件中的位置也同樣是.rdata段的起始地址處,文件地址應(yīng)該為PE頭的400h+.text段的200h即600h處。我們打造導(dǎo)入表同樣也要遵循IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu),該結(jié)構(gòu)有5個(gè)成員:
成員1,4個(gè)字節(jié),他實(shí)際上是指向一個(gè) IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組的RVA,而IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組記錄所有從某個(gè).dll庫(kù)中導(dǎo)入的所有函數(shù)名稱(chēng)的RVA。實(shí)際上它是指從某個(gè)DLL文件中導(dǎo)入的所有函數(shù)名稱(chēng)所在地址的地址表,該地址表由一個(gè)全零DWORD值0x00000000結(jié)束。因此我們需要構(gòu)造這樣一個(gè)表。首先需要把導(dǎo)入的所有函數(shù)名稱(chēng)依次保存到某個(gè)位置,然后計(jì)算其RVA去構(gòu)造函數(shù)名稱(chēng)地址表。
為了緊湊,我們可以將每個(gè)函數(shù)名和動(dòng)態(tài)庫(kù)名字符串放在導(dǎo)入表之后。前面已經(jīng)計(jì)算得到導(dǎo)入表的長(zhǎng)度為0x3c,那么字符串的位置應(yīng)該保存到文件中的地址為:600h(導(dǎo)入表的起始文件偏移地址)+3ch(導(dǎo)入表長(zhǎng)度)= 63ch處。在這里準(zhǔn)備填寫(xiě)所有字符串的AscII碼。首先輸入“MessageBoxA”字符串,這里有一點(diǎn)要注意,一個(gè)PE程序在導(dǎo)入函數(shù)的時(shí)候可以按照函數(shù)名來(lái)導(dǎo)入,也就是我們準(zhǔn)備采取的方式,也可以按照函數(shù)序號(hào)導(dǎo)入。函數(shù)序號(hào)在各個(gè)動(dòng)態(tài)庫(kù)的導(dǎo)出表中可以查詢(xún)到。該序號(hào)是一個(gè)WORD類(lèi)型的值。無(wú)論是否我們以序號(hào)方式導(dǎo)入都要保留其位置,也就是在“MessageBoxA”字符串前應(yīng)該預(yù)留一個(gè)WORD值的位置。因?yàn)槲覀儾⒉皇前凑蘸瘮?shù)序號(hào)的方式導(dǎo)入,所以這里可以填寫(xiě)任意值,我們就填寫(xiě)0x0000。然后緊接其后寫(xiě)入“MessageBoxA”字符串,注意字符串要以一個(gè)字節(jié)的0x00結(jié)尾。如果還導(dǎo)入了其他函數(shù),那么依次輸入那些被導(dǎo)入的函數(shù)名AscII值即可。最后輸入導(dǎo)入庫(kù)名的AscII值,即“user32.dll”字符串。這樣我們完成了一個(gè)導(dǎo)入庫(kù)的名稱(chēng)表,緊隨其后以相同方式完成另一個(gè)導(dǎo)入庫(kù),即“ExitProcess”字符串和“kernel32.dll”字符串。在導(dǎo)入表后輸入的內(nèi)容如下:
“00004D657373616765426F7841007573657233322E646C6C0080004578697450726F63657373006B65726E656C33322E646C6C00”。
如圖9所示:
     
  

               圖9 導(dǎo)入表用到的字符串

完成函數(shù)名稱(chēng)表后就可以構(gòu)造函數(shù)名稱(chēng)地址表。緊隨名稱(chēng)表之后,函數(shù)名稱(chēng)表起始地址為文件偏移63ch處,長(zhǎng)度是34h。所以其后的函數(shù)名稱(chēng)地址表的起始應(yīng)該為:63ch+34h==670h。函數(shù)名稱(chēng)地址表中保存了從某一個(gè)DLL庫(kù)中導(dǎo)入的所有函數(shù)名稱(chēng)所在內(nèi)存地址的RVA值,并且以一個(gè)0x00000000作結(jié)束。導(dǎo)入了幾個(gè)DLL庫(kù),那么就有幾個(gè)函數(shù)名稱(chēng)地址表。我們這里總共導(dǎo)入了兩個(gè)DLL庫(kù),那么就有兩個(gè)函數(shù)名稱(chēng)地址表。我們分別完成它。首先是user32.dll庫(kù)中導(dǎo)入了MessageBoxA,因?yàn)楹瘮?shù)名稱(chēng)表已經(jīng)構(gòu)造完畢,所以我們可以得到它的文件偏移,是63ch,怎樣由文件偏移計(jì)算得到RVA值呢?這取決于內(nèi)存對(duì)齊粒度和文件對(duì)齊粒度。PE加載器將PE文件加載入內(nèi)存是按照內(nèi)存對(duì)其粒度進(jìn)行加載的。讓們看看從文件首到0x063C處內(nèi)容如何加載入內(nèi)存。首先PE頭經(jīng)過(guò)文件對(duì)齊占400h,而此部分內(nèi)容經(jīng)過(guò)內(nèi)存對(duì)齊加載入內(nèi)存后占1000h。然后是.text節(jié)經(jīng)文件對(duì)齊占200h,而此節(jié)內(nèi)容經(jīng)過(guò)內(nèi)存對(duì)齊加載如內(nèi)存后占1000h,也就是說(shuō)文件偏移600h對(duì)應(yīng)內(nèi)存RVA是2000h,因此文件地址0x63C對(duì)應(yīng)的RVA應(yīng)該是0x203C。所以函數(shù)名稱(chēng)地址表中填寫(xiě)0x0000203C,即“3C200000”,user32.dll庫(kù)中只導(dǎo)入了一個(gè)函數(shù),所以后面填寫(xiě)一個(gè)全零的DWORD值0x00000000,即“00000000”表示結(jié)束。接著完成kernel32.dll庫(kù)的導(dǎo)入函數(shù)地址表。由函數(shù)名稱(chēng)表中可以得到被導(dǎo)入的ExitProcess的文件偏移為0x655。它對(duì)應(yīng)的RVA值為0x2055,那么緊隨前一個(gè)函數(shù)名稱(chēng)地址表填寫(xiě)“55200000”,由于也只導(dǎo)入了一個(gè)函數(shù),所以后面填寫(xiě)全零的DWORD值表示此表的結(jié)束。這樣完成了整個(gè)導(dǎo)入表所需的兩個(gè)庫(kù)函數(shù)名稱(chēng)地址表,如圖10所示:


                圖10導(dǎo)入名稱(chēng)地址表

由此可知user32.dll庫(kù)函數(shù)名稱(chēng)地址表的起始文件偏移為0x670,對(duì)應(yīng)的RVA值為0x2070。而kernel32.dll庫(kù)函數(shù)的名稱(chēng)地址表的起始文件偏移為0x678,對(duì)應(yīng)的RVA值為0x2078。此時(shí)可以完成關(guān)于user32.dll庫(kù)的導(dǎo)入表的第一個(gè)成員,就是指由user32.dll庫(kù)導(dǎo)入的函數(shù)名稱(chēng)地址表起始地址的RVA值,應(yīng)該是0x2070,因?yàn)榇顺蓡T占4個(gè)字節(jié),所以編輯器中應(yīng)該填寫(xiě)“70200000”。
成員2,成員3各4各字節(jié),用處不大,我們用零填充。
成員4,4個(gè)字節(jié),是指向DLL名字的RVA。由user32.dll導(dǎo)入函數(shù)名稱(chēng)表可以得知此DLL名稱(chēng)所在地址的文件偏移為0x64A,對(duì)應(yīng)的RVA值應(yīng)該為0x204A,所以此成員應(yīng)該填寫(xiě)0x0000204A,即“4A200000”。
成員5,4個(gè)字節(jié),指向一個(gè) IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組的RVA,同成員1一樣。但是此IMAGE_THUNK_DATA 數(shù)組結(jié)構(gòu)含義確和成員1完全不同。它將保存所有導(dǎo)入函數(shù)的真實(shí)調(diào)用地址。換句話說(shuō)實(shí)際上它也指向一個(gè)地址表,這個(gè)地址不再像成員1一樣是函數(shù)名稱(chēng)地址表,而是函數(shù)真實(shí)調(diào)用地址表,這個(gè)表又稱(chēng)為導(dǎo)入地址表,簡(jiǎn)稱(chēng)為IAT。既然這個(gè)表存放的是函數(shù)調(diào)用的真實(shí)地址,在設(shè)計(jì)PE文件時(shí)還沒(méi)有得到導(dǎo)入函數(shù)的調(diào)用地址,所以無(wú)法填寫(xiě)此表。該表由PE文件被裝載到內(nèi)存時(shí),PE加載器獲得導(dǎo)入函數(shù)的真實(shí)地址來(lái)填充這個(gè)表。同樣這個(gè)表以一個(gè)全零的DWORD作為結(jié)束標(biāo)志。同樣為了緊湊,我們將函數(shù)調(diào)用地址表安排在函數(shù)名稱(chēng)地址表之后,函數(shù)名稱(chēng)地址表的起始文件偏移是0x670,總共0x10個(gè)字節(jié),那么其后的函數(shù)調(diào)用地址表的起始文件偏移為0x670+0x10=0x680。轉(zhuǎn)換為RVA應(yīng)該為0x2080,所以成員5應(yīng)該填寫(xiě)“80200000”。
 (注意:
雖然函數(shù)調(diào)用地址最終由PE加載器來(lái)填充,我們只需指定該表的位置。但是由于該表以全零的DWORD值作為結(jié)束標(biāo)記。所以如果我們開(kāi)始也填充全零將使PE加載器填充失敗,所以我們需要隨便填入一個(gè)非零值,這里筆者填入0x00000011。之后再填寫(xiě)結(jié)束標(biāo)記0x00000000。緊隨其后是第二導(dǎo)入庫(kù)的函數(shù)調(diào)用地址表,導(dǎo)入了幾個(gè)函數(shù)就需要填寫(xiě)幾個(gè)非零的DWORD值,然后填寫(xiě)結(jié)束標(biāo)記0x00000000。最終完成的導(dǎo)入函數(shù)調(diào)用地址表如圖11所示:)

 

                 圖 11 IAT

至此,完成了導(dǎo)入表中的關(guān)于導(dǎo)入庫(kù)user32.dll的部分,按照相同的方法繼續(xù)完成關(guān)于導(dǎo)入庫(kù)kernel32.dll部分。最終導(dǎo)入表如圖12所示:


                     圖 12 導(dǎo)入表

.rdata的其余部分用00填充,直到文件偏移0x800處。
最后是.data段,這個(gè)段非常簡(jiǎn)單,就是MessageBoxA所需的參數(shù),消息框的標(biāo)題和內(nèi)容:即“消息框”、“Hello World !”兩個(gè)字符串的AscII值。其余部分用00填充,直到0xA00處。如圖13所示:


                     圖 13 導(dǎo)入表

最后我們繼續(xù)完成.text段的程序執(zhí)行代碼。代碼如下:
push    0          ; MessageBoxA的第四個(gè)參數(shù),即消息框的風(fēng)格,這里傳入0。                      
push    0x403000   ;第三個(gè)參數(shù),消息框的標(biāo)題字符串所在的地址。                   
push    0x403007   ;第二個(gè)參數(shù),消息框的內(nèi)容字符串所在的地址。                     
push    0          ;第一個(gè)參數(shù),消息框所屬窗口句柄,這里填0。                      
call    ???? ;調(diào)用MessageBoxA,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。     
push    0       ;ExitProcess函數(shù)的參數(shù),程序退出碼,傳入0.                         
call    ???? ;調(diào)用ExitProcess,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。
jmp    ???? ;跳轉(zhuǎn)到MessageBoxA的真正地址處。
jmp    ???? ;跳轉(zhuǎn)到ExitProcess的真正地址處。
看上面的兩個(gè)jmp跳轉(zhuǎn),它是跳轉(zhuǎn)到函數(shù)的真正地址處,然而函數(shù)真正地址是由PE加載得到并填充的,我們?nèi)绾沃滥??通過(guò)函數(shù)導(dǎo)入表的構(gòu)造,我們指定了各個(gè)導(dǎo)入函數(shù)真正地址所要填充的位置,PE加載器將把函數(shù)調(diào)用的真實(shí)地址填充到此處,那么我們只需這樣來(lái)設(shè)計(jì):jmp dword ptr [被填充地址],也就是跳轉(zhuǎn)到被填充地址所指向的內(nèi)容處即可。通過(guò)導(dǎo)入表可以輕松得到MessageBoxA的填充地址為0x2080,這是RVA值。得到絕對(duì)地址還需要加上基址。即:0x400000+0x2080=0x402080。同理,ExitProcess函數(shù)的填充地址為0x402088。
更新后的執(zhí)行代碼如下:
push    0          ; MessageBoxA的第四個(gè)參數(shù),即消息框的風(fēng)格,這里傳入0。                      
push    0x403000   ;第三個(gè)參數(shù),消息框的標(biāo)題字符串所在的地址。                   
push    0x403007   ;第二個(gè)參數(shù),消息框的內(nèi)容字符串所在的地址。                     
push    0          ;第一個(gè)參數(shù),消息框所屬窗口句柄,這里填0。                      
call    ???? ;調(diào)用MessageBoxA,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。     
push    0       ;ExitProcess函數(shù)的參數(shù),程序退出碼,傳入0.                         
call    ???? ;調(diào)用ExitProcess,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。
jmp   dword ptr [0x402080] ;跳轉(zhuǎn)到MessageBoxA的真正地址處。
jmp   dword ptr [0x402088] ;跳轉(zhuǎn)到ExitProcess的真正地址處。
還剩兩個(gè)call的地址沒(méi)有確定,她們表示該函數(shù)的跳轉(zhuǎn)指令所在地址。也就是指令 jmp   dword ptr [0x402080] 和 jmp   dword ptr [0x402088]兩條指令所在的地址。如何得到他們呢?因?yàn)閳?zhí)行代碼起始地址為.text段的起始地址,偏移為0x1000。而后面的各條指令長(zhǎng)度分別為:
push    0 ;指令長(zhǎng)度為2。
push    0x403000  ;指令長(zhǎng)度為5。           
push    0x403007   ;指令長(zhǎng)度為5。    
push    0          ;指令長(zhǎng)度為2。 
call    ???? ;指令長(zhǎng)度為5。  
push    0       ;指令長(zhǎng)度為2。                         
call    ???? ;指令長(zhǎng)度為5。
總長(zhǎng)度為2+5+5+2+5+2+5=26。轉(zhuǎn)換為十六進(jìn)制為1A,那么緊隨其后的兩條jmp指令的地址偏移應(yīng)該為0x101A和0x1020。加上基址后得到其絕對(duì)地址分別為0x401041和0x401020.。更新后的執(zhí)行代碼如下:
push    0          ; MessageBoxA的第四個(gè)參數(shù),即消息框的風(fēng)格,這里傳入0。                      
push    0x403000   ;第三個(gè)參數(shù),消息框的標(biāo)題字符串所在的地址。                   
push    0x403007   ;第二個(gè)參數(shù),消息框的內(nèi)容字符串所在的地址。                     
push    0          ;第一個(gè)參數(shù),消息框所屬窗口句柄,這里填0。                      
call    40101A ;調(diào)用MessageBoxA,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。     
push    0       ;ExitProcess函數(shù)的參數(shù),程序退出碼,傳入0.                         
call    401020 ;調(diào)用ExitProcess,實(shí)際是跳轉(zhuǎn)到該函數(shù)的跳轉(zhuǎn)指令所在地址。
jmp   dword ptr [0x402080] ;跳轉(zhuǎn)到MessageBoxA的真正地址處。
jmp   dword ptr [0x402088] ;跳轉(zhuǎn)到ExitProcess的真正地址處。
將這些指令翻譯成機(jī)器碼后如下:
6A00680030400068073040006A00E8070000006A00E806000000FF2580204000FF2588204000
最后將其填入.text段,其余部分用00填充,直到600h處。如圖14所示:

 

                     圖 14導(dǎo)入表

到此為止一個(gè)完整的顯示Hello World!的可執(zhí)行程序就完成了,按Ctrl+S鍵將編寫(xiě)完畢的內(nèi)容保存成文件,如圖15所示: 


                     圖 15保存文件

我們將其保存到桌面即可,并且命名為HelloWorld.exe。雙擊運(yùn)行該程序,運(yùn)行結(jié)果如圖16所示:


                     圖 16保存文件

程序成功運(yùn)行。
這樣,沒(méi)有依賴(lài)任何編譯器,按照PE結(jié)構(gòu)的原理,純手工成功打造了一個(gè)Win32可執(zhí)行程序。通過(guò)這個(gè)過(guò)程,我們學(xué)習(xí)了PE頭的結(jié)構(gòu)和節(jié)表的結(jié)構(gòu),掌握了導(dǎo)入表結(jié)構(gòu)。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶(hù)發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
PE文件詳解
用C/C++實(shí)現(xiàn)SMC動(dòng)態(tài)代碼加密技術(shù)
逆向工程工具介紹2-IDA
【原創(chuàng)】一種保護(hù)應(yīng)用程序的方法 模擬Windows PE加載器,從內(nèi)存資源中加載DLL [文字模式]
api掛接
PE文件概述
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服