抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

基础知识

  • 基地址(ImageBase): 当PE文件通过Windows加载器载入内存后,内存中的版本称为模块,映射文件的起始地址称为模块句柄,可通过句柄访问内存中其他数据结构,这个内存起始地址称为基地址。
  • 虚拟地址(VA): 在Windows系统中,PE文件被系统加载到内存后,每个程序都有自己的虚拟空间,这个虚拟空间的内存地址称为虚拟地址。
  • 相对虚拟地址(RVA): 可执行文件中,有许多地方需要指定内存中的地址。例如,应用全局变量时需要指定它的地址。为了避免在PE文件中出现绝对内存地址引入了相对虚拟地址,它就是在内存中相对于PE文件载入地址的偏移量。
  • 文件偏移地址(FOA): 当PE文件存储在磁盘中时,某个数据的位置相对于文件头的偏移量称为文件偏移地址(FOA)。文件偏移地址从PE文件中的第一个字节开始计数,起始值为0。

它们之间的关系:虚拟地址(VA) = 基地址(ImageBase) + 相对虚拟地址(RVA)

数据目录表结构

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 虚拟地址,就是数据目录表的起始位置
DWORD Size; // 尺寸,起始地址 + 尺寸 = 结束的位置
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

Directory Entries

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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 TLS表
#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

地址函数转换

与PE结构有关的三种地址

VA(虚拟地址):PE文件映射到内存后的地址
RVA(相对虚拟地址):内存地址相对于映射基地址的偏移地址
FileOffset(文件偏移地址):相对PE文件在磁盘上的文件开头的偏移地址

  • FOA = RVA - H
  • FOA = VA - ImageBase - H

  1. 判断RVA是否在头部
    FOA = RVA

  2. 判断RVA位于哪个节
    RVA >= Section[i]->VirtualAddress
    RVA <= Section[i]->VirtualAddress + 当前节内存对齐后的大小

    FOA = RVA - Section[i]->VirtualAddress + Section[i]->PointerToRawData

转换函数代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
DWORD RVAToFOA(DWORD dwRVA, char* buffer)
{
// dwRVA 相对虚拟函数 buffer 已加载的文件内存映像
// DOS头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT 头
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// 区段
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);
// 判断是否落在头部
if(dwRVA < pSectionHeader[0].VirtualAddress)
{
return dwRVA;
}
for(int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
{
// 判断落在哪个区段
if(dwRVA >= pSectionHeader[i].VirtualAddress && dwRVA <= pSectionHeader[i].VirtualAddress + pSectionHeader[i].Misc.VirtualSize)
{
return dwRVA - pSectionHeader[i].VirtualAddress + pSectionHeader[i].PointerToRawData;
}
}
return dwRVA;
}

_IMAGE_IMPORT_DESCRIPTOR(导入表)

IMAGE_IMPORT_DESCRIPTOR结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;

