首页
社区
课程
招聘
[原创]Windows环境下,利用C语言程序读取PE文件头中的各个结构
发表于: 2018-10-12 20:44 9363

[原创]Windows环境下,利用C语言程序读取PE文件头中的各个结构

2018-10-12 20:44
9363

一、PE文件结构

PE文件结构大致可以划分为为以下四个部分
 DOS MZ头
 DOS Stub
 PE头
 PE数据区
PE文件的各个数据结构都在winnt.h中定义,这些结构一般有32位和64位之分,以下所有结构体定义也均来自于该文件,感兴趣者可阅读该文件,或查阅相关文献,作为参考。以下仅对PE文件中的重要部分做说明。

1、DOS MZ头

        PE文件的起始部分为MS-DOS头部,即IMAGE_DOS_HEADER。其中较为重要的两个字段为e_magic和e_flanew,e_magic被设置为0x5A4D,e_flanew指出PE头的文件偏移位置。

2、DOS Stub

在一个普通的PE文件中,DOS Stub部分的二进制数据如下:

但DOS Stub的大小不固定,这也导致了DOS头大小不固定,而且这部分程序的功能很简单,就是通过调用int 21h中断功能在屏幕上面显示'This program...'字符串,在做文件压缩或者手动打造PE文件时可利用此部分,大家也可尝试手动修改。目前所遇到的最小PE文件为97字节,可参考看雪上面一位大佬的文章——手动打造97字节PE文件

3、PE头

由于DOS Stub大小不确定,而 IMAGE_DOS_HEADER结构中的e_flanew字段又指向PE头,故PE头的起始位置可表示为
PE头 addr = DOS MZ基地址+ IMAGE_DOS_HEADER.e_flanew
即可通过 IMAGE_DOS_HEADER.e_flanew定位到PE文件头的起始位置。

3.1 IMAGE_NT_HEADERS头部

这个结构是广义上的PE头,也是PE文件的主要定义信息所在。偏移位置由 IMAGE_DOS_HEADER.e_flanew给出。具体结构如下:
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
其中, Signature字段值被设置为0x00004550,ASCII码为“PE00”,该字段可用于判断一个.exe文件是否是一个有效PE文件。
IMAGE_FILE_HEADER被称为标准PE头,大小为20字节,结构如下:
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;
该结构定义了PE文件的一些基本信息,NumberOfSections指出了该PE文件的区块数量;SizeOfOptionalHeader指出了IMAGE_OPTIONAL_HEADER的大小;Characteristics是指明文件属性的一套旗标。
IMAGE_OPTIONAL_HEADER32被称为扩展PE头,通常称为可选头,结构如下:
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;
此结构中,AddressOfEntryPoint指定了程序执行入口点;ImageBase指定文件在内存中的首选装入位置,默认的ImageBase=0x400000;SectionAlignment指定PE文件在装入内存时的区块对齐大小;FileAlignment指定PE文件内的区块对齐大小;SizeOfImage指定映像装入内存后的总尺寸大小;SizeOfHeaders为MS DOS头部、PE头部、区块表的组合尺寸;NumberOfRvaAndSizes是数据目录表的项数,该字段值为16(目前只遇到过该值);DataDirectory[...]即数据目录表。

4、区块表(The Section Table)

区块表紧跟IMAGE_NT_HEADERS之后,是一个IMAGE_SECTION_HEADER结构数组,其数量由IMAGE_NT_HEADERS.FileHeader.NumberOfSections字段给出,结构如下:
typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;<br>
其中,Misc.VirtualSize指出实际的、被使用的区块大小;VirtualAddress指定该块装载到内存的RVA;SizeOfRawData指定该块在磁盘文件中的大小;PointerToRawData指定该块在磁盘文件中的偏移;Characteristics指定该块的属性。

5、三种不同的地址

文件中的地址(FA): 在文件地址空间中,文件载入的起始位置总是等于0,FA地址表现为一个偏移地址;

虚拟地址(VA):PE文件被映射到内存中时,在内存中的映射地址;
相对偏移地址(RVA):程序某一结构的VA减去FA的结果;
程序被装载入内存中时,由文件结构不变,故其RVA不变,但装载起始地址不变,因为很难保证计算机的运行状态时时刻刻都是一致的。

二、读取思路

1、获取文件保存路径
2、创建文件内核对象
3、获取内核对象句柄
4、获取文件内存映射基址
5、读取各个结构内容
6、格式化输出

三、实现代码


代码如下:
#include <stdio.h>
#include <windows.h>
#include <Commdlg.h>

