PE文件结构分析及应用一

只做参考资料使用。创作于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

 本文参考了网上若干资料

  • 13
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bczheng1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值