滴水逆向第三期:PE头解析_手动(1)

一、PE文件结构

PE文件按顺序由DOS头、NT头(包含标准PE头和可选PE头)、节表以及节区部分组成

DOS头

typedef struct IMAGE_DOS_HEADER
{
+0h		WORD    e_magic        //DOS可执行文件标记,若其所存值为MZ(4Dh 5Ah),则是可执行文件     
+2h     WORD    e_cblp
+4h		WORD    e_cp
+6h		WORD    e_crlc
+8h		WORD    e_cparhdr
+0ah    WORD    e_minalloc
+0ch    WORD    e_maxalloc
+0eh    WORD    e_ss
+10h    WORD    e_sp
+12h    WORD    e_csum
+14h    WORD    e_ip
+16h    WORD    e_cs
+18h    WORD    e_lfarlc
+1ah    WORD    e_ovno
+1ch    WORD    e_res[4]
+24h    WORD    e_oemid
+26h    WORD    e_oeminfo
+29h    WORD    e_res2[10]
+3ch    DWORD   e_lfanew     //(RVA相对虚地址)指向PE文件头(即从DOS头起始处向后数这么多个字节处为真正的PE文件开始的地方)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

需要重点记忆的是第一个WORD数据e_magic和最后一个DWORD数据e_lfanew。
e_magic是一种标记,如果值为MZ(4Dh 5Ah),则是可执行文件;而e_lfanew则是一个RVA,它指向的是PE文件真正开始的地方
在这里插入图片描述
被选中的数据就是e_lfanew,由于大小端序的原因,它的值是0x00000100,因此,PE真正的起始位置是0x00000100地址处
而从e_lfanew到0x00000100之间的这段区域通常存储一些不是很重要的数据,如注释等,可以暂时忽略

NT头

typedef struct IMAGE_NT_HEADERS
{
+0h       DWORD    Signature
+4h       IMAGE_FILE_HEADER    FileHeader   //标准PE头
+18h      IMAGE_OPTIONAL_HEADER32   OptionalHeader  //可选PE头
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;

NT头只包含了三个成员,其中FileHeader是标准PE头,占20个字节。OptionalHeader是可选PE头,大小不确定

标准PE头

0x00 WORD Machine; //运行平台(CPU型号,值为14C(4C 01)即表示是386及后续处理器,为0则是任何处理器)
0x02 WORD NumberOfSections; //文件存在节的总数,若要新增节或者合并节则需要修改这个值
0x04 DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数,由编译器填写
0x08 DWORD PointerToSymbolicTable;
0x0C DWORD NumberOfSymbols;
0x10 WORD SizeOfOptionalHeader;//可选PE头的大小,32位PE文件默认为0XE0,64位PE文件默认为0XF0,大小可自定义
0x12 WORD Characteristics;//文件的属性值,由16位值为0或1组成,每个位代表的属性不一样

NumberOfSections记录了该PE文件所存在的节的总数,若后续涉及新增节或者合并节之类的操作就需要修改这个值
Characteristics的大小有16位,每个位都有不同的含义,用来定义PE文件的属性

可选PE头

typedef struct _IMAGE_OPTIONAL_HEADER
{
    //
    // Standard fields
    //
+18h    WORD    Magic;                   // 标志字,32位普通可执行文件(010Bh),64位普通可执行文件(020Bh)
+1Ah    BYTE    MajorLinkerVersion;
+1Bh    BYTE    MinorLinkerVersion;
+1Ch    DWORD   SizeOfCode;              // 所有含代码的节的总大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+20h    DWORD   SizeOfInitializedData;   // 所有含已初始化数据的节的总大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+24h    DWORD   SizeOfUninitializedData; // 所有含未初始化数据的节的大小,必须是FileAlignment(文件对齐)的整数倍,编译器填写,没用
+28h    DWORD   AddressOfEntryPoint;     // 程序执行入口(RVA)
+2Ch    DWORD   BaseOfCode;              // 代码的区块的起始基址(RVA),编译器填写,没用
+30h    DWORD   BaseOfData;              // 数据的区块的起始基址(RVA),编译器填写,没用
    //
    // NT additional fields.    以下是属于NT结构增加的领域
    //
+34h    DWORD   ImageBase;               // 程序的优先装载地址
+38h    DWORD   SectionAlignment;        // 内存对齐(1000h)
+3Ch    DWORD   FileAlignment;           // 文件对齐(200h(旧)1000(新))
+40h    WORD    MajorOperatingSystemVersion;  
+42h    WORD    MinorOperatingSystemVersion;  
+44h    WORD    MajorImageVersion;       
+46h    WORD    MinorImageVersion;       
+48h    WORD    MajorSubsystemVersion;   
+4Ah    WORD    MinorSubsystemVersion;   
+4Ch    DWORD   Win32VersionValue;       
+50h    DWORD   SizeOfImage;             // 映像装入内存后的总尺寸,可以比实际的尺寸大,但必须是SectionAlignment的整数倍
+54h    DWORD   SizeOfHeaders;           // 所有头 + 区块表的尺寸大小 严格按照FileAlignment对齐
+58h    DWORD   CheckSum;                // 映像的校检和,用来检测文件是否被修改,可修改值
+5Ch    WORD    Subsystem;               
+5Eh    WORD    DllCharacteristics;      
+60h    DWORD   SizeOfStackReserve;      // 初始化时的栈大小
+64h    DWORD   SizeOfStackCommit;       // 初始化时实际提交的栈大小
+68h    DWORD   SizeOfHeapReserve;       // 初始化时保留的堆大小
+6Ch    DWORD   SizeOfHeapCommit;        // 初始化时实际提交的堆大小
+70h    DWORD   LoaderFlags;             
+74h    DWORD   NumberOfRvaAndSizes;     // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   // 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

AddressOfEntryPoint是程序执行的入口地址;ImageBase是进程的基址,假设它的值为400000h,则PE文件会被装载到这个地址处(RVA);NumberOfRvaAndSizes定义了数据目录表的项数,一直是16

代码

下面附上代码

//#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h>

#define FILE_PATH "C:\\Users\\Allure_Love\\Desktop\\notepad.exe"

LPVOID ReadPEFile(LPCSTR lpszFile)	//const char[]相当于const char*,LPSTR相当于char*,两者不兼容,所以可以把LPSTR改成LPCSTR
{
	FILE *pFile;
	DWORD fileSize = 0;
	LPVOID pFileBuffer = NULL;

	//打开文件
	errno_t err;
	err = fopen_s(&pFile,lpszFile,"rb");	//"rb"只读
	if (!pFile)
	{
		printf("无法打开.exe文件!");
	}

	//读取文件大小
	fseek(pFile,0,SEEK_END);
	//fseek(FILE * stream, long offset, int fromwhere);	//函数设置文件指针stream的位置,以fromwhere为基址,偏移offset个字节的位置
	fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);

	//分配缓冲区
	pFileBuffer = malloc(fileSize);
	if (!pFileBuffer)
	{
		printf("分配文件失败!");
		fclose(pFile);	//关闭文件
		return NULL;
	}

	//将文件数据读取到缓冲区
	size_t n = fread(pFileBuffer, fileSize, 1, pFile);	//size_t无符号整数,sieof()返回的结果类型
	//size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) 从给定流stream读取nmemb个size大小的数据到ptr所指向的数组中
	
	if (!n)
	{
		printf("读取数据失败!");
		free(pFileBuffer);
		fclose(pFile);
		return NULL;
	}

	//关闭文件
	fclose(pFile);
	return pFileBuffer;
}

void PrintNTHeaders()
{
	//初始化
	LPVOID pFileBuffer = NULL;
	PIMAGE_DOS_HEADER pDosHeader = NULL;	//DOS头指针
	PIMAGE_NT_HEADERS pNTHeader = NULL;		//NT头指针
	PIMAGE_FILE_HEADER pPEHeader = NULL;	//标准PE头指针
	PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = NULL;	//可选PE头指针ReadPEFile(FILE_PATH)

	pFileBuffer = ReadPEFile(FILE_PATH);
	if (!pFileBuffer)
	{
		printf("文件读取失败!");
		return;
	}

	if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的MZ标志!");
		free(pFileBuffer);	//释放指针内存
		return;
	}

	//打印DOS头
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	printf("***************DOS头****************\n");
	printf("MZ标志:%x\n",pDosHeader->e_magic);
	printf("PE偏移:%x\n", pDosHeader->e_lfanew);

	//判断是否是有效的PE标志
	if (*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志!");
		free(pFileBuffer);
		return;
	}

	//打印NT头
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
	printf("***************NT头****************\n");
	printf("NT:%x\n", pNTHeader->Signature);

	//打印标准PE头
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	printf("*************标准PE头****************\n");
	printf("PE:%x\n", pPEHeader->Machine);
	printf("节的数量:%x\n", pPEHeader->NumberOfSections);
	printf("可选PE头的大小:%x\n", pPEHeader->SizeOfOptionalHeader);

	//打印可选PE头
	pOptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(((DWORD)pPEHeader)+ IMAGE_SIZEOF_FILE_HEADER);	//const IMAGE_SIZEOF_FILE_HEADER = 20	标准PE头大小
	printf("*************可选PE头****************\n");
	printf("Optional_PE:%x\n", pOptionalHeader->Magic);

	//释放内存
	free(pFileBuffer);
}

int main()
{
	PrintNTHeaders();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值