IMAGE_IMPORT_DESCRIPTOR

  • OriginalFirstThunk: 该字段指向导入名称表(INT)的RVA,该RVA指向的是一个IMAGE_THUNK_DATA的结构体
  • TimeDateStamp: 该字段可以被忽略,一般为0即可
  • ForwarderChain: 该字段一般为0
  • Name: 该字段指向DLL名称的RVA地址
  • FirstThunk: 该字段包含导入地址表(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA的结构体数组

IMAGE_THUNK_DATA结构体

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

IMAGE_THUNK_DATA

  • ForwarderString: 是转向它的第一个索引的函数的名称的RVA
  • Function: 代表输入函数的地址
  • Ordinal: 代表该函数在导入DLL中的序号。只有当IMAGE_THUNK_DATA的最高位为1时才代表使用序号导入,此时低31位代表在导入DLL中该函数的序号
  • AddressOfData: 指向IMAGE_IMPORT_BY_NAME的一个指针,它表示用函数名进行导入。当IMAGE_THUNK_DATA的最高位为0时代表使用函数名进行导入,此时这四个字节代表着IMAGE_IMPORY_BY_NAME的RVA

  1. 每一个IMAGE_THUNK_DATA对应一个DLL中的导入函数。IMAGE_THUNK_DATAIMAGE_IMPORT_DESCRIPORT类似,同样是一个以全”0”的IMAGE_THUNK_DATA为结束
  2. IMAGE_THUNK_DATA值的最高位为1时,表示函数以序号方式导入,这时低31位被看作一个导入序号。当其最高位为0时;表示函数以函数名称字符串的方式导入,这时DWORD的值表示一个RVA,并指向一个IMAGE_IMPORT_BY_NAME结构体

IMAGE_IMPORT_BY_NAME结构体

1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

IMAGE_IMPORT_BY_NAME

  • Hint: 该字段表示该函数在其导入表中的序号
  • Name: 该字段表示导入函数的函数名。导入函数是一个以ASCII编码的字符串,并以NULL结尾。在IMAGE_IMPORT_BY_NAME中使用Name[1]来定义该字段,表示这是只有1个长度大小的字符串,但是函数名不可能只有一个字节的长度,通过越界访问来达到访问变长字符串的功能

注意

  1. IMAGE_IMPORT_DESCRIPTOR结构体中的OriginalFirstThunkFirstThunk都指向了IMAGE_THUNK_DATA这个结构体,但是两者是有区别的。当文件在磁盘上时,两者指向的IMAGE_THUNK_DATA是相同的内容,而当文件被载入内存后,两者指向的就是不同的内容
  2. 在磁盘上时,OriginalFirstThunk指向的IMAGE_THUNK_DATA中保存的是指向函数名的RVA,称其为INT。FirstThunk通常指向的IMAGE_THUNK_DATA中保存的也是指向函数名的RVA,它们在磁盘上是没有差异的
  3. 当文件被载入内存后,OriginalFirstThunk指向的IMAGE_THUNK_DATA中保存的是指向函数名的RVA;FirstThunk通常指向的IMAGE_THUNK_DATA中由装载器填充的导入函数地址,称其为IAT。

解析导入表代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
DWORD PrintImportTable(char* buffer)
{
// DOS 头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT 头
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// 定位导入表
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT);
// 填充结构体
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)(RVAToFOV(pData.Dir.VirtualAddress, buffer) + buffer);
while(pImport->Name)
{
char* szDllName = (char*)(RVAToFOA(pImport->Name, buffer) + buffer);
printf("DllName: %s\n", szDllName);
printf("TimeDateStamp:%08x\n", pImport->TimeDateStamp);
printf("ForwarderChain:%08x\n", pImport->ForwarderChain);
printf("Name Offset:%08x\n", pImport->Name);
printf("FirstThunk:%08x\n", pImport->FirstThunk);
printf("OriginalFirstThunk:%08x\n\n", pImport->OriginalFirstThunk);

PIMAGE_THUNK_DATA pIAT = (PIMAGE_THUNK_DATA)(RVAToFOA(pImport->FirstThunk, buffer) + buffer);
while(pIAT->u1.Ordinal != 0)
{
if(!IMAGE_SNAP_BY_ORDINAL32(pIAT->u1.Ordinal))
{
PIMAGE_IMPORT_BY_NAME pFunctionName = (PIMAGE_IMPORT_BY_NAME)(RVAToFOA(pIAT->u1.AddressOfData, buffer) + buffer);
printf("Function Name: %s\n", pFunctionName);
}
pIAT++;
}
pImport++
}
}

_IMAGE_EXPORT_DIRECTORY(导出表)

IMAGE_EXPORT_DIRECTORY结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
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;

IMAGE_EXPORT_DIRECTORY

  • Characteristics: 保留,必须为0
  • TimeDateStamp: 时间戳
  • MajorVersion: 主要版本号,主要和次要版本号可由用户设置
  • MinorVersion: 次要版本号
  • Name: 名称RVA,包含导出文件名称的ASCII字符串地址
  • Base: 此映像中导出的起始序号,指定导出地址表(AddressOfFunctions)的起始序号,通常设置为1
  • NumberOfFunctions: 导出函数的个数,导出地址表(AddressOfFunctions)中的条目数
  • NumberOfNames: 按名称导出的函数个数,名称表(AddressOfFunctions)中的条目数,同样也是序号表(AddressOfNameOrdinals)中的条目数
  • AddressOfFunctions: 导出地址表RVA
  • AddressOfNames: 导出名称表RVA
  • AddressOfNameOrdinals: 序号表RVA

