PE文件结构分析,这里我就直接从网上摘抄了,都千篇一律
常见的PE文件有EXE、DLL、OCX、SYS、COM,像位图文件一样,它们也有固定的格式,PE 文件是由五大部分构成,如下所示:
1:DOS MZ Header(DOS文件头)一个IMAGE_DOS_HEADER结构,大小为64字节
2:DOS Stub(DOS加载模块)没有固定大小
3:PE Header(PE文件头)一个IMAGE_NT_HEADERS结构,大小为248字节
4:Section Table(节表)一个IMAGE_SECTION_HEADER结构数组,数组大小依据节而定,如果PE文件有5个节,则数组大小为5
5:Sections(节或段)没有固定大小,可以有多个节。
第一二部分DOS文件头和DOS加载模块
PE文件的一二部分完全是为了程序能在DOS运行下时给出一个提示,在Windows下几乎已经没什么作用了,所以我们只要了解IMAGE_DOS_HEADER里的e_lfanew成员,这个成员指明了IMAGE_NT_HEADERS(PE文件头)在PE文件中的偏移量(位置)
IMAGE_DOS_HEADER结构的定义,以及各成员的意思
typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
WORD e_magic; // 魔术数字
WORD e_cblp; // 文件最后页的字节数
WORD e_cp; // 文件页数
WORD e_crlc; // 重定义元素个数
WORD e_cparhdr; // 头部尺寸,以段落为单位
WORD e_minalloc; // 所需的最小附加段
WORD e_maxalloc; // 所需的最大附加段
WORD e_ss; // 初始的SS值(相对偏移量)
WORD e_sp; // 初始的SP值
WORD e_csum; // 校验和
WORD e_ip; // 初始的IP值
WORD e_cs; // 初始的CS值(相对偏移量)
WORD e_lfarlc; // 重分配表文件地址
WORD e_ovno; // 覆盖号
WORD e_res[4]; // 保留字
WORD e_oemid; // OEM标识符(相对e_oeminfo)
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留字
LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
第三部分PE文件头
IMAGE_NT_HEADERS结构定义及其各成员意思
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // PE文件头标志:"PE\0\0",占4字节
IMAGE_FILE_HEADER FileHeader; // PE文件物理分布的信息,占20字节
IMAGE_OPTIONAL_HEADER32 OptionalHeader; // PE文件逻辑分布的信息,占224字节
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; IMAGE_FILE_HEADER结构定义及其成员意思
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;//表示该程序要执行的环境及平台,0x14c Intel 80386 处理器以上 0x014d Intel 80486 处理器以上。。。
WORD NumberOfSections;//指明PE文件最后一部分有多少个节,同时也指明了PE 文件第四部分数组的大小。
DWORD TimeDateStamp;//文件建立的时间
DWORD PointerToSymbolTable;//用在调试信息中,用途不太明确,不过它们的值总为0。
DWORD NumberOfSymbols;//用在调试信息中,用途不太明确,不过它们的值总为0。
WORD SizeOfOptionalHeader;//可选头的长度(sizeof IMAGE_OPTIONAL_HEADER),可以用它来检验PE文件的正确性。
WORD Characteristics;//是一个标志的集合,其大部分位用于OBJ或LIB文件中。
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_OPTIONAL_HEADER32结构定义及其成员意思
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// 标准域
//
WORD Magic; //这个值好像总是0x010b。
BYTE MajorLinkerVersion;// 链接器的版本号,这个值不太可靠。
BYTE MinorLinkerVersion; //链接器的版本号,这个值不太可靠。
DWORD SizeOfCode; //可执行代码的长度
DWORD SizeOfInitializedData; // 初始化数据的长度(数据段)
DWORD SizeOfUninitializedData; //未初始化数据的长度(bss段)
DWORD AddressOfEntryPoint; // 代码的入口RVA地址,程序从这儿开始执行,常称为程序的原入口点OEP(Original Entry Point)
DWORD BaseOfCode; //可执行代码起始位置
DWORD BaseOfData; //初始化数据起始位置
//
// NT附加域
//
DWORD ImageBase; // 载入程序首选的RVA地址。这个地址可被Loader改
变
DWORD SectionAlignment; // 段加载后在内存中的对齐方式
DWORD FileAlignment; // 段在文件中的对齐方式。
WORD MajorOperatingSystemVersion;//操作系统版本
WORD MinorOperatingSystemVersion;//操作系统版本
WORD MajorImageVersion;// 程序版本
WORD MinorImageVersion; //程序版本
WORD MajorSubsystemVersion; //子系统版本号,这个域系统支持
WORD MinorSubsystemVersion; //子系统版本号,这个域系统支持
DWORD Win32VersionValue; // 这个值总是为0
DWORD SizeOfImage; //程序调入后占用内存大小(字节),等于所有段的长度之和
DWORD SizeOfHeaders; // 所有文件头长度之和,它等于从文件开始到第一个段的原始数据之间的大小。
DWORD CheckSum; // 校验和,仅用在驱动程序中,在可执行文件中可能为
WORD Subsystem; // 一个标明可执行文件所期望的子系统的枚举
值
WORD DllCharacteristics; // DLL状态
DWORD SizeOfStackReserve; //保留堆栈大小
DWORD SizeOfStackCommit; //启动后实际申请的堆栈数,可随实际情况变大
DWORD SizeOfHeapReserve; // 保留堆大小
DWORD SizeOfHeapCommit; // 实际堆大小
DWORD LoaderFlags; // 目前没有用
DWORD NumberOfRvaAndSizes; //下面的目录表入口个数,这个值也不可靠 ,这个值在目前Windows版本中设为16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//目录表入口
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;
IMAGE_DATA_DIRECTORY结构定义及其成员意思:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 起始RVA地址
DWORD Size; // 长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
第四部分节表
IMAGE_SECTION_HEADER数组中每一个元素对应着PE文件中第五部分的一个节IMAGE_SECTION_HEADER结构定义及其意思
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如".text",IMAGE_SIZEOF_SHORT_NAME为8
union {
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 真实长度
} Misc;
DWORD VirtualAddress; // RVA
DWORD SizeOfRawData; // 物理长度
DWORD PointerToRawData; // 节基于文件的偏移量
DWORD PointerToRelocations; // 重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移
WORD NumberOfRelocations; // 重定位项数目
WORD NumberOfLinenumbers; // 行号表的数目
DWORD Characteristics; // 节属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
第五部分节(段)
第四部分后面就是一个一个的节了,如
Section1,section2,section3,section4....SectionN一个PE文件里节的数量和每一个节的大小都是不确定的,但是具体一个PE文件里节的数量,以每一个节各自的大小,都可以根据其前面的提供的信息得出,如节的数量由
IMAGE_FILE_HEADER结构的NumberOfSections成员给出。
Windows根据数据类型的不同而把它划分成一个个的节,如代码部分都存储在.text段里。位图,图标,菜单等资源都存储在.rsrc节里
例子1:读取一个PE文件里的所有节名(如果有)
首先我们要知道有这个PE文件里有多少个节,这一点,可以根据PE文件第三部分里IMAGE_FILE_HEADER结构的NumberOfSections(节数量)成员给出,然后我们只要依次读出PE文件第四部分IMAGE_SECTION_HEADER结构数组里的每个元素,并输出其成员Name(节名)就行了。
假设E盘下有一个abc.exe文件
#include
#include
int main()
{
HANDLE hFile=CreateFile("e:\\abc.exe",GENERIC_READ|GENERIC_WRITE,NULL,
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
IMAGE_DOS_HEADER dosHeader;
DWORD dwSize;
ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//读取DOS文件头
SetFilePointer(hFile,dosHeader.e_lfanew+4,NULL,FILE_BEGIN);//移到文件头位置,加
4略过PE标志
IMAGE_FILE_HEADER fileHeader;
ReadFile(hFile,(void *)&fileHeader,sizeof(IMAGE_FILE_HEADER),&dwSize,NULL);//读取文件头
int NumSection=fileHeader.NumberOfSections;//获取节数量
SetFilePointer(hFile,dosHeader.e_lfanew+248,NULL,FILE_BEGIN);//移到节表位置
for(int i=0;i { IMAGE_SECTION_HEADER Section; ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL); printf("%s\n",https://www.doczj.com/doc/7516143965.html,);//输出节名 } return 0; } 例子2:获取一个PE文件里的所有导入函数(如果有) 导入函数什么意思呢,其实就是程序要调用的动态链接库函数,当在写程序的时候,如果在程序里调用了MessageBox函数,那么这个MessageBox函数便是这个程序的导入函数,并不是调用函数才会使一个函数成为导入函数,如果在程序里调用了类似GetAddrProcess函数获取的某个函数的地址,即使不调用该函数,也导入该函数。 导入函数的相关信息都存储在一个IMAGE_IMPORT_DESCRIPTOR(导入表)结构数组里,数组每一个元素对应着一个动态链接库(如user32.dll),也可以依此来获取该程序加载了哪些动态链接库,而IMAGE_IMPORT_DESCRIPTOR结构的各成员就指明了具体从这个动态链接库导入了哪些函数,以及函数对应的地址。但这里要说明的是,在磁盘上的PE文件,这里的函数对应地址是无效的,因为不可能提前知道动态链接库的函数地址,只有当程序执行的时候,PE文件被映射到内存,里面的函数地址会被系统动态赋予当前函数对应的地址,这便是所谓的动态链接。 先来看一下IMAGE_IMPORT_DESCRIPTOR的结构及各成员意思 typedef struct _IMAGE_IMPORT_DESCRIPTOR { DWORD OriginalFirstThunk;//指向一个IMAGE_THUNK_DATA结构数组,该结构记录函数序号和名称 DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name;//导入模块偏移量(动态链接库名称) DWORD FirstThunk;//存储一个DWORD数组的首地址(其实就是偏移),该数组存储函数地址,对应OriginalFirstThunk,为0数组结束 } IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR; 先来看一下OriginalFirstThunk这个成员,这个成员指向了一个IMAGE_THUNK_DATA数组,这个数组的大小是如何确定的呢,依据从模块导入函数的数量来确定,如果说这个结构的Name成员对应的动态链接库名为user32.dll,并从这个库导入了十个函数,那么 IMAGE_THUNK_DATA数组大小就是十。那要如何获得函数地址呢,比如该数组第二个元素对应的是MessageBox函数,那么它对应的地址是 ((DWORD *)FirstThunk)[1];这个数值就是函数的地址值 IMAGE_THUNK_DATA结构数组定义及其成员意思 typedef struct _IMAGE_THUNK_DATA32 { union { PBYTE ForwarderString; PDWORD Function;//该成员用于判断数组是否结束,为0则结束 DWORD Ordinal; PIMAGE_IMPORT_BY_NAME AddressOfData;//一个指向存储函数序号,名称的结构的指针 } u1; } IMAGE_THUNK_DATA32; typedef IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA; IMAGE_THUNK_DATA32结构前二字节储存有函数序号,后二字节指向函数名称 知道了上面这些,我们就还剩下一个问题了,如何找到IMAGE_IMPORT_DESCRIPTOR数组(导入表)在PE文件里的起始地址。 我们先转到PE文件第三部分,看一下IMAGE_NT_HEADERS结构里的 IMAGE_OPTIONAL_HEADER32结构,它里面有一个成员是大小为16的IMAGE_DATA_DIRECTORY 结构数组,而导入表的地址就由该数组第二个元素里的VirtualAddress成员指明,也就是 DataDirectory[1].VirtualAddress。关于每个数组元素意思在windows下有如下定义 #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory 导出目录#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory 导入目录#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory 资源目录#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory 异常目录 #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory 安全目录#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table 重定位基本表 #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory 调试目录 // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) X86使用?描述字符串 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory TLS目录 #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory 装载配置目录 #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers 在表头中绑定的导入目录 #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table 导入地址表 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors 延迟装载导入描述符 #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor COM 运行描述符 还是拿VC编的程序来试吧,假设它在E盘下,名为abc.exe #include #include int main() { HANDLE hFile=CreateFile("e:\\abc.exe",GENERIC_READ,NULL,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); IMAGE_DOS_HEADER dosHeader; DWORD dwSize; ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL); SetFilePointer(hFile,dosHeader.e_lfanew+24,NULL,FILE_BEGIN);//移到可选头(IMAGE_OPTIONAL_HEADER32) IMAGE_OPTIONAL_HEADER32 optHeader; ReadFile(hFile,(void *)&optHeader,sizeof(IMAGE_OPTIONAL_HEADER32),&dwSize,NULL);//读取可选头SetFilePointer(hFile,optHeader.DataDirectory[1].VirtualAddress,NULL,FILE_BEGIN) ;//移到导入表位置 IMAGE_IMPORT_DESCRIPTOR impDesc[25]; int imCount=0; ReadFile(hFile,(void *)&impDesc[imCount],sizeof(IMAGE_IMPORT_DESCRIPTOR),&dwSize,NULL);//读取第一个元素 while(impDesc[imCount].FirstThunk)//循环读入数组中的元素 { imCount++; ReadFile(hFile,(void *)&impDesc[imCount],sizeof(IMAGE_IMPORT_DESCRIPTOR),&dwSize,NULL); } for(int i=0;i { char DllName[25]; SetFilePointer(hFile,impDesc[i].Name,NULL,FILE_BEGIN);//移到动态链接库名称处 ReadFile(hFile,(void *)DllName,25,&dwSize,NULL);//读25个字节,可能会多读,但不影响输出 printf("从%s动态链接库导入的函数:\n",DllName); SetFilePointer(hFile,impDesc[i].OriginalFirstThunk,NULL,FILE_BEGIN); IMAGE_THUNK_DATA thunkData[75]; int thunkCount=0; ReadFile(hFile,(void *)&thunkData[thunkCount],sizeof(IMAGE_THUNK_DATA),&dwSize,NULL); while(thunkData[thunkCount].u1.Function) { thunkCount++; ReadFile(hFile,(void *)&thunkData[thunkCount],sizeof(IMAGE_THUNK_DATA),&dwSize,NULL); } for(int j=0;j { SetFilePointer(hFile,(DWORD)thunkData[j].u1.AddressOfData+2,NULL,FILE_BEGIN); char FunName[25]; ReadFile(hFile,(void *)FunName,25,&dwSize,NULL); printf("%s\n",FunName); } } return 0; } 例子3:读取一个PE文件里的资源(如果有) PE文件的资源(像位图,图标,光标,菜单,对话框等)一般都存在名为".rsrc"节下,要想获得资源节的起始位置有两种方法,第一种跟前面获取导入表的方法一样,导入表的起始地址是DataDirectory[1].VirtualAddress,那么资源节的起始位置对应着该数组的第三个元素,也就是DataDirectory[2].VirtualAddress,第二种方法就是遍历PE文件的第四部 分(节表)IMAGE_SECTION_HEADER数组,找到名为“.rsrc"节名的对应元素,那么这个结 构的PointerToRawData成员指明了资源节在文件中的位置,这个结构也指明了一个节的范围,节的范围是VirtualAddress字段的值开始(包括这个值),到 VirtualAddress+Misc.VirtualSize的值结束(不包括这个值)。 知道了资源节的位置,我们就来分析一下资源节的存储格式吧。 资源节的开头是一个IMAGE_RESOURCE_DIRECTORY结构,紧跟这个结构之后的是一个 IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组。 IMAGE_RESOURCE_DIRECTORY结构定义如下: typedef struct _IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; //标识此资源的类型 DWORD TimeDateStamp;//资源编译器产生资源的时间 WORD MajorVersion;//资源主版本号 WORD MinorVersion;//资源次版本号 WORD NumberOfNamedEntries;//这是用户自定义资源类型的个数 WORD NumberOfIdEntries;//这是典型资源,如位图,图标,对话框等资源类型的个数 } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY; NumberOfNamedEntries和NumberOfIdEntries两个成员之和指明了紧跟其后的 IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组大小 IMAGE_RESOURCE_ DIRECTORY_ENTRY结构定义如下: typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { struct { DWORD NameOffset:31;//当NameIsString为1时,这个成员才有效,它指向一个IMAGE_RESOURCE_DIR_ STRING_U结构(偏移) DWORD NameIsString:1;//该成员指明资源类型表示方法,0用ID表示,1用字符串表示,也就是自定义资源类型和常用类型 }; DWORD Name; WORD Id;//资源类型ID,NameIsString为0时才有效 }; union { DWORD OffsetToData; struct { DWORD OffsetToDirectory:31;//由DataIsDirectory决定,为1是一个IMAGE_RESOURCE_DIRECTORY结构偏移,否则是一个 //IMAGE_RESOURCE_D ATA_ENTRY结构的偏移(相对于第一个资源表位置的偏移) DWORD DataIsDirectory:1; }; }; } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY; 关于Id(资源类型ID)成员的取值意思如下: 1 :光标(Cursor) 2 :位图(Bitmap) 3 :图标(Icon) 4 :菜单(Menu) 5 :对话框(Dialog) 6 :字符串(String) 7 :字体目录(Font Directory) 8 :字体(Font) 9 :加速键(Accelerators) 10 :未格式化资源(Unformatted) 11 :消息表(Message Table) 12 :光标组(Group Cursor) 14 :图标组(Group Icon) 16 :版本信息(Version Information) PE中的资源一般是一个三层表, 第一层指出资源的类型ID或名字,第二层指出资源ID, 第三层指出资源语言ID,根据每一个层的不同,IMAGE_RESOURCE_ DIRECTORY_ENTRY结构数组代表的意思也不同 //第一层IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组指明了有多少种资源类型。(数组大小) //第二层IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组指明了每种类型有多少个 IMAGE_RESOURCE_DIR_ STRING_U结构定义: typedef struct _IMAGE_RESOURCE_DIR_STRING_U { WORD Length;//指明NameString字符串的长度 WCHAR NameString[1];//Unicode字符串 } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U; IMAGE_RESOURCE_DATA_ENTRY结构定义: typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData;//资源数据所在的RVA(偏移?),相对整个PE文件(感觉在磁盘上的PE文件RVA和偏移似乎没有什么区别) DWORD Size;//资源的大小,以字节为单位 DWORD CodePage;//代码页 DWORD Reserved;//保留项 } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY; 例子3。1:获取一个PE文件里的位图数量以及它们的ID号 这里的PE文件还是我们自己用VC创建吧,新建一个MFC对话框,并往里面导入四张位图。 如图: 在Resource.h头文件里它们对应的ID号分别是129,130,131,132 如下图: 然后我们点编译,运行,记住不要Debug调试版的,把生成的发行版PE文件复制到E盘下去,并改名为abc.exe。 接着我们就来读取里面的位图数量和ID号吧,代码如下:(由于已知PE文件里含有位图,所以该PE文件的资源目录树一定是在两层以上,所以在第一层到下一层的时候,便不需要判断IMAGE_RESOURCE_DIRECTORY_ENTRY结构的DataIsDirectory成员,它必然是1) #include #include int main() { HANDLE hFile=CreateFile("e:\\abc.exe",GENERIC_READ|GENERIC_WRITE,NULL, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); IMAGE_DOS_HEADER dosHeader; DWORD dwSize; ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//读取 DOS文件头 SetFilePointer(hFile,dosHeader.e_lfanew+4,NULL,FILE_BEGIN);//移到文件头位置,加4略过PE标志 IMAGE_FILE_HEADER fileHeader; ReadFile(hFile,(void *)&fileHeader,sizeof(IMAGE_FILE_HEADER),&dwSize,NULL);//读取文件头 int NumSection=fileHeader.NumberOfSections;//获取节数量 SetFilePointer(hFile,dosHeader.e_lfanew+248,NULL,FILE_BEGIN);//移到节表位置IMAGE_SECTION_HEADER Section; for(int i=0;i { ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL); if(strcmp((char *)https://www.doczj.com/doc/7516143965.html,,".rsrc")==0)//如果是资源节头 break; } SetFilePointer(hFile,Section.PointerToRawData,NULL,FILE_BEGIN);//移到资源表IMAGE_RESOURCE_DIRECTORY resDirectory; //读取第一层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int dirEntryCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; IMAGE_RESOURCE_DIRECTORY_ENTRY resDirEntry; for(i=0;i IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素 { ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); if(https://www.doczj.com/doc/7516143965.html,IsString==0&&resDirEntry.Id==2) //如果是位图 break; } SetFilePointer(hFile,Section.PointerToRawData+resDirEntry.OffsetToDirectory, NULL,FILE_BEGIN);//移到第二层目录 //读取第二层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int bmpCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; printf("该PE文件里有%d张位图\n它们的ID号如下:\n",bmpCount); for(i=0;i { ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); if(https://www.doczj.com/doc/7516143965.html,IsString==0) printf("%d\n",resDirEntry.Id); } return 0; } 运行效果如下图: 下接PE文件结构分析及应用2 本文参考了网上若干资料 例子3。2:获取一个PE文件里的所有位图并保存在磁盘里 关于位图结构分析请参考“VC API常用函数简单例子大全”中的第八十九个GetDIBits函数 资源节其实就是一个由IMAGE_RESOURCE_DIRECTORY和IMAGE_RESOURCE_DIRECTORY_ENTRY 结构组合起来而构成的目录树,而我们要做的就是顺着这一目录树一层一层的找下去,直到找到IMAGE_RESOURCE_DATA_ENTRY结构。 这里的PE文件还是以前面的"abc.exe"为例,(因为这个PE文件在我们的控制范围之内,其它的可能会有各种各样的意外情况,这里只是最理想的状态,比如PE文件有没有加壳,压缩等,毕竟是基础,先从最理想状态开始,后面慢慢会有介绍的) 好了先用文字描述一下这个过程,只要仔细顺着这一层一层的看下去,就一定会成功的!整个过程如下: 先获取资源节在PE文件的位置,然后读取PE文件资源表第一层IMAGE_RESOURCE_DIRECTORY 结构, 再遍历其下的IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组,找出哪个元素代表位图类型,然后再根据找出的元素,获取下一层的IMAGE_RESOURCE_DIRECTORY结构位置, 再遍历其下的IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组, 此时这层IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组每一个元素就代表一副位图, 再获取第一个元素指向的IMAGE_RESOURCE_DIRECTORY结构,此时就进了语言ID层, 语言ID层其下IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组大小已知为1, 而且这个IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素的指向不是 IMAGE_RESOURCE_DIRECTORY结构, 而是具体包含位图信息,数据的IMAGE_RESOURCE_DATA_ENTRY结构,获取这个结构后, 就根据这个结构提供的信息读取实际位图数据。这里由于要把所有的位图都读取出来, 所以要对第二层的每个元素进行同样的操作 代码如下:(位图保存在E盘下) #include #include void SaveBmp(char *bmpPath,BYTE *bmData,int Size); int main() { HANDLE hFile=CreateFile("e:\\abc.exe",GENERIC_READ|GENERIC_WRITE,NULL, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); IMAGE_DOS_HEADER dosHeader; DWORD dwSize; ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//读取DOS文件头 SetFilePointer(hFile,dosHeader.e_lfanew+4,NULL,FILE_BEGIN);//移到文件头位置,加4略过PE标志 IMAGE_FILE_HEADER fileHeader; ReadFile(hFile,(void *)&fileHeader,sizeof(IMAGE_FILE_HEADER),&dwSize,NULL);//读取文件头 int NumSection=fileHeader.NumberOfSections;//获取节数量 SetFilePointer(hFile,dosHeader.e_lfanew+248,NULL,FILE_BEGIN);//移到节表位置IMAGE_SECTION_HEADER Section; for(int i=0;i { ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL); if(strcmp((char *)https://www.doczj.com/doc/7516143965.html,,".rsrc")==0)//如果是资源节头 break; } SetFilePointer(hFile,Section.PointerToRawData,NULL,FILE_BEGIN);//移到资源表IMAGE_RESOURCE_DIRECTORY resDirectory; //读取第一层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int dirEntryCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; IMAGE_RESOURCE_DIRECTORY_ENTRY resDirEntry; for(i=0;i IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素 { ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); if(https://www.doczj.com/doc/7516143965.html,IsString==0&&resDirEntry.Id==2) break; } SetFilePointer(hFile,Section.PointerToRawData+resDirEntry.OffsetToDirectory, NULL,FILE_BEGIN);//移到第二层目录 //读取第二层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int bmpCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; IMAGE_RESOURCE_DIRECTORY_ENTRY *pResDirEntry; pResDirEntry=new IMAGE_RESOURCE_DIRECTORY_ENTRY[bmpCount]; for(i=0;i { ReadFile(hFile,(void *)&pResDirEntry[i],sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); } for(i=0;i { //移到第三层(语言ID),并读取第三层的IMAGE_RESOURCE_DIRECTORY结构SetFilePointer(hFile,Section.PointerToRawData+pResDirEntry[i].OffsetToDirectory ,NULL,FILE_BEGIN); ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //已知第三层的IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组大小为1 ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); SetFilePointer(hFile,Section.PointerToRawData+resDirEntry.OffsetToDirectory, NULL,FILE_BEGIN);//移到IMAGE_RESOURCE_DATA_ENTRY结构处 IMAGE_RESOURCE_DATA_ENTRY resDataEntry; ReadFile(hFile,(void *)&resDataEntry,sizeof(IMAGE_RESOURCE_DATA_ENTRY),&dwSize,NULL); //移到位图存储位置 SetFilePointer(hFile,resDataEntry.OffsetToData,NULL,FILE_BEGIN); BYTE *bmData=new BYTE[resDataEntry.Size]; ReadFile(hFile,(void *)bmData,resDataEntry.Size,&dwSize,NULL);//读取位图数据char bmpPath[15]; sprintf(bmpPath,"e:\\%d.bmp",pResDirEntry[i].Id); SaveBmp(bmpPath,bmData,resDataEntry.Size);//保存位图 } return 0; } void SaveBmp(char *bmpPath,BYTE *bmData,int Size)//该函数用于保存位图到磁盘 { DWORD dwSize; BITMAPFILEHEADER bfh;//定义位图文件头 bfh.bfType=0x4d42; bfh.bfSize=Size+sizeof(BITMAPFILEHEADER); bfh.bfReserved1=0; bfh.bfReserved2=0; bfh.bfOffBits=54; HANDLE hFBmp=CreateFile(bmpPath,GENERIC_WRITE,0,NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL); WriteFile(hFBmp,(void *)&bfh,sizeof(BITMAPFILEHEADER),&dwSize,NULL);//写入位图文件头 WriteFile(hFBmp,(void *)bmData,Size,&dwSize,NULL);//写入PE文件中的位图数据 } 例子3。3:获取PE文件图标并保存在磁盘 相对于位图在PE文件的储存方式,图标并没有像位图那样去掉了位图文件头,图标所有的数据都存储在PE文件里,但图标跟位图的区别不只这一点,图标在PE文件是分开来储存的,也就是图标被分为了两部分存储在PE文件里,这一点从“Id(资源类型ID)成员的取值”那里可以看出,3代表Icon(图标)14代表图标组(图标组)。 先来分析一下图标图片结构:(一个图标文件里可能有多幅图片,16X16,32X32大小等。) 图标文件开头是下面这样一个结构 typedef struct { unsigned short Reserved; unsigned short ResourceType; unsigned short ImageCount;//指出这个图标文件包含多少图片。 } GroupIcon; 然后是下面是这样一个结构数组,大小由GroupICon结构的ImageCount成员指出。 typedef struct { unsigned char Width; unsigned char Height; unsigned char Colors; unsigned char Reserved; unsigned short Planes; unsigned short BitsPerPixel; unsigned long ImageSize; unsigned short ResourceID; } IconDirResEntry; 接下来就是图标具体数据了。 由于这里我们只是读取PE文件里的图标,所以不对每个成员做具体分析,只要了解一下IconDirResEntry结构的ResourceID成员就行了,这个成员的意思具体取决于它在PE文件中,还是图标文件中,在PE文件中,这个成员对应每副图片数据ID,而在图标文件中则代表每副图片数据在图标文件的位置(偏移)。 前面说了,PE文件中的图标是分两部分存储的,到底是哪两部分呢? GroupICon, IconDirResEntry结构作为一部分(对应图标组14), 最后的所有图片数据作为一部分(对应图标3)。 我们要怎么获得图标在PE文件里的数量和ID号呢,比如我导入了三个图标,是依据(图标3)第二层IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组大小,还是依据(图标组14)。 答案是依据图标组14,也就是说把前面那个"例子3.1:获取一个PE文件里的位图数量以及它们的ID号"里的(位图2)改成(图标14)就可以了。 如有一个PE文件,我导入了三个图标,运行下面代码: #include #include int main() { HANDLE hFile=CreateFile("e:\\abc.exe",GENERIC_READ|GENERIC_WRITE,NULL, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); IMAGE_DOS_HEADER dosHeader; DWORD dwSize; ReadFile(hFile,(void *)&dosHeader,sizeof(IMAGE_DOS_HEADER),&dwSize,NULL);//读取DOS文件头 SetFilePointer(hFile,dosHeader.e_lfanew+4,NULL,FILE_BEGIN);//移到文件头位置,加 4略过PE标志 IMAGE_FILE_HEADER fileHeader; ReadFile(hFile,(void *)&fileHeader,sizeof(IMAGE_FILE_HEADER),&dwSize,NULL);//读取文件头 int NumSection=fileHeader.NumberOfSections;//获取节数量 SetFilePointer(hFile,dosHeader.e_lfanew+248,NULL,FILE_BEGIN);//移到节表位置IMAGE_SECTION_HEADER Section; for(int i=0;i { ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL); if(strcmp((char *)https://www.doczj.com/doc/7516143965.html,,".rsrc")==0)//如果是资源节头 break; } SetFilePointer(hFile,Section.PointerToRawData,NULL,FILE_BEGIN);//移到资源表IMAGE_RESOURCE_DIRECTORY resDirectory; //读取第一层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int dirEntryCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; IMAGE_RESOURCE_DIRECTORY_ENTRY resDirEntry; for(i=0;i IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素 { ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL); if(https://www.doczj.com/doc/7516143965.html,IsString==0&&resDirEntry.Id==14) //改成图标组14 break; } SetFilePointer(hFile,Section.PointerToRawData+resDirEntry.OffsetToDirectory, NULL,FILE_BEGIN);//移到第二层目录 //读取第二层目录IMAGE_RESOURCE_DIRECTORY结构 ReadFile(hFile,(void *)&resDirectory,sizeof(IMAGE_RESOURCE_DIRECTORY),&dwSize,NULL); //计算其后IMAGE_RESOURCE_DIRECTORY结构数组大小 int bmpCount=resDirectory.NumberOfIdEntries+resDirectory.NumberOfNamedEntries; printf("该PE文件里有%d个图标\n它们的ID号如下:\n",bmpCount); for(i=0;i { ReadFile(hFile,(void PE文件格式详解(一)――基础知识 什么是PE文件格式: 我们知道所有文件都是一些连续(当然实际存储在磁盘上的时候不一定是连续的)的数据组织起来的,不同类型的文件肯定组织形式也各不相同;PE文件格式便是一种文件组织形式,它是32位Wind ow系统中的可执行文件EXE以及动态连接库文件DLL的组织形式。为什么我们双击一个EXE文件之后它就会被Window运行,而我们双击一个DOC文件就会被Word打开并显示其中的内容;这说明文件中肯定除了存在那些文件的主体内容(比如EXE文件中的代码,数据等,DOC文件中的文件内容等)之外还存在其他一些重要的信息。这些信息是给文件的使用者看的,比如说EXE文件的使用者就是Window,而DOC文件的使用者就是Word。Window可以根据这些信息知道把文件加载到地址空间的那个位置,知道从哪个地址开始执行;加载到内存后如何修正一些指令中的地址等等。那么PE文件中的这些重要信息都是由谁加入的呢?是由编译器和连接器完成的,针对不同的编译器和连接器通常会提供不同的选项让我们在编译和 联结生成PE文件的时候对其中的那些Window需要的信息进行设定;当然也可以按照默认的方式编译连接生成Window中默认的信息。例如:WindowNT默认的程序加载基址是0x40000;你可以在用VC连接生成EXE文件的时候使用选项更改这个地址值。在不同的操作系统中可执行文件的格式是不同的,比如在Linux上就有一种流行的ELF格式;当然它是由在Linux上的编译器和连接器生成的, 所以编译器、连接器是针对不同的CPU架构和不同的操作系统而涉及出来的。在嵌入式领域中我们经常提到交叉编译器一词,它的作用就是在一种平台下编译出能在另一个平台下运行的程序;例如,我们可以使用交叉编译器在跑Linux的X86机器上编译出能在Arm上运行的程序。 程序是如何运行起来的: 一个程序从编写出来到运行一共需要那些工具,他们都对程序作了些什么呢?里面都涉及哪些知识需要学习呢?先说工具:编辑器-》编译器-》连接器-》加载器;首先我们使用编辑器编辑源文件;然后使用编译器编译程目标文件OBJ,这里面涉及到编译原理的知识;连接器把OBJ文件和其他一些库文件和资源文件连接起来生成EXE文件,这里面涉及到不同的连接器的知识,连接器根据OS的需要生成EXE文件保存着磁盘上;当我们运行EXE文件的时候有W indow的加载器负责把EXE文件加载到线性地址空间,加载的时候便是根据上一节中说到的PE文件格式中的哪些重要信息。然后生成一个进程,如果进程中涉及到多个线程还要生成一个主线程;此后进程便开始运行;这里面涉及的东西很多,包括:PE文件格式的内容;内存管理(CPU内存管理的硬件环境以及在此基础上的OS内存管理方式);模块,进程,线程的知识;只有把这些都弄清楚之后才能比较清楚的了解这整个过程。下面就让我们先来学习PE文件格式吧。 PE可选头部 PE可执行文件中接下来的224个字节组成了PE可选头部。虽然它的名字是“可选头部”,但是请确信:这个头部并非“可选”,而是“必需”的。OPTHDROFFSET宏可以获得指向可选头部的指针: PEFILE.H #define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \ ((PIMAGE_DOS_HEADER)a)->e_lfanew + \ SIZE_OF_NT_SIGNATURE + \ sizeof(IMAGE_FILE_HEADER))) 可选头部包含了很多关于可执行映像的重要信息,例如初始的堆栈大小、程序入口点的位置、首选基地址、操作系统版本、段对齐的信息等等。IMAGE_OPTIONAL_HEADER结构如下: WINNT.H typedef struct _IMAGE_OPTIONAL_HEADER { // // 标准域 // USHORT Magic; UCHAR MajorLinkerVersion; UCHAR MinorLinkerVersion; ULONG SizeOfCode; ULONG SizeOfInitializedData; ULONG SizeOfUninitializedData; ULONG AddressOfEntryPoint; ULONG BaseOfCode; ULONG BaseOfData; // // NT附加域 // ULONG ImageBase; ULONG SectionAlignment; ULONG FileAlignment; USHORT MajorOperatingSystemVersion; USHORT MinorOperatingSystemVersion; USHORT MajorImageVersion; USHORT MinorImageVersion; USHORT MajorSubsystemVersion; USHORT MinorSubsystemVersion; ULONG Reserved1; ULONG SizeOfImage; ULONG SizeOfHeaders; ULONG CheckSum; USHORT Subsystem; USHORT DllCharacteristics; ULONG SizeOfStackReserve; ULONG SizeOfStackCommit; ULONG SizeOfHeapReserve; ULONG SizeOfHeapCommit; ULONG LoaderFlags; ULONG NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER; 如你所见,这个结构中所列出的域实在是冗长得过分。为了不让你对所有这些域感到厌烦,我会仅仅讨论有用的——就是说,对于探究PE文件格式而言有用的。 标准域 首先,请注意这个结构被划分为“标准域”和“NT附加域”。所谓标准域,就是和UNIX可执行文件的COFF 格式所公共的部分。虽然标准域保留了COFF中定义的名字,但是Windows NT仍然将它们用作了不同的目的——尽管换个名字更好一些。 ·Magic。我不知道这个域是干什么的,对于示例程序EXEVIEW.EXE示例程序而言,这个值是0x010B 2009-06-08 11:38:40 https://www.doczj.com/doc/7516143965.html, 来源:黑客防线 一直以来都在学习PE文件结构,从不敢轻视,但是即使如此还是发现自己在这方面有所不足,于是便想到了用纯手工方式打造一个完整的可执行的PE文件。在这期间我也查了大量资料,但是这些资料都有一个通病就是不... 一直以来都在学习PE文件结构,从不敢轻视,但是即使如此还是发现自己在这方面有所不足,于是便想到了用纯手工方式打造一个完整的可执行的PE文件。在这期间我也查了大量资料,但是这些资料都有一个通病就是不完整,看雪得那个只翻译了一部分,加解密技术内幕介绍的更是笼统,而且是打造一个只有180字节的PE文件,是高手们茶余饭后的怡情小游戏。 鉴于此,心想为什么不自己摸索着手工打造一个完整些的呢?一是加强一下自己对于PE文件的了解,二是写出一篇参考性比较强的文章,给有志于在此发展的朋友们铺一铺路,也算是干了一件利国利民的好事。 对于手工打造PE文件,我个人认为至少要分为三篇文章来阐述,每篇相对独立,合起来形成一个相对的体系。第一篇文章(也就是本文)用来介绍怎样用手工打造一个最典型、最简单的PE文件,而后两篇文章的问世还要引用潘爱民先生的一句话“还需要时日与机缘”。 本文介绍的PE文件手工编辑方式,是本着以下三个原则所写的,望读者注意: 1、完整性:对于手工打造PE文件所不需注意的字段也进行了必要的介绍,因此整文可能显得非常臃肿。 2、典型性:完全按照典型的PE文件结构构造,因此对于某些不常见的PE文件结构有一定差距。 3、易学性:对于字段之间的逻辑关系进行了比较细致的介绍,因此对于一部分底子比较好的读者来说可能显得有些啰嗦。 为了方便各位阅读与查阅,我将文章分成了三各部分,以便各位读者各取所需,不用把宝贵的精力浪费在查找上。 1、PE文件整体信息,提供了一个剖析PE文件的图表,以便于读者对于PE文件有一个整体的了解,并监督自己的工作进度。 2、对于重点字段的介绍,以及字段之间的逻辑关系,建议首先从这里开始看。 3、手工构造PE文件字段清单,此清单包含构造一个完整PE文件的每一个字节,跟着这个清单走就可以构造一个PE文件。 对于第一次手工打造PE文件的朋友们来说,你们可以以“一、整体性息”为大纲,并参考第三部分一块一块的慢慢打造,如果有不懂的地方就去看第二部分。 选读:为什么要手工打造PE文件? 我们知道,往往从一个系统可执行文件结构上,就可以看整个操作系统的一些特性。也就是说PE里有Windows操作系统结构与运行机理的影子。由此可见,PE文件必然是一个非常庞杂且逻辑复杂的结构,那么为什么我们还要“自取其辱”来手工制造一个PE文件呢?这就要从PE文件的重要性说起了。 我们现今组成Windows大家庭的主要成员就是PE文件了,里面包括EXE、DLL、OCX、SYS等一切最有价值的文件都是PE文件格式,出于对版权的考虑或对某种技术的渴求,任何一种与Windows系统相关的行为最终都要归集到这里--PE文件。 特别是对于想学习加壳、破解、搞虚拟机的朋友们来说,熟知PE文件结构更是必不可少的基本功! 但也正是由于PE文件的复杂性,我们才要采取一些特别的办法来攻克它,其中手工打造PE文件就是一条捷径。 你可以想像一下,如果你都可以手工打造PE文件的话,那么对于PE文件的了解更是 三年前,我曾经写了一个手工打造可执行程序的文章,可是因为时间关系,我的那篇文章还是有很多模糊的地方,我一直惦记着什么时候再写一篇完美的,没想到一等就等了三年。因为各种原因直到三年后的今天我终于完成了它。现在把它分享给大家,希望大家批评指正。 我们这里将不依赖任何编译器,仅仅使用一个十六进制编辑器逐个字节的手工编写一个可执行程序。以这种方式讲解PE结构,通过这个过程读者可以学习PE结构中的PE头、节表以及导入表相关方面的知识。为了简单而又令所有学习程序开发的人感到亲切,我们将完成一个Hello World! 程序。功能仅仅是运行后弹出一个消息框,消息框的内容是Hello World!。 首先了解一下Win32可执行程序的大体结构,就是通常所说的PE结构。 如图1所示PE结构示意图: 图1 标准PE结构图 由图中可以看出PE结构分为几个部分: MS-DOS MZ 头部:所有PE文件必须以一个简单的DOS MZ 头开始。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随MZ header 之后的DOS程序。以此达到对Dos系统的兼容。(通常情况DOS MZ header总共占用64byte)。 MS-DOS 实模式残余程序:实际上是个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显 示一个错误提示,大多数情况下它是由汇编编译器自动生成。通常,它简单调用中断21h,服务9来显示字符串"This program cannot run in DOS mode"。(在我们写的程序中,他不是必须的,可以不予以实现,但是要保留其大小,大小为112byte,为了简洁,可以使用00来填充。) PE文件标志:是PE文件结构的起始标志。(长度4byte, Windows程序此值必须为0x50450000) PE文件头:是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从DOS MZ header中找到PE header的起始偏移量,跳过了MS-DOS 实模式残余程序,直接定位到真正的文件头PE header,长度20byte。 PE文件可选头:虽然它的名字是“可选头部”,但是请确信:这个头部并非“可选”,而是“必需”的。(长度 224byte )。 各段头部:又称节头部,一个Windows NT的应用程序典型地拥有9个预定义段(节),它们是“.text”、“.bss”、“.rdata”、“.data”、“.rsrc”、“.edata”、“.idata”、“.pdata”和“.debug”。一些应用程序不需要所有的这些段,同样还有些应用程序为了自己特殊的需要而定义了更多的段。(每个段头部占40byte,我们这里也不需要所有的段,仅需3个段。) 通常我们是将PE整个结构分成四个部分,把MS-DOS MZ 头部和MS-DOS 实模式残余程序作为第一部分,可以称他为DOS部分,而PE文件标志、PE文件头、PE文件可选头三个部分作为第二部分,称之为PE头部分,因为这部分才是Windows下真正需要的部分,所以从PE文件标志开始才是真正的PE部分。各段头部是第三部分,称之为节表。它详细描述了PE文件中各个节的详细信息。最后就是各个节的实体部分了,称为节数据。 以上仅仅是对PE结构各部分的大体讲解。接下来再手写这个Hello World!程序过程中,我将详细介绍每个部分的含义。 首先准备一下工具,一个十六进制编辑器足以。我们这里使用VC++ 6.0所携带的十六进制编辑器,您也可以使用如WinHex等十六进制编辑工具。 打开VC,选择文件,新建菜单项,然后选择一个二进制文件,单击确定。一切就绪了,下面就开始手写可执行程序,如图2所示: pe文件格式:PE文件格式(1) 疯狂代码 https://www.doczj.com/doc/7516143965.html,/ ?:http:/https://www.doczj.com/doc/7516143965.html,/Waigua/Article60255.html 介绍说明:希望本文能够对初级入门CRACKER有定帮助翻译存在疏漏或者不准确希望来信指出感谢您指导!感谢看雪为我们提供这个交流平台让我们技术和时俱进!! 前言: PE("portableexecutable")文件格式是针对MSwindowsNT,windows95and win32s可执行 2进制代码(DLLsandprograms)在windowsNT内,驱动也是这个格式也可以用于对象文件和库 这个格式是Microsoft设计并在1993经过TIS(toolerfacestandard)委员会 (Microsoft,Intel,Borland,Watcom,IBM等)标准化了它基于在UNIX和VMS上运行对象文件和可执行文件COFF"commonobjectfileformat"格式 win32SDK包括个头文件 《PE文件剖析实验》报告 学院:计算机学院 日期: 2016年 4 月 25 日 目录 1. 实验目的 (2) 2. 实验过程 (2) 3. 实现结果 (2) 4. 遇到的问题及感想收获 (5) 1. 实验目的 熟悉各种PE编辑查看工具,详细了解PE文件格式,为后续计算机病毒学习打下基础,学会分析PE文件文件头、引入表、引出表以及资源表。 2. 实验过程 PE文件被称为可移植的执行体,是Portable Execute的简称,常见的EXE、DLL、OCX、SYS、COM等文件都是PE文件。 本次实验将综合运用Peview和LordPE,自由选择一款EXE文件或DLL文件,对其展开分析,找出文件的PE标志,入口地址、基址,节表个数及列出节表、输出表、输入表等信息。 3. 实现结果 ●自由选择一款EXE文件或DLL文件,对其展开分析,各项结果截图如下:●文件的PE标志 ●入口地址 ●基址 ●节表个数及列出节表 ●输出表 ●输入表 4. 遇到的问题及感想收获 本次实验时对PE文件格式进行分析,对PE文件有了一个大致的了解,如什么是PE文件的文件头,什么是区段,什么是数据目录。由于现阶段大都的病毒都是PE病毒,所以了解它们可以更好地知道PE病毒是如何感染PE文件的。不仅如此,对PE文件的修改加壳等操作也证实了PE文件并不是不可编辑的,也可以让我们更好地了解PE文件的内部结构。 这次实验主要使用了LoadPE和Peview软件对PE文件进行查看,提高了我们对PE文件结构的认识,帮助了我们吸收理论课上所学的知识,如基址与相对虚拟地址及其查看方法,PE文件首部,区段的含义等。这对我们以后的学习有很大的帮助,达到了预期的实验目的。 通过本次课程设计,我深刻认识到要掌握知识,不仅仅是要把书上的基本知识学好而且还要不断进行实践,将所学的跟实践操作结合起来才能更好地巩固所学,才能提高自己的实践能力。 PE文件格式分析及修改(图) PE 的意思是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行文件格式。它的一些特性继承自Unix的Coff(common object file format)文件格式。“Portable Executable”(可移植的执行体)意味着此文件格式是跨Win32平台的;即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。 PE文件在文件系统中,与存贮在磁盘上的其它文件一样,都是二进制数据,对于操作系统来讲,可以认为是特定信息的一个载体,如果要让计算机系统执行某程序,则程序文件的载体必须符合某种特定的格式。要分析特定信息载体的格式,要求分析人员有数据分析、编码分析的能力。在Win32系统中,PE 文件可以认为.exe、.dll、.sys 、.scr类型的文件,这些文件在磁盘上存贮的格式都是有一定规律的。 一、PE格式基础 下表列出了PE的总体结构 一个完整的PE文件,前五项是必定要有的,如果缺少或者数据出错,系统会拒绝执行该文件如下图 DOS MZ header部分是DOS时代遗留的产物,是PE文件的一个遗传基因,一个Win32程序如果在DOS 下也是可以执行,只是提示:“This program cannot be run in DOS mode.”然后就结束执行,提示执行者,这个程序要在Win32系统下执行。 DOS stub 部分是DOS插桩代码,是DOS下的16位程序代码,只是为了显示上面的提示数据。这段代码是编译器在程序编译过程中自动添加的。 PE header 是真正的Win32程序的格式头部,其中包括了PE格式的各种信息,指导系统如何装载和执行此程序代码。 Section table部分是PE代码和数据的结构数据,指示装载系统代码段在哪里,数据段在哪里等。对于不同的PE文件,设计者可能要求该文件包括不同的数据的Section。所以有一个Section Table 作为索引。Section多少可以根据实际情况而不同。但至少要有一个Section。如果一个程序连代码都没有,那么他也不能称为可执行代码。在Section Table后,Section数目的多少是不定的。 二、程序的装入 当我们在explorer.exe(资源管理器)中双击某文件,执行一个可执行程序,系统会根据文件扩展名启动一个程序装载器,称之为Loader。Loader会首先检查DOS MZ Header,如果存在,就继续寻找PE header,如果这两项都不存在,就认为是DOS 16位代码,如果只存在DOS MZ Header,而其中又指示 一、PE文件结构 PE文件被称为可移植的执行体是Portable Execute的全称,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL),Portable 是指对于不同的Windows版本和不同的CPU类型上PE文件的格式是一样的,当然CPU不一样了,CPU指令的二进制编码是不一样的。只是文件中各种东西的布局是一样的。 在下面关于结构的定义中,WORD 表示变量大小为2个字节,DWORD表示变量大小是4个字节。 1.1 PE文件的结构 PE文件有着固定的结构,分为五个部分,如下: 1:DOS MZ Header(DOS文件头) 一个IMAGE_DOS_HEADER结构,大小为64字节。 2:DOS Stub(DOS加载模块) 没有固定大小。 3:PE Header(PE文件头)一个IMAGE_NT_HEADERS结构,大小为248字节。 4:Section Table(节表)一个IMAGE_SECTION_HEADER结构数组,数组大小依据节而定,如果PE文件有5个节,则数组大小为5。 5:Sections(节或段)没有固定大小,可以有多个节。 1.2 DOS文件头和DOS加载模块 PE文件的一二部分完全是为了程序能在DOS运行下时给出一个提示。 IMAGE_DOS_HEADER结构的定义如下: Typedef struct IMAGE_DOS_HEADER{ WORD e_magic; // 魔术数字 WORD e_cblp; // 文件最后页的字节数 WORD e_cp; // 文件页数 WORD e_crlc; // 重定义元素个数 WORD e_cparhdr; // 头部尺寸,以段落为单位 WORD e_minalloc; // 所需的最小附加段 WORD e_maxalloc; // 所需的最大附加段 WORD e_ss; // 初始的SS值(相对偏移量) WORD e_sp; // 初始的SP值 WORD e_csum; // 校验和 WORD e_ip; // 初始的IP值 WORD e_cs; // 初始的CS值(相对偏移量) WORD e_lfarlc; // 重分配表文件地址 WORD e_ovno; // 覆盖号 WORD e_res[4]; // 保留字 WORD e_oemid; // OEM标识符(相对e_oeminfo) WORD e_oeminfo; // OEM信息 WORD e_res2[10]; // 保留字 LONG e_lfanew; // 新exe头部的文件地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; PE文件格式分析实验 使用工具LordPE/PEview、winhex 选择一个exe或者DLL文件 阶段一:(本次实验) 1.DOS头部查看、对应DOS头结构进行数据逐项分析 2.PE头部查看、对应PE头结构进行数据逐项分析 3.Exe文件和DLL文件均是PE格式,他们的区别在哪里? 4.Section表结构的查看(是否可以增加一个新的section表?对齐边界是多少?) 5.PE文件section查看、对应section 块表结构进行数据分析 6.VA、RVA、RA计算 7.问题:你查看的PE文件DOS、PE头部的空隙是多大? 8.问题:你查看的PE文件在那个section的空隙最大/最小? 9.问题:如果手工增加一个section,要修改哪些字段,请手工试验。(提高:你的新块表 增加是否引起原文件的对齐位置的改变?) 10.问题(提高):一个section的属性字节如何设置,请在上一个问题基础上实验。 本次实验要求对照以上要求,自行选择文件进行分析,撰写报告。 阶段二:(后一阶段的工作) 1.资源查看、修改 2.编写PE文件分析程序 3.编写PE病毒程序 附录1PE格式详细讲解(一) 前几天发了一个PE信息查看器的小工具,本来想用那个获取邀请码的,可是觉得几率不是太大,于是再献上一篇教程,既是为了自己能获得邀请码,也是帮助那些想学习PE格式的人,让知识来源于网络再回归网络。 N年没写文章了,不知道句子还能不能写通顺,最近正在看《软件加密技术内幕》,刚看完PE结构那部分内容,所以想起来写篇教程作为读书笔记,既可加强记忆又可帮助别人,何乐而不为呢。 好了,废话少说好戏正式上场,PE是英文Portable Executable(可移植的执行体)的缩写,从缩写可以看出它是跨平台的,即使在非intel的CPU上也能正常运行的。它是 Win32环境自身所带的执行体文件格式。其实不光是EXE文件是PE格式,其它的一些重要文件,例如动态链接库文件(DLL),驱动文件(SYS)等也是PE格式的,所以学好PE格式是非常重要的,以下我把这类文件统称为PE文件。学习PE文件结构不仅可以使我们知道可执行文件是怎样运行的,也可以使我们了解一下windows操作系统的一些工作机制,精通PE是成为计算机高手的必经之路。 其实说白了PE文件格式就是一种文件组织的方式,里面对一些重要信息的存放做了一些规定,比如文件要运行,我们就得先知道入口地址,可是我们从哪去得到入口地址呢,我们必须把保存有入口地址信息 PE文件结构详解(三)PE导出表 上篇文章PE文件结构详解(二)可执行文件头的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,DataDirectory中的每一项都可以用这个函数获取,函数原型如下: PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size); Base:模块基地址。 MappedAsImage:是否映射为映象。 Directory:数据目录项的索引。 Size:对应数据目录项的大小,比如Directory为0,则表示导出表的大小。 返回值表示数据目录项的起始地址。 这次来看看第一项:导出表。 导出表是用来描述模块中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址。函数导出的方式有两种,一种是按名字导出,一种是按序号导出。这两种导出方式在导出表中的描述方式也不相同。模块的导出函数可以通过Dependency walker工具来查看: 上图中红框位置显示的就是模块的导出函数,有时候显示的导出函数名字中有一些符号,像 ??0CP2PDownloadUIInterface@@QAE@ABV0@@Z,这种是导出了C++的函数名,编译器将名字进行了修饰。 下面看一下导出表的定义吧: 结构还算比较简单,具体每一项的含义如下: Characteristics:现在没有用到,一般为0。 TimeDateStamp:导出表生成的时间戳,由连接器生成。 MajorVersion,MinorVersion:看名字是版本,实际貌似没有用,都是0。 PE文件结构详解 1 摘要 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy, Specifications, Windows NT File Format Specifications),但是它非常之晦涩。 然而这一的文档并未提供足够的信息,所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题,它会对整个的PE文件格式作一个十分彻底的解释,另外,本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。 为了获得PE文件中所包含的重要信息,我编写了一个名为PEFILE.DLL的动态链接库,本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中(译注:示例程序请在MSDN中寻找,本站恕不提供),你可以在你自己的应用程序中使用这个DLL;同样,你亦可以依你所愿地使用并构建它的源码。在本文末尾,你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。 2 介绍 Windows操作系统家族最近增加的Windows NT为开发环境和应用程序本身带来了很大的改变,这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX操作系统所通用的COFF规范,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。 在本文之中,PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中,本文会详细讨论PE文件的每一个组成部分。 很多解决PE文件格式的工作和直接观看数据有关。例如,要弄懂导入地址名称表是如何构成的,我就得同时查看.idata段头部、导入映像数据目录、可选头部以及当前的.idata段实体,而EXEVIEW.EXE就是查看这些信息的最佳示例。 在针对PE文件的有关编程中,你可能用到以下一些数据结构: IMAGE_DOS_HEADER IMAGE_IMPORT_DESCRIPTOR IMAGE_NT_HEADERS IMAGE_SECTION_HEADER IMAGE_OPTIONAL_HEADER IMAGE_DA TA_DIRECTORY IMAGE_FILE_HEADER 3 PE文件结构图 PE文件格式详解(上) Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式。PE文件格式的规范包含在了MSDN的CD 中(Specs and Strategy, Specifications, Windows NT File Format Specifications),但是它非常之晦涩。 然而这一的文档并未提供足够的信息,所以开发者们无法很好地弄懂PE格式。本文旨在解决这一问题,它会对整个的PE文件格式作一个十分彻底的解释,另外,本文中还带有对所有必需结构的描述以及示范如何使用这些信息的源码示例。 为了获得PE文件中所包含的重要信息,我编写了一个名为PEFILE.DLL的动态链接库,本文中所有出现的源码示例亦均摘自于此。这个DLL和它的源代码都作为PEFile示例程序的一部分包含在了CD中(译注:示例程序请在MSDN 中寻找,本站恕不提供),你可以在你自己的应用程序中使用这个DLL;同样,你亦可以依你所愿地使用并构建它的源码。在本文末尾,你会找到PEFILE.DLL的函数导出列表和一个如何使用它们的说明。我觉得你会发现这些函数会让你从容应付PE文件格式的。 介绍 Windows操作系统家族最近增加的Windows NT为开发环 境和应用程序本身带来了很大的改变,这之中一个最为重大的当属PE文件格式了。新的PE文件格式主要来自于UNIX 操作系统所通用的COFF规范,同时为了保证与旧版本 MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ头部。 在本文之中,PE文件格式是以自顶而下的顺序解释的。在你从头开始研究文件内容的过程之中,本文会详细讨论 PE文件的每一个组成部分。 许多单独的文件成分定义都来自于Microsoft Win32 SDK开发包中的WINNT.H文件,在这个文件中你会发现用来描述文件头部和数据目录等各种成分的结构类型定义。但是,在WINNT.H中缺少对PE文件结构足够的定义,在这 种情况下,我定义了自己的结构来存取文件数据。你会在PEFILE.DLL工程的PEFILE.H中找到这些结构的定义,整套的PEFILE.H开发文件包含在PEFile示例程序之中。 PE文件结构详解(六)重定位 前面两篇PE文件结构详解(四)PE导入表和PE文件结构详解(五)延迟导入表介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里,IE的iexplorer.exe导入了Kernel32.dll的GetCommandLineA函数,可以看到这是个间接call,00401004这个地址的内存里保存了目的地址,根据图中显示的符号信息可知,00401004这个地址是存在于iexplorer.exe模块中的,实际上也就是一项IAT的地址。这个是IE6的exe中的例子,当然在dll 中如果导入其他dll中的函数,结果也是一样的。这样就有一个问题,代码里call 的地址是一个模块内的地址,而且是一个VA,那么如果模块基地址发生了变化,这个地址岂不是就无效了?这个问题如何解决? 答案是:Windows使用重定位机制保证以上代码无论模块加载到哪个基址都能正确被调用。听起来很神奇,是怎么做到的呢?其实原理并不很复杂,这个过程分三步: 1.编译的时候由编译器识别出哪些项使用了模块内的直接VA,比如push一个全局变量、函数地址,这些指令的操作数在模块加载的时候就需要被重定位。 2.链接器生成PE文件的时候将编译器识别的重定位的项纪录在一张表里,这张表就是重定位表,保存在DataDirectory中,序号是 IMAGE_DIRECTORY_ENTRY_BASERELOC。 3.PE文件加载时,PE 加载器分析重定位表,将其中每一项按照现在的模块基址进行重定位。 以上三步,前两部涉及到了编译和链接的知识,跟本文的关系不大,我们直接看第三步,这一步符合本系列的特征。 在查看重定位表的定义前,我们先了解一下他的存储方式,有助于后面的理解。按照常规思路,每个重定位项应该是一个DWORD,里面保存需要重定位的RVA,这样只需要简单操作便能找到需要重定位的项。然而,Windows并没有这样设计,原因是这样存放太占用空间了,试想一下,加入一个文件有n个重定位项,那么就需要占用4*n个字节。所以Windows采用了分组的方式,按照重定位项所在的页面分组,每组保存一个页面其实地址的RVA,页内的每项重定位项使用一个WORD 保存重定位项在页内的偏移,这样就大大缩小了重定位表的大小。 offset 0123456789A B C D E F 00000000 00000010 00000020 00000030 000000400000005000000060000000700000008000000090000000A0 000000B0 000000C0 Maj orL ink erV ers ion Min orL ink erV ers ion 000000D0 Signature IMAGE_FILE_HEADER DOS stub M S -D o s 部首 IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER32 IMAGE_FILE_HEADER NumberOfSymbols SizeOfO ptional Header Characte ristics Magic SizeOfCode e_lfanew Signature Machine NumberOf Sections TimeDataStamp PointerTpSymbo lTable e_oemid e_oemin fo e_res2 e_ss e_sp e_csum e_ip e_cs e_lfarl c e_ovno e_res PE文件结构DOS"MZ"HEADER e_magic e_cblp e_cp e_crlc e_cparh dr e_minal loc e_maxal loc 000000E0 000000F0 00000100 00000110 00000120 00000130 00000140 00000150 IMAGE_OPTIONAL_HEADER32 DataDirectory( PE文件头 IMAGE_NT_HEADERS IMAGE_OPTIONAL_HEADER32 DataDirectory IMAGE_DIRECTORY_ENTRY_BASERELOC IMAGE_DIRECTORY_ENTRY_DEBUG ISSN 1009-3044 Computer Knowledge and Technology V ol.5 No.9, March 2009 系统软件与软件工程2379 电脑知识与技术 PE文件格式分析 李卓远, 鲜 明 (国防科大 电子科学与工程学院, 湖南 长沙 410073) 摘要:主要分析了PE(Portable Executable)文件格式,重点探讨了PE文件中的输入表、输出表以及资源段的格式,还探究了在微软全新的.Net平台上对PE文件的扩展。 关键词:PE文件格式;段;PE扩展 中图分类号:TP311.12 文献标识码:A 文章编号:1009-3044(2009)09-2379-03 Analysis of PE File Format LI Zhuo-yuan, XIAN Ming ( Dept. of Electronic Science&Technology, NUDT, Changsha 410073, China) Abstract: This paper analyzes the PE(Portable Executable)? le format. We explores the import table, export table and resource section of PE ? le and also discusses the extensions to PE ? le format in .Net platform. Key words : PE(Portable Executable)File Format; Sections; Extensions to PE 1 引言 PE文件格式是Win32平台上(包括 Windows9x/NT/2000/XP/2003/Vista/CE)主流的可执行文件格式,是Portable Executable File Format(可移植的执行体)简写。它衍生于早期建立在VAX/VMS上的COFF(Common Object File Format)文件格式。对PE格式和COFF文件的主要描述存放在winnt.h文件中,它是PE文件定义的最终决定者。 EXE和DLL文件是PE文件格式的两种主要文件,它们的区别完全是语义上的,二者使用完全相同的PE格式。用IMAGE_FILE_HEADER中的Characteristics字段的第13位来标识出这个文件到底是EXE还是DLL。 64位的Windows只对PE格式作了一些简单的修饰,新格式叫做PE32+,并未加入新的结构,只简单的将以前的32位字段扩展为64位。图1是PE文件的基本结构图。 2 PE的基本概念 文件偏移地址是指当PE文件存贮在磁盘上时,某个数据的位置相对于头文件的偏移量,称为文件偏移地址(File Offset)或物理地址(RAW Offset)。文件偏移地址从PE文件的第一个字节开始计数,起始值为0。 虚拟地址是指当PE文件装入内存时,各数据在内存中的位置。当PE文件通过Windows加载器被装入内存后,内存中的版本被称作模块(Module)。映射文件的起始地址被称为模块句柄(hModule),可以通过模块句柄访问内存中其他的数据结构。这个初始内存地址就称为基地址(ImageBase)。基地址的值是由PE文件本身设定的。按照默认设置,Visual C++建立的EXE文件基地址是00400000h,DLL文件基地址是10000000h。 相对虚拟地址(RVA)只是内存中的一个简单的相对于PE文件装入地址的偏移位置,它是一个“相对”地址,或称“偏移量”。因为尽管PE文件有一个首选的装入地址,但是并非一定会装入指定的位置,在这种情况下需要根据实际的装入位置重新定位,这就需要各部分相对于基地址的偏移量。图2显示了PE文件在装入前后的相对位置变化。 虚拟地址(VA)=基地址(ImageBase)+ 相对虚拟地址(RVA) 3 MS-DOS头部 3.1 DOS MZ header 每个PE文件都由一个DOS头开始的,DOS头包括两部分:“DOS MZ header”和“DOS stub”。PE文件的第一个字节起始于一个传统的MS_DOS头部,被称作IMAGE_DOS_HEADER,其结构如下: typedef struct _IMAGE_DOS_HEADER { WORD e_magic; 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]; LONG e_lfanew; } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;此结构中,有两个字段比较重要,分别是e_magic和e_lfanew字段,e_magic被设置为5A4Dh,在winnt.h中定义为IMAGE_DOS_SIGNATURE,它的ASCⅡ值为“MZ”。 3.2 DOS stub DOS stub实际上是一个有效的EXE,在不支持PE文件格式的操作系统中,它简单显示一个错误提示“This program cannot be run in MS-DOS mode”。程序员也可以根据自己的意图写出完整的DOS代码。 图1 PE文件的框架结构图2 PE文件磁盘与内存映像结构图pe文件格式
PE文件头解析大全
手工构造典型PE文件
pe文件结构 入门 教程
pe文件格式
实验1《 PE文件剖析实验》
PE文件格式分析及修改
PE文件结构与ELF文件结构
PE文件格式实验
PE文件结构详解(三)PE导出表
PE文件结构详解
PE文件格式详解(上)
PE文件结构详解(六)重定位
PE文件结构详解--对照《加密与破解》第十章.
PE文件格式分析