PE文件结构(九)

Last updated on October 7, 2023 pm

PE文件练习

移动导出表、重定位表、手动修复重定位表

回顾

导出表是告诉其他PE文件本身可以提供什么函数,并且提供名字索引和序号索引两种方式找到一个函数地址。导出表下又指向了三张新表,分别是函数地址表,序号表,名称表。

重定位表是在PE文件无法按照预计ImageBase装载时,提供所有需要修改地址的一张表。因为编译器在编译文件时会按照预计ImageBase计算所有地址,以硬编码方式生成,那么在ImageBase无法正确装载时,这些硬编码地址就需要更改,重定位表就负责提供这些硬编码地址。

现在就尝试去移动这两张表,并根据重定位表原理手动修复一下。

移动导出表

先来看看导出表的结构

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;//指向导出表文件名 RVA -->FOA+FileBuff=char *name;
DWORD Base;//导出函数起始序号
DWORD NumberOfFunctions;//导出函数个数
DWORD NumberOfNames;//以名称导出函数个数
DWORD AddressOfFunctions;//导出函数地址表 RVA-->FOA +FileBuff
DWORD AddressOfNames; //导出函数名称表 // RVA from base of image
DWORD AddressOfNameOrdinals; //导出函数序号表 // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY;

其中 AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals对应三张表的地址。

我们目标就是把这三张表移动一个新的节区,再把导出表结构放到这三张表后面。

可以分为以下几个步骤来移动

1.创建一个新的节区

2.将函数地址表,即AddressOfFunctions对应的表移到新节区开头,并将原AddressOfFunctions进行修正

3.将函数序号表,即AddressOfNameOrdinals对应的表接续移到新节区,并将原AddressOfNameOrdinals进行修正

4.将函数名称表,即AddressOfNames对应的表接续移到新节区,并将原AddressOfNames进行修正

5.将函数名称表对应的所有名字接续移到新节区,并把函数名称表里地址进行修正

6.将整个导出表接续移到新节区,并把原来的Directory目录项进行修正

NOTE:

1.需要主要各种地址之间的转化,是RVA还是FOA

2.需要主要各种指针类型之间的转化,这在指针运算中十分重要

3.可以尝试使用新移动后的dll,验证是否成功移动

代码

代码中会有详细注释,仅给出移动导出表函数实现,其余函数已在其余文章中有实现,不再重复给出。

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
62
63
64
65
66
67
68
69
70
71
72
73
74

char* Move_ExportTable(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
PIMAGE_SECTION_HEADER New_SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);
New_SectionHeader=(PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER +IMAGE_SIZEOF_SECTION_HEADER*(FILEHeader->NumberOfSections-1) + FILEHeader->SizeOfOptionalHeader);

DWORD Export_DATA_RVA = OptionalHeader->DataDirectory[0].VirtualAddress;
DWORD Export_DATA_FOA = RVA_TO_FOA(Export_DATA_RVA, Buffer);
PIMAGE_EXPORT_DIRECTORY Export_Table = (PIMAGE_EXPORT_DIRECTORY)(Export_DATA_FOA + Buffer);

//用来记录在新节区中偏移,就是已经复制了多少个字节
DWORD index = 0;
char* New_Sec = New_SectionHeader->PointerToRawData + Buffer;

//定位到函数地址表,使用char*类型指针即可,因为不需要对其具体求值,只需要按字节复制
char* AddressOfFunctions = Buffer + RVA_TO_FOA(Export_Table->AddressOfFunctions,Buffer);
//复制函数地址表,每个地址表表项都占4个字节
memcpy(New_Sec, AddressOfFunctions, Export_Table->NumberOfFunctions * 4);
//修正导出表AddressOfFunctions地址,使其指向新函数地址表 注意是RVA 这里就是新节区起始RVA
Export_Table->AddressOfFunctions = New_SectionHeader->VirtualAddress + index;
//记录已经copy多少字节
index += Export_Table->NumberOfFunctions * 4;

//定位到序号表,同理使用char*类型指针
char* AddressOfOrdinals = Buffer + RVA_TO_FOA(Export_Table->AddressOfNameOrdinals,Buffer);

//复制序号表
memcpy(New_Sec + index, AddressOfOrdinals, Export_Table->NumberOfNames * 2);
//修正导出表AddressOfNameOrdinals,使其指向新序号表,也是RVA
Export_Table->AddressOfNameOrdinals = New_SectionHeader->VirtualAddress + index;
//记录目前总共已经copy多少字节
index += Export_Table->NumberOfNames * 2;

//名称表实现稍显复杂,因为还需要copy名称表对应的名字
//定位到名称表
char* AddressOfNames = Buffer + RVA_TO_FOA(Export_Table->AddressOfNames,Buffer);
//新节中 名称表起始地址
char* AddressOfNames_FIX = New_Sec + index;
//修复导出表AddressOfNames,使其指向新名称表
Export_Table->AddressOfNames = New_SectionHeader->VirtualAddress + index;

