pe文件格式
Dos头
Dos头数据格式定义如下
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number (固定标志,不会变的标志)4D5A MZ,用来判断是否是DOS程序的
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; // DOS代码的执行位置,16位DOS下告知程序无法运行
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 (PE文件头偏移,文件偏移值FA)
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
在32位程序下,有用的就是2个字段,一个是第一个字段标志和最后一个字段指向PE文件头格式的偏移值
NT头
NT头数据结构定义如下:包含文件头和选项头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //固定值 PE文件标志4个字节
IMAGE_FILE_HEADER FileHeader; //文件头结构体
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //选项头结构体(注意这个结构体是变长的)所以要偏移到下一个数据段的时候,跳过这个就不能写加sizeof IMAGE_OPTIONAL_HEADER32 (结构体大小为E0)不然读取PE文件格式就出错
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
文件头数据结构定义如下
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //CPU类型 不可更改
WORD NumberOfSections; //节数量 可以更改
DWORD TimeDateStamp; //文件创建时间 可以更改
DWORD PointerToSymbolTable; //符号表偏移 可以更改
DWORD NumberOfSymbols; //符号数量 可以更改
WORD SizeOfOptionalHeader; //指定选项头大小 可以更改
WORD Characteristics; //文件属性 不可更改
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
可以利用字段SizeOfOptionalHeader修改选项头大小实现反调试,因为调试器大部分解析PE文件的时候是固定用sizeof IMAGE_OPTIONAL_HEADER32(E0)去解析的选项头大小的,所以你修改后,文件格式变了,调试器解析就会出问题,记住只能增加选项头大小,而不能缩小,因为选项头缩小那原来的数据就破坏了,肯定会有问题
**注意点:**修改后比较麻烦的是文件内所有涉及文件偏移的位置都要减去增加部分的偏移,这样才能保证原来的偏移访问的位置是正确的
文件头最后一个字段Characteristics(文件属性)是按位查看的,如第14位为1则是0x2000,表示是一个dll文件,这个字段可以区分文件是dll还是exe
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved external references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
选项头数据结构如下
//YES:表示可以改 NO:表示不能改 RES:表示能改但是有限制
typedef struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic; //NO 机器型号,判断是 PE是 32位还是 64位
BYTE MajorLinkerVersion; //YES 链接器版本号高版本
BYTE MinorLinkerVersion; //YES 链接器版本号低版本,组合起来就是 5.12 其中 5是高版本,C是低版本
DWORD SizeOfCode; //YES 代码节的总大小(512为一个磁盘扇区)
DWORD SizeOfInitializedData; //YES 初始化数据的节的总大小,也就是.data
DWORD SizeOfUninitializedData; //YES 未初始化数据的节的大小,也就是.data?
DWORD AddressOfEntryPoint; //NO 程序执行入口地址(OEP) RVA(相对虚拟偏移地址)
DWORD BaseOfCode; //YES 代码的节的起始 RVA(相对偏移)也就是代码区的偏移,偏移+模块首地址定位代码区
DWORD BaseOfData; //YES 数据结的起始偏移(RVA),同上
DWORD ImageBase; //YES 程序的建议模块基址(意思就是说作参考用的,模块建议基址如果被使用了就会使用别的地址)
DWORD SectionAlignment; //RES 内存中的节对齐 一般是0x1000
DWORD FileAlignment; //RES 文件中的节对齐 一般是0x200
WORD MajorOperatingSystemVersion; //YES 操作系统版本号高位
WORD MinorOperatingSystemVersion; //YES 操作系统版本号低位
WORD MajorImageVersion; //YES PE版本号高位
WORD MinorImageVersion; //YES PE版本号低位
WORD MajorSubsystemVersion; //NO 子系统版本号高位
WORD MinorSubsystemVersion; //YES 子系统版本号低位
DWORD Win32VersionValue; //YES 32位系统版本号值,注意只能修改为4 5 6表示操作系统支持nt4.0 以上,5的话依次类推
DWORD SizeOfImage; //RES 整个程序也就是整PE文件在内存中占用的空间(包含PE映射尺寸)
DWORD SizeOfHeaders; //RES 所有头大小(头的结构体大小)+节表结构体大小,记得值一定是文件对齐值的倍数,也就是到第一节区实际位置的偏移
DWORD CheckSum; //YES 校验和,对于驱动程序,可能会使用
WORD Subsystem; //NO 文件的子系统 :0x02表示窗口程序
WORD DllCharacteristics; //NO DLL文件属性,也可以成为特性,可能DLL文件可以当做驱动程序使用
DWORD SizeOfStackReserve; //RES 预留的栈的大小
DWORD SizeOfStackCommit; //RES 立即申请的栈的大小(分页为单位)
DWORD SizeOfHeapReserve; //RES 预留的堆空间大小
DWORD SizeOfHeapCommit; //RES 立即申请的堆的空间的大小
DWORD LoaderFlags; //YES 与调试有关
DWORD NumberOfRvaAndSizes; //RES 下面数据目录的数量:0x10表示有16个
IMAGE_DATA_DIRECTORY DataDirectory[16]; //RES 数据目录,默认16个,可以查看宏*/
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
数据目录项IMAGE_DATA_DIRECTORY 定义如下
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress; //相对虚拟地址RVA
DWORD Size; //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
重点关注的数据目录项有几个
IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出表
IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入表
IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源表
IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入表地址
节表头
节表头的数据结构定义如下
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //YES 节区的名字 8个字节
union
{
DWORD PhysicalAddress;
DWORD VirtualSize; //YES 节区在内存的大小,实际值是按内存对齐算
} Misc;
DWORD VirtualAddress; //虚拟地址 节区的 RVA地址(拷到内存中哪个位置)
DWORD SizeOfRawData; //在文件中对齐的尺寸(拷多大)
DWORD PointerToRawData; //在文件中的偏移FA(从文件哪里开始拷)
DWORD PointerToRelocations; //在 OBJ文件中使用
DWORD PointerToLinenumbers; //行号表位置,调试使用
WORD NumberOfRelocations; //在 OBJ文件中使用
WORD NumberOfLinenumbers; //行号表的数量
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
添加节表操作
1修改_IMAGE_FILE_HEADER中的节数量
2修改_IMAGE_OPTIONAL_HEADER中的程序占用空间大小
3结表末尾增加一个节表结构体
4按实际文件偏移填写结构体各成员值