PE 格式详解与试验
Posted Debroon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE 格式详解与试验相关的知识,希望对你有一定的参考价值。
可执行文件结构分析
可执行文件有:
- windows 的 PE,如 .exe、.dll、.ocx、.sys、.com
- Linux 的 ELF
PE 整体结构图:
几个重点:
- DOS 部首的 header 头
- PE 文件头的 file_header、optioal_header 头
具体结构:
DOS 部首的 header 头
DOS 部首的 header 头,是 PE 文件第一个结构,总共占 64 个字节。
第一个成员 e_magic
,叫魔术,占 2 个字节,固定俩个字符 MZ
的内存编码,这是发明人的首字母缩写。
- M 的内存编码:4D
- Z 的内存编码:5A
但在 x86 平台,是低位优先,看到的是 5A4D,而不是 4D5A。
第二个成员 e_cbip
,PE 文件最后那一页所包含的字节数。
第三个成员 e_cp
,PE 文件包含多少页。
第四个成员 e_crlc
,PE 文件有多少重地位个数。
第五个成员 e_cparhdr
,PE 文件有多少断头。
第六个成员 e_minalloc
,所需的最小附加段。
第七个成员 e_maxalloc
,所需的最大附加段。
第八个成员 e_ss
,栈底寄存器的初始值。
第九个成员 e_sp
,栈顶寄存器的初始值。
第十个成员 e_csum
,校验码,验证 PE 文件有木有被改。
第十一成员 e_ip
,指令寄存器的初始值。
第十二成员 e_cs
,段寄存器的初始值。
第十三成员 e_lfarlc
,重定向表的文件地址。
第十四成员 e_ovno
,覆盖号,早期内存小,运行大程序就需要这个。
第十五成员 e_res[4]
,保留字段。
第十六成员 e_oemid
,oem 的 id。
第十七成员 e_oeminfo
,oem 的信息。
第十八成员 e_res2[10]
,保留字段。
第十九成员 e_lfanew
,PE 头的起始地址 0x0000003C。
- 判断 PE 文件的有效性:
e_magic
(5A4D)、e_lfanew
( PE,0,0,(x00004550) )
解析 DOS Header:
int peDosHeaderAnalyze(char *file)
{
if(file==NULL)
return -1;
FILE *fp = NULL;
IMAGE_DOS_HEADER dosheader; // PE 结构体公开的
unsigned long pesig;
fp = fopen(file, "r+b"); // 打开文件
if(fp == NULL)
return -1;
fread(&dosheader, sizeof(dosheader), 1, fp); // 读出 dos header
fseek(fp, dosheader.e_lfanew,SEEK_SET); // 读出 dos header 最后一个成员
fread(&pesig, 4, 1, fp); // 读 4 个字节,PE 头签名 PE,0,0
fclose(fp);
printf("IMAGE_DOS_HEADER info:\\n");
printf("e_magic : %04x\\n", dosheader.e_magic); // "MZ"-->"ZM":0x5A4D
printf("e_cblp : %04x\\n", dosheader.e_cblp);
printf("e_cp : %04x\\n", dosheader.e_cp);
printf("e_crlc : %04x\\n", dosheader.e_crlc);
printf("e_cparhdr : %04x\\n", dosheader.e_cparhdr);
printf("e_minalloc: %04x\\n", dosheader.e_minalloc);
printf("e_maxalloc: %04x\\n", dosheader.e_maxalloc);
printf("e_ss : %04x\\n", dosheader.e_ss);
printf("e_sp : %04x\\n", dosheader.e_sp);
printf("e_csum : %04x\\n", dosheader.e_csum);
printf("e_ip : %04x\\n", dosheader.e_ip);
printf("e_cs : %04x\\n", dosheader.e_cs);
printf("e_lfarlc : %04x\\n", dosheader.e_lfarlc);
printf("e_ovno : %04x\\n", dosheader.e_ovno);
printf("e_res[0] : %04x\\n", dosheader.e_res[0]);
printf("e_oemid : %04x\\n", dosheader.e_oemid);
printf("e_oeminfo : %04x\\n", dosheader.e_oeminfo);
printf("res2[0] : %04x\\n", dosheader.e_res2[0]);
printf("lfanew : %08x\\n", dosheader.e_lfanew);
return 0;
}
判断 PE 有效性:
bool isPeValid(char *file) {
if(file == NULL)
return FALSE;
FILE *fp = NULL;
IMAGE_DOS_HEADER dosheader; // PE 结构体公开的
unsigned long pesig;
fp = fopen(file, "r+b"); // 打开文件
if(fp == NULL)
return false;
fread(&dosheader, sizeof(dosheader), 1, fp); // 读出 dos header
fseek(fp, dosheader.e_lfanew, SEEK_SET); // 读出 dos header 最后一个成员 e_lfanew
fread(&pesig, 4, 1, fp); // 读 4 个字节,PE 头签名 PE,0,0
fclose(fp);
if((dosheader.e_magic ==IMAGE_DOS_SIGNATURE) &&
(pesig == IMAGE_NT_SIGNATURE))
//dos header e_magic和PE头部里的signature必须为固定值:MZ(0x5A4D )和PE00(0x00004550)
return true;
return false;
}
调用:
int main(int argc, char* argv[])
{
char *pefile="D:\\\\mfpedemo_x64debug.exe";
if(isPeValid(pefile))
peDosHeaderAnalyze(pefile);
else
printf("not a valid pe file\\n");
return 0;
}
PE 文件头的 file_header
PE 文件头的 file_header 结构,三部分组成:
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
#ifdef _WIN64
typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS;
#endif
IMAGE_FILE_HEADER 占 20 个字节:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 运行平台
WORD NumberOfSections; // 块数目
DWORD TimeDataStamp; // 时间日期标记
DWORD PointerToSymbolTable; // COFF 符号指针,这是程序调试信息
DWORD NumberOfSymbols; // 符号数
WORD SizeOfOptionalHeader; // 可选部首长度,是 IMAGE_FILE_HEADER 的长度
WORD Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine 指定运行平台:
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64(K8)
Characteristics 文件属性取值:
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001
// Relocation info stripped from file.
#define IMAGE_FILE_ EXECUTABLE_IMAGE 0x0002
// File is executable.
#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
// Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AMARE 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 nachine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200
// Debugging info stripped .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400
#define IMAGE_FILE_NET_RUN _FROH _SWAP 0x0800
// If Image is on Net.
#define IMAGE_FILE_SYSTEM 0x1000
// System File.
#define IMAGE_FILE_DLL 0x2000
// File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_CNLY 0x4000
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000
// Bytes of machine word are reversed.
从 PE 头进入 IMAGE_FILE_HEADER,只需要在成员 e_ifanew
+ 4 个字节即可。
PE 文件头的 Optioal_header
Optioal_header 分 32、64 位,俩者相差不大。
可以下载软件 CFF Explorer 直接查看。
PE RVA 地址与文件地址转换
块表
导入表
基址重定位
编程解析 PE 结构
ELF 格式
readelf 命令解析 ELF 格式
以上是关于PE 格式详解与试验的主要内容,如果未能解决你的问题,请参考以下文章