【EXE PE】初識pe結構--手寫可執行程式
【文章標題】: 手寫可執行程式
【文章作者】: dncwbc【作者郵箱】: [email protected]
【作者QQ號】: 182445917
【軟體名稱】: Hello World!
【軟體大小】: 2.5K
【下載地址】: 自己搜尋下載
【編寫語言】: 機器碼
【使用工具】: VC++ 6.0
【操作平臺】: Winxp
【作者宣告】: 只是感興趣,沒有其他目的。失誤之處敬請諸位大俠賜教!
--------------------------------------------------------------------------------
【詳細過程】
最近,學習PE結構的知識。之後深有感觸,隨即便萌發了不依賴任何開發環境和編譯器,純手工寫一個小程式的念
頭。為了簡單而又令所有學習程式開發的人感到親切,就寫一個Hello World! 程式吧...
在這裡,我們首先複習一下Win32可執行程式的大體結構,就是通常所說的PE結構。
PE 的意思就是Portable Executable(可移植的執行體)。
PE結構如下圖:
︱ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄︱
︱ MS-DOS ︱
︱ MZ 頭部 ︱--------------> 64 byte
︱ ︱
︱  ̄  ̄  ̄  ̄  ̄  ̄︱
︱ MS-DOS ︱
︱ 真實模式殘餘程式 ︱--------------> 112 byte
︱ ︱
︱ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄︱
︱ PE檔案標誌 ︱--------------> 4 byte
︱ ︱
︱ ̄  ̄  ̄  ̄  ̄  ̄ ︱
︱ PE檔案頭 ︱--------------> 20 byte
︱ ︱
︱ ̄  ̄  ̄  ̄  ̄  ̄ ︱
︱ PE檔案可選頭 ︱--------------> 224 byte
︱ ︱
︱ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄︱
︱ 各段頭部 ︱--------------> n * 40 byte
︱ ︱
 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
