PE文件结构(五)
下一个使节头。首先我们做代码节的,代码节将包含前面所编的汇编语句。它有32字节长,所以代码节也就是这么长。节头从0x138处开始,有0x28字节长:
Name 2e 63 6f 64 65 00 00 00 ; ".code"的ASCII码值 VirtualSize 00 00 00 00 ; 未用 VirtualAddress ?? ?? ?? ?? ; 待定 SizeOfRawData 20 00 00 00 ; 代码的大小 PointerToRawData ?? ?? ?? ?? ; 待定 PointerToRelocations 00 00 00 00 ; 未用 PointerToLinenumbers 00 00 00 00 ; 未用 NumberOfRelocations 00 00 ; 未用 NumberOfLinenumbers 00 00 ; 未用 Characteristics 20 00 00 60 ; 代码节,可执行,可读 第二节将包含数据。节头开始于0x160处,有0x28字节长: Name 2e 64 61 74 61 00 00 00 ; ".data"的ASCII码值 VirtualSize 00 00 00 00 ; 未用 VirtualAddress ?? ?? ?? ?? ; 待定 SizeOfRawData ?? ?? ?? ?? ; 待定 PointerToRawData ?? ?? ?? ?? ; 待定 PointerToRelocations 00 00 00 00 ; 未用 PointerToLinenumbers 00 00 00 00 ; 未用 NumberOfRelocations 00 00 ; 未用 NumberOfLinenumbers 00 00 ; 未用 Characteristics 40 00 00 c0 ; 已初始化的,可读,可写 下一个字节位于0x188处,但节需要按32字节(的倍数)对齐(因为我是这样选择的),所以我们需要添一些(0)字节直到0x1a0处: 00 00 00 00 00 00 ; 填充的 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 现在第一节,就是上面所汇编的代码节,“到”了。它开始于0x1a0处,有0x20字节长: 6A 00 ; push 0x00000000 68 ?? ?? ?? ?? ; push offset _written 6A 0D ; push 0x0000000d 68 ?? ?? ?? ?? ; push offset hello_string 6A F5 ; push 0xfffffff5 2E FF 15 ?? ?? ?? ?? ; call dword ptr cs:__imp__GetStdHandle@4 50 ; push eax 2E FF 15 ?? ?? ?? ?? ; call dword ptr cs:__imp__WriteConsoleA@20 C3 ; ret 因为这一节的长度(刚好32字节),在下一节(数据节)前我们不需要填充任何字节。下一节到了,从0x1c0处开始: 68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n"的ASCII码值 00 00 00 ; 填充几个0以和_written对齐 00 00 00 00 ; _written 现在剩下的只有输入目录了。本文件将从"kernel32.dll"库中输入2个函数,输入目录将从本节的变量后面立即开始。首先我们先将上面的数据按32字节对齐: 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的 在0x1e0处开始输入描述(IMAGE_IMPORT_DESCRIPTOR): OriginalFirstThunk ?? ?? ?? ?? ; 待定 TimeDateStamp 00 00 00 00 ; 未绑定 ForwarderChain ff ff ff ff ; 无中转 Name ?? ?? ?? ?? ; 待定 FirstThunk ?? ?? ?? ?? ; 待定 我们需要用一个0字节项来结束输入目录(我们现在位于0x1f4): OriginalFirstThunk 00 00 00 00 ; 结束符号 TimeDateStamp 00 00 00 00 ; ForwarderChain 00 00 00 00 ; Name 00 00 00 00 ; FirstThunk 00 00 00 00 ; 现在只剩下DLL名字,还有2个换长,以及换长数据和函数名字了。但现在我们真的很快就要完成了。 DLL名字,以0结尾,开始于0x208处: 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll"的ASCII码值 00 00 00 ; 填充到32位边界 原始第一个换长,开始于0x218处: AddressOfData ?? ?? ?? ?? ; "WriteConsoleA"函数名的RVA AddressOfData ?? ?? ?? ?? ; "GetStdHandle"函数名的RVA 00 00 00 00 ; 结束符号 第一个换长就是同样的列表,开始于0x224处: (__imp__WriteConsoleA@20, at 0x224) AddressOfData ?? ?? ?? ?? ; "WriteConsoleA"函数名的RVA (__imp__GetStdHandle@4, at 0x228) AddressOfData ?? ?? ?? ?? ; "GetStdHandle"函数名的RVA 00 00 00 00 ; 结束符号 现在剩下的只有输入名字(IMAGE_IMPORT_BY_NAME)形式的两个函数名了。我们现处于0x230字节。 01 00 ; 序数,不需要正确 57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00 ; "WriteConsoleA"的ASCII码值 02 00 ; 序数,不需要正确 47 65 74 53 74 64 48 61 6e 64 6c 65 00 ; "GetStdHandle"的ASCII码值 Ok, 这就全部结束了。下一个字节,我们并不真正需要,是0x24f。我们必须将节填充到0x260处: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的 00 ------------ 我们已经完成了。因为我们已经知道了所有的字节偏移量,我们可以应用我们的修正到所有原先被用“??”符号标为“未知”的地址和大小了。 我将不强迫你一步一步地去读它(很好懂的),只直接给出结果来: ------------ DOS-头, 开始于0x0: 00 | 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 20 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 | 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00 签名, 开始于0x40: 50 45 00 00 文件头, 开始于0x44: Machine 4c 01 ; i386 NumberOfSections 02 00 ; 代码和数据 TimeDateStamp 00 00 00 00 ; 谁管它? PointerToSymbolTable 00 00 00 00 ; 未用 NumberOfSymbols 00 00 00 00 ; 未用 SizeOfOptionalHeader e0 00 ; 常量 Characteristics 02 01 ; 可执行于32位机器上 可选头, 开始于0x58: Magic 0b 01 ; 常量 MajorLinkerVersion 00 ; 我是 0.0版 :-) MinorLinkerVersion 00 ; SizeOfCode 20 00 00 00 ; 32字节代码 SizeOfInitializedData a0 00 00 00 ; 数据节大小 SizeOfUninitializedData 00 00 00 00 ; 我们没有 BSS节 AddressOfEntryPoint a0 01 00 00 ; 代码节的开始处 BaseOfCode a0 01 00 00 ; 代码节的RVA BaseOfData c0 01 00 00 ; 数据节的RVA ImageBase 00 00 10 00 ; 1 MB, 任意选择 SectionAlignment 20 00 00 00 ; 32字节对齐 FileAlignment 20 00 00 00 ; 32字节对齐 MajorOperatingSystemVersion 04 00 ; NT 4.0 MinorOperatingSystemVersion 00 00 ; MajorImageVersion 00 00 ; 0.0版本 MinorImageVersion 00 00 ; MajorSubsystemVersion 04 00 ; Win32 4.0 MinorSubsystemVersion 00 00 ; Win32VersionValue 00 00 00 00 ; 未用? SizeOfImage c0 00 00 00 ; 所有节大小的总数 SizeOfHeaders a0 01 00 00 ; 第一节的偏移量 CheckSum 00 00 00 00 ; 非驱动程序不须用 Subsystem 03 00 ; Win32控制台程序 DllCharacteristics 00 00 ; 未用(不是一个DLL) SizeOfStackReserve 00 00 10 00 ; 1 MB 栈 SizeOfStackCommit 00 10 00 00 ; 开始时4 KB SizeOfHeapReserve 00 00 10 00 ; 1 MB 堆 SizeOfHeapCommit 00 10 00 00 ; 开始时4 KB LoaderFlags 00 00 00 00 ; 未知 NumberOfRvaAndSizes 10 00 00 00 ; 常量 数据目录,开始于 0xb8: 地址 大小 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXPORT (0) e0 01 00 00 6f 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IMPORT (1) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_RESOURCE (2) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_EXCEPTION (3) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_SECURITY (4) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BASERELOC (5) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_DEBUG (6) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_COPYRIGHT (7) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_TLS (9) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11) 00 00 00 00 00 00 00 00 ; IMAGE_DIRECTORY_ENTRY_IAT (12) 00 00 00 00 00 00 00 00 ; 13 00 00 00 00 00 00 00 00 ; 14 00 00 00 00 00 00 00 00 ; 15 节头(代码节), 开始于0x138: Name 2e 63 6f 64 65 00 00 00 ; ".code" VirtualSize 00 00 00 00 ; 未用 VirtualAddress a0 01 00 00 ; 代码节的RVA SizeOfRawData 20 00 00 00 ; 代码的大小 PointerToRawData a0 01 00 00 ; 代码节的文件偏移量 PointerToRelocations 00 00 00 00 ; 未用 PointerToLinenumbers 00 00 00 00 ; 未用 NumberOfRelocations 00 00 ; 未用 NumberOfLinenumbers 00 00 ; 未用 Characteristics 20 00 00 60 ; 代码节,可执行,可读 节头(数据节),开始于0x160: Name 2e 64 61 74 61 00 00 00 ; ".data" VirtualSize 00 00 00 00 ; 未用 VirtualAddress c0 01 00 00 ; 数据节的RVA SizeOfRawData a0 00 00 00 ; 数据节的大小 PointerToRawData c0 01 00 00 ; 数据节的文件偏移量 PointerToRelocations 00 00 00 00 ; 未用 PointerToLinenumbers 00 00 00 00 ; 未用 NumberOfRelocations 00 00 ; 未用 NumberOfLinenumbers 00 00 ; 未用 Characteristics 40 00 00 c0 ; 已初始化,可读,可写 (填充) 00 00 00 00 00 00 ; 填充的 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 代码节, 开始于0x1a0: 6A 00 ; push 0x00000000 68 d0 01 10 00 ; push offset _written 6A 0D ; push 0x0000000d 68 c0 01 10 00 ; push offset hello_string 6A F5 ; push 0xfffffff5 2E FF 15 28 02 10 00 ; call dword ptr cs:__imp__GetStdHandle@4 50 ; push eax 2E FF 15 24 02 10 00 ; call dword ptr cs:__imp__WriteConsoleA@20 C3 ; ret 数据节,开始于0x1c0: 68 65 6C 6C 6F 2C 20 77 6F 72 6C 64 0A ; "hello, world\n" 00 00 00 ; 填充到和_written对齐 00 00 00 00 ; _written 填充: 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的 输入描述(IMAGE_IMPORT_DESCRIPTOR),开始于0x1e0: OriginalFirstThunk 18 02 00 00 ; 原始第一个换长的RVA TimeDateStamp 00 00 00 00 ; 未绑定 ForwarderChain ff ff ff ff ; -1,无中转 Name 08 02 00 00 ; DLL名字的RVA FirstThunk 24 02 00 00 ; 第一个换长的RVA 结束标志(0x1f4): OriginalFirstThunk 00 00 00 00 ; 结束标志 TimeDateStamp 00 00 00 00 ; ForwarderChain 00 00 00 00 ; Name 00 00 00 00 ; FirstThunk 00 00 00 00 ; DLL名字, 开始于0x208: 6b 65 72 6e 65 6c 33 32 2e 64 6c 6c 00 ; "kernel32.dll" 00 00 00 ; 填充到32位边界 原始第一个换长, 开始于0x218: AddressOfData 30 02 00 00 ; 函数名"WriteConsoleA"的RVA AddressOfData 40 02 00 00 ; 函数名"GetStdHandle"的RVA 00 00 00 00 ; 结束标志 第一个换长,开始于0x224: AddressOfData 30 02 00 00 ; 函数名"WriteConsoleA"的RVA AddressOfData 40 02 00 00 ; 函数名"GetStdHandle"的RVA 00 00 00 00 ; 结束标志 输入函数名称(IMAGE_IMPORT_BY_NAME),开始于0x230: 01 00 ; 序数,不需要正确 57 72 69 74 65 43 6f 6e 73 6f 6c 65 41 00 ; "WriteConsoleA"的ASCII码值 IMAGE_IMPORT_BY_NAME,开始于0x240: 02 00 ; 序数,不需要正确 47 65 74 53 74 64 48 61 6e 64 6c 65 00 ; "GetStdHandle"的ASCII码值 (填充) 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; 填充的 00 第一个未使用字节开始于: 0x260 -------------- 噢,这个文件能在NT上却不能在windows 95上运行。windows 95不能运行按32字节节对齐的应用程序,它要求节对齐为4 KB;并且很明显的,文件对齐也应为512字节。因此要想在windows 95上运行,你得插入很多的0字节(为了对齐)并调整RVA。感谢D. Binette在windows 95上的(运行)试验。 -- 全文结束 -- |
转载于:https://blog.51cto.com/heihu/100628