PE结构-2

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数组,数组以一个空结构结尾。

OriginalFirstThunkFirstThunk很重要,分别指向了保存导入名称和导入地址的_IMAGE_THUNK_DATA64结构数组,即INTIAT,该数组以空的结构结尾。

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[IMAGE_DIRECTORY_ENTRY_IMPORT].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。

导出表应该在edata段,但这个段一般和rdata合并。

导出供其它PE文件使用的函数、变量和类的行为,这些也叫符号(Symbol)。

导出的东西有:地址,序号,名称。其实就是最后3个成员。

序号就是模块文件.def里@后面的数字。序号表中的值加上Base就是真正的序号。

注意:序号不是按顺序排列的,函数名、序号索引与函数地址索引也不对应。画成图的话有点像小学连线题。

在这里插入图片描述

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部分:

  • 函数表,保存入口地址
  • 名称表
  • 序号表

后两个表的作用就是索引,导向函数表。

在这里插入图片描述

从序号查找函数入口地址

  1. _IMAGE_OPTIONAL_HEADER64.DataDirectory找到导出表的RVA;
  2. Base字段得到起始序号;
  3. 将需要查找的导出序号减去起始序号,得到函数在入口地址表的索引,并确保小于NumberOfFunctions
  4. 用该索引值在AddressOfFunctions指向的导出函数入口地址表中取出函数入口RVA。函数装载时,RVA加上模块基地址就是函数入口地址。

从函数名称查找入口地址

  1. 由导出表的NumberOfNames确定已命名的函数的个数,在AddressOfNames指向的函数名称地址表循环这么多次,查找指定函数;
  2. 找到后,记下名称字符串地址表索引,在AddressOfNameOrdinals指向的序号数组中取出序号;
  3. 以序号作为索引值,在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层树形结构,

数据目录表下标2,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;

NumberOfNamedEntriesNumberOfIdEntries加起来就是本目录的目录项总和,即_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字段有效;
  • 第二层目录时,定义资源名称,IdDUMMYSTRUCTNAME结构有效;
  • 第三层目录时,定义代码页编号,Name有效,保存资源语言区域类型。

资源类型如下表:

valuetypevaluetype
0x0000 0001cursor0x0000 0008font
0x0000 0002bitmap0x0000 0009accelerators,快捷键
0x0000 0003icon0x0000 000Auunformatted,非格式化资源
0x0000 0004menu0x0000 000Bmessage table
0x0000 0005dialog0x0000 000Cgroup cursor
0x0000 0006string table0x0000 000Egroup icon
0x0000 0007font directory0x0000 0010version

DUMMYUNIONNAME2

再回到_IMAGE_RESOURCE_DIRECTORY_ENTRY.DUMMYUNIONNAME2

  • 最高位,DataIsDirectory,为1时,低位数据,OffsetToDirectory,指向下一层目录块的起始地址;
  • 最高位为0时,指针指向_IMAGE_RESOURCE_DATA_ENTRY结构,该结构描述了资源数据的位置和大小,是真正的资源数据。我们找资源的时候,目标就是要一直找到该字段最高位为0。

NameOffsetToData用作指针时注意,不是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有效。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值