pe文件学习
本文目录
- pe文件概述
pe文件概述
pe文件就是windows系统下的可执行文件所要遵循的格式,再深究一点就是Windows的装载器是按照pe文件的格式把程序装载到内存的。
如何好好的学习pe文件
pe文件说白了就是很多的结构体,程序的很多运行信息都存储在这个结构体中,只有把这些结构体掌握了才能对于pe文件有个大体掌握,为了简单,我们先来学习很标准的pe文件结构,也就是正常情况下编译器给你生成的正常pe文件结构,很多变形的pe文件结构都是建立在基础pe文件结构之上的,我们要先易后难。
pe文件大体
从最外面一层来讲pe文件包括pe头和pe主体
pe头讲解
pe头包括上面提到的DOS头,DOS stub,NT头,节区头这四个部分。前面说到pe其实大部分都是结构体,而且这些结构体都定义在winnt.h这个头文件里面。
DOS头
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number pe指纹
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;
对于dos头我们只需要关注上面代码翻译的部分 pe指纹 和 文件头的偏移位置
那么这里有小伙伴就要问了,为什么上面结构体那么多属性,我们只需要关注这两个属性呢?原因是,Windows装载器并没有参考上面其他的属性,就算你把其他的属性都手动修改为任意值,可执行文件也不会出现错误。
pe指纹:装载器装载的时候首先要查询文件开头的pe指纹,好确定这是个pe文件,如果这个指纹错了,那么操作系统会不认识你这个文件。指纹这个东西在多种文件结构中都有,其实他就是一种用来区分文件格式的标志。
文件头的偏移位置:这个属性告诉装载器文件头在文件中的偏移位置是多少。什么是文件偏移?就是这个数据相对于文件开头的偏移。下面我们来用winhex打开一个pe文件来看一看。
相信聪明的小伙伴们一定能推断出我上面用红框标注的两个属性,没错,就是pe指纹和文件头的文件偏移属性。
鉴于可能有人是第一次这样查看文件内容,所以这里有必要说明一下,这个是按照小端来放置的内容,什么是小端?就是低字节内容在低位置,对应的还有一个大端,是和小端相反的。
DOS stub
根据文件头偏移我们可以得出上方指向的位置就是DOS stub了,至于这个部分是历史遗留,那些内容其实都是汇编指令,我们运行一下对应的汇编指令发现他的作用就是在控制台打印
This program cannot be run in DOS mode。
这个部分随意更改也不会影响文件在Windows系统上的运行。
这里那么大一个地方其实是可以存储一些我们自己定义的信息的,这个等以后学了非正常的pe文件再说。
文件头
这里补充一下文件头前面还有一个pe标志,这个和之前的pe指纹是一样的作用,装载器也会寻找这个标志,否则也不认它是pe文件。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台
WORD NumberOfSections; //节区数
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; //可选头的大小
WORD Characteristics; //pe文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
文件头这里我们也是只需要关注上面翻译的部分。
首先是运行平台。我们每个cpu都有一个对应的机器码,其定义也可以在winnt.h中找到。
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_TARGET_HOST 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest.
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
节区数:我们的pe主体是按照一节一节来分的,其中有代码段,数据段,资源段等等节区,如果这里没有明白,那么可以先放一放,我们稍后会介绍,这个时候就记住他是一个节的数目就行了。
可选头大小:在文件头之中规定了可选头的大小,那么我们怎么定位可选头呢?其实可选头就是紧挨着文件头的,这个时候我告诉你pe文件那么多结构体属性中并没有给出可选头所在的文件偏移而只给出了它的大小,就是因为可选头是紧挨着在文件头后面的,否则我们只知道大小不知道位置,就尴尬了。
pe文件属性:这两个字节标识着pe文件的一些属性,下面我们一一列出来你就明白了。
#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.
这里我们只要记住0x0002和0x2000就行了,一个是可执行文件,一个是dll文件。
下面我们给一张图片包括了上面各种属性的翻译(既然有现成的,那么我就不一个一个敲了)
此处图片保留。