PE文件结构解析1

admin 2022年4月20日03:32:35SecIN安全技术社区 程序逆向PE文件结构解析1已关闭评论4 views10131字阅读33分46秒阅读模式

0x0导读

今天复习一下pe文件结构,本文主要讲了pe文件结构基本概念和Dos头,Nt头,可选头的一些比较重要的成员,话不多说来看文章。

0x1环境

编译器:VirsualStudio2022

16进制查看工具:winhex

0x2基本概念

我们先来看下百度给出的定义:

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件。

我的理解:

pe文件结构就像一张说明书,用来说明这个pe文件的情况,而pe文件结构是表示符合pe文件结构的文件如exe,sys(驱动文件),com等文件的信息。举个例子,买电脑的时候都会有一张配置单,来说明这个电脑的cpu是啥,内存条多大,显卡是哪个厂的等信息。pe文件结构就像是这一张配置单,只不过配置单里面的信息是表示这台电脑的情况,而pe文件结构表示的是这个exe(这里就只用exe来举例),相关的信息,比如哪一部分是代码,哪一部分是数据,执行的时候加载到内存的哪里,pe文件的大小是多少等信息。

下面是pe文件结构图,因为pe文件结构内容太多了所以今天只讲Dos头,PE文件头(nt头),标准pe头(file头),可选文件头(option头),剩下的以后在讲(因为清楚的图片比较大上传不上去只能凑活着看了)。

PE文件结构解析1

0x3Dos头解析

Dos头定义,其实就是一个结构体,比较重要的有e_magic和e_lfanew,这两个成员。

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;

e_magic是一个WORD类型,两个字节,用来判断该pe文件是不是可执行的,值是0x4D5A,用字符串就是MZ所以它也叫mz标记,如果把它给改掉那么程序就无法执行。

修改前

PE文件结构解析1

修改后

PE文件结构解析1

PE文件结构解析1

e_lfanew是一个LONG类型大小是4字节,可以把它理解为一个偏移,通过它加基址(代码开始的地方)来找到pe文件头(nt头)。

PE文件结构解析1

代码解析

```

include

include

define path "C:\Users\allen\Desktop\ipmsg.exe"

void main()
{
FILE* fp = fopen(path, "rb");
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
rewind(fp);
PBYTE ptr = (PBYTE)malloc(size);
memset(ptr, 0, size);
fread(ptr, size, 1, fp);
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
printf("E_magic:%x\n", Dos->e_magic);
printf("E_lfanew:%x\n", Dos->e_lfanew);
getchar();
}

```

前三行代码主要是包含头文件,因为不包含头文件有一些函数就无法使用,第三行是定义一个宏来作为fopen函数的参数。

FILE* fp = fopen(path, "rb");定义一个文件指针来接受fopen函数返回值,fopen第一个参数是要打开文件的路径,第二个参数是以什么方式打开,这里是rb也就是以二进制方式打开一个文件,只能读不可以写。

fseek(fp, 0, SEEK_END);第一个参数是要设置文件的文件指针,第二个参数是一个相对于第三个参数是一个偏移量,第三个参数SEEK_END代表文件的末尾,代码大致意思文件流重定向到文件末尾。

int size = ftell(fp);定义一个变量用来接收ftell函数的返回值,ftell函数作用是计算文件的大小,第一个参数是要计算那个文件的文件指针。

rewind(fp);将文件流重定向到文件开头,为下面读取数据做准备。

PBYTE ptr = (PBYTE)malloc(size);定义一个指针指向malloc函数申请的内存,malloc函数第一个参数是申请内存的大小,PBYTE就是char*

memset(ptr, 0, size);填充刚才申请的内存块为0,第一个参数内存块的地址,第二个参数用什么填充,第三个参数填充多大。

fread(ptr, size, 1, fp);用于读取数据到内存,第一个参数是要读到哪里,第二个参数是读多少字节,第三个参数读多少次,第四个参数要读取文件的文件指针。

PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;定义一个结构体指针来指向那块内存,也就是用这块内存中的数据来填充这个结构体指针所指向的结构体。

printf("E_magic:%x\n", Dos->e_magic);//打印结构体成员e_magic的值,打印结构体成员要用->
printf("E_lfanew:%x\n", Dos->e_lfanew);//打印结构体成员e_lfanew的值
getchar();//暂停等待用户输入。

