一、什么是可执行文件
1、可执行文件(executable file)指的是可以由操作系统进行加载执行的文件。
2、可执行文件的格式:
- Windows平台:- PE(Portable Executable)文件结构
- Linux平台:- ELF(Executable and Linking Format)文件结构
二、如何识别PE文件
1、PE文件的特征(PE指纹)
分别打开
.exe .dll .sys
等文件,观察特征前2个字节,都是4D5A,变成字母是MZ,是DOS系统开发人员的一个名字,再看3C字节是什么,3C存的是一个地址,这个地址对应的5045,变为字母就是PE。
2、不要仅仅通过文件的后缀名来认定PE文件
三、对齐
对齐是为了读写速度,找起来快,用空间换时间
1、硬盘对齐(200h)
早期计算机硬盘水平比较低,只有很小的几个G,节与节之间的空隙比较小,硬盘对齐为200H,后来随着硬件水平提高了,硬盘对齐与内存对齐一样了为1000H
2、内存对齐(1000h)
四、为什么要分节
1、节省硬盘空间.(这个不是决定的,由编译器决定)
硬盘间隔小,内存间隔大,这是老的编译器
任何一个exe程序都会有一个自己独立的4G内存空间,虚拟内存
2G是平时写应用程序用的,2G是给操作系统用的
这里注意:还有一些exe程序 当我们用winhex打开时
它在硬盘上和内存中是一样的
这个时候我们要有两个概念 就是硬盘对齐(200h字节)和内存对齐(1000h字节),它是为了增加读写速度
2、一个应用程序多开
比如一个qq程序有两个部分,数据1(可读),数据2(可读可写) ,都是100M,我运行一个账号就需要200M,我再开一个,数据1(可读)已经有了,就没必要在复制一份丢在内存中,只需要复制一份数据2就可以了
PE磁盘文件与内存映像图
节表(块表): PE文件中所有节的属性都被定义在节表中
PE文件头/DOS头:对当前整个exe程序做概要性描述
DOS头(64个字节)
typedef struct IMAGE_DOS_HEADER{
WORD e_magic; //DOS头的标识,为4Dh和5Ah。分别为字母MZ
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
DWORD e_lfanew; //指向IMAGE_NT_HEADERS的所在(PE头相对于文件的偏移,用于定位PE文件)
}IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
0x00 WORD e_magic; //5A4D * MZ标记用于判断是否为可执行文件
0x02 WORD e_cblp; //0090
0x04 WORD e_cp; //0003
0x06 WORD e_crlc; //0000
0x08 WORD e_cparhdr; //0004
0x0a WORD e_minalloc; //0000
0x0c WORD e_maxalloc; //FFFF
0x0e WORD e_ss; //0000
0x10 WORD e_sp; //00B8
0x12 WORD e_csum; //0000
0x14 WORD e_ip; //0000
0x16 WORD e_cs; //0000
0x18 WORD e_lfarlc; //0040
0x1a WORD e_ovno; //0000
0x1c WORD e_res[4]; //0000000000000000
0x24 WORD e_oemid; //0000
0x26 WORD e_oeminfo; //0000
0x28 WORD e_res2[10]; //20
0x3c DWORD e_lfanew; //00000080 * PE头相对于文件的偏移,用于定位PE文件
从文件开始的地方算,过80个字节,就是PE文件真正开始的地方
中间这一部分大小是不确定的
留了一块空间,可以放一些随意的数据
PE文件头(NT头)
typedef struct IMAGE_NT_HEADERS{
0x00 DWORD Signature; //PE标识
0x04 IMAGE_FILE_HEADER FileHeader; //标准PE头(20字节)
0x18 IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头(大小不确定)
}IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS;
标准PE头(20字节)
0x00 WORD Machine; //014C * 程序运行的CPU型号,0x0任何处理器/0x14C Intel 386及后续处理器
0x02 WORD NumberOfSections; //0008 * 文件中存在的节的总数,除了头,还有几节数据,如果要新增节或者合并节就要修改这个值.
0x04 DWORD TimeDateStamp; //3E22F0DF * 时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的
0x08 DWORD PointerToSymbolTable; //00000000
0x0c DWORD NumberOfSymbols; //00000000
0x10 WORD SizeOfOptionalHeader; //00E0 * 可选PE头的大小,32位PE文件默认E0h=16*14,64位PE文件默认为F0h,大小可以自定义
0x12 WORD Characteristics; //010E * 每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1
TimeDateStamp
.map
文件是对
.exe
文件中函数的描述,对
.exe
文件的说明
当
.map
文件和
.exe
文件 不同步时
就是检查 时间戳是否 一致
Characteristics
打勾的 即为1
把所有值 对应起来 0 1 0 E
(第六位是标志保留位,但是没有显示出来)
0000 0001 0000 1110
可选PE头(大小是不确定的)
程序入口 + 内存镜像基址 才是真正的地址
0x00 WORD Magic; * 说明文件类型:10B->32位下的PE文件 20B->64位下的PE文件
0x02 BYTE MajorLinkerVersion;
0x03 BYTE MinorLinkerVersion;
0x04 DWORD SizeOfCode; * 代码大小/所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用
0x08 DWORD SizeOfInitializedData; * 已初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x0c DWORD SizeOfUninitializedData; * 未初始化数据大小的和,必须是 FileAlignment的整数倍 编译器填的 没用
0x10 DWORD AddressOfEntryPoint; * 程序入口
0x14 DWORD BaseOfCode; * 代码开始的基址,编译器填的 没用
0x18 DWORD BaseOfData; * 数据开始的基址,编译器填的 没用
0x1c DWORD ImageBase; * 内存镜像基址
0x20 DWORD SectionAlignment; * 内存对齐,区段对齐
0x24 DWORD FileAlignment; * 文件对齐
0x28 WORD MajorOperatingSystemVersion;
0x2a WORD MinorOperatingSystemVersion;
0x2c WORD MajorImageVersion;
0x2e WORD MinorImageVersion;
0x30 WORD MajorSubsystemVersion;
0x32 WORD MinorSubsystemVersion;
0x34 DWORD Win32VersionValue;
0x38 DWORD SizeOfImage; *镜像大小/ 内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍,是拉伸之后的大小,,,
0x3c DWORD SizeOfHeaders; * 所有头+节表,技照‘文件对齐 FileAlignment’后的大小,否则加载会出错
0x40 DWORD CheckSum; * 校验和,一些系统文件有要求,用来判断文件是否被修改
0x44 WORD Subsystem;
0x46 WORD DllCharacteristics;
0x48 DWORD SizeOfStackReserve; * 初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; * 初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; * 初始化时保留的堆大小
0x54 DWORD SizeOfHeapCommit; * 初始化时实践提交的大小
0x58 DWORD LoaderFlags;
0x5c DWORD NumberOfRvaAndSizes; * 目录项数目,RVA数目和大小
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16]; 16个结构体,每个结构体是8个字节
进行了拉伸,完成之后完全遵守操作系统,就可以执行了
程序入口 + 内存镜像基址 才是真正的入口点地址
编写程序读取一个.exe文件,输出所有的PE头信息.
通过指针偏移方式实现
上述代码有个极大的缺点,就是只能加载打印特定固定的notepad.exe文件,而且在 不同的操作系统下面会有异常,可移植性差
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int* OpenFile()
{
FILE* PointToFile = NULL;
int FileSize = 0;
int* StrBuffer = NULL;
int Num = 0;
//打开文件
if ((PointToFile = fopen("C:\\notepad.exe","rb")) == NULL) {
printf("打开文件失败!\n");
exit(1);
}
//获取文件大小
fseek(PointToFile,0,2);
FileSize = ftell(PointToFile);
//重定位指针
fseek(PointToFile,0,0);
//buffer指向申请的堆
StrBuffer = (int*)(malloc(FileSize));
if (!StrBuffer)
{
printf("堆空间分配失败!\n");
free(StrBuffer);
return 0;
}
//读取文件内容
Num = fread(StrBuffer,FileSize,1,PointToFile);
if (!Num)
{
printf("读取文件内容失败!\n");
free(StrBuffer);
return 0;
}
//关闭文件
fclose(PointToFile);
//将缓冲区内的文件内容的地址返回到调用函数的地方
return StrBuffer;
}
int* FileSizes = OpenFile();
int PrintfNtHeaders()
{
//文件指针
unsigned int* PointBuffer = (unsigned int*)FileSizes;
unsigned short* pBuffer = (unsigned short*)PointBuffer;
unsigned char* pcBuffer = (unsigned char*)PointBuffer;
//判断MZ和PE的标志
unsigned short Cmp1 = 0x5A4D;
unsigned int Cmp2 = 0x00004550;
//判断文件是否读取成功
if(!PointBuffer)
{
printf("文件读取失败!\n");
free(PointBuffer);
return 0;
}
//判断是否为MZ标志
if (*pBuffer != Cmp1)
{
printf("不是有效MZ标志!\n");
printf("%X\n",*pBuffer);
free(PointBuffer);
return 0;
}
printf("*********打印DOS头*********\n");
printf("e_magic:\t\t\t%X\n",*(pBuffer));
printf("e_ifanew:\t\t\t%08X\n\n\n",*(PointBuffer+15));
//判断是否为PE标志
if (*(PointBuffer+56) != Cmp2)
{
printf("不是有效的PE标志!\n");
printf("%X\n",*(PointBuffer+56));
free(PointBuffer);
return 0;
}
printf("*********打印标准PE文件头*********\n");
printf("PE标志:\t\t\t\t%X\n",*(PointBuffer+56));
printf("Machine:\t\t\t%04X\n",*(pBuffer+114));
printf("NumberOfSection:\t\t%04X\n",*(pBuffer+115));
printf("TimeDateStamp:\t\t\t%08X\n",*(PointBuffer+58));
printf("PointerToSymbolTable:\t\t%08X\n",*(PointBuffer+59));
printf("NumberOfSymbols:\t\t%08X\n",*(PointBuffer+60));
printf("SizeOfOptionalHeader:\t\t%04X\n",*(pBuffer+122));
printf("Chrarcteristics:\t\t%04X\n\n\n",*(pBuffer+123));
printf("*********打印标准可选PE头*********\n");
printf("Magic:\t\t\t\t%04X\n", *(pBuffer+124));
printf("MajorLinkerVersion:\t\t%02X\n", *(pcBuffer+250));
printf("MinorLinkerVersion:\t\t%02X\n", *(pcBuffer+251));
printf("SizeOfCode:\t\t\t%08X\n", *(PointBuffer+63));
printf("SizeOfInitializedData:\t\t%08X\n", *(PointBuffer+64));
printf("SizeOfUninitializedData:\t%08X\n", *(PointBuffer+65));
printf("AddressOfEntryPoint:\t\t%08X\n", *(PointBuffer+66));
printf("BaseOfCode:\t\t\t%08X\n", *(PointBuffer+67));
printf("BaseOfData:\t\t\t%08X\n", *(PointBuffer+68));
printf("ImageBase:\t\t\t%08X\n", *(PointBuffer+69));
printf("SectionAlignment:\t\t%08X\n", *(PointBuffer+70));
printf("FileAlignment:\t\t\t%08X\n", *(PointBuffer+71));
printf("MajorOperatingSystemVersion:\t%04X\n", *(pBuffer+144));
printf("MinorOperatingSystemVersion:\t%04X\n", *(pBuffer+145));
printf("MajorImageVersion:\t\t%04X\n", *(pBuffer+146));
printf("MinorImageVersion:\t\t%04X\n", *(pBuffer+147));
printf("MajorSubsystemVersion:\t\t%04X\n", *(pBuffer+148));
printf("MinorSubsystemVersion:\t\t%04X\n", *(pBuffer+149));
printf("Win32VersionValue:\t\t%08X\n", *(PointBuffer+75));
printf("SizeOfImage:\t\t\t%08X\n", *(PointBuffer+76));
printf("SizeOfHeaders:\t\t\t%08X\n", *(PointBuffer+77));
printf("CheckSum:\t\t\t%08X\n", *(PointBuffer+78));
printf("Subsystem:\t\t\t%04X\n", *(pBuffer+158));
printf("DllCharacteristics:\t\t%04X\n", *(pBuffer+159));
printf("SizeOfStackReserve:\t\t%08X\n", *(PointBuffer+80));
printf("SizeOfStackCommit:\t\t%08X\n", *(PointBuffer+81));
printf("SizeOfHeapReserve:\t\t%08X\n", *(PointBuffer+82));
printf("SizeOfHeapCommit:\t\t%08X\n", *(PointBuffer+83));
printf("LoaderFlags:\t\t\t%08X\n", *(PointBuffer+84));
printf("NumberOfRvaAndSizes:\t\t%08X\n", *(PointBuffer+85));
free(PointBuffer);
return 0;
}
int main()
{
PrintfNtHeaders();
OpenFile();
return 0;
}
优化写法
#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <malloc.h>
#define F_PATH "C:\\cntflx\\ipmsg.exe"
FILE* open_file(char* file_path,char* open_mode);
int compute_file_size(FILE* file_address);
char* allocate_buffer(int file_size);
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address);
void analysis_PE_head(char* File_buffer);
VOID PrintNTHeaders()
{
// 初始化
//char file_path[] = "C:\\Windows\\System32\\notepad.exe";
char file_path[] = F_PATH;
char open_mode[] = "rb";
// 打开文件,返回文件指针
FILE* file_address = open_file(file_path,open_mode);
// 计算文件长度
int file_size = compute_file_size(file_address);
// 分配内存
char* File_buffer = allocate_buffer(file_size);
// 写入内存,返回内存地址
File_buffer = readfile2memory(File_buffer,file_size,file_address);
// 打印PE头部信息
analysis_PE_head(File_buffer);
// 释放内存、关闭文件流
free(File_buffer);
fclose(file_address);
}
FILE* open_file(char* file_path,char* open_mode)
{
FILE* file_address = fopen(file_path,open_mode); // fopen() 参数是字符串也就是常量指针类型
if(!file_address)
{
printf("打开文件失败!\r\n");
return 0;
}
return file_address;
}
int compute_file_size(FILE* file_address)
{
int size = 0;
fseek(file_address,0,SEEK_END);
size = ftell(file_address);
fseek(file_address,0,SEEK_SET);
return size;
}
char* allocate_buffer(int file_size)
{
char* file_buffer = (char*)malloc(file_size);
if(!file_buffer)
{
printf("申请内存失败!\r\n");
return 0;
}
memset(file_buffer,0,file_size);
return file_buffer;
}
char* readfile2memory(char* file_buffer,int file_size,FILE* file_address)
{
if(!(fread(file_buffer,file_size,1,file_address)))
{
printf("从文件向内存中读取数据失败!\r\n");
return 0;
}
return file_buffer; // 如果写入内存成功,则返回内地址
}
void analysis_PE_head(char* File_buffer)
{
// 实例化PE文件头几个结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
// 强制类型转换
pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
// 判断是不是有效的MZ标志
if(*((PWORD)pDosHeader) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ标志!\r\n");
free(File_buffer);
return;
}
// 强制类型转换 PIMAGE_DOS_HEADER结构体
pDosHeader = (PIMAGE_DOS_HEADER)File_buffer;
// 打印DOS头
printf("=============================DOS头信息如下=============================\r\n");
printf("MZ标志:\t\t\t%04X\r\n",pDosHeader->e_magic);
printf("PE偏移:\t\t\t%08X\r\n",pDosHeader->e_lfanew);
// 判断是不是有效的PE标志
if(*((PDWORD)((DWORD)File_buffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志!\r\n");
free(File_buffer);
return;
}
// 强制类型转换 PIMAGE_NT_HEADERS结构体
pNTHeader = PIMAGE_NT_HEADERS((DWORD)File_buffer+pDosHeader->e_lfanew);
// 打印NT头
printf("=============================NT头信息如下===============================\r\n");
printf("NT:\t\t\t\t%04X\r\n",pNTHeader->Signature);
// 强制类型转换 PIMAGE_FILE_HEADER结构体
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4);
// 打印标准PE文件头
printf("=============================标准PE头信息如下============================\r\n");
printf("PE_machine:\t\t\t%04X\r\n",pPEHeader->Machine);
printf("NumberOfSections:\t\t%04X\n",pPEHeader->NumberOfSections);
printf("SizeOfOptionalHeader:\t\t%04X\r\n",pPEHeader->SizeOfOptionalHeader);
// 强制类型转换
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);//
// 打印可选PE头
printf("==============================可选PE头信息如下==============================\r\n");
printf("Magic:\t\t\t\t%04X\r\n",pOptionHeader->Magic);
printf("AddressOfEntryPoint:\t\t%08X\r\n",pOptionHeader->AddressOfEntryPoint);
printf("ImageBase:\t\t\t%08X\r\n",pOptionHeader->ImageBase);
printf("SizeOfImage:\t\t\t%08X\r\n",pOptionHeader->SizeOfImage);
printf("SizeOfHeaders:\t\t\t%08X\r\n",pOptionHeader->SizeOfHeaders);
printf("SectionAlignment:\t\t%08X\r\n",pOptionHeader->SectionAlignment);
printf("FileAlignment:\t\t\t%08X\r\n",pOptionHeader->FileAlignment);
// 强制类型转换
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
printf("==============================节表信息如下===============================\n");
//printf("name:%s\n",pSectionHeader->Misc);
DWORD dwNumberOfSection = pPEHeader->NumberOfSections;
/*
printf("%x\n",pPEHeader->NumberOfSections);
printf("IMAGE_SIZEOF_SHORT_NAME:%x\n",IMAGE_SIZEOF_SHORT_NAME);
printf("option_add:%x\n",pOptionHeader);
printf("psection_add:%x\n",pSectionHeader);
printf("==============================================================\n");*/
for(DWORD i = 0;i<dwNumberOfSection;i++,pSectionHeader++)
{
printf("========================第%d个节信息:===============================\n",i+1);
printf("section_name:");
for(DWORD j = 0;j<IMAGE_SIZEOF_SHORT_NAME;j++)
{
printf("%c",pSectionHeader->Name[j]);
}
printf("\r\n");
printf("Misc:\t\t\t\t%08X\r\n",pSectionHeader->Misc);
printf("VirtualAddress:\t\t\t%08X\r\n",pSectionHeader->VirtualAddress);
printf("SizeOfRawData:\t\t\t%08X\r\n",pSectionHeader->SizeOfRawData);
printf("PointerToRawData:\t\t%08X\r\n",pSectionHeader->PointerToRawData);
printf("Characteristics:\t\t%08X\r\n",pSectionHeader->Characteristics);
}
}
int main(int argc, char* argv[])
{
PrintNTHeaders();
//getchar();
return 0;
}
版权归原作者 骑着驴子追宝马 所有, 如有侵权,请联系我们删除。