解析导出表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void PrintExportTable(char* buffer)
{
// DOS
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// 定位导出表
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT);
// 填充结构体
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RVAToFOA(pDataDir->VirtualAddress, buffer) + buffer);
if (pExport->AddressFunctions == 0)
{
printf("当前没有导出表!\n");
return;
}
char* szDllName = (char*)(RVAToFOA(pExport->Name, buffer) + buffer);
printf("DllName: %s\n", DllName);
printf("Base: %#08x\n", pExport->Base);
printf("NumberOfFunctions: %#08x\n", pExport->NumberOfFunctions);
printf("NumberOfNames: %#08x\n", pExport->NumberOfNames);
printf("AddressOfFunctions: %#08x\n", pExport->AddressOfFunctions);
printf("AddressOfNames: %#08x\n", pExport->AddressOfNames);
printf("AddressOfNameOrdinals: %#08x\n", pExport->AddressOfNameOrdinals);
// 函数数量
DWORD dwNumberOfFunctions = pExport->NumberOfFunctions;
// 函数名数量
DWORD dwNumberOfNames = pExport->NumberOfNames;
// Base
DWORD dwBase = pExport->Base;
// 导出地址表
PDWORD pExportAddrTable = (PDWORD)(RVAToFOA(pExport->AddressOfFunctions, buffer) + buffer);
// 导出名称表
PDWORD pExportNameTable = (PDWORD)(RVAToFOA(pExport->AddressOfNames, buffer) + buffer);
// 导出序号表
PWORD pExportIdTable = (PWORD)(RVAToFOA(pExport->AddressOfNameOrdinals, buffer) + buffer);
for(int i = 0; i < dwNumberOfFunctions; i++)
{
if(pExportAddrTable[i] == i)
{
continue;
}
DWORD id = 0;
for (; id < dwNumberOfNames; id++)
{
if(pExportIdTable[i] == id)
{
break;
}
}
if (id == dwNumberOfNames)
{
printf("ID: %x Address: %#08x Name[NULL]\n", i + dwBase, pExportAddrTable[i]);
}
else
{
char* szFunName = (char*)(RVAToFileOffset(pExportNameTable[id], buffer) + buffer);
printf("ID: %x Address: %#08x Name[%s]\n", i + dwBase, pExportAddrTable[i], szFunName);
}
}
}

_IMAGE_BASE_RELOCATION(重定位表)

1
2
3
4
5
6
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

_IMAGE_BASE_RELOCATION

  • VirtualAddress: 指向需要重定位地址的RVA,每个INAGE_BASE_RELOCATION只负责4kb大小分页内的重定位信息。因此结构中的VirtualAddress值为0x1000的倍数
  • SizeOfBlock: imageBase结构体和TypeOffset的总和 重定位块的大小
  • TypeOffset[1]: 自定义的一个字段,表示这个结构体下面会出现WORD类型的数组,该数组元素的就是硬编码在程序当中的偏移

自定义TypeOffset结构
typedef struct _TYPE
{
WORD Offset: 12; // 大小2bit重定位的偏移
WORD Type: 4;
} TYPE, *PTYPE;

Windows的PE装载器进行PE重定位处理的操作原理流程

  1. 在应用程序当中查找硬编码位置
  2. 读取之后减去ImageBase,也就是用VA - 基址 = RVA
  3. 加上实际加载地址得到真正的VA

其中最关键的就是找到硬编码的位置,而要找到硬编码的位置,首先要找到基址重定位表,该表位于.reloc区段,找到基址重定位表的正确打开方式是通过数据目录表IMAGE_DATA_DIRECTORY条目查找

解析重定位表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void PrintBaseRelocTable(char* buffer)
{
// 自定义TypeOffset结构
typedef struct _TYPE{
WORD Offset: 12;
WORD Type: 4;
} TYPE, *PTYPE;
// DOS
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// SECTION Header
PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRSET_SECTION(pNtHeader);
// 定位重定位表
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNtHeader->OptionalHeader.DataDirtory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
// 填充结构体
PIMAGE_BASE_RELOCATION pBaseReloc = (PIMAGE_BASE_RELOCATION)(RVAToFOA(pDataDir->VirtualAddress, buffer) + buffer);
while(pBaseReloc->SizeOfBlock != 0)
{
// 找到本0x1000个字节的起始位置
// 重定位个数 = (SizeOfBlock - 8(IMAGE_BASE_RELOCATION)) / 2(每个TypeOffset是2个字节)
DWORD dwCount = (DWORD)(pBaseReloc->SizeOfBlock - 8) / 2;
DWORD dwRVA = pBaseReloc->VirtualAddres;
PTYPE pRelocAddr = (PTYPE)(pBaseReloc + 1);
printf("SECTION: %#08X\n", pSectionHeader->Name);
printf("RVA: %#08X\n", dwRVA);
printf("ITEMS: %x H / %d D\n", pBaseReloc->SizeOfBlock, pBaseReloc->SizeOfBlock);
// 找到下一个0x1000个字节
pBaseReloc = (PIMAGE_BASE_RELOCATION)((char*)pBaseReloc + pBaseReloc->SizeOfBlock);
for (int i = 0; i < dwCount; i++)
{
PDWORD pData = (PDWORD)(RVAToFOA(pRelocAddr[i].Offset + dwRVA, buffer) + buffer);
DWORD pDataOffset = RVAToFOA(pRelocAddr[i].Offset + dwRVA, buffer);
printf("SECTION: %#08x\n", *pData);
printf("RVA: %#08x\n", pRelocAddr[i].Offset + dwRVA);
printf("OFFSET: %#08x\n\n", pDataOffset);
}
}
}