程序执行结果,因为我这里打开的是另一个程序所以e_lfanew的值不一样。

PE文件结构解析1

0x3Nt头解析

Nt头定义。

typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature是一个DWORD里面存储着PE标记也就是0x4550,这个值代表此文件是一个有效的pe文件,如果被修改程序也会无法运行。

FileHeader一个结构体,是标准pe头开始的地方,结构体定义如下,大小是20个字节。因为主要讲的是nt头所以对此结构说的不是很详细,下面会讲到此结构。

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;

OptionalHeader也是一个结构体,是可选头开始的地方,定义如下,因为主要讲的是nt头所以对此结构说的不是很详细,下面会讲到这个结构。

```
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;

```

代码解析

```

include

include

define path "C:\Users\allen\Desktop\ipmsg.exe"

void main()
{
FILE* fp = fopen(path, "rb");

fseek(fp, 0, SEEK_END);
int size = ftell(fp);
rewind(fp);
PBYTE ptr = (PBYTE)malloc(size);
memset(ptr, 0, size);
fread(ptr, size, 1, fp);
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;

printf("E_magic:%x\n", Dos->e_magic);
printf("E_lfanew:%x\n", Dos->e_lfanew);
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
printf("Signature:%x\n", Nt->Signature);
printf("FileHeader:%x\n", Nt->FileHeader);
printf("OptionalHeader:%x\n", Nt->OptionalHeader);
getchar();
}

```

上面讲过的代码就不在赘述了。

PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);也是定义一个结构体指针然后指向基址加上Dos头的e_lfanew成员,定位到nt头,并填充这个结构体指针指向的结构体。实际上还是基址加偏移的方式定位的。

printf("Signature:%x\n", Nt->Signature);
printf("FileHeader:%x\n", Nt->FileHeader);
printf("OptionalHeader:%x\n", Nt->OptionalHeader);

上面这几行代码就是打印结构体的数据了,printf函数就是打印数据。

程序运行结果

PE文件结构解析1

0x4File头解析

File头定义,大小20字节。

typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;//COFF符号表格的偏移位置。此字段只对COFF除错信息有用
DWORD NumberOfSymbols;//COFF符号表格中的符号个数。该值和上一个值在release版本的程序里为0
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine大小两字节,表示当前pe文件的目标CPU类型,0代表任何平台,14c代表i386及后续处理器。

NumberOfSections大小两字节,代表节表(区块)的数量。

TimeDateStamp大小四字节,一个时间戳,表示当前pe文件何时创建时间。

因为PointerToSymbolTableNumberOfSymbols这两个成员很少被使用所以就不多介绍,直接看SizeOfOptionalHeader大小两字节,表示可选头(Option头)的大小。

Characteristics大小两字节,表示文件的类型,010f代表可执行文件。

代码解析

```

include

include

define path "C:\Users\allen\Desktop\ipmsg.exe"

void main()
{
FILE* fp = fopen(path, "rb");
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
rewind(fp);
PBYTE ptr = (PBYTE)malloc(size);
memset(ptr, 0, size);
fread(ptr, size, 1, fp);
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
printf("E_magic:%x\n", Dos->e_magic);
printf("E_lfanew:%x\n", Dos->e_lfanew);
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
printf("Signature:%x\n", Nt->Signature);
printf("FileHeader:%x\n", Nt->FileHeader);
printf("OptionalHeader:%x\n", Nt->OptionalHeader);
PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);
printf("Machine:%x\n", File->Machine);
printf("NumberOfSections:%x\n", File->NumberOfSections);
printf("Characteristics:%x\n",File->Characteristics);
printf("TimeDateStamp:%x\n", File->TimeDateStamp);
printf("SizeOfOptionalHeader:%x\n", File->SizeOfOptionalHeader);
printf("TimeDateStamp:%x\n",File->TimeDateStamp);
getchar();
}

```

PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);先定义一个结构体指针,在用基址+Dos头的e_lfanew成员定位到nt头,跟据nt头的结构体定义可知到Signature成员后面就是File头而Signature的大小是四字节,所以加4就定位到File头,用File头的数据填充上面定义的结构体指针指向的结构体即可。

程序运行结果

PE文件结构解析1

0x5Option头解析

Option头结构体定义

```
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;
```

