文章來源:
www.xfocus.net文章提交:
watercloud (watercloud_at_xfocus.org)
本文是我在學習PE文件格式時打造小型PE文件的學習歷程的一個記錄性文字,最終我的目標是完全手工打造一個小的EXE文件,第一個為1024字節,最終改造到512字節。
廢話不多說,開始工作:
寫個匯編程序:
start:
xor ebx,ebx
jz tcall
tjmp:
pop eax
push ebx
push eax
add eax,7
push eax
push ebx
push ExitProcess
push MessageBoxA
ret
tcall: call tjmp
db "ExeDIY",0
db "Hello!",0
用masm32編譯鏈接后生成Hello.exe
該exe為2k大小,為了把它大小裁減下來,用UltraEdit新建一個16進制文件,
然后我們參考原來的文件來手工創建一個新的hello.exe。
文件如附件最終為1k大小,已經在WinXp/2k/Nt上測試。
我們作的主要工作是:
1. 把Dos Stub去掉;按照新的信息構造文件頭。
2. 新文件對齊值為0x200,在0x200處填入指令信息。
3. 把數據段去掉。(但由于WinXp上section個數不能為一個,否則報不是Win32程序,所以仍
然保留了數據段的section信息,但把它指向都改為了.text段)
4. 由于數據段已經被去掉,需要將Imports相關信息搬入.text段0x300處。并從新計算
輸入表的地址和輸入地址表的地址及Import結構相關鏈表和指針的地址。由于我們在0x300
處,執行時內存映像中地址為0x401100,需要按照0x1100為參照修改鏈表的各個值。
在制作過程中發現文件對齊WinXp不能小于0x200,否則為非法win32程序,而NT可以時32字節倍數的對齊
,如果只針對NT的話可以把文件構造的更小.
雖然我們只用到1個段,但pe文件還是必須要有兩個section!
修改Import相關地址后必須修改指令信息中的地址。因為每次對API的引用都是在操作編譯器提供的一個地址,
該地址存放的時一條jmp [xxxx] 的指令, xxxx 地址即為Import的函數指針數組,Loader在加載程序時
會根據調用的DLL函數的具體加載地址來填充這里,但我們已經把這一地址移到了0x401100,要作修正。
整個文件dump出來的內容如下(WinHex真是強大呀!)。具體意義在后面描述。
你可以在這里下載它:
http://watercloud.diy.163.com/others/Hello1.exeOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 4D 5A D5 E2 C0 EF BB F9 B1 BE B6 BC C3 BB D3 D0 MZ這里基本都沒有
00000010 D3 C3 2C 44 4F 53 B1 A3 C1 F4 B5 C4 CD B7 B2 BF 用,DOS保留的頭部
00000020 D0 C5 CF A2 2C CE D2 C3 C7 B6 BC B2 BB D3 C3 2E 信息,我們都不用.
00000030 D2 BB D6 B1 B5 BD D5 E2 C0 EF C0 B2 40 00 00 00 一直到這里啦@...
00000040 50 45 00 00 4C 01 02 00 00 00 00 00 00 00 00 00 PE..L...........
00000050 00 00 00 00 E0 00 0F 01 0B 01 00 00 00 02 00 00 ....?..........
00000060 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 ................
00000090 00 30 00 00 00 02 00 00 00 00 00 00 02 00 00 00 .0..............
000000A0 00 01 00 00 00 00 00 00 00 01 00 00 00 10 00 00 ................
000000B0 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 10 11 00 00 3C 00 00 00 00 00 00 00 00 00 00 00 ....<...........
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 00 00 00 00 00 11 00 00 10 00 00 00 ................
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 00 00 00 00 00 00 00 43 6C 6F 75 64 00 00 00 ........Cloud...
00000140 00 02 00 00 00 10 00 00 00 02 00 00 00 02 00 00 ................
00000150 00 00 00 00 00 00 00 00 00 00 00 00 60 00 00 60 ............`..`
00000160 4E 53 46 4F 43 55 53 00 00 02 00 00 00 20 00 00 NSFOCUS...... ..
00000170 00 02 00 00 00 02 00 00 00 00 00 00 00 00 00 00 ................
00000180 00 00 00 00 60 00 00 60 D5 E2 C0 EF BF AA CA BC ....`..`這里開始
00000190 CA C7 B6 D4 C6 EB CC EE B3 E4 D0 C5 CF A2 A3 AC 是對齊填充信息,
000001A0 C3 BB D3 D0 D3 C3 2E 20 D2 BB D6 B1 B5 BD 30 78 沒有用. 一直到0x
000001B0 32 30 30 68 BF AA CA BC CE AA 2E 74 65 78 74 28 200h開始為.text(
000001C0 D2 B2 BE CD CA C7 43 6C 6F 75 64 29 B6 CE BF AA 也就是Cloud)段開
000001D0 CA BC 2C CE D2 C3 C7 B0 D1 20 49 6D 70 6F 72 74 始,我們把 Import
000001E0 B1 ED D2 B2 B7 C5 B5 BD D5 E2 B8 F6 B6 CE C0 EF 表也放到這個段里
000001F0 C0 B4 C1 CB 2F 2F BA C7 BA C7 20 3A 29 20 20 20 來了//呵呵 :)
00000200 33 DB 74 13 58 53 50 83 C0 07 50 53 68 2A 10 40 3踭.XSP兝.PSh*.@
00000210 00 68 30 10 40 00 C3 E8 E8 FF FF FF 45 78 65 44 .h0.@.描?ExeD
00000220 49 59 00 48 65 6C 6C 6F 21 00 FF 25 00 11 40 00 IY.Hello!.%..@.
00000230 FF 25 08 11 40 00 00 00 00 00 00 00 00 00 00 00 %..@...........
00000240 B4 FA C2 EB BE CD D5 E2 C3 B4 D2 BB B5 E3 A3 AC 刖駝餉匆壞悖?
00000250 D5 E2 C0 EF D3 D6 CA C7 CC EE B3 E4 A3 AC 49 6D 這里又是填充,Im
00000260 70 6F 72 74 B1 ED CE D2 C3 C7 D2 B2 B7 C5 B5 BD port表我們也放到
00000270 D5 E2 B8 F6 B6 CE C0 B4 C1 CB A3 AC CE AA C1 CB 這個段來了,為了
00000280 BC C6 CB E3 B5 D8 D6 B7 B7 BD B1 E3 CE D2 C3 C7 計算地址方便我們
00000290 B0 D1 CB FC B7 C5 B5 BD 30 78 33 31 30 68 B4 A6 把它放到0x310h處
000002A0 C1 CB C6 E4 B6 D4 D3 A6 C4 DA B4 E6 B5 D8 D6 B7 了其對應內存地址
000002B0 CE AA A3 BA 30 78 34 30 31 31 30 30 00 00 00 00 為:0x401100....
000002C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000002D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000002E0 00 00 00 00 00 00 00 00 B5 BD D5 E2 C0 EF BF AA ........到這里開
000002F0 CA BC 49 6D 70 6F 72 74 B1 ED D0 C5 CF A2 A3 BA 始Import表信息:
00000300 5C 11 00 00 00 00 00 00 78 11 00 00 00 00 00 00 \.......x.......
00000310 4C 11 00 00 00 00 00 00 00 00 00 00 6A 11 00 00 L...........j...
00000320 00 11 00 00 54 11 00 00 00 00 00 00 00 00 00 00 ....T...........
00000330 86 11 00 00 08 11 00 00 00 00 00 00 00 00 00 00 ?..............
00000340 00 00 00 00 00 00 00 00 00 00 00 00 5C 11 00 00 ............\...
00000350 00 00 00 00 78 11 00 00 00 00 00 00 AB 00 45 78 ....x.......?Ex
00000360 69 74 50 72 6F 63 65 73 73 00 4B 45 52 4E 45 4C itProcess.KERNEL
00000370 33 32 2E 64 6C 6C 00 00 BB 01 4D 65 73 73 61 67 32.dll..?Messag
00000380 65 42 6F 78 41 00 55 53 45 52 33 32 2E 64 6C 6C eBoxA.USER32.dll
00000390 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000003A0 BA C3 C1 CB A3 AC BD E1 CA F8 A3 AC D5 E2 C0 EF 好了,結束,這里
000003B0 CA C7 D7 EE BA F3 D2 BB B8 F6 B6 D4 C6 EB D3 C3 是最后一個對齊用
000003C0 B5 C4 CC EE B3 E4 D6 B5 C1 CB A1 A3 20 20 20 20 的填充值了。
000003D0 77 61 74 65 72 63 6C 6F 75 64 40 6E 73 66 6F 63 watercloud@nsfoc
000003E0 75 73 2E 63 6F 6D 20 20 32 30 30 32 C4 EA 31 32 us.com 2002年12
000003F0 D4 C2 31 38 C8 D5 20 20 20 CD FB B8 AB D5 FD 21 月18日 望斧正!
PE文件整個結構如圖:
+-------------------+
| DOS頭 |
+-------------------+
| File-Header |
+-------------------+
| Optional-Header |
|- - - - - - - - - -|
| |----------------+
| Data Directories | |
| | |
| ( RVA, size) |-------------+ |
| |
| |---------+ | |
| | | | |
+-------------------+ | | |
| |-----+ | | |
| Section-Headers | | | | |
| |--+ | | | |
| | | | | | |
+-------------------+<-+ | | | |
| | | <-+ | |
| Section Data 1 | | | |
| | | <-----+ |
+-------------------+<----+ |
| | |
| Section Data 2 | |
| | <--------------+
+-------------------+
相關結構摘錄MSDN和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 //除了這項和e_magic基本都可以隨便填
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
我們關心的為 e_lfanew,該地址指向了PE頭結構在文件中的偏移.如我們的這個文件該值為
0x40,而0x40開始就是PE頭的開始,標志為"PE"兩個字符.
PE頭部信息包括一個標志值 "PE\0\0",和FileHeader及OptionHeader,結構如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //總是"PE\0\0"
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
FileHeader的類型定義如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections; //Section個數,WinXp不能少于2(即使真正數據段只有一個),其他平臺未知.
DWORD TimeDateStamp; //可以隨便填
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
OptionHeader類型定義如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //入口地址,佷重要
DWORD BaseOfCode; //代碼段起始地址
DWORD BaseOfData; //數據段起始地址.
DWORD ImageBase; //NT/2k/XP基本都是0x400000
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; //這個別亂填,要不然可能又是非法Win32程序,MajorSubsystemVersion也是.
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //以下DataDirectory
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //每個地址都是Virtual,就是程序加載后該地址在內存中相對于ImageBase的偏移,帶virtual的地址是這樣
//在文件中的偏移為 Addr - 對應Section的VirtualAddress + 對應Section的文件內起始偏移
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
其中各個項意義如下:
1 Export table address and size
2 Import table address and size
3 Resource table address and size
4 Exception table address and size
5 Certificate table address and size
6 Base relocation table address and size
7 Debugging information starting address and size
8 Architecture-specific data address and size
9 Global pointer register relative virtual address
10 Thread local storage (TLS) table address and size
11 Load configuration table address and size
12 Bound import table address and size
13 Import address table address and size
14 Delay import descriptor address and size
15 Reserved
16 Reserved
各Section-Header結構如下:
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;
對應這一結構我們的這個文件各重要字段為:
46 Section個數 : 2
5c 代碼大小 : 200
68 OEP : 1000
6c BaseOfCode : 1000
74 ImageBase: 400000
78 Section數據內存對齊: 1000
7c Section數據文件對齊: 200
90 Image在內存整個空間大小: 3000 /2000也行,起始我們只用了1000,但填這個值提示非法Win32程序:(
94 頭部大小: 200,好像填400,800也沒關系.
B4 DataDirectory中的數目 : 10
c0 輸入表地址: 1110
11c 輸入地址表地址: 1100
B8-138 為 DataDirectory數組
138 開始為.text節頭部 ,在我們這里名字就叫Cloud啦:)
140 size :36
144 Vaddr:1000
148 RawSize: 200
14c PointToRawData: 200
15c 屬性 : 60000060 可讀寫執行段
160 開始為第二個節,這個節我們沒有用,但是系統不允許只有一個節,放到這里作樣子的了,騙騙加載器.
200 開始為真正的代碼開始
300 開始為Import相關鏈表.
程序加載后內存中情況為
400000開始: MZ . . . 文件頭部信息
401000開始: 對應為文件0x200開始的內容
401100開始: 對應為文件0x300開始的Import表 ,但此時加載器已經把各外部函數地址填好了.
ExitPorcess函數的地址填在 401100處
MessageBoxA函數的地址填在 401108處
--------------------改造----------------------
打開UltraEdit再次重寫,這次的目標是512字節:
你可以在這里下載它:
http://watercloud.diy.163.com/others/Hello2.exeOffset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 4D 5A 33 DB 74 13 58 53 50 83 C0 07 50 53 68 2C MZ3踭.XSP兝.PSh,
00000010 00 40 00 68 32 00 40 00 C3 E8 E8 FF FF FF 45 78 .@.h2.@.描? Ex
00000020 65 44 49 59 00 48 65 6C 6C 6F 21 00 FF 25 20 11 eDIY.Hello!.% .
00000030 40 00 FF 25 28 11 40 00 00 00 00 00 40 00 00 00 @.%(.@.....@...
00000040 50 45 00 00 4C 01 02 00 00 00 00 00 00 00 00 00 PE..L...........
00000050 00 00 00 00 70 00 0F 01 0B 01 00 00 00 02 00 00 ....p...........
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 40 00 00 10 00 00 00 02 00 00 ......@.........
00000080 00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 ................
00000090 00 30 00 00 00 02 00 00 00 00 00 00 02 00 00 00 .0..............
000000A0 00 01 00 00 00 00 00 00 00 01 00 00 00 10 00 00 ................
000000B0 00 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 30 11 00 00 3C 00 00 00 2E 74 65 78 74 00 00 00 0...<....text...
000000D0 00 02 00 00 00 10 00 00 00 02 00 00 00 01 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 60 00 00 60 ............`..`
000000F0 2E 72 64 61 74 61 00 00 02 00 00 00 00 20 00 00 .rdata.........
00000100 00 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000110 00 00 00 00 60 00 00 60 00 00 00 00 00 00 00 00 ....`..`........
00000120 7C 11 00 00 00 00 00 00 98 11 00 00 00 00 00 00 |.......?......
00000130 6C 11 00 00 00 00 00 00 00 00 00 00 8A 11 00 00 l...........?..
00000140 20 11 00 00 74 11 00 00 00 00 00 00 00 00 00 00 ...t...........
00000150 A6 11 00 00 28 11 00 00 00 00 00 00 00 00 00 00 ?..(...........
00000160 00 00 00 00 00 00 00 00 00 00 00 00 7C 11 00 00 ............|...
00000170 00 00 00 00 98 11 00 00 00 00 00 00 AB 00 45 78 ....?......?Ex
00000180 69 74 50 72 6F 63 65 73 73 00 4B 45 52 4E 45 4C itProcess.KERNEL
00000190 33 32 2E 64 6C 6C 00 00 BB 01 4D 65 73 73 61 67 32.dll..?Messag
000001A0 65 42 6F 78 41 00 55 53 45 52 33 32 2E 64 6C 6C eBoxA.USER32.dll
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
加載后內存空間布局如下:
ImageBase: 400000
程序入口地址 : 400000 即從文件偏移000開始, 我們把代碼56字節放到了dos頭部
里了.
.text : 401000 -- 對應 文件 off 100 (但程序加載時從000開始加載的)
.data : 沒有用
Import表信息在401130處(對應為文件130)
Import1
Org -> 40116c -> 40117c -> ExitProcess
Name -> 40118a -> Kernel32.dll
First -> 401120
Import2
Org -> 401174 -> 401198 -> MessageBoxA
Name -> 4011a6 -> User32.dll
First -> 401128
dll加載后在內存 401120 處存放了ExitProcess的地址
內存401128 處存放了MessageBoxA的地址
程序代碼的jmp表就是:
40002c : jmp 401120
400032 : jmp 401128
然后程序里調用API就使用了40002c和400032這兩個地址
對應文件最終被加載了兩分,
一個在400000 系統自身要使用的表結構,但我們同時把執行代碼也
放到了這里,并修改了程序入口指向這里.
另一個在401000處,這里我們主要利用了這個地址除了了Import表.
本來想使用400000處的導入表信息,但我把相關指針修改為000后系統報
非法win32程序,就沒有辦法了.
文件布局如下:
00-40 : dos頭,但我們把代碼也放到這里了.
40-58 : PE-File-Header
58-C8 : PE-Option-Header,其中B8-C8為DataDirectory (ExportDirectory和
ImportDirectory)
我們把DataDirectory已經減到不能在小了,無論如何要ImportDirectory.
c8-F0 : .text Section-Header
F0-118 : .rdata Section-Header (沒有用處的節信息,但系統不允許只有一個節只好
裝模作樣放一個.)
好了真正的PE頭部信息結束,后面本來是 文件對齊的填充,我們利用這里存放
ImportTable.
120-1B0: Import-Table
而且從中可以看到,就算把DataDirectory都去掉(WinXp去掉后會被認為非法Win32程
序),把Section減到只有
1個(Winxp最低也要兩個才是合法文件) 文件頭部信息也需要 0x88個字節. 如果支持
100文件對齊,并且不使用
Import表那么可以將PE文件做到256字節 ,但對于讓xp也能運行512字節應該是極限了.
最后提供一個512字節的小工具:鎖屏(Win2k/Xp)
http://watercloud.diy.163.com/others/LockWin.exe參考:
http://watercloud.nease.net/others/pe.txt 英文 作者不詳
http://watercloud.diy.163.com/others/PE文件格式分析.pdf 中文 作者不詳
MicroSoft MSDN
本文只是一個記錄性文字,且本人水平很差,望大家斧正。
watercloud
2002 - 12 - 21