PE文件解析

  • IMAGE_DOS_HEADER结构(DOS头部)

typedef struct _IMAGE_DOS_HEADER {

    WORD   e_magic;         // 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];

    LONG   e_lfanew;        // PE文件头偏移

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

上述为IMAGE_DOS_HEADER结构结构体,最重要的参数只有两个—— e_magic, e_lfanew。 e_magic是MZ标志位,为0x5A4D, e_lfanew放着PE文件开头的位置0x000000f8(0x3c-0x3f),根据e_lfanew可以跳过IMAGE_DOS_HEADER来到PE头部

  • IMAGE_NT_HEADERS结构学习(PE头部)

  typedef struct _IMAGE_NT_HEADERS {

          DWORD Signature;

          IMAGE_FILE_HEADER FileHeader;

          IMAGE_OPTIONAL_HEADER32 OptionalHeader;

      } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

从上文中的e_lfanew(0x000000b8),我们可以直接找到PE文件头部,即IMAGE_NT_HEADERS中的Signature,为0x50450000(0xb8-0xbb)。IMAGE_NT_HEADERS中第二个元素为IMAGE_FILE_HEADER FileHeader,标明PE文件的一些属性,其结构体及其内部元素解释如下

typedef struct _IMAGE_FILE_HEADER {

          WORD    Machine;

          WORD    NumberOfSections;

          DWORD   TimeDateStamp;

          DWORD   PointerToSymbolTable;

          DWORD   NumberOfSymbols;

          WORD    SizeOfOptionalHeader;

          WORD    Characteristics;

      } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine指明PE文件支持的CPU类型,0x014c表明是Intel i386的CPU

NumberOfSections指明PE文件包含的节区数量,0x0004表明有4块

TimeDateStamp字段指明文件的创建时间为1970年1月1日以来用格林威治时间计算的秒数

SizeOfOptionalHeader字段,指明在IMAGE_NT_HEADERS中紧跟在FileHeader后的OptionalHeader的大小,对于32位的PE文件而言,这个值的大小通常为0x00E0;

Characteristics字段,表明文件的属性。如果Characteristics & 0x2000 = 0x2000,那么表明这是一个DLL文件

IMAGE_NT_HEADERS中第三个元素为 IMAGE_OPTIONAL_HEADER32 OptionalHeader,用于为加载器提供加载信息,注意OptionalHeader的大小并不固定,位置在0xb8(e_lfanew)+0x4(size(Signature))+0x14(size(IMAGE_FILE_HEADER))=0xd0处,其大小由FileHeader中的SizeOfOptionalHeader字段来决定。其结构体及其内部元素解释如下:

   typedef struct _IMAGE_OPTIONAL_HEADER {

          WORD    Magic;

          BYTE    MajorLinkerVersion;

          BYTE    MinorLinkerVersion;

          DWORD   SizeOfCode;

          DWORD   SizeOfInitializedData;

          DWORD   SizeOfUninitializedData;

          DWORD   AddressOfEntryPoint;

          DWORD   BaseOfCode;

          DWORD   BaseOfData;

          DWORD   ImageBase;

          DWORD   SectionAlignment;

          DWORD   FileAlignment;

          WORD    MajorOperatingSystemVersion;

          WORD    MinorOperatingSystemVersion;

          WORD    MajorImageVersion;

          WORD    MinorImageVersion;

          WORD    MajorSubsystemVersion;

          WORD    MinorSubsystemVersion;

          DWORD   Win32VersionValue;

          DWORD   SizeOfImage;

          DWORD   SizeOfHeaders;

          DWORD   CheckSum;

          WORD    Subsystem;

          WORD    DllCharacteristics;

          DWORD   SizeOfStackReserve;

          DWORD   SizeOfStackCommit;

          DWORD   SizeOfHeapReserve;

          DWORD   SizeOfHeapCommit;

          DWORD   LoaderFlags;

          DWORD   NumberOfRvaAndSizes;

          IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

      } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Magic,表明是ROM映像还是普通的可执行映像,普通的可执行文件的值为0x010B,PE32+可执行文件的值为0x020B(PE32+即64位的PE文件,这种文件只能运行在64位的Windows操作系统下)(0xd0-0xd1)

SizeOfCode,代码区块的大小,通常而言可执行文件仅有一个代码区块.text,所以通常就是.text区块的大小(磁盘对齐);(0xd4-0xd7)

AddressOfEntryPoint,重要字段,程序执行的入口点地址,是一个RVA值;(0xe0-0xe3)

BaseOfCode,代码段的起始地址,是一个RVA值,通常而言就是.text区块的起始RVA;(0xe4-0xe7)

BaseOfData,数据段的起始地址,是一个RVA值,通常而言就是.data区块的起始RVA;(0xe8-0xeb)

ImageBase,重要字段,PE文件在内存中首选的装载基地址,文件真实装载地址位于ImageBase+AddressOfEntryPoint;(0xec-0xef)

SectionAlignment,当PE文件装载到内存时区块的对齐大小,假设.text区块的大小为0x7748,而SectionAlignment的大小为0x1000,那么对齐后的大小为0x8000字节;(0xf0-0xf3)

FileAlignment,磁盘上PE文件中区块的对齐大小,对齐方式类似SectionAlignment;(0xf4-0xf7)

SizeOfImage,PE文件被装载到内存空间后总的大小,指从ImageBase到最后一个区块的大小(内存中开头+几个段在内存中的大小,是SectionAlignment的倍数);(0x108-0x10b)

SizeOfHeaders,Dos头、DosStub、PE头以及区块头的总大小,并进行FileAlignment对齐后的大小(exe文件中程序正式开始的地方,为FileAlignment的倍数)(0x10c-0x10f);

NumberOfRvaAndSizes,数据目录项的个数,固定为16,即后面一个成员DataDirectory的数组元素个数;(0x12c-0x12f)

DataDirectory,数据目录表,包含有输入表、输出表等表项的具体信息;

 DataDirectory是一个数据目录表数组,数组的元素个数为16,各个元素所对应的表项如下图所示:

IMAGE_DATA_DIRECTORY结构体在WinNt.h头文件中的定义如下:

      typedef struct _IMAGE_DATA_DIRECTORY {

          DWORD   VirtualAddress;

          DWORD   Size;

      } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

      其中VirtualAddress为数据块的起始RVA地址,Size为数据块的长度。数据目录表中比较重要的表项有输出表、输入表、资源表以及重定位表等。

这里共有16 项,大小为16*0x8=0x80

  •  节表头解析以及RVA与文件偏移地址的转换

 PE头之后就是节表头,节表头通常为 _IMAGE_SECTION_HEADER 类型。节表头位于0xb8(e_lfanew)+0x4(size(Signature))+0x14(size(IMAGE_FILE_HEADER))+0xe0(SizeOfOptionalHeader)=0x1f0,且每一个节表头占40bit,下面对节表头数据类型和每个变量的含义进行解释

typedef struct _IMAGE_SECTION_HEADER {

    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];

    union {

            DWORD   PhysicalAddress;

            DWORD   VirtualSize;

    } Misc;

    DWORD   VirtualAddress;

    DWORD   SizeOfRawData;

    DWORD   PointerToRawData;

    DWORD   PointerToRelocations;

    DWORD   PointerToLinenumbers;

    WORD    NumberOfRelocations;

    WORD    NumberOfLinenumbers;

    DWORD   Characteristics;

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER

Name,为对应节区的名字,其中IMAGE_SIZEOF_SHORT_NAME的值固定为8。如果名字的长度小于8,则以NULL字符结束;如果名字的长度等于8,则没有NULL字符,因为数组长度为8。节区的名字通常以点号开头,如.text、.data等,通常而言,这个名字可以随便修改。

VirtualSize,在未对齐的情况下,区块所有数据的大小。

VirtualAddress,区块被装载到内存时的RVA,这个值总是SectionAlignment的整数倍。

SizeOfRawData,区块数据在磁盘文件中按照FileAlignment对齐后的大小。

PointerToRawData,区块数据在磁盘文件中的偏移地址。

PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers:不是很重要的字段,这里不作研究。

Characteristics,区块的属性值,表明区块的可读、可写、可执行等相关属性。

  • IMAGE_IMPORT_DESCRIPTOR结构分析(导入表分析)

从DataDirectory数据结构中可以找到导入表开头在内存中的RVA为0x20d4,则输入表开头位于.rdate向后偏移0xd4,而.rdate在磁盘的起始地址在0xc00,则导入表开头位于0xc00+0xd4=0xcd4(数据对应数据所在段偏移=数据在内存中的位置-数据所在段在内存的基地址=数据在磁盘中的位置-数据所在段在磁盘的基地址)数据对于数据所在段的偏移=数据在内存的实际地址-数据所在段的内存基地址=数据在磁盘的实际地址-数据所在段的磁盘基地址,大小为0x64。下面将对IMAGE_IMPORT_DESCRIPTOR结构和数据类型进行分析

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

          union {

              DWORD   Characteristics;

              DWORD   OriginalFirstThunk;

          } DUMMYUNIONNAME;

          DWORD   TimeDateStamp;

          DWORD   ForwarderChain;

          DWORD   Name;

          DWORD   FirstThunk;

      } IMAGE_IMPORT_DESCRIPTOR;