//指向后面的空白区,开始复制名字
index += Export_Table->NumberOfNames * 4;
for(int i = 0;i < Export_Table->NumberOfNames;i++){
//复制名字 //AddressOfNames在定义时使用的char*类型,这里取值需要转为DWORD*型
char* name = Buffer + RVA_TO_FOA(*((DWORD*)AddressOfNames + i),Buffer);
int len = strlen(name);
//+1是因为要把字符串后面的0也复制过去,当然不复制也是可以的,因为创建新节区时就已经全部初始为0
memcpy(New_Sec + index,name,len+1);
//这里时修复名称表表项的地址,在根据原表项找到并复制名字后就要把表项给修正为新复制的地址,这里就是FOA转RVA了
*((DWORD*)AddressOfNames_FIX + i) = FOA_TO_RVA((New_Sec + index) - Buffer,Buffer);
//这里必须加1,因为字符串结束地址一定为0
index += len + 1;
}

//这里开始复制导出表数据
int Size_Of_Exportable = sizeof(IMAGE_EXPORT_DIRECTORY);
memcpy(New_Sec + index,Export_Table,Size_Of_Exportable);
//这里就是修复原目录项
OptionalHeader->DataDirectory[0].VirtualAddress = New_SectionHeader->VirtualAddress + index;

Export_Table_INFO(Buffer);
return Buffer;
}

移动重定位表

重定位表结构比导出表结构简单许多,只需要把目录项的VirtualAddress指向新节区,并把重定位表数据copy到新节区即可

代码

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

char* Move_RelocTable(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;
PIMAGE_SECTION_HEADER New_SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);
New_SectionHeader=(PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER +IMAGE_SIZEOF_SECTION_HEADER*(FILEHeader->NumberOfSections-1) + FILEHeader->SizeOfOptionalHeader);

//定位到重定位表
DWORD Reloc_RVA = OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
DWORD Reloc_FOA = RVA_TO_FOA(Reloc_RVA,Buffer);
PIMAGE_BASE_RELOCATION Reloc_Table = (PIMAGE_BASE_RELOCATION)(Buffer + Reloc_FOA);
//记录新节区偏移
DWORD index = 0;

//结束标志
while(Reloc_Table->VirtualAddress && Reloc_Table->SizeOfBlock){
//直接复制即可
memcpy(New_SectionHeader->PointerToRawData + Buffer + index,Reloc_Table,Reloc_Table->SizeOfBlock);
//记录复制了多少字节
index += Reloc_Table->SizeOfBlock;
//继续复制下张重定位表
Reloc_Table = (PIMAGE_BASE_RELOCATION) (((byte*)(Reloc_Table) + Reloc_Table->SizeOfBlock));
}
//修正目录项的VirtualAddress
OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = New_SectionHeader->VirtualAddress;

return Buffer;
}


修复重定位表

前面提及,重定位表是在PE文件无法按照语句ImageBase装载时发挥作用,那么我们就可以手动来改一改PE文件的ImageBase然后自己来修复那些地址。

比如说一个重定位表的VisualAddress是0x1000,它其中一个数据项有效值是0x12,那么就说明RVA0x1012处有一个需要修复的地址,需要根据实际ImageBase来重新计算。

那么我们这里修改了ImageBase,就相当于模拟了无法按照ImageBase来装载,我们就得按照重定位表去找那些需要修改的地址,来重新计算。比如我们给ImageBase增加0x100000,那么所有的那些需要修改的地址都一起增加0x100000

NOTE:重定位表是记录需要修改地址的表,其本身是不需要修改的。

代码

INCREATEMENT是我定义的一个宏,作为ImageBase修改的值

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

void Reloc_Fix(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FILEHeader = NULL;
PIMAGE_OPTIONAL_HEADER64 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FILEHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER64)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FILEHeader->SizeOfOptionalHeader);

//定位到重定位表
DWORD Reloc_RVA = OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
DWORD Reloc_FOA = RVA_TO_FOA(Reloc_RVA,Buffer);
PIMAGE_BASE_RELOCATION Reloc_Table = (PIMAGE_BASE_RELOCATION)(Buffer + Reloc_FOA);
//手动更改ImageBase
OptionalHeader->ImageBase =OptionalHeader->ImageBase + INCREATIMENT;

while(Reloc_Table->VirtualAddress && Reloc_Table->SizeOfBlock){
//定位到重定位表的数据项
WORD* Reloc_Add = (WORD*)((char*)Reloc_Table + 8);
//数据项项数
DWORD RealSize =( Reloc_Table->SizeOfBlock - 8) / 2;

for(int i = 0;i < RealSize ; i++){
//数据项低12位是偏移
DWORD Offset = (*(Reloc_Add + i) )& 0x0fff;
//VirtualAddress是基址,与数据项低12位一起组成地址
DWORD Base = Reloc_Table->VirtualAddress;
//找到需要修改的地址
DWORD* Addr = (DWORD*)(RVA_TO_FOA(Base + Offset,Buffer) + Buffer);
printf("Beferoe: %x ",*Addr);
//进行重定位,也就是根据ImageBase进行修复
*FuncAddr =*Addr + INCREATIMENT;
printf("After: %x\n",*Addr);
}
//继续下一个重定位表
Reloc_Table =(PIMAGE_BASE_RELOCATION) ((char*)Reloc_Table + Reloc_Table->SizeOfBlock);
}
}



PE文件结构(九)
http://example.com/2023/07/12/PE_8/
Author
yring
Posted on
July 12, 2023
Licensed under