PE结构-2
文章目录
5. 导入表
_IMAGE_IMPORT_DESCRIPTOR
,简称IID。
_IMAGE_OPTIONAL_HEADER64.DataDirectory
的第2个元素就是指向输入表的。
下面是导入表的定义。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
官方文档:https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_enclave_import
输入表以这个结构开始,每个被PE文件链接进来的dll都对应一个IID数组,数组以一个空结构结尾。
OriginalFirstThunk
和FirstThunk
很重要,分别指向了保存导入名称和导入地址的_IMAGE_THUNK_DATA64
结构数组,即INT
和IAT
,该数组以空的结构结尾。
PE装载器的核心操作:遍历OriginalFirstThunk
指向的数组,找到每个_IMAGE_IMPORT_BY_NAME
所指向的导入函数的入口地址,用它们重写FirstThunk
指向的数组(IAT)。
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;
- ForwarderString:导入表
ForwarderChain
不为0时有效,并指向导出函数的映像文件名的字符串RVA。 - Function:导入表导入函数的实际内存地址,仅在此映像被加载,且此结构为IAT的前提下有效。PE被加载时,os会遍历INT,逐一取出api地址,填入IAT。
- Ordinal:导入api的序号,
_IMAGE_THUNK_DATA64
的最高位为1时有效,其余位是序号。 - AddressOfData:以上3值都无效时生效,指向
_IMAGE_IMPORT_BY_NAME
。
然后是_IMAGE_IMPORT_BY_NAME
结构,它也是以数组形式存在,以空结构结尾。
//
// Import Format
//
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // api ordinal
CHAR Name[1]; // api name
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
查找导入函数地址
查找INT
通过_IMAGE_OPTIONAL_HEADER64.DataDirectory.VirtualAddress
确定导入表的RVA;
找到该区块.idata
;
通过该区块_IMAGE_SECTION_HEADER.PointerToRawData
确定.idata
段的文件偏移,在hexeditor中定位导入表。
由_IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk
确定INT
内存虚拟偏移;
计算INT文件偏移地址= RVA −段起始偏移 + 起始文件偏移
,这里存有INT
的第一个_IMAGE_THUNK_DATA64
的内存虚拟偏移;
再次计算文件偏移,在hexeditor中定位_IMAGE_IMPORT_BY_NAME
,此时可以看到序号Hint
和函数名Name
。
查找IAT
回到.idata
,找到_IMAGE_IMPORT_DESCRIPTOR.FirstThunk
(偏移10h),确定IAT
虚拟偏移;
再次计算文件偏移,这里存有IAT
的第一个_IMAGE_THUNK_DATA64
的内存虚拟偏移;
再次计算,可以找到与INT同一个_IMAGE_IMPORT_BY_NAME
。
dump
用LordPE可以把运行中的程序的虚拟地址dump下来,它比原程序大一些,且虚拟偏移和文件偏移相同。
此时再找一次IAT,_IMAGE_THUNK_DATA64.Function
就是真正的函数地址。
6. 导出表
image export directory,简称IED。
exe不存在导出表,也有特殊情况;大部分dll包含导出表,纯粹用作资源的dll不需要导出表。
在一个dll中,函数由16位序数标识,但因为dll会维护修改,不提倡用序数索引函数。
//
// Export Format
//
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; //unused
DWORD TimeDateStamp;
WORD MajorVersion; //unused
WORD MinorVersion; //unused
DWORD Name; // RVA,the real name of dll
DWORD Base; // +10h , which plus ordinal is the index of the func address array
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
可以核实Name
检验是否改过名。
*Names
是指有名称的函数。
逻辑上,导出表有3部分:
- 函数表,保存入口地址
- 名称表
- 序号表
后两个表的作用就是索引,导向函数表。
从序号查找函数入口地址
- 从
_IMAGE_OPTIONAL_HEADER64.DataDirectory
找到导出表的RVA; - 由
Base
字段得到起始序号; - 将需要查找的导出序号减去起始序号,得到函数在入口地址表的索引,并确保小于
NumberOfFunctions
; - 用该索引值在
AddressOfFunctions
指向的导出函数入口地址表中取出函数入口RVA。函数装载时,RVA加上模块基地址就是函数入口地址。
从函数名称查找入口地址
- 由导出表的
NumberOfNames
确定已命名的函数的个数,在AddressOfNames
指向的函数名称地址表循环这么多次,查找指定函数; - 找到后,记下名称字符串地址表索引,在
AddressOfNameOrdinals
指向的序号数组中取出序号; - 以序号作为索引值,在
AddressOfFunctions
指向的导出函数入口地址表中取出函数入口RVA。
一般病毒是依靠函数名称查找入口地址的。
7. 基址重定位
dll不是每次都能加载到预设的_IMAGE_OPTIONAL_HEADER64.ImageBase
上,所以基址重定位主要用于dll。类似push,inc
这样的语句就会被修改。
重定位表(Base Relocation Table)位于.reloc
区块内,它把需要进行重定位修正的代码的地址放在了数组里。
重定位算法:将直接寻址指令中需要修正的地址加上模块的实际装入地址与模块建议装入地址之差。
以上涉及3个地址。
建议装入的地址在PE文件头定义。实际装入的地址只有装载器知道。如果未能载入预设基址,加载器就会修改数组中的重定位信息。
//
// Based relocation format.
//
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; //RVA
DWORD SizeOfBlock;
// WORD TypeOffset[1]; //end with [00 00]
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5
#define IMAGE_REL_BASED_RESERVED 6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9
#define IMAGE_REL_BASED_DIR64 10
( SizeOfBlock-8 ) / 2
即需要重定位的项目TypeOffset
的数量。8是2个DWORD大小,2是1个WORD大小。
重定位结构由多个_IMAGE_BASE_RELOCATION
结构组成,每个负责4kB(0x1000)分页的重定位信息,所以PE文件中每隔0x1000字节就有一个_IMAGE_BASE_RELOCATION
与之对应,VirtualAddress
总是为0x1000的倍数。
TypeOffset
并不属于_IMAGE_BASE_RELOCATION
结构,它们互相配合来描述需要重定位的偏移。
struct {
WORD Offset:12; //lower 12 bit relocation offset
WORD Type:4; //higher 4 bit relocation type value : Based relocation types above
}TypeOffset;
TypeOffset.Offset
与_IMAGE_BASE_RELOCATION.VirtualAddress
相加即为需要修改的代码的RVA。将这个地址与_IMAGE_OPTIONAL_HEADER64.ImageBase
比较来判断发生了重定位。该地址加上0x10000000
即需要重定位的虚拟地址。
8.资源
学习这一部分,可以去除软件广告、汉化等。资源包括位图(bitmap)、光标(cursor)、对话框(dialog box)、图标(icon)、菜单(menu)、串表(string table)、工具栏(toobar)和版本信息(version)等。
Resource Hacker工具可以更改资源。但加壳后就不容易更改了。
资源的组织方式要复杂得多。PE文件中的资源按照资源类型->资源ID->资源代码页的3层树形结构,
数据目录表的第3项,IMAGE_DIRECTORY_ENTRY_RESOURCE
包含了资源的RVA和大小。资源的3层目录结构的每一个节点都是由_IMAGE_RESOURCE_DIRECTORY
头部结构和紧跟的_IMAGE_RESOURCE_DIRECTORY_ENTRY
数组组成。
_IMAGE_RESOURCE_DIRECTORY
//
// Resource Format.
//
//
// Resource directory consists of two counts, following by a variable length
// array of directory entries. The first count is the number of entries at
// beginning of the array that have actual names associated with each entry.
// The entries are in ascending order, case insensitive strings. The second
// count is the number of entries that immediately follow the named entries.
// This second count identifies the number of entries that have 16-bit integer
// Ids as their name. These entries are also sorted in ascending order.
//
// This structure allows fast lookup by either name or number, but for any
// given resource entry only one form of lookup is supported, not both.
// This is consistant with the syntax of the .RC file and the .RES file.
//
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries;
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
NumberOfNamedEntries
和NumberOfIdEntries
加起来就是本目录的目录项总和,即_IMAGE_RESOURCE_DIRECTORY_ENTRY
的数目。
_IMAGE_RESOURCE_DIRECTORY_ENTRY
#define IMAGE_RESOURCE_NAME_IS_STRING 0x80000000
#define IMAGE_RESOURCE_DATA_IS_DIRECTORY 0x80000000
//
// Each directory contains the 32-bit Name of the entry and an offset,
// relative to the beginning of the resource directory of the data associated
// with this directory entry. If the name of the entry is an actual text
// string instead of an integer Id, then the high order bit of the name field
// is set to one and the low order 31-bits are an offset, relative to the
// beginning of the resource directory of the string, which is of type
// IMAGE_RESOURCE_DIRECTORY_STRING. Otherwise the high bit is clear and the
// low-order 16-bits are the integer Id that identify this resource directory
// entry. If the directory entry is yet another resource directory (i.e. a
// subdirectory), then the high order bit of the offset field will be
// set to indicate this. Otherwise the high bit is clear and the offset
// field points to a resource data entry.
//
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31;
DWORD NameIsString:1;
} DUMMYSTRUCTNAME;
DWORD Name; //name of id
WORD Id;
} DUMMYUNIONNAME;
union {
DWORD OffsetToData;
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
DUMMYUNIONNAME
_IMAGE_RESOURCE_DIRECTORY_ENTRY.DUMMYUNIONNAME.DUMMYSTRUCTNAME
的最高位,NameIsString
,
- 为0时,则作为id使用;
- 为1时,则低位数据
NameOffset
作为指针使用,但不是指向字符串,而是指向以下结构。
//
// For resource directory entries that have actual string names, the Name
// field of the directory entry points to an object of the following type.
// All of these string objects are stored together after the last resource
// directory entry and before the first resource data object. This minimizes
// the impact of these variable length objects on the alignment of the fixed
// size directory entry objects.
//
typedef struct _IMAGE_RESOURCE_DIRECTORY_STRING {
WORD Length;
CHAR NameString[ 1 ]; //variable
} IMAGE_RESOURCE_DIRECTORY_STRING, *PIMAGE_RESOURCE_DIRECTORY_STRING;
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length;
WCHAR NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
第一个联合体_IMAGE_RESOURCE_DIRECTORY_ENTRY.DUMMYUNIONNAME
位于:
- 第一层目录时,定义资源类型,
Name
字段有效; - 第二层目录时,定义资源名称,
Id
或DUMMYSTRUCTNAME
结构有效; - 第三层目录时,定义代码页编号,
Name
有效,保存资源语言区域类型。
资源类型如下表:
value | type | value | type |
---|---|---|---|
0x0000 0001 | cursor | 0x0000 0008 | font |
0x0000 0002 | bitmap | 0x0000 0009 | accelerators,快捷键 |
0x0000 0003 | icon | 0x0000 000A | uunformatted,非格式化资源 |
0x0000 0004 | menu | 0x0000 000B | message table |
0x0000 0005 | dialog | 0x0000 000C | group cursor |
0x0000 0006 | string table | 0x0000 000E | group icon |
0x0000 0007 | font directory | 0x0000 0010 | version |
DUMMYUNIONNAME2
再回到_IMAGE_RESOURCE_DIRECTORY_ENTRY.DUMMYUNIONNAME2
,
- 最高位,
DataIsDirectory
,为1时,低位数据,OffsetToDirectory
,指向下一层目录块的起始地址; - 最高位为0时,指针指向
_IMAGE_RESOURCE_DATA_ENTRY
结构,该结构描述了资源数据的位置和大小,是真正的资源数据。我们找资源的时候,目标就是要一直找到该字段最高位为0。
将Name
和OffsetToData
用作指针时注意,不是RVA,而是从资源区块k开始的地方算起的偏移量。
至此,已经有3层_IMAGE_RESOURCE_DIRECTORY_ENTRY
了。
//
// Each resource data entry describes a leaf node in the resource directory
// tree. It contains an offset, relative to the beginning of the resource
// directory of the data for the resource, a size field that gives the number
// of bytes of data at that offset, a CodePage that should be used when
// decoding code point values within the resource data. Typically for new
// applications the code page would be the unicode code page.
//
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; //RVA
DWORD Size;
DWORD CodePage; //0 usually
DWORD Reserved; //保留
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
找到该结构体后,可以用eXeScope工具验证。
第二个联合体_IMAGE_RESOURCE_DIRECTORY_ENTRY.DUMMYUNIONNAME2
,如果下一级为:
- 子目录,则
DUMMYSTRUCTNAME2
结构有效; - 资源数据,则
OffsetToData
有效。