_IMAGE_TLS_DIRECTORY(TLS表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD AddressOfIndex; // PDWORD
DWORD AddressOfCallBacks; // PIMAGE_TLS_CALLBACK *
DWORD SizeOfZeroFill;
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;

_IMAGE_TLS_DIRECTORY32

  • StartAddressOfRawData: TLS初始化数据起始地址
  • EndAddressOfRawData: TLS初始化结束地址,两个正好定位一个范围,范围放初始化的值
  • AddressOfIndex: TLS索引位置
  • AddressOfCallBacks: TLS回调函数的数组指针
  • SizeOfZeroFill: 填充0的个数
  • Characteristics: 保留

TLS: 线程本地存储器,可以将数据与执行的特定线程联系起来。怎么理解?

  1. 如果一个变量是全局的,那么所有线程访问的是同一份,某一个线程对其修改会影响其他所有线程。如果我们需要一个变量在每个线程中都能访问,并且值在每个线程中互不影响,这就是TLS。
  2. 线程局部存储在不同平台有不同的实现,可移植性不好。线程局部存储不难实现,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程ID不同,查到的数据自然也不同。

解析TLS表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void PrintTLSTable(char* buffer)
{
// DOS
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// 定位TLS表
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNtHeader->OptionalHeader.VirtualAddres + IMAGE_DIRECTORY_ENTRY_TLS);
// 填充结构体
PIMAGE_TLS_DIRECTORY pTLS = (PIMAGE_TLS_DIRECTORY)(RVAToFOA(pDataDir->VirtualAddress, buffer) + buffer);
prinf("StartAddressOfRawData: %#08x\n", pTLS->StartAddressOfRawData);
prinf("EndAddressOfRawData: %#08x\n", pTLS->EndAddressOfRawData);
prinf("AddressOfIndex: %#08x\n", pTLS->AddressOfIndex);
prinf("AddressOfCallBacks: %#08x\n", pTLS->AddressOfCallBacks);
prinf("SizeOfZeroFill: %#08x\n", pTLS->SizeOfZeroFill);
prinf("Characteristics: %#08x\n", pTLS->Characteristics);
}

_IMAGE_DELAYLOAD_DESCRIPTOR(延时加载表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {
union {
DWORD AllAttributes;
struct {
DWORD RvaBased : 1; // Delay load version 2
DWORD ReservedAttributes : 31;
} DUMMYSTRUCTNAME;
} Attributes;

DWORD DllNameRVA; // RVA to the name of the target library (NULL-terminate ASCII string)
DWORD ModuleHandleRVA; // RVA to the HMODULE caching location (PHMODULE)
DWORD ImportAddressTableRVA; // RVA to the start of the IAT (PIMAGE_THUNK_DATA)
DWORD ImportNameTableRVA; // RVA to the start of the name table (PIMAGE_THUNK_DATA::AddressOfData)
DWORD BoundImportAddressTableRVA; // RVA to an optional bound IAT
DWORD UnloadInformationTableRVA; // RVA to an optional unload info table
DWORD TimeDateStamp; // 0 if not bound,
// Otherwise, date/time of the target DLL

} IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;

解析延时加载表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// DOS
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
// NT
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + buffer);
// 定位DelayImportTable
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNtHeader->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT);
// 填充结构体
PIMAGE_DELAYLOAD_DESCRIPTOR pDelayLoad = (PIMAGE_DELAYLOAD_DESCRIPTOR)(RVAToFileOffset(pDataDir->VirtualAddress, buffer) + buffer);
char* szDllName = (char*)(RVAToFileOffset(pDelayLoad->DllNameRVA, buffer) + buffer);
printf("%s\n", szDllName);
printf("Attributes: %#08x\n", pDelayLoad->Attributes);
printf("ModuleHandleRVA: %#08x\n", pDelayLoad->ModuleHandleRVA);
printf("ImportAddressTableRVA: %#08x\n", pDelayLoad->ImportAddressTableRVA);
printf("ImportNameTableRVA: %#08x\n", pDelayLoad->ImportNameTableRVA);
printf("BoundImportAddressTableRVA: %#08x\n", pDelayLoad->BoundImportAddressTableRVA);
printf("UnloadInformationTableRVA: %#08x\n", pDelayLoad->UnloadInformationTableRVA);
printf("TimeDateStamp: %#08x\n", pDelayLoad->TimeDateStamp);

评论