DOS MZ header:所有PE檔案(甚至32位的DLLs) 必須以一個簡單的DOS MZ header 開始。有了它,一旦程式在DOS下執
行,DOS就能識別出這是有效的執行體,然後執行緊隨MZ header 之後的DOS程式。以此達到對Dos系統的相容。
(DOS MZ header總共佔用64byte)
MS-DOS 真實模式殘餘程式:實際上是個有效的EXE,在不支援PE檔案格式的作業系統中,它將簡單顯示一個錯誤提示,
大多數情況下它是由彙編器/編譯器自動生成。通常,它簡單呼叫中斷21h服務9來顯示字串
"This program cannot run in DOS mode"。(在我們寫的程式中,他不是必須的,可以不於以實現,但是要保留其大
小,大小為112byte,為了簡潔,我們就用00來填充。)
PE檔案標誌:是PE檔案結構的起始標誌。(長度4byte, Windows程式此值必須為0x50450000)
PE檔案頭:是PE相關結構 IMAGE_NT_HEADERS 的簡稱,其中包含了許多PE裝載器用到的重要域。執行體在支援PE檔案
結構的作業系統中執行時,PE裝載器將從DOS MZ header中找到PE header的起始偏移量,跳過了MS-DOS 真實模式殘餘程式 ,
直接定位到真正的檔案頭PE header, 長度20byte)。
PE檔案可選頭:雖然它的名字是“可選頭部”,但是請確信:這個頭部並非“可選”,而是“必需”的。
(長度 224byte )
各段頭部:一個Windows NT的應用程式典型地擁有9個預定義段,它們是.text、.bss、.rdata、.data、.rsrc、
.edata、.idata、.pdata和.debug。一些應用程式不需要所有的這些段,同樣還有一些應用程式為了自己特殊
的需要而定義了更多的段。(每段佔40byte,我們這裡也不需要所有的段,僅需3個段。)
以上僅僅是對PE結構各部分的大體講解,先熱熱身而已。接下來在手寫這個Hello World!程式中,我會詳細介紹
每個位元組的含義。
首先準備一下工具(肯定有人要問了,不是純手寫嗎?怎麼還要準備工具啊?),畢竟一個十六進位制編輯器是少
不了的,否則在哪寫啊?我在這裡就使用VC++ 6.0所攜帶的十六進位制編輯器。好了,工具暫時就這個了,開工了。
開啟VC,選擇檔案,新建選單項,然後選擇一個二進位制檔案,確定。一切就緒了,下面就開始我們的手寫可執行
程式旅程吧......
首先我們來完成“DOS MZ header”,“DOS MZ header”的功能前面已經講過,在這裡就不多說了,我們直接來
看一下如何實現他。“DOS MZ header”總共64byte,他對應的結構是IMAGE_DOS_HEADER ,在WINNT.H檔案中有定義。通過
這個結構我們可以看到,這64位元組被分成19個成員,每個成員都有他的功能,與其說我們在一個位元組一個位元組的手寫可執行
程式,倒不如說我們是在一個成員一個成員的寫。因為單獨的一個位元組並不一定具有什麼意義。我們在學習過程中,就是要
把整個部分拆分成幾個成員,然後一個成員一個成員的去學習。(實際上並不是我們去拆分,人家已經幫我們拆分好了。我
們只需按照標準的結構去學習就可以了。所有的結構都定義在WINNT.H檔案中。)
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;
第一個成員佔2個位元組,它被用於表示一個MS-DOS相容的檔案型別,他的值是固定的----“4D5A”
(注意:因為我們是在十六進位制編輯器下寫資料,所以所有的資料格式都是十六進位制式的。但是我們在開發環境中預設
都是十進位制的,所以必須在資料前加 0x ,即:0x4D5A。而我為了方便,就直接寫成“4D5A”,也就是直接輸入到編輯器
中的值,是十六進位制,後面的都照此規定書寫。)
第2個成員到第18個成員總共58個位元組,是對DOS程式環境的初始化等操作,對於我們這個程式來說,沒什麼影響,
我們通通用“00”來填充。(如果讀者想對其進行詳細瞭解,請查閱相關書籍。)注意:因為我們不可能把PE結構所
有的東西都面面俱到,他十分的龐大。當然也沒有必要都去記他,只需掌握關鍵的地方就可以了。以後我們都將把不
影響程式執行的成員填充為零,這樣做,一方面使程式看起來簡潔,另一方面可以使您快速定位PE結構中要重點掌握
的地方。
第19個成員非常重要,他佔4個位元組,用來表示“PE檔案標誌”在檔案中的偏移,單位是byte。而從上圖中可以看到
“PE檔案標誌”緊隨“MS-DOS 真實模式殘餘程式”其後。知道這一點,我們就可以計算一下了,我們的“DOS MZ header”總
共64 byte,後面的“MS-DOS 真實模式殘餘程式”佔112 byte, 64 + 112 = 176 byte,但是要注意,我們這裡的176可是十進位制
的,轉化成十六進位制是B0,對了,就是這個值,因為是4個位元組,所以我們應該填“B0000000”。
接下來我們來完成“MS-DOS 真實模式殘餘程式”,我們已經知道,他是用在DOS下執
行的,我們這裡可以直接用“00”來填充,注意總共112 byte。 這兩部分完成之後程式碼如下:
00000 4D 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ..............
00010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00030 00 00 00 00 00 00 00 00 00 00 00 00 B0 00 00 00 ............?...
00040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
接下來我們便進入主題,開始寫真正的PE結構部分:微軟將“PE檔案標誌”,“PE檔案頭 ”,“PE檔案可選頭 ”
這三個部分用一個結構來定義,即:IMAGE_NT_HEADERS32(WINNT.H中有定義,後面象這樣的結構均在WINNT.H中有定義),
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
這個結構含有3個成員:
第一個成員表示“PE檔案標誌”,可以看到他是一個DWORD型別,因此佔4個位元組,它是PE開始的標記,對Windows程
序這個值必須為“50450000”。
第二個成員表示“PE檔案頭 ”,他的型別是一個IMAGE_FILE_HEADER的結構。也就是說“PE檔案頭”的20個位元組被
定義為IMAGE_FILE_HEADER結構,
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;
這個結構具有7個成員:
成員1,佔2個位元組,表示該檔案執行所要求的CPU。對於Intel平臺,該值是“4C01”。
成員2,佔2個位元組,表示該檔案中段的總數,我們這裡計劃寫3個段,(.text(程式碼段)、.rdata(只讀資料段)、
.data(全域性變數資料段))。所以此處值是“0300”。
成員3,佔4個位元組,表示檔案建立日期和時間,從1970.1.1 00:00:00以來的秒數,我們這裡填“0000”即可。
成員4,佔4個位元組,表示符號表的指標,主要用於除錯,在這裡填“0000”。
成員5,佔4個位元組,表示符號的數目,主要用於除錯,在這裡填“0000”。
成員6,佔2個位元組,表示後面的“PE檔案可選頭 ”部分所佔空間大小,我們已經知道“PE檔案可選頭 ”的大小是
224 byte,轉換成十六進位制就是E0,所以這裡的值為“E000”
成員7,佔2個位元組,表示關於檔案資訊的標記,比如檔案是exe還是dll。這個值實際上是二進位制位進行或運算得到的值。
各二進位制位表示的意義如下:
Bit 0 :置1表示檔案中沒有重定向資訊。每個段都有它們自己的重定向資訊。這個標誌在可執行檔案中沒有使用,
在可執行檔案中是用一個叫做基址重定向目錄表來表示重定向資訊的,這將在下面介紹。
Bit 1 :置1表示該檔案是可執行檔案(也就是說不是一個目標檔案或庫檔案)。
Bit 2 :置1表示沒有行數資訊;在可執行檔案中沒有使用。
Bit 3 :置1表示沒有區域性符號資訊;在可執行檔案中沒有使用。
Bit 4 :
Bit 7
Bit 8 :表示希望機器為32位機。這個值永遠為1。
Bit 9 :表示沒有除錯資訊,在可執行檔案中沒有使用。
Bit 10:置1表示該程式不能運行於可移動介質中(如軟碟機或CD-ROM)。在這種情況下,OS必須把檔案拷貝到
交換檔案中執行。
Bit 11:置1表示程式不能在網上執行。在這種情況下,OS必須把檔案拷貝到交換檔案中執行。
Bit 12:置1表示檔案是一個系統檔案例如驅動程式。在可執行檔案中沒有使用。
Bit 13:置1表示檔案是一個動態連結庫(DLL)。
Bit 14:表示檔案被設計成不能運行於多處理器系統中。
Bit 15:表示檔案的位元組順序如果不是機器所期望的,那麼在讀出之前要進行
交換。在可執行檔案中它們是不可信的(作業系統期望按正確的位元組順序執行程式)。
注意,因為我們寫的是可執行程式,所以Bit 1必須置為1,其他的按照需要置位即可,這裡我們僅將第二位置位,
由此得到成員7的值為“0200”。
第三個成員,表示“PE檔案可選頭 ”,他的型別是一個IMAGE_OPTIONAL_HEADER32結構。也就是說“PE檔案頭 ”
的224個位元組被定義為IMAGE_OPTIONAL_HEADER32結構,
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;
具有31個成員:
成員1,佔2個位元組,表示檔案的格式,值為0x010B表示.EXE檔案,為0x0107表示ROM映像,因為我們寫的是一個可執
行程式,所以此值應該為“0B01”。
成員2,佔1個位元組,表示連結器的主版本號,此值不會影響程式的執行,我們這裡填充零,此值為“00”。
成員3,佔1個位元組,表示連結器的幅版本號,此值不會影響程式的執行,我們這裡填充零,此值為“00”。
成員4,佔4個位元組,表示可執行程式碼的長度,此值不會影響程式的執行,我們這裡填充零,此值為“00000000”。
成員5,佔4個位元組,表示初始化資料的長度(資料段)。此值不會影響程式的執行,我們這裡填充零,此值為“00000000”。
成員6,佔4個位元組,表示未初始化資料的長度(bss段)。此值不會影響程式的執行,我們這裡填充零,此值為“00000000”。
(在介紹成員7之前,有必要了解一個很重要的知識------檔案對映到記憶體。在可執行程式執行之前,PE載入器將
把PE檔案載入到程序空間的記憶體中去,並且初始化每個段實體。那麼載入到記憶體中的哪個地址去呢?這將由
IMAGE_OPTIONAL_HEADER32結構的成員10的值指出載入的起始地址(又叫基地址)。這個值通常是“00400000”, 那麼PE
檔案的首地址“00000”就被對映到記憶體地址“00400000”處,那麼相對於檔案偏移10個位元組的地址為“00010”,被對映
到記憶體後的偏移也應該是10個位元組,對映後的地址應該為“00400010”。)
成員7,4個位元組,表示程式碼的入口RVA(檔案對映到記憶體的偏移地址)地址,程式從這兒開始執行。PE裝載器準備
執行的PE檔案的第一個指令的RVA。若您要改變整個執行的流程,可以將該值指定到新的RVA,這樣新RVA處的指令首先被
執行。那麼這個值我們怎麼得到呢?我們知道在檔案中有個.text段,他包含了所有的程式碼,我們可以從中找到我們的入口
地址,在這裡就是.text段裡的第一行程式碼,也就是.text段的首地址,而在.text段頭部就給出了他對映到記憶體後的首地
址的偏移,我們找到他取出添到此處,這裡為“00100000”。(此處不理解沒關係,我們講完段結構後自能迎刃而解。)
成員8,4個位元組,表示可執行程式碼起始位置。當然就是.text段的首地址,此值不會影響程式的執行,我們這裡
填充零,此值為“00000000”。
成員9,4個位元組,表示初始化資料的起始位置,此值不會影響程式的執行,我們這裡填充零,此值為“00000000”。
成員10,4個位元組,就是上面所講的檔案對映到記憶體是的基地址。PE檔案的優先裝載地址。通常設為“00400000”,
PE裝載器將嘗試把檔案裝到虛擬地址空間的00400000h處。字眼"優先"表示若該地址區域已被其他模組佔用,那PE裝載器會
選用其他空閒地址。我們這裡的值設為“00400000”。
成員11,4個位元組,表示段載入後在記憶體中的對齊方式。記憶體中節對齊的粒度。例如,如果該值是4096 (1000h),
那麼每節的起始地址必須是4096的倍數。若第一節從401000h開始且大小是10個位元組,則下一節必定從402000h開始,
即使401000h和402000h之間還有很多空間沒被使用。因為Windows管理記憶體採用分頁管理的方式,而每頁的大小為4k,也
就是1000h,所以我們這個值為“00100000”。
成員12,4個位元組,表示段在檔案中的對齊方式。檔案中節對齊的粒度。例如,如果該值是(200h),,那麼每節的起
始地址必須是512的倍數。若第一節從檔案偏移量200h開始且大小是10個位元組,則下一節必定位於偏移量400h: 即使偏移
量512和1024之間還有很多空間沒被使用。此值最好設為200h,所以該成員的值為“00020000”。
成員13,2個位元組,表示作業系統主版本號,此值不會影響程式的執行,我們這裡填充零,此值為“0000”。
成員14,2個位元組,表示作業系統副版本號,此值不會影響程式的執行,我們這裡填充零,此值為“0000”。
成員15,2個位元組,表示程式主版本號,此值不會影響程式的執行,我們這裡填充零,此值為“0000”。
成員16,2個位元組,表示程式副版本號,此值不會影響程式的執行,我們這裡填充零,此值為“0000”。
成員17,2個位元組,表示子系統主版本號。win32子系統版本。PE檔案是專門為Win32設計的,該子系統版本必定是
4.0那麼此處值為“04”。
成員18,2個位元組,表示子系統副版本號,根據上面所說,此值應為“00”。
成員19,2個位元組,此值一般為“00”。
成員20,4個位元組,表示程式調入後佔用記憶體大小(位元組),等於所有段的長度之和。所有頭和節經過節對齊處理後
的大小。我們知道,我們檔案PE結構總長小於1000h,但是記憶體中的對齊粒度是1000h,所以PE結構被對映後要佔1000h,盡
管很多空間沒有使用,另外我們有3個段,每個段的長度小於1000h,但是被對映後同樣要佔1000h,所以總共佔用記憶體的大
小為1000h + 3 * 1000h = 4000h,因此此值為“00400000”。
成員21,4個位元組,表示所有檔案頭的長度之和(從檔案開始到第一個段之間的大小)。所有頭+節表的大小,也就等
於檔案尺寸減去檔案中所有節的尺寸。可以以此值作為PE檔案第一節的檔案偏移量。那麼我們怎麼得到這個值呢?我們的
PE檔案結構總大小為:64 + 112 + 4 + 20 + 224 + 3 * 40 = 544 byte 轉化成十六進位制為220h,那麼此值就是220h嗎?
不是的,因為我們檔案中的對齊粒度是200h,那麼220h實際上要佔用400h的空間,所以此值為“00040000”。
成員22,4個位元組,表示校驗和。它僅用在驅動程式中,在可執行檔案中可能為0。它的計算方法Microsoft不公開,
在imagehelp.dll中的CheckSumMappedFile()函式可以計算它,此處我們設為填充零,此值為“00000000”。
成員23,2個位元組,表示NT子系統,可能是以下的值:
IMAGE_SUBSYSTEM_NATIVE (1) 不需要子系統。用在驅動程式中。
IMAGE_SUBSYSTEM_WINDOWS_GUI(2) WIN32 graphical程式(它可用AllocConsole()來開啟一個控制檯,但是不能在一
開始自動得到)。
IMAGE_SUBSYSTEM_WINDOWS_CUI(3) WIN32 console程式(它可以一開始自動建立)。
IMAGE_SUBSYSTEM_OS2_CUI(5) OS/2 console程式(因為程式是OS/2格式,所以它很少用在PE)。
IMAGE_SUBSYSTEM_POSIX_CUI(7) POSIX console程式。
Windows程式總是用WIN32子系統,所以只有2和3是合法的值。也就是說此值必須為2或3,如果是3,那麼程式執行後
會自動開啟一個控制檯,我們為了看一下效果,這裡設為3,此值為“0300“。
成員24,2個位元組,表示Dll狀態,我們這裡填充零,此值為“0000”。
成員25,4個位元組,保留堆疊大小,我們這裡填充零,此值為“00000000”。
成員26,4個位元組,啟動後實際申請的堆疊數,可隨實際情況變大,我們這裡填充零,此值為“00000000”。
成員27,4個位元組,保留堆大小,我們這裡填充零,此值為“00000000”。
成員28,4個位元組,實際堆大小,我們這裡填充零,此值為“00000000”。
成員29,4個位元組,裝載標誌,我們這裡填充零,此值為“00000000”。
成員30,4個位元組,在講這個成員之前,我們應該先了解成員31,成員31實際上是一個IMAGE_DATA_DIRECTORY結構
的陣列,成員30的值就是表示該陣列的大小。通常有16個元素,所以此值為:“10000000”。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
成員31,128個位元組,上面說過他是一個IMAGE_DATA_DIRECTORY結構的陣列,通常具有16個元素。
IMAGE_DATA_DIRECTORY結構有兩個成員,各佔4個位元組,那麼也就得到成員31的總大小:2 * 4 * 16 = 128byte。16個元素
中每個元素代表一個目錄表,每個目錄表表示的目錄如下:
IMAGE_DIRECTORY_ENTRY_EXPORT (0)匯出目錄用於DLL
IMAGE_DIRECTORY_ENTRY_IMPORT (1匯入目錄
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)除錯目錄
IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7)描述版權串
IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)機器值
IMAGE_DIRECTORY_ENTRY_TLS (9)Thread local storage目錄
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)Load configuration 目錄
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)Bound import directory目錄
IMAGE_DIRECTORY_ENTRY_IAT (12)Import Address Table輸入地址表目錄
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
是不是所有的目錄表都要關心呢?其實要把這些目錄表都研究清楚是個很大的課題,對於我們這個程式,只需關心第2個
元素,匯入目錄,它標識了我們的程式從其他模組匯入的函式資訊。因為我們要顯示一個訊息框,所以要匯入user32.dll
庫中的MessageBoxA函式,程式退出,又要匯入kernel32.dll庫中的ExitProcess函式,這個目錄表需要使用。然而上面已
說明每個目錄是一個IMAGE_DATA_DIRECTORY結構,該結構具有兩個成員,第一個成員表示目錄表的起始RVA地址,第二個
成員表示目錄表的長度。這兩個值要根據.rdata段實體來確定,暫時先不填寫。為了記錄該位置,我們先都填寫為x,即:
“xxxxxxxx","xxxxxxxx"。其餘的統統添零即可。
接下來是各段頭部,我們這裡有3個段,.text(程式碼段), .rdata(只讀資料段),data(全域性變數資料段)。每段是一
個IMAGE_SECTION_HEADER 結構,具有10個成員。首先我們來看.text段。
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;
成員1,8個位元組,表識該段的名稱,我們這裡是.text,那麼此值是他的ASKII碼應該為“2E74657874000000”。
成員2,4個位元組,表示有效程式碼所佔的位元組數。我們這裡所有程式碼數一下總共26h個,固此值為“26000000”。
成員3,4個位元組,表示在.text段對映到記憶體中的起始地址,那麼這個值如何得來呢?我們知道.text是緊跟PE結構後
的,然後整個PE結構對映到記憶體後佔的大小為1000h(因為PE結構小於1000h個位元組,而對齊力度粒度是1000h),那麼此值
便得到了,為“00100000”。
成員4,4個位元組,表示.text段在檔案中所佔的大小。因為我們的實際程式碼只有26h個位元組,那麼這個值是不是26h呢?
並不是,一定要注意段在檔案中的對齊粒度是200h,所以此值為“00020000”。
成員5,4個位元組,表示.text段在檔案中的起始地址,上面已經計算過PE檔案的總長度為400h,他實際上也就是.text
的起始偏移地址,此值為“00040000”。
成員6,7,8,9,均佔4個位元組,都僅用於目標檔案,我們這裡統統填為零。
成員10,4個位元組。包含標記以指示節屬性,比如節是否含有可執行程式碼、初始化資料、未初始資料,是否可寫、
可讀等。這個值實際上是二進位制位進行或運算得到的值。各二進位制位表示的意義如下:
bit 5 (IMAGE_SCN_CNT_CODE),置1,節內包含可執行程式碼。
bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA)置1,節內包含的資料在執行前是確定的。
bit 7 (IMAGE_SCN_CNT_UNINITIALIZED_DATA) 置1,本節包含未初始化的資料,執行前即將被初始化為0。一般是BSS.
bit 9 (IMAGE_SCN_LNK_INFO) 置1,節內不包含映象資料除了註釋,描述或者其他文件外,是一個目標檔案的一部分,
可能是針對連結器的資訊。比如哪個庫被需要。
bit 11 (IMAGE_SCN_LNK_REMOVE) 置1,在可執行檔案連結後,作為檔案一部分的資料被清除。
bit 12 (IMAGE_SCN_LNK_COMDAT) 置1,節包含公共塊資料,是某個順序的打包的函式。
bit 15 (IMAGE_SCN_MEM_FARDATA) 置1,不確定。
bit 17 (IMAGE_SCN_MEM_PURGEABLE) 置1,節的資料是可清除的。
bit 18 (IMAGE_SCN_MEM_LOCKED) 置1,節不可以在記憶體內移動。
bit 19 (IMAGE_SCN_MEM_PRELOAD)置1, 節必須在執行開始前調入。
Bits 20 to 23指定對齊。一般是庫檔案的物件對齊。
bit 24 (IMAGE_SCN_LNK_NRELOC_OVFL) 置1, 節包含擴充套件的重定位。
bit 25 (IMAGE_SCN_MEM_DISCARDABLE) 置1,程序開始後節的資料不再需要。
bit 26 (IMAGE_SCN_MEM_NOT_CACHED) 置1,節的 資料不得快取。
bit 27 (IMAGE_SCN_MEM_NOT_PAGED) 置1,節的 資料不得交換出去。
bit 28 (IMAGE_SCN_MEM_SHARED) 置1,節的資料在所有映象例程內共享,如DLL的初始化資料。
bit 29 (IMAGE_SCN_MEM_EXECUTE) 置1,程序得到“執行”訪問節記憶體。
bit 30 (IMAGE_SCN_MEM_READ) 置1,程序得到“讀出”訪問節記憶體。
bit 31 (IMAGE_SCN_MEM_WRITE)置1,程序得到“寫入”訪問節記憶體。
在我們這裡,因為這是程式碼段,所以bit 5 (IMAGE_SCN_CNT_CODE)位置1,一般程式碼段都含有初始化資料,那麼
bit 6 (IMAGE_SCN_CNT_INITIALIZED_DATA)位置1,有因為程式碼段的程式碼可以執行的,所以
bit 29 (IMAGE_SCN_MEM_EXECUTE) 置1,那麼這3個二進位制位進行或運算最終得到此成員值“20000060”。
這個整個.text頭就編寫完畢,按照上面的方法,分別在編寫.rdata段和.data段。因為要對齊,所以後面的程式碼用零補齊。
最後的編寫結果如下:
000B0 50 45 00 00 4C 01 03 00 00 00 00 00 00 00 00 00 PE..L.........
000C0 00 00 00 00 E0 00 02 00 0B 01 00 00 00 00 00 00 ....?.......
000D0 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ...............
000E0 00 00 00 00 00 00 40 00 00 10 00 00 00 02 00 00
000F0 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 ...............
00100 00 40 00 00 00 04 00 00 00 00 00 00 03 00 00 00 [email protected]
00110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00120 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ...............
00130 10 20 00 00 3C 00 00 00 00 00 00 00 00 00 00 00 ..<...........
00140 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00150 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
001A0 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00 .........text...
001B0 26 00 00 00 00 10 00 00 00 02 00 00 00 04 00 00 &............
001C0 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`
001D0 2E 72 64 61 74 61 00 00 92 00 00 00 00 20 00 00 .rdata..?... ..
001E0 00 02 00 00 00 06 00 00 00 00 00 00 00 00 00 00 ..............
001F0 00 00 00 00 40 00 00 40 2E 64 61 74 61 00 00 00
00200 16 00 00 00 00 30 00 00 00 02 00 00 00 08 00 00 ....0........
00210 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0 [email protected]
00220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00230 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00280 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00290 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
002F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00310 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00360 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00370 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
至此,我們已經完成了PE結構的編寫。是不是已經大功告成了呢?彆著急,為了讓我們寫的程式可以執行,我們還要
完成.text(程式碼段), .rdata(只讀資料段),data(全域性變數資料段)三個段的實體部分。
首先編寫.text段,他緊接著PE結構後面,但是我們如何編寫這些內容呢?前面已經說過,.text段中存放所有的可執行
程式碼(機器碼),我們可以通過先編寫彙編指令(呼叫MessageBoxA和ExitProcess兩個函式),然後反彙編出機器程式碼抄
到這裡就可以了。這裡有一點要注意,我們在為MessageBoxA函式傳遞引數時,如何將“Hello World!”字串以及訊息
框的標題“訊息框”字串傳過去呢?這就要用到我們的.data(全域性變數資料段)了,我們可以把這兩個字串放到這個段
中,然後把字串的偏移首地址作為引數傳給MessageBoxA即可。因為要以200h對齊,所以剩餘部分用零補齊,最終得到
的程式碼如下:
00400 6A 00 68 00 30 40 00 68 07 30 40 00 6A 00 E8 07
00410 00 00 00 6A 00 E8 06 00 00 00 FF 25 08 20 40 00 ...j.?...% @.
00420 FF 25 00 20 40 00 00 00 00 00 00 00 00 00 00 00 %. @...........
00430 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00450 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00460 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00480 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00490 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
004F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00500 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00510 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00520 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00530 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00540 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00550 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00560 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00570 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00580 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00590 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
005F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
接下來完成.rdata段,這個段非常重要,也有些繁瑣。因為這個程式我們只用完成資料目錄陣列的第二個元素匯入表
目錄,這個值我們當時沒有填寫,暫時添的x作為標記,現在我們要一併解決這個問題。前面已經說過,每個資料目錄具有
兩個成員,第一個成員表示目錄表的起始RVA地址,第二個成員表示目錄表的長度。對於我們這個匯入表目錄來說,他指的
就是匯入表了,這個匯入表實際上是一個IMAGE_IMPORT_DESCRIPTOR 結構陣列,每個結構包含PE檔案引入函式的一個相關
DLL的資訊。比如,如果該PE檔案從10個不同的DLL中引入函式,那麼這個陣列就有10個成員。該陣列以一個全0的成員結尾。
那麼.rdata段的內容應該就是IMAGE_IMPORT_DESCRIPTOR 結構陣列了,我們這裡用到user32.dll和kennel32.dll兩個庫,
那麼這個結構陣列的元素個數就是2了。第一個元素的成員1的地址是不是就是.rdata的首地址呢?然後依次完成其餘成員?
不是這樣的,這裡有個規律,我們匯入了多少個函式,那麼就要空出8乘以匯入函式個數個字元的空間(為什麼要空些空間
呢?要放什麼呢?一會便可知曉),在其後才是IMAGE_IMPORT_DESCRIPTOR結構成員的首地址。在這裡我們匯入了2個函式,
那麼應該空2 * 8 = 16個字元,.rdata段的首地址是600h,那麼IMAGE_IMPORT_DESCRIPTOR結構成員的首地址應該是610h。
這時我們便得到了資料目錄陣列的第二個元素匯入表目錄結構成員1的值,是不是就是“10060000”呢?不是的,因為這裡
的地址是RVA(對映後記憶體的地址),而檔案的600h被對映為2000h,所以610h被對映後的RVA為2010h,故此值為
“10200000”,我們可以把其中的“xxxxxxxx”替換為“10200000”。資料目錄陣列的第二個元素匯入表目錄結構成員
2的值是指IMAGE_IMPORT_DESCRIPTOR 結構陣列的長度,IMAGE_IMPORT_DESCRIPTOR 結構長度是20個位元組,我們這裡的
MAGE_IMPORT_DESCRIPTOR 結構陣列元素是2個,並且以全零結尾,那麼總長度就是3 * 20 = 60byte,轉換成十六進位制是
3C,我們可以把另一個“xxxxxxxx”替換為“3C000000”。
下面我們填寫匯入表即IMAGE_IMPORT_DESCRIPTOR 結構陣列的成員。第一個陣列元素的有關user32.dll資訊,5個成員
中:
成員1,4個位元組,他實際上是指向一個 IMAGE_THUNK_DATA 結構陣列的RVA,而IMAGE_THUNK_DATA 結構陣列記錄所有從
某個.dll庫中匯入函式名稱的RVA。IMAGE_THUNK_DATA 結構位於IMAGE_IMPORT_DESCRIPTOR 結構之後,由
IMAGE_IMPORT_DESCRIPTOR 結構陣列的起始地址和長度可以得到IMAGE_THUNK_DATA 結構陣列的起始地址。即610h + 3Ch =
64Ch,那麼這個IMAGE_IMPORT_DESCRIPTOR 結構陣列的第一個元素的成員1的值是不是就是64Ch呢?並不是的,因為這是一個
RVA值,所以對映後的值應該是“4C200000”。
成員2,成員3各4各位元組,用處不大,我們用零填充。
成員4,4個位元組,是指向DLL名字的RVA,即指向DLL名字的指標,也是一個ASCIIZ字串。我們將在後面編寫
“user32.dll”字串,計算得到其RVA為“6A200000”。
成員5,4個位元組,指向一個 IMAGE_THUNK_DATA 結構陣列的RVA,同成員1一樣,但是此IMAGE_THUNK_DATA 陣列結構保
存了所有匯入函式的指標。PE檔案被裝載到記憶體時,用引入函式真實地址來替代由這裡的IMAGE_THUNK_DATA 數組裡的元
素值。那麼這裡填哪裡的地址呢?這就要用到我們剛剛空下的空間了。我們將此值設為“08200000”。
按照上面方法填寫匯入表即IMAGE_IMPORT_DESCRIPTOR 結構陣列的成員。第二個陣列元素的有關kennel.dll資訊的5個
成員的值,我們這裡分別為“5420000”,“00000000”,“00000000”,“84200000”,“00200000”。
最後以全零結尾這個IMAGE_IMPORT_DESCRIPTOR 結構陣列,即:
“00000000”,“00000000”,“00000000”,“00000000”,“00000000”。
下面填寫有關user32.dll資訊的IMAGE_IMPORT_DESCRIPTOR 結構陣列的IMAGE_THUNK_DATA結構陣列。 他記錄了所有從
user32.dll庫中匯入函式名稱的RVA。我們這裡只有MessageBoxA函式,我們可以在後面編寫此字串,並得到他的地址,
這裡為“5C200000”。緊接著以“00000000”結束user32.dll中匯入函式的輸入。
然後是kennel32.dll資訊的IMAGE_IMPORT_DESCRIPTOR 結構陣列的IMAGE_THUNK_DATA結構陣列。他記錄了所有從
kennel32.dll庫中匯入函式名稱的RVA。我們這裡只有ExitProcess函式,我們可以在後面編寫此字串,並得到他的地址,
這裡為“76200000”。緊接著以“00000000”結束kennel32.dll中匯入函式的輸入。
最後完成這些字串的輸入,之後要對齊.rdata段的剩餘部分,剩餘部分用零填充。最終得到的程式碼如下:
000600 A2 CA 81 7C 00 00 00 00 8A 05 D5 77 00 00 00 00 ⑹亅....?誻....
000610 4C 20 00 00 00 00 00 00 00 00 00 00 6A 20 00 00 L ..........j ..
000620 08 20 00 00 54 20 00 00 00 00 00 00 00 00 00 00 ..T ..........
000630 84 20 00 00 00 20 00 00 00 00 00 00 00 00 00 00 ?... ..........
000640 00 00 00 00 00 00 00 00 00 00 00 00 5C 20 00 00 ............\ ..
000650 00 00 00 00 76 20 00 00 00 00 00 00 9D 01 4D 65 ....v ......?Me
000660 73 73 61 67 65 42 6F 78 41 00 75 73 65 72 33 32 ssageBoxA.user32
000670 2E 64 6C 6C 00 00 80 00 45 78 69 74 50 72 6F 63 .dll..€.ExitProc
000680 65 73 73 00 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C ess.kernel32.dll
000690 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0006F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000700 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000710 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000720 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000730 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000740 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000750 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000760 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000770 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000780 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000790 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0007F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
最後一個是.data段,這個段非常簡單,就是MessageBoxA所需的引數,訊息框的標題和內容,最終程式碼如下:
(注意對齊問題,補足200h位元組。)
000800 CF FB CF A2 BF F2 00 48 65 6C 6C 6F 2C 20 57 6F 訊息框.Hello, Wo
000810 72 6C 64 20 21 00 00 00 00 00 00 00 00 00 00 00 rld !...........
000820 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000830 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000840 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000850 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000860 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000870 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000880 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000890 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0008F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000900 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000910 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000920 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000930 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000940 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000950 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000960 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000970 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000980 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000990 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0009F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
好了,到此為止一個完整的顯示Hello World!的可執行程式就完成了,趕快雙擊執行一下吧...費了這麼大勁就這麼個小
功能,是不是有點事倍功半呢?其實手寫這麼個程式只是為了更加熟練掌握PE結構,相信當您真正的手工完成了這個程式,
您對PE結構一定有一個非常深刻的理解。(提示:請按照以上步驟完成這個程式,之後再回頭從頭到尾聯絡上下文仔細看一遍,
因為很多地方都是前後關聯緊密的,只作一遍,或只讀一遍是很難融匯貫通的。)
由於水平和時間的原因,難免有許多問題,或者表述不清楚的地方,請高手不吝賜教。
相關推薦
【EXE PE】初識pe結構--手寫可執行程式
【文章標題】: 手寫可執行程式 【文章作者】: dncwbc 【作者郵箱】: [email protected] 【作者QQ號】: 182445917 【軟體名稱】: Hello World! 【軟體大小】: 2.5K 【下載地址】: 自己搜尋下載 【編寫語言】
【makefile學習記錄2】初識makefile結構
1、makefile的意義及定義: 用於定義原始檔間的依賴關係(通過自己特定的語法),通過這種依賴關係來說明如何編譯各個原始檔並生成可執行檔案。 依賴的定義:(①定義依賴規則時,依賴條件可以省略②可以把command1不用,此時分號可去掉) targets : prerequisite ; c
【學習筆記】初識FreeMarker簡單使用
als 大小 宋體 屬性 list mage equal port template 楔子: 之前在和同事討論,同事說“jsp技術太古老了,有幾種頁面技術代替,比如FreeMarker、Velocity、thymeleaf,jsp快廢棄了……”雲雲。我這一聽有點心虛…
【TOJ 1224】數據結構練習題――後序遍歷二叉樹
給定 esc 中序遍歷 二叉 以及 max 構造 參數 練習 Description 給定一顆二叉樹,要求輸出二叉樹的深度以及後序遍歷二叉樹得到的序列。本題假設二叉樹的結點數不超過1000。 Input 輸入數據分為多組,第一行是測試數據的組數n,下面的n行分別代表一棵二叉
iOS【安全攻防】初識匯編
常識 ... 定義 特點 總線 執行 重要 機器語言 play 今天我們來學習以下內容: 匯編概述 總線 進制 寄存器 我們在學習逆向開發之前,我們要了解一個基本的逆向原理。首先我們是逆向iOS系統上面的APP,那麽我們知道,一個APP安裝在手機上面
【TOJ 5438】數據結構實驗:生成BST
代碼 二叉 return tree time 函數 結點 man 完成 描述 給定一個從小到大排序的序列,將其轉換成一棵二叉搜索樹。 BST定義: 二叉搜索樹,又稱為二叉排序樹,它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於
【Normal Form】資料庫表結構設計所遵從的正規化
目錄 1. 第一正規化:原子性,不可再分 1.1. 是否為原子性 1.2. 典型的例子:多個資訊用分隔符拼接記錄 2. 第二正規化:非主鍵必須完全依賴於主鍵,而不能只依賴於主鍵的
【c語言】用迴圈結構輸出下列數字金字塔
#include<stdio.h> void main(){ int i,j,k; for(i=1;i<=10;i++){ for(j=10;j>i;j--) printf(" "); for(k=1;k<=(2*i-1)/2
【併發程式設計】CPU cache結構和快取一致性(MESI協議)
一、cache cpu cache已經發展到了三級快取結構,基本上現在買的個人電腦都是L3結構。 1. cache的意義 為什麼需要CPU cache?因為CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘週期內,CPU常常需要等待主存,浪費資源。所以cac
【Laravel 框架】之 目錄結構
目錄 目錄結構分析 1. app目錄 專案的核心目錄,主要用於存放核心程式碼,也包括控制器、模型。 比如控制器存放如下位置:app/Http/Controllers 模型檔案存放位置,模型檔案
【易錯】C語言結構體記憶體對齊問題
對於一個結構體的位元組數大家有沒有遇到什麼疑問呢? 先看一個結構體: typedef struct Test { char a1; int a2; char a3; short a4; }Test_T; 在32位編譯系統下這一個結構體的位元組數是多少呢?是1+4
【Objective-C】類與結構體的區別
只能在類裡面寫方法,不能在結構體裡面寫方法類——物件,結構體——值類——引用型別 位於棧上的指標(引用)位於堆上的實體物件結構體——值型別 例項直接位於棧中拷貝行為: classname *a = b
【知識積累】C#中結構體和類的區別
【類】 類是對現實生活中一類具有共同特徵的事物的抽象。類的實質是一種資料型別,類似於int、char等基本型別,不同的是它是一種複雜的資料型別。因為它的本質是型別,而不是資料,所以不存
【Python初學】GUI猜數遊戲+打包(生成.exe可執行程式)
Python初學——GUI猜數遊戲 對於像我一樣的Python初學者,做練習總是像學C一樣從命令視窗輸出我們的程式,但是這樣子很無聊,不會變著花樣來。所以我在做Python練習時候,練習了一些有遊戲題目,但都是命令視窗式的遊戲,所以我就想把這種遊戲圖形化出來,
【UOJ #228】 基礎資料結構練習題
Description 給出一個長度為 nn 的數列 A,接下來有 m 次操作,操作有三種: 對於所有的 i∈[l,r],將 Ai 變成 Ai+x。 對於所有的 i∈[l,r],將 Ai 變成 ⌊
【機房重構】——七層結構
【前言】 七層就是再三層的基礎上與設計模式相結合演化而來,加入了外觀模式、抽象工廠模式加反射,介面層,實體層。其目的是為了降低各個層之間的耦合度。明確每一層的作用,為後面的學習打好基礎。 【實質】 一、七層包圖
【Java基礎】Jar包結構結構分析和操作詳解
一 JAR包結構分析 JAR(Java Archive FIle)Java歸檔檔案,是Java標準的文件格式,是一個或多個Java位元組碼檔案的打包壓縮檔案,採用常見的ZIP壓縮演算法,和ZIP檔案十分類似,可以直接解壓。 JAR檔案主要用來壓縮和釋
【軟體開發底層知識修煉】二十 深入理解可執行程式的結構
上一篇文章記錄了GDB除錯從入門到熟練掌握的學習全過程。點選連結檢視:【軟體開發底層知識修煉】十九 GDB除錯從入門到熟練掌握超級詳細實戰教程學習目錄 還記得在以前的學習Binutils工具的時候,學習了很多工具來檢視可執行程式的結構,那個時候並沒有詳細說明程式的
【機器學習演算法實現】kNN演算法__手寫識別——基於Python和NumPy函式庫
【機器學習演算法實現】系列文章將記錄個人閱讀機器學習論文、書籍過程中所碰到的演算法,每篇文章描述一個具體的演算法、演算法的程式設計實現、演算法的具體應用例項。爭取每個演算法都用多種語言程式設計實現。所
【藍橋杯】根據給定的手機尾號(4位),按照一定的規則來打分
import java.util.Scanner; /** * *30年的改革開放,給中國帶來了翻天覆地的變化。2011全年中國手機產量約為11.72億部。手機已經成為百姓的基本日用品! * *給手機選個好聽又好記的號碼可能是許多人的心願。但號源有限,只能輔以有