1.什么是可执行文件(executable file)
指的是由操作系统进行加载执行的文件。
windows平台 | PE(protable Executable)文件结构 |
Linux平台 | ELF(Executable Linking Format)文件结构 |
哪些领域用到PE文件格式:
1.病毒反病毒
2.外挂反外挂
3.加壳与脱壳(保护与破解)
2.如何识别PE文件?
1.PE文件的特征(PE指纹)
打开exe dll sys等观察前两个字节 PE文件的前两个字节为"MZ"(4D5A) 再看3c位置存储的地址偏移 ,看偏移处存储的值是否为“PE”字符(50 45)
3.PE文件整体结构
PE 结构学习,就是学习一些结构体
2.PE文件的两种状态
目的:学习如何从一个二进制文件拆分成PE文件格式
2.1PE文件的第一部分 DOS Header部分
该部分对应结构体为dos mz文件头IMAGE_DOS_HEADER(64个字节)、DOS STUB运行时跟在硬盘中的PE文件是有差异的。
dos HEADER部分如图所示:
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;
IMAGE_DOS_HEADER 的e_ifanew
2.1PE文件的第2部分 PE文件头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;//PE标识
IMAGE_FILE_HEADER FileHeader;//标准PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//扩展pe头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
2.1.1 IMAGE_FILE_HEADER (20个字节)为标准PE头,结构如下:
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;
上图所示,从c0(pe头基址开始)往后数四个字节(Signature部分)后面的20个字节便是标准PE头
2.0.2 扩展PE头 IMAGE_OPTIONAL_HEADER32
IMAGE_OPTIONAL_HEADER32 (在32位下大小是224字节)
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;
扩展PE头大小可改,大小可由标准PE头IMAGE_FILE_HEADER 中的 SizeOfOptionalHeader指定。如果是32位的PE程序,默认值是E0(十进制224),如果是64位程序,则存储的是F0。
扩展PE头如图所示:
2.3PE文件的第3部分 节表
真的的数据存储在一个个节中,所以节表非常重要。
_IMAGE_SECTION_HEADER(大小为40个字节)
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;
从扩展PE头后再查40个字节便是第一个节表中的数据。
节表有n个成员,节表后面的数据是编译器加的垃圾数据。
节表后面有多少垃圾数据可以从 扩展pe头属性计算出来。_IMAGE_OPTIONAL_HEADER 下的SizeOfHeaders 值(dos头家上pe头加上节表按照文件对齐(FileAlignment)大小),查出来为0x200,查找SizeOfHeaders 为0x400.所以节表+pe头+dos头总共占用400地址空间
FileAlignment pe中剩下的所有的节都按照这个200标准对齐。
此时做个实验最能直观:打开notpad++,用winhex打开硬盘中的notpad++.exe 然后再用winhex-工具-打开ram-找到notpad++.exe 发现因公暗中的0x400是第一个节的起始地址,而内存中第一个节的起始地址是0x1000(IMAGE_OPTIONAL_HEADER32 ->SectionAlignment)所以,pe文件在内存中和在硬盘中有两种状态。
三. DOS头属性说明
插入上面已经插过的dos头结构体:
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;
实际上除了第一个跟最后一个成员,其它都没影响,删了,所以没必要了解
04 标准PE头属性说明
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;//标识cpu运行在什么cpu上。任意0 intel386以及后续 14c x64:8664
WORD NumberOfSections;//节的数量
DWORD TimeDateStamp;//编译器填写的时间搓
DWORD PointerToSymbolTable;//调试相关
DWORD NumberOfSymbols;//调试相关
WORD SizeOfOptionalHeader;//可选pe头大小(32位0xe0 64位0xf0)
WORD Characteristics;//文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
打开windows-system32 找到notepadd.exe 查找machine为8664,证明是在64为可运行
属性位含义如下图
05 扩展PE头属性说明
在介绍扩展pe头属性之前,我们需要区别一下32位和64位程序,上述扩展pe头结构体是32位的,64位有一点不同。
//32位pe文件头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;//PE标识
IMAGE_FILE_HEADER FileHeader;//标准PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//扩展pe头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
//64位pe文件头
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
实际上没什么太大差异,64位可选pe头展开为下:
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG 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;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
32位如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //PE32:10B PE32+:20B
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; //内存中整个PE文件的映射尺寸,一定是内存对齐的整数倍
DWORD SizeOfHeaders; //dos+pe+节表 大小 按文件对齐整数倍
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;
06 PE节表
节表用来描述节的特性。节表是一个结构体数组,单个节表结构体总共40个字节,结构乳如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ascall吗字符串 8字节
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;
07 RVA与FOA转换
此过程简单省略
08 空白区添加代码
如图MessageBox(0,0,0,0) 硬编码为6A 00 6A 00 6A 00 6A 00 FF15 30834100
(有的od显示 的是FF15为E8,我观察了一下这个主要还是跟被调函数自身是 stdcall 还是cdecl相关, 在编译的时候就由编译器决定了.)
FF15后面这个值不是要跳转的地址,而是通过计算算出来的,公式如下:要跳转地址-指令当前地址-5=得到实际值
JMP硬编码是E9计算方法跟刚才是一样的。
所以构造出来的硬编码为:6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
插入至文件的0x300位置,也就是节区与头区之间的空白位置,如下图:
要跳转地址-指令当前地址-5=得到实际值
od拿到messagebox地址 0x77D66534
0x77D66534-0x309-imgbase(0x400000)-5=0x77966226
E9后面跳转的实际上是原程序的entryPoint 0x411104
0x411104 -0x30D-imgbase(0x400000)-5=0x00010DF2
然后把entryPoint改为现在的base = 0x300
imgbase 位于可选pe头第17个字节(原来是0x00011104)现在是0x300,修改后如图所示:
09 扩大节
如果添加的代码过多,空白区不够用,则需要扩大节。由于扩大前面的节,后面的偏移需要修正,所以最简单的方法是扩大最后一个节。手动扩大一个节步骤如下:
节表结构如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ascall字符串 可自定义 共8个字节
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;
对齐的概念
已经知道了文件对齐跟内存对齐,查看pe文件最后一个节的实际大小,visualsize+扩大的字节数=n,把n按照内存对齐计算出值m,visualsize=m。
文件对齐值 = 文件对齐值+扩大的字节数。
更改sizeofimage=sizeofimage+扩大的字节数
10 新增节
1.新增节,复制第一个节的结构体一共40个字节,
2.更改numberofSections = numberofSections+1
3.磁盘文件新增字节(内存对齐的倍数,自然也是文件对齐的倍数)
4.更改新增节结构体 实际大小、文件对齐后大小
5.更改内存偏移= 上一节内存偏移 + 上一节内存对齐大小,文件偏移 = 上一节文件偏移+上一节文件对齐大小
5.更改sizeofimage =sizeofimage + 新增字节
11 合并节
视频无声音,未完待续...