int main(int argc, char* argv[])
{
	char szFilePath[MAX_PATH];//要分析的文件名及路径
	OPENFILENAME ofn;//定义结构,调用打开对话框选择要分析的文件及其保存路径

	HANDLE hFile;// 文件句柄
	HANDLE hMapping;// 映射文件句柄
	LPVOID ImageBase;// 映射基址

	PIMAGE_DOS_HEADER  pDH = NULL;//指向IMAGE_DOS结构的指针
	PIMAGE_NT_HEADERS  pNtH = NULL;//指向IMAGE_NT结构的指针
	PIMAGE_FILE_HEADER pFH = NULL;//指向IMAGE_FILE结构的指针
	PIMAGE_OPTIONAL_HEADER pOH = NULL;//指向IMAGE_OPTIONALE结构的指针

	//必要的初始换
	memset(szFilePath, 0, MAX_PATH);
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = NULL;
	ofn.hInstance = GetModuleHandle(NULL);
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrInitialDir = ".";
	ofn.lpstrFile = szFilePath;
	ofn.lpstrTitle = "选择 PE文件打开 by For";
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	ofn.lpstrFilter = "*.exe\0*.exe\0";//过滤器

	if (!GetOpenFileName(&ofn))//调用打开对话框,选择要分析的文件
	{
		MessageBox(NULL, "打开文件错误", NULL, MB_OK);
		return 0;
	}

	//选择要分析的文件后,经过3步打开并映射选择的文件到虚拟内存中
	//1.创建文件内核对象,其句柄保存于hFile,将文件在物理存储器的位置通告给操作系统
	hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (!hFile)
	{
		MessageBox(NULL, "打开文件错误", NULL, MB_OK);
		return 0;
	}

	//2.创建文件映射内核对象(分配虚拟内存),句柄保存于hFileMapping
	hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
	if (!hMapping)
	{
		CloseHandle(hFile);
		return FALSE;
	}

	//3.将文件数据映射到进程的地址空间,返回的映射基址保存在ImageBase中
	ImageBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
	if (!ImageBase)
	{
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return FALSE;
	}

	//IMAGE_DOS Header结构指针
	pDH = (PIMAGE_DOS_HEADER)ImageBase;
	//IMAGE_NT Header结构指针
	pNtH = (PIMAGE_NT_HEADERS)((DWORD)pDH + pDH->e_lfanew);
	//IMAGE_File Header结构指针
	pFH = &pNtH->FileHeader;
	//IMAGE_Optional Header结构指针
	pOH = &pNtH->OptionalHeader;

	//输出各个结构中重要成员的取值
	printf("Dos header RVA:%08lX\n", pDH - ImageBase);
	printf("NT header RVA:%08lX\n", pDH->e_lfanew);
	printf("File header RVA:%08lX\n", pDH->e_lfanew + sizeof(pNtH->Signature));
	printf("Optional header RVA:%08lX\n", pDH->e_lfanew +
		sizeof(pNtH->Signature) + +sizeof(pNtH->FileHeader));
	printf("Section header RVA:%08lX\n",
		pDH->e_lfanew + sizeof(pNtH->Signature) +
		sizeof(pNtH->OptionalHeader) + sizeof(pNtH->FileHeader));

	printf("e_magic:            %04X   ASCII值为:%c%c\n",
		pDH->e_magic, pDH->e_magic % 256, pDH->e_magic / 256);
	printf("e_lfarlc:           %08X\n", pDH->e_lfarlc);

	printf("\n\nSignature:      %08X      ASCII值:%c%c00\n",
		pNtH->Signature, pNtH->Signature % 4096, pNtH->Signature / 256);

	printf("Machine:            %04X\n", pFH->Machine);
	printf("NumberOfSections:   %04X\n", pFH->NumberOfSections);
	printf("Characteristics:    %04X\n", pFH->Characteristics);

	printf("Magic:              %04X\n", pOH->Magic);
	printf("SizeOfCode:         %08X\n", pOH->SizeOfCode);
	printf("AddressOfEntryPoint:%08X\n", pOH->AddressOfEntryPoint);
	printf("ImageBase:          %08X\n", pOH->ImageBase);
	printf("SectionAlignment:   %08X\n", pOH->SectionAlignment);
	printf("FileAlignment:      %08X\n", pOH->FileAlignment);
	printf("SizeOfImage:        %08X\n", pOH->SizeOfImage);
        
        system("pause");
	return 0;
}

参考:
《软件加密技术内幕》——看雪学院
《逆向工程核心原理》
《Windows PE 权威指南》
《C Primer Plus (第6版)中文版》
《winnt.h》

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2020-1-5 16:33 被For@*编辑 ,原因:
收藏
免费 1
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  junkboy   +1.00 2018/10/12
最新回复 (11)
雪    币: 11716
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
支持
2018-10-12 23:50
0
雪    币: 9218
活跃值: (1640)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
3
好好学习下,感谢分享
2018-10-13 07:42
0
雪    币: 848
活跃值: (1262)
能力值: ( LV7,RANK:118 )
在线值:
发帖
回帖
粉丝
4
互相学习,逆向路感觉还很长
2018-10-14 23:55
0
雪    币: 914
活跃值: (2468)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
5
问题是这关我冷酷的VS2017叼事
VS2017不是IDE吗
真正的PE结构在头文件里定义的啊
2018-10-15 01:53
0
雪    币: 848
活跃值: (1262)
能力值: ( LV7,RANK:118 )
在线值:
发帖
回帖
粉丝
6
你压根就没读上面写的东西吧,麻烦读了再做评论
2018-10-15 18:34
0
雪    币: 300
活跃值: (2477)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享
2018-10-15 18:41
0
雪    币: 3
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
感谢分享
2018-10-17 11:33
0
雪    币: 20
活跃值: (29)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
导入表和导出表没写啊,重点啊
2018-10-18 06:29
0
雪    币: 12
活跃值: (423)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
都8102年了.为啥总有人去发这些?.
快月经贴了。。
2018-10-18 08:17
0
雪    币: 28
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
喵喵喵~~
2018-10-18 16:45
0
雪    币: 8930
活跃值: (5152)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jgs
12
VS2013下编译,一堆错误,全部是字符转换方面的,总算修改编译通过,不错,可移植获取文件大小。谢谢楼主。
2020-1-18 19:25
0
游客
登录 | 注册 方可回帖
返回
//