PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节。
· DOS头是用来兼容MS-DOS操作系统的,目的是当这个文件在MS-DOS上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode.还有一个目的,就是指明NT头在文件中的位置。
· NT头包含windows PE文件的主要信息,其中包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER)和PE可选头(IMAGE_OPTIONAL_HEADER32),头部的详细结构以及其具体意义在PE文件头文章中详细描述。
· 节表:是PE文件后续节的描述,windows根据节表的描述加载每个节。
· 节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义,未必是上图中的三个。
DOS头:
PE文件的开头:DOS MZ header结构。
定义:
- typedef struct _IMAE_DOS_HEADER { //DOS .EXE header 位置
- WORD e_magic; //Magic number; (解析时作为是否是PE文件的第一个标志) 0x00
- WORD e_cblp; //Bytes on last page of file 0x02
- WORD e_cp; //Pages in file 0x04
- WORD e_crlc; //Relocations 0x06
- WORD e_cparhdr; //Size of header in paragraphs 0x08
- WORD e_minalloc; //Minimum extra paragraphs needed 0x0A
- WORD e_maxalloc; //Maximum extra paragraphs needed 0x0C
- WORD e_ss; //Initial (relative) SS value 0x0E
- WORD e_sp; //Initial SP value 0x10
- WORD e_csum; //Checksum 0x12
- WORD e_ip; //Initial IP value 0x14
- WORD e_cs; //Initial (relative) CS value 0x16
- WORD e_lfarlc; //File address of relocation table 0x18
- WORD e_ovno; //Overlay number 0x1A
- WORD e_res[4]; //Reserved words 0x1C
- WORD e_oemid; //OEM identifier (for e_oeminfo) 0x24
- WORD e_oeminfo; //OEM information; e_oemid specific 0x26
- WORD e_res2[10]; //Reserved words 0x28
- LONG e_lfanew; //File address of new exe header(PE解析时用它找到PE头位置) 0x3C
- } IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;
判断是否是DOS头:
if ((PIMAGE_DOS_HEADER) pFile->e_magic != IMAGE_DOS_SIGNATURE)
{
//不是DOS头,返回
return;
}
2.e_lfanew,用于计算出偏移,找出后面的NT头在文件中的位置。
假设已将文件读入内存,首地址是pFile,void类型的指针
(long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew;
1 2 3 4 5 | typedefstruct _IMAGE_NT_HEADERS { DWORD Signature; //标记(判断是否为PE文件的第二个标志) IMAGE_FILE_HEADER FileHeader; //文件头(存储着PE文件的基本信息) IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展头(存储关于PE文件加载时加载的信息) } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; |
PIMAGE_NT_HEADERS32 pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);
//判断是否NT头
if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
{
//不是NT头,说明不是PE文件,返回
return;
}
PIMAGE_FILE_HEADER pFileHeader = &(pNTHeader->FileHeader);
PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNTHeader->OptionalHeader);
1 2 3 4 5 6 7 8 9 | typedefstruct _IMAGE_FILE_HEADER { WORD Machine; //文件的运行平台 WORD NumberOfSections; //区段的数量 DWORD TimeDateStamp; //文件创建时间 DWORD PointerToSymbolTable; //符号表偏移 DWORD NumberOfSymbols; //符号个数 WORD SizeOfOptionalHeader; //扩展头的大小 WORD Characteristics; //PE文件的一些属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; |
PIMAGE_NT_HEADERS32 pNTHerader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);
PIMAGE_FILE_HEADER pFileHeader = &(pNTHeader->FileHeader);
pFileHeader所指出的数据都可以显示出来。
2.关于时间的转换的代码
tm * FileTime = gmtime((time_t *)pFileHeader->TimeDataStamp);
typedefstruct _IMAGE_OPTIONAL_HEADER {//// Standard fields. //
+18h WORD 1 Magic;// 标志字,普通可执行文件32位(010B),64位(020B),ROM 映像(0107h)
+1Ah BYTE 2 MajorLinkerVersion;// 链接程序的主版本号
+1Bh BYTE 3 MinorLinkerVersion;// 链接程序的次版本号
+1Ch DWORD 4 SizeOfCode;// 所有含代码的节的总大小
+20h DWORD 5 SizeOfInitializedData;// 所有含已初始化数据的节的总大小
+24h DWORD 6 SizeOfUninitializedData;// 所有含未初始化数据的节的大小
+28h DWORD 7 AddressOfEntryPoint;// 程序执行入口的相对虚拟地址RVA,也称OEP,Orginal Entry Point,源入口点
+2Ch DWORD 8 BaseOfCode;// 代码的区块的起始RVA
+30h DWORD 9 BaseOfData;// 数据的区块的起始RVA//// NT additional fields. 以下是属于NT结构增加的领域。//
+34h DWORD 10 ImageBase;//默认加载基址(如果没有加载到这个地址,会发生重定位)
+38h DWORD 11 SectionAlignment;// 内存中的区块的对齐大小,一般为0x1000
+3Ch DWORD 12 FileAlignment;// 文件中的区块的对齐大小,一般为0x200
+40h WORD 13 MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
+42h WORD 14 MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
+44h WORD 15 MajorImageVersion;// 可运行于操作系统的主版本号
+46h WORD 16 MinorImageVersion;// 可运行于操作系统的次版本号
+48h WORD 17 MajorSubsystemVersion;// 要求最低子系统版本的主版本号
+4Ah WORD 18 MinorSubsystemVersion;// 要求最低子系统版本的次版本号
+4Ch DWORD 19 Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
+50h DWORD 20 SizeOfImage;// 映像装入内存后的总尺寸
+54h DWORD 21 SizeOfHeaders;// 所有头 + 区块表的尺寸大小,一般也是文件主体相对文件起始的偏移
+58h DWORD 22 CheckSum;// 映像的校检和
+5Ch WORD 23 Subsystem;// 可执行文件期望的子系统
+5Eh WORD 24 DllCharacteristics;// DllMain()函数何时被调用,默认为 0
+60h DWORD 25 SizeOfStackReserve;// 初始化时的栈大小,栈可以增长的最大值,一般1MB
+64h DWORD 26 SizeOfStackCommit;// 初始化时实际提交的栈大小,每次增长的值,一般4KB
+68h DWORD 27 SizeOfHeapReserve;// 初始化时保留的堆大小,进程中对可以增长的最大值一般1MB
+6Ch DWORD 28 SizeOfHeapCommit;// 初始化时实际提交的堆大小,即堆的初始值
+70h DWORD 29 LoaderFlags;// 与调试有关,默认为 0
+74h DWORD 30 NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h 31 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];// 数据目录表
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;
typedef struct_IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //数据的相对虚拟地址(RVA)
DWORD Size; //数据的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 //索引导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 //索引导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 //索引资源
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 //索引异常处理程序表
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 //索引安全结构
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //索引基址重定位信息
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 //索引调试信息
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 //版权
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 //全局指针目录,用在64位平台
#define IMAGE_DIRECTORY_ENTRY_TLS 9 //指向线程局部存储初始化节
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 //载入配置
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //绑定输入目录
#define IMAGE_DIRECTORY_ENTRY_IAT 12 //导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //COM信息
最后15是预留位置。
PIMAGE_NT_HEADERS32 pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew); //获取NT头
PIMAGE_OPTIONAL_HEADER32 pOptionalFileHeader = &(pNTHeader->OptionalHeader); //获取扩展头
PIMAGE_DATA_DIRECTORY pDataDirectory = pFileHeader->DataDirectory; //获取数据目录表
//循环读取出数据目录中的相对虚拟地址(RVA)和大小。
DWORDi = 0;
while (i != 0x10)
{
显示pDataDirectory[i].VirtualAddress; //相对虚拟地址
显示pDataDirectory[i].Size; //大小
}
DWORD CalcOffect(DWORD Rva)
{
//1.获取NT头
PIMAGE_NT_HEADERS32 pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);
//2.获取区段头表
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);
//3.循环比较它在哪一个区段中,不在这个区段就继续循环,注意假如VirtualAddress比SizeOfRawData大得话,说明大出来的数据是未初始化的数据,所以这里要用SizeOfRawData。
while (!(Rva >= pSectionHeader->VirtualAddress && Rva < pSectionHeader->VirtualAddress + pSetionHeader->SizeOfRawData))
{
++pSectionHeader;
//防止错误的PE文件引发崩溃
if (0 == pSectionHeader->PointerToRawData)
return 0;
}
return Rva - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
void ViewSecitonInfo()
{
//1.获取NT头
PIMAGE_NT_HEADERS32 pNTHeader = (PIMAGE_NT_HEADERS32)((long)pFile + (PIMAGE_DOS_HEADER)pFile->e_lfanew);
//2.获取区段头表
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNTHeader);
//3.循环输出区段表的信息,可以用两种方式判断结束,一个是头文件给出了区段的数量,可以用那个,也可以判断最后一个全零的区段头
while (pSectionHeader->PointerToRawData)
{
//输出或者获取每一个pSectionHeader中的成员
++pSectionHeader;
}
}