逆向工程核心原理之第13章PE文件格式导入表的初步理解

作者分析的是32位的notepad.exe,我们通过模仿分析64位的notepad.exe来认识PE文件格式,跟随作者的脚步,下面我们开始分析:

PE的头由 1DOS头 2DOS存根 3NT头 4各种节区头 组成。

*******************************************************************************

第一部分DOS头结构,总共是64个字节

typedef struct _IMAGE_DOS_HEADER

{

0 WORD e_magic ;  // DOS  signature:4D5A('MZ')

2 WORD  e_cblp ;    

4 WORD  e_cp   ;

6 WORD  e_crlc  ;

8 WORD  e_cparhdr ;  

10 WORD  e_minalloc  ;

12 WORD  e_maxalloc  ;

14 WORD  e_ss   ;

16 WORD  e_sp  ;  

18 WORD  e_csum  ;  

20 WORD  e_ip ;   

22 WORD  e_cs ;   

24 WORD  e_lfarlc ;   

26 WORD  e_ovno  ;      

28 WORD  e_res[4] ;  

36 WORD  e_oemid  ;  

38 WORD  e_oeminfo  ;

40 WORD  e_res2[10] ;

60 LONG   e_lfanew  ;   // Offset to NT header  不同于32位E0,64位的开始位置为E8

} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

*******************************************************************************

第二部分DOS存根,大小不固定,我们可以用 E8h - 40h = A8H, 64位的notepad.exe的dos存根大小为A8h,为了重现执行dos存根的效果,我们安装了DOSBox.exe,在其中成功执行notepad.exe得到。“This program cannot be run in DOS mode.”

如图:

 逆向工程核心原理之第13章PE文件格式导入表的初步理解

*******************************************************************************

第三部分NT头,NT头由3部分组成: 1签名 2文件头 3可选头

32位的NT头和64位的NT头有点不同,如下:

//32位的NT头

typedef struct _IMAGE_NT_HEADERS {

  DWORD                   Signature;

  IMAGE_FILE_HEADER       FileHeader;

  IMAGE_OPTIONAL_HEADER32 OptionalHeader;

} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

//64位的NT头

typedef struct _IMAGE_NT_HEADERS64 {

  DWORD                   Signature;

  IMAGE_FILE_HEADER       FileHeader;

  IMAGE_OPTIONAL_HEADER64 OptionalHeader;

} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

在于结构体的第三个参数不同,下面我们从签名开始分析

签名是固定的4个字节"PE"00

文件头的结构体如下:共20个字节

typedef struct _IMAGE_FILE_HEADER {

0  WORD  Machine;  //64 86  8664 IMAGE_FILE_MACHINE_AMD64  AMD64

2  WORD  NumberOfSections; //07 00  有7个节区 作者书中只有3个节区

4  DWORD TimeDateStamp;

8  DWORD PointerToSymbolTable;

12  DWORD NumberOfSymbols;

16  WORD  SizeOfOptionalHeader; //F0 00   可选头的大小为F0

18  WORD  Characteristics;

} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

*******************************************************************************

可选头的结构体如下: 共F0(240)个字节

typedef struct _IMAGE_OPTIONAL_HEADER64 {

0  WORD                 Magic; // 0B 02  20B是64位PE标志

2  BYTE                 MajorLinkerVersion;

3  BYTE                 MinorLinkerVersion;

4  DWORD                SizeOfCode;

8  DWORD                SizeOfInitializedData;

12  DWORD                SizeOfUninitializedData;

16  DWORD                AddressOfEntryPoint;

20  DWORD                BaseOfCode;

24  ULONGLONG            ImageBase; //00 00 00 40 01 00 00 00   0140000000

32  DWORD                SectionAlignment; //00 10 00 00   在内存最小单位  1000

36  DWORD                FileAlignment; //00 02 00 00 在磁盘最小单位 0200

40  WORD                 MajorOperatingSystemVersion;

42  WORD                 MinorOperatingSystemVersion;

44  WORD                 MajorImageVersion;

46  WORD                 MinorImageVersion;

48  WORD                 MajorSubsystemVersion;

50  WORD                 MinorSubsystemVersion;

52  DWORD                Win32VersionValue;

56  DWORD                SizeOfImage; //00 90 03 00  039000内存映像大小

60  DWORD                SizeOfHeaders;  //00 04 00 00  0400 头部大小

64  DWORD                CheckSum;

68  WORD                 Subsystem;  //02 00   2  gui文件 窗口应用程序

70  WORD                 DllCharacteristics;

72  ULONGLONG            SizeOfStackReserve;

80  ULONGLONG            SizeOfStackCommit;

88  ULONGLONG            SizeOfHeapReserve;

96  ULONGLONG            SizeOfHeapCommit;

104  DWORD                LoaderFlags;

108  DWORD                NumberOfRvaAndSizes;

// 下面数组DataDirectory的实际大小 10 00 00 00   10h=16

112  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

保存导入导出函数,后面重点要介绍

} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