OriginalFirstThunk,RVA,指向输入名称表(简称INT),INT是一个类型为IMAGE_THUNK_DATA结构的数组,同样的,通过在数组末尾附加一个空的IMAGE_THUNK_DATA结构来表示数组的结束,每一个输入的函数都有一个对应的IMAGE_THUNK_DATA结构;

Name,RVA,指向DLL的名字,如“User32.dll”;

FirstThunk,RVA,指向输入地址表(简称IAT),IAT也是一个类型为IMAGE_THUNK_DATA结构的数组,同样的,通过在数组末尾附加一个空的IMAGE_THUNK_DATA结构来表示数组的结束,每一个输入的函数都有一个对应的IMAGE_THUNK_DATA结构;

我们发现导入表一共有四项,即有四个IMAGE_THUNK_DATA结构,在结尾还有一个空的IMAGE_THUNK_DATA结构表示结束,一个IMAGE_THUNK_DATA结构大小为0x14,则导入表大小为0x14*5=0x64,下面将对第一个导入表(User32.dll)对导入表进行解释

输入表的结构如下图所示:

     

      IAT以及INT结构的数组类型均为IMAGE_THUNK_DATA,该结构体在WinNT.h头文件中的定义如下:

      typedef struct _IMAGE_THUNK_DATA32 {

          union {

              DWORD ForwarderString;

              DWORD Function;

              DWORD Ordinal;

              DWORD AddressOfData;

          } u1;

      } IMAGE_THUNK_DATA32;

      可以看出,该类型仅有的一个成员u1是一个联合体(union),而联合体内的类型都是DWORD,所以IMAGE_THUNK_DATA的大小是4字节。当该类型的最高位为1时,表示函数以序号的方式进行输入,这时候低31位的值就表示函数的序号;当该类型的最高位为0时,表示函数以名字的方式进行输入,这时候值就表示一个指向IMAGE_IMPORT_BY_NAME结构的RVA。

       IMAGE_IMPORT_BY_NAME结构体在WinNT.h头文件中的定义如下:

      typedef struct _IMAGE_IMPORT_BY_NAME {

          WORD    Hint;

          BYTE    Name[1];

      } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

      其中Hint是WORD类型,表示函数在其原始DLL文件的输出表中的序号;Name是一个BYTE类型的数组,数组的内容为函数的名称字符串,以NULL字符结尾。需要注意的是,尽管结构体中Name的大小被指定为1,其实这里是一个可变的大小。

      还有一个遗留问题是:OriginalFirstThunk指向的INT数组和FirstThunk指向的IAT数组到底有什么区别呢?答案是当PE文件被加载时,PE加载器会遍历INT结构中的数组项,通过其指向的IMAGE_IMPORT_BY_NAME结构来找到函数的名字,PE加载器可以通过函数的名字找到函数的地址,随后把得到的函数地址填充到IAT结构中,此后,通过IAT结构中的函数地址就可以进行函数调用了。

      IAT被PE装载器填充后的输入表结构如下图所示。

     

 

 

我们从上面可以看到OriginalFirstThunk的值为0x21a4,和上面计算方式相同,其指向磁盘的位置在0xc00+(0x21a4-0x2000)=0xda4,在0xda4我们找到了0x000022c8,由于最高位为0,那么该值是指向IMAGE_IMPORT_BY_NAME结构的RVA。该RVA指向磁盘的地址为0xc00+(0x22c8-0x2000)=0xec8,我们可以看到在这里放着函数的序号0x1b3和该函数的名称。同理可以找到其他函数

我们从上面知道函数名字RVA为0x2352,其位于磁盘位置为0xc00+(0x2352-0x2000)=0xf52,在此处存储着导入库的名称

FirstThunk存储的RVA为0x206c,其指向磁盘的位置为0xc00+(0x206c-0x2000)=0xc6c,我们在0xc6c发现数值0x000022c8,按照前面的方法,我们就可以找到第一个函数。

 

 

 

 

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值