Magic大小两字节,用于表示是32位pe文件还是64位pe文件,如果是10B就代表32位文件,20B代表64位文件。

SizeOfCode大小四字节,代码总大小,需要文件对齐。

SizeOfInitializedData大小四字节,代表已经初始化数据的大小,需要按照文件对齐

SizeOfUninitializedData大小四字节,代表未初始化数据大小,也是要按照文件对齐。

AddressOfEntryPoint大小四字节,是程序入口地址,也就是OEP(需要加上ImageBase才是真正的程序入口)。

BaseOfCode大小四字节,代码节开始的地方。

BaseOfData大小四字节,数据开始的地方。

ImageBase大小四字节,内存镜像基址也就是程序加载进内存中时的基址(一般是0x400000)。

FileAlignment大小四字节,文件对齐,如果文件对齐是200那么不足200的会在后面补0,如过一个数是188那么它按照文件对齐后就是200,主要作用是提高cpu工作效率。

SectionAlignment大小四字节,内存对齐,和文件对齐一样,也是为了提高cpu工作效率。

SizeOfImage大小四字节,PE文件在内存中的总大小,按照内存对齐

SizeOfHeaders大小四字节,所有头的大小,Dos头+Nt头成员Signature+File头+Option头+节表的总大小,需要按照文件对齐。

代码解析

```

include

include

define path "C:\Users\blue\Desktop\ipmsg.exe"

void main()
{
FILE* fp = fopen(path, "rb");
fseek(fp, 0, SEEK_END);
int size = ftell(fp);
rewind(fp);
PBYTE ptr = (PBYTE)malloc(size);
memset(ptr, 0, size);
fread(ptr, size, 1, fp);
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
printf("E_magic:%x\n", Dos->e_magic);
printf("E_lfanew:%x\n", Dos->e_lfanew);
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
printf("Signature:%x\n", Nt->Signature);
printf("FileHeader:%x\n", Nt->FileHeader);
printf("OptionalHeader:%x\n", Nt->OptionalHeader);
PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);
printf("Machine:%x\n", File->Machine);
printf("NumberOfSections:%x\n", File->NumberOfSections);
printf("Characteristics:%x\n",File->Characteristics);
printf("TimeDateStamp:%x\n", File->TimeDateStamp);
printf("SizeOfOptionalHeader:%x\n", File->SizeOfOptionalHeader);
printf("TimeDateStamp:%x\n",File->TimeDateStamp);
PIMAGE_OPTIONAL_HEADER32 Option=(PIMAGE_OPTIONAL_HEADER32)(ptr + Dos->e_lfanew + 20+4);
printf("AddressOfEntryPoint:%x\n",Option->AddressOfEntryPoint);
printf("BaseOfCode:%x\n",Option->BaseOfCode);
printf("BaseOfData:%x\n",Option->BaseOfData);
printf("FileAlignment:%x\n",Option->FileAlignment);
printf("SectionAlignment:%x\n",Option->SectionAlignment);
printf("ImageBase:%x\n",Option->ImageBase);
printf("Magic:%x\n",Option->Magic);
printf("SizeOfCode:%x\n",Option->SizeOfCode);
printf("SizeOfHeaders:%x\n",Option->SizeOfHeaders);
printf("SizeOfImage:%x\n",Option->SizeOfImage);
printf("SizeOfInitializedData:%x\n",Option->SizeOfInitializedData);
printf("SizeOfUninitializedData:%x\n",Option->SizeOfUninitializedData);
getchar();
}

```

PIMAGE_OPTIONAL_HEADER32 Option=(PIMAGE_OPTIONAL_HEADER32)(ptr + Dos->e_lfanew + 20+4);还是定义一个结构体指针,然后填充结构体指针指向的结构体,咱们主要看怎么找到Option头的,(ptr + Dos->e_lfanew + 20+4);还是先通过基址加Dos头成员e_lfanew找到nt头在加Nt头成员Signature的大小找到File头这里加上File头的大小就可以定位到Option头。

程序运行结果

PE文件结构解析1

0x6结语

主要是介绍了Dos头,Nt头,File头,Option头的一些比较重要的成员,和如何定位到它们,涉及到指针与结构体相关操作。

由于作者水平有限,文章如有错误欢迎指出。

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月20日03:32:35
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  PE文件结构解析1 http://cn-sec.com/archives/917276.html