IMAGE_NUMBEROF_DIRECTORY_ENTRIES 的值为16

IMAGE_DATA_DIRECTORY的结构体如下: 共8个字节

typedef struct _IMAGE_DATA_DIRECTORY {

0  DWORD VirtualAddress;

4  DWORD Size;

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

所以最后的数组字节大小为: 16 * 8 = 128个字节

*******************************************************************************

第四部分节区头,我们这里有7个节区头 text rdata data pdata didat rsrc reloc

#define IMAGE_SIZEOF_SHORT_NAME 8

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;

下面是对节区头中包含节区的内存展示:

文件RAW地址

 

内存RVA地址

400 磁盘节区起始地址

 

1000  内存节区起始地址

.....

.....

.....

26200

 

 

25e00

 

 

text

.....

.....

.....

27000

 

 

25ccf

 

.....

2fc00

9a00

rdata

.....

31000

98d6

......

0a00

0e00

data

......

34000

2788

......

31c00

1200

pdata

......

36000

1188

......

31e00

0200

didat

......

37000

0178

......

32a00

0c00

rsrc

......

38000

0bdb

......

32DE0

0400

reloc

......

39000

02e8

注意上面表格两边虽然格式上对齐,但是数值上是不一样的

 

我们查看notepad.exe的文件大小为:203 KB (208,384 字节),和这里的32DE0h是一样的。

如图所示:

 逆向工程核心原理之第13章PE文件格式导入表的初步理解

内存映像大小和 结构体_IMAGE_OPTIONAL_HEADER64的如下字段相对应

56  DWORD                SizeOfImage; //00 90 03 00  039000内存映像大小

 

*******************************************************************************

经过上面的铺垫,我们现在来看DataDirectory数组中的第二个元素导入库的存在,

对比我们实际的数据:D8 E6 02 00 44 02 00 00  

typedef struct _IMAGE_DATA_DIRECTORY {

0  DWORD VirtualAddress; //2E6D8h ->(2e6d8-27000+26200=2d8d8)

4  DWORD Size;  //244h

} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

首先认识一下导入库的结构体,共20个字节,

对应的实际数据为:F0 E9 02 00 00 00 00 00 00 00 00 00 46 F8 02 00 F8 78 02 00

27000-26200=E00

typedef struct _IMAGE_IMPORT_DESCRIPTOR {  

    union {  

        DWORD   Characteristics;           

        DWORD   OriginalFirstThunk;  

// INT(import name table) 地址 RVA  F0 E9 02 00  02E9F0->(02e9f0-e00=2dbf0)

//2dbf0保存的内容为5C F4 02 00即2f45c ->(2f45c-e00=2e65c)

    } ;  

    DWORD   TimeDateStamp;  // 00 00 00 00                

    DWORD   ForwarderChain;  //00 00 00 00            

DWORD  Name; //库名称字符串的地址 RVA 46 F8 02 00  02F846->(2F846-e00=2ea46)

//2ea46保存的地址内容为 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C即字符串KERNEL32.dll

DWORD   FirstThunk; // IAT地址 RVA F8 78 02 00  0278F8 ->(278f8-e00=26af8)

//26af8保存的内容为 5C F4 02 00 即2f45c ->(2f45c-e00=2e65c),和上面字段INT一样     

} IMAGE_IMPORT_DESCRIPTOR;  

typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;  

 

typedef struct _IMAGE_IMPORT_BY_NAME {

    WORD    Hint;  //B8 02  02B8

    BYTE    Name[1]; //变长字符串

//47 65 74 50 72 6F 63 41 64 64 72 65 73 73 即为GetProcAddress

} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

  

根据上述分析,所以引入的第一个dll是KERNEL32.dll,KERNEL32.dll的第一个函数是GetProcAddress。类似的我们可以分析引入的第二个dll是GDI32.dll,GDI32.dll的第一个函数是CreateDCW。以此类推,至此分析完毕。

*******************************************************************************

 

上一篇:树莓派3B 实现通过GPU加速pytorch计算


下一篇:2022.01.14日总结