.idata
详解
前置信息
.idata节区是可执行文件中的一个常见节区,用于存储导入表(Import Table)的相关信息。
在可执行文件中,导入表用于记录程序需要从外部导入的函数和符号的信息。这些函数和符号通常是由其他模块(如动态链接库)提供的,程序在运行时需要通过导入表来获取这些函数和符号的地址,并进行调用或引用。
IMAGE_DATA_DIRECTORY
各节区的信息首次出现在IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32.DataDirectory[]
中
我们可以得到Import Table
-IT
的基本信息:
- 相对地址:
- RVA:
00 30 00 00
即3000h
- FOA:
0xE00
- RVA:
- 导入表大小:
70 06 00 00
,即0x670 --> 1648
在OllyDbg中的展示
DataDirectory
:由IMAGE_DATA_DIRECTORY
结构体组成的数组
struct _IMAGE_DATA_DIRECTORY
{
ULONG VirtualAddress; //0x0
ULONG Size; //0x4
};
-
VirtualAddress
:数据目录的起始地址,以相对虚拟地址(Relative Virtual Address, RVA)的形式表示 -
Size
:数据目录的大小,以字节为单位。表示从VirtualAddress
开始的数据段的总长度。
DataDirectory列表i信息如下
#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)
#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
#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
#define Reserved 15 //系统保留
_IMAGE_SECTION_HEADER
节区表的基本信息在_IMAGE_SECTION_HEADER
头中
这里面主要信息如下:
-
Name
:长度为8的ASCII节区名.idata
-
VirtualSize
:.idata
节区大小4096
,未对齐处理前节区实际使用的大小 -
VirtualAddress
:内存中节区起始地址3000
(RVA),与IMAGE_DATA_DIRECTORY
照应。 -
SizeOfRawData
:磁盘文件中节区所占大小800
,前面我们得知导入表的大小为0x670
,这里是
0x800h
,是因为在文件中以200h
为一个单位来存储 -
PointerToRawData
:磁盘文件中的起始地址E00h
(FOA)
在OllDbg中的展示:
节表的结构如下,整体为40个字节
//0x28 bytes (sizeof)
struct _IMAGE_SECTION_HEADER
{
UCHAR Name[8]; //0x0
union
{
ULONG PhysicalAddress; //0x8
ULONG VirtualSize; //0x8
} Misc; //0x8
ULONG VirtualAddress; //0xc
ULONG SizeOfRawData; //0x10
ULONG PointerToRawData; //0x14
ULONG PointerToRelocations; //0x18
ULONG PointerToLinenumbers; //0x1c
USHORT NumberOfRelocations; //0x20
USHORT NumberOfLinenumbers; //0x22
ULONG Characteristics; //0x24
};
重要成员:
-
Name
:长度为8的ASCII节区名(比如.text)。 -
VirtualSize
-0x08
:未对齐处理前节区实际使用的大小 -
VirtualAddress
-0x0C
:内存中节区起始地址(RVA) -
SizeOfRawData
-0x10
:磁盘文件中节区所占大小 -
PointerToRawData-0X14
:磁盘文件中的起始地址FOA
-
Charateristics
-0x24
:节区属性(bit OR)
结构详解
导入表的数据结构包含以下几个主要部分:
- 导入描述符(Import Descriptor):导入描述符是一个指向导入表中一个DLL模块的指针。每个导入描述符对应一个被依赖的DLL模块,它包含了该模块的名称、导入地址表(Import Address Table)的位置的位置等信息。
- 导入地址表(Import Address Table):导入地址表是一个指针数组,用于存储被依赖的外部函数或符号的地址。每个指针指向相应的外部函数或符号在内存中的地址。在程序加载时,这些指针会被动态地填充为正确的地址。
- 导入名称表(Import Name Table):导入名称表是一个存储外部函数或符号名称的表格,用于与导入地址表进行对应。每个表项包含了一个被依赖的外部函数或符号的名称。
- 段定位表(Thunk Table):段定位表是一个指针数组,用于存储导入地址表中每个表项的位置。它指示了导入地址表中每个表项的地址或名称的位置。
导入描述符(Import Descriptor)
在.idata
的起始部分是导入描述符
关键信息:
-
OriginalFirstThunk
:,里面的内容是,导入名称表(Import Name Table)存储的API的Name的指针。- 虚拟偏移地址
3078h
(RVA) - 文件偏移地址
0xE78
(FOA)
- 虚拟偏移地址
-
Name
:要导入的DLL的文件Name的地址3290h
(RVA),文件地址0x1090
(FOA)。 -
FirstThunk
:实际IAT
的起始地址3184h
(RVA),IAT填充后里面存储的是API的实际内存地址- 在未导入内存中和
OriginalFirstThunk
内容一样 - 导入内存中是API的实际地址。
- 在未导入内存中和
ollyDbg中显示
我们查看两个IAT
地址区别,未导入内存中
IMAGE_IMPORT_DESCRIPTOR
IMAGE_IMPORT_DESCRIPTOR是一个结构体,用于描述可执行文件中的导入表信息。它定义在Windows的PE文件格式中,用于存储导入表的相关信息。
//winnt.h
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 用于终止的空导入描述符时为0
DWORD OriginalFirstThunk; // 指向INT,指向原始未绑定导入地址表(PIMAGE_THUNK_DATA)的RVA
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 如果未绑定为0,已绑定为-1,实际时间戳存储在IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT中
DWORD ForwarderChain; // 如果没有前向链为-1
DWORD Name; // DLL模块名称的RVA
DWORD FirstThunk; // 导入地址表(IAT)的RVA
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
-
Characteristics/OriginalFirstThunk
:这是一个联合体,用于表示导入描述符的特征或原始未绑定的导入地址表(INT)的RVA(相对虚拟地址)。 -
TimeDateStamp
:用于表示DLL模块的时间戳。如果DLL模块没有绑定,该值为0;如果已绑定,该值为-1,并且实际的时间戳存储在IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT中。 -
ForwarderChain
:表示导入表的前向链的索引。如果没有前向链,该值为-1。 -
Name
:表示DLL模块的名称的RVA。 -
FirstThunk
:表示导入地址表(Import Address Table,IAT)的RVA。如果已绑定,该IAT包含实际的地址。
IMAGE_THUNK_DATA32
IMAGE_THUNK_DATA32是一个结构体,用于描述导入表和延迟加载表中的函数地址。它定义在Windows的PE文件格式中,用于存储函数地址或函数名称的相关信息。
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // 如果是转发函数,指向转发字符串的RVA
DWORD Function; // 函数地址或函数名称的RVA
DWORD Ordinal; // 函数的序号
DWORD AddressOfData; // 指向IMAGE_IMPORT_BY_NAME结构体的RVA
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
导入地址表(Import Address Table)
我们前面得知IMAGE_IMPORT_DESCRIPTOR.FirstThunk
存储的是IAT的RVA。
导入地址表(Import Address Table
):导入地址表是一个指针数组,用于存储被依赖的外部函数或符号的地址。每个指针指向相应的外部函数或符号在内存中的地址。
- 程序未运行时:IAT存储的是API的Name指针
- 在程序加载时,这些指针会被动态地填充为正确的地址。
我们以CRACKME.EXE为案例,IAT的FOA
为0xF84
我们在OllyDbg中查看,导入内存中
我们以KillTimer
为案例
-
[F84] == CC 32 00 00
,即KillTimer
的Name指针ImageBase + [F84]
存储是..KillTimer
-
操作系统可以根据这个指针,定位到相应的API函数名称,然后通过调用
GetProcAddress
来获取MessageBoxA
的地址0x75FC2B30
然后将其覆盖
FAC
的内容50 1A A5 75
因此我们可以这样理解
-
OriginalFirstThunk
:原始IAT
的起始地址3078h
(RVA),里面的内容是,导入名称表(Import Name Table)存储的API的Name -
Name
:要导入的DLL的文件Name的地址3290h
(RVA),文件地址0x1090
(FOA)。 -
FirstThunk
:实际IAT
的起始地址3184h
(RVA),IAT填充后里面存储的是API的实际内存地址
导入名称表(Import Name Table)
我们通过010 Editor
查看得知,在DLL_Name后面就是各DLL的API函数名称
导入名称表(Import Name Table
):导入名称表是一个存储外部函数或符号名称的表格,用于与导入地址表进行对应。每个表项包含了一个被依赖的外部函数或符号的名称。
IMAGE_IMPORT_BY_NAME是一个结构体,用于描述导入表中每个函数的信息。它定义在Windows的PE文件格式中,用于存储导入函数的名称和序号。
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 函数的序号
BYTE Name[1]; // 函数的名称
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- Hint:函数的序号,用于快速查找函数。在导入表中,函数的名称通常会与其序号一起存储,以提高查找效率。
- Name:函数的名称。由于结构体中的Name成员只定义了一个字节,实际上,函数的名称是一个以null结尾的字符串,该字符串紧随Hint成员之后。
IAT 填充过程
-
解析PE文件头
当一个可执行文件(EXE或DLL)加载到内存中时,操作系统的PE(Portable Executable)加载器首先解析PE文件头,以找到导入表(Import Table)的位置。
-
定位导入表
导入表包含了程序需要的所有DLL及其导入的函数名称。导入表的结构包括一个或多个导入描述符,每个描述符对应一个DLL。
-
加载DLL
对于每个导入描述符,加载器会加载相应的DLL文件到内存中。如果DLL文件尚未加载,则加载器调用
LoadLibrary
函数加载它。 -
获取函数地址
加载器解析导入描述符中的函数名称,并调用
GetProcAddress
获取每个导入函数的实际内存地址。 -
填充IAT
一旦函数地址被解析,加载器将这些地址填充到IAT中。此时,IAT中的每个条目都指向实际的DLL函数地址。
- 新建文件夹.zip 下载