只做参考资料使用。创作于2012年。
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; // 校验和,仅用在驱动程序中,在可执行文件中可能为0
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<stdio.h>
#include<windows.h>
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<NumSection;i++)//依次读出IMAGE_SECTION_HEADER数组的每个元素
{
IMAGE_SECTION_HEADER Section;
ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL);
printf("%s\n",Section.Name);//输出节名
}
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<stdio.h>
#include<windows.h>
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<imCount;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<thunkCount;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结构后都跟着一个IMAGE_RESOURCE_DIRECTORY_ENTRY数组,事实上IMAGE_RESOURCE_DIRECTORY结构就是为了描述其后的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_DATA_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<stdio.h>
#include<windows.h>
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<NumSection;i++)//依次读出IMAGE_SECTION_HEADER数组的每个元素
{
ReadFile(hFile,(void *)&Section,sizeof(IMAGE_SECTION_HEADER),&dwSize,NULL);
if(strcmp((char *)Section.Name,".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<dirEntryCount;i++)//依次读出第一目录下的IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组元素
{
ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL);
if(resDirEntry.NameIsString==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<bmpCount;i++)//依次读出第二目录下的IMAGE_RESOURCE_DIRECTORY_ENTRY数组元素
{
ReadFile(hFile,(void *)&resDirEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&dwSize,NULL);
if(resDirEntry.NameIsString==0)
printf("%d\n",resDirEntry.Id);
}
return 0;
}
运行效果如下图:
下接PE文件结构分析及应用2
本文参考了网上若干资料