Contents
  1. 1. 导出表
  2. 2. 移动导出表的步骤
  3. 3. 移动导出表

导出表

导出表一般多见于DLL和SYS文件,EXE在少部分情况下拥有导出表,导出表中存储了,当前DLL可供调用的函数的地址、函数名称,导出表的地址存储在数据目录项的第一个结构中。

IMAGE_SECTION_HEADER[0]

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;//导出表开始的地址 RVA
DWORD Size;//导出表大小
}IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

存储了导出表的地址(RVA)及其大小

导出表结构

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
DWORD AddressOfNames; // 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

导出表中最重要的就是三张表,AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals,分别存储了函数地址,函数名称,函数序号,这里有两种函数查找方式,分别是按照函数名称查找,还有按照序号查找。

我们在移动的时候,要注意RVA和FOA的转换,RVA是在内存中拉伸后的偏移,需要我们转换成文件中的偏移FOA才可以在文件中进行复制和修改。

导出函数名称表的移动是一个难点,导出函数名称表中存储了导出函数名称的偏移地址RVA,需要根据地址再进行访问函数名称。

移动导出表的步骤

  1. 在PE文件中新增一个节,并且新增对应的节表。
  2. 复制:AddressOfFunction(size:NumberOfFunctions * 4) 到新增的节
  3. 复制:AddressOfNameOrdinals(size:NumberOfNames * 2) 到新增的节中
  4. 复制:AddressOfNames(szie:NumberOfNames * 4) 到新增的节中
  5. 复制:AddressOfNames表中对应的所有地址,及其函数名,移动字符串时也需要注意大小\0结尾(每复制一个函数名就要计算偏移RVA添加到函数名称表的地址中)
  6. 复制:导出表结构体 到新增的节中
  7. 修改导出表结构中的对应AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames 地址,其他的参数不影响运行。
  8. 修复目录项中的VirtualAddress,指向新的导出表的地址

这里要梳理清楚在移动过程中的各个参数,在什么时候转换为FOA,什么时候转换为RVA

定位导出表的地址时,需要将IMAGE_SECTION_HEADER[0].VirtualAddress的RVA->FOA,以便于在文件中定位导出表的地址

在移动导出表的AddressOfFunction、AddressOfNameOrdinals、AddressOfNames时,需要将RVA->FOA以便于在文件中复制

在移动AddressOfNames中的地址指向的函数名字符串时,要将AddressOfNames中的地址RVA->FOA,并且在移动完后,修改AddressOfNames中存储的地址时,要FOA->RVA

将导出表中的AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames 地址修改为新复制的对应的地址时,要将新复制的对应的地址FOA->RVA

修复数据目录项中的VirualAddress时,要将新复制的导出表的存储地址FOA->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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
DWORD RVATOFOA(DWORD RVA,LPVOID pFileBuffer)
{
DWORD FOA = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;

pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

if(RVA <= pOptionalHeader->SizeOfHeaders)
return RVA;
for(;RVA > (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);pSectionHeader++);//定位到所在节
FOA = RVA - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
return FOA;

}

DWORD FOATORVA(DWORD FOA,LPVOID pFileBuffer)
{
DWORD RVA = NULL;

PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNtHeaders = NULL;
PIMAGE_FILE_HEADER pFileHeader = NULL;
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;

pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

if(FOA <= pOptionalHeader->SizeOfHeaders)
return FOA;
for(;FOA > (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);pSectionHeader++);//定位到所在节
RVA = FOA - pSectionHeader->PointerToRawData + pSectionHeader->VirtualAddress ;
return RVA;
}

BOOL MoveExport(LPVOID pFileBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = NULL;//DOS头
PIMAGE_NT_HEADERS pNtHeaders = NULL;//NT头
PIMAGE_FILE_HEADER pFileHeader = NULL;//标准PE头
PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;//拓展PE头
PIMAGE_SECTION_HEADER pSectionHeader = NULL;//节表
PIMAGE_SECTION_HEADER pNewSec = NULL;//新节表结构
PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL; //导出表结构体

pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

//判断是否有足够的空间添加节表
if ((pOptionalHeader->SizeOfHeaders - ((DWORD)pSectionHeader - (DWORD)pFileBuffer + pFileHeader->NumberOfSections * 40)) < 80)
{
printf("空间不足");
return false;
}

//新增节表结构
pNewSec = (PIMAGE_SECTION_HEADER)(pSectionHeader + pFileHeader->NumberOfSections);

//修改节表内容
memcpy(pNewSec->Name,".export",8);//修改节表名

PIMAGE_SECTION_HEADER upSecHeader = (PIMAGE_SECTION_HEADER)(pSectionHeader + pFileHeader->NumberOfSections-1);

if(upSecHeader->Misc.VirtualSize > upSecHeader->SizeOfRawData)//修改节表VrituallAddress
{
pNewSec->VirtualAddress = upSecHeader->VirtualAddress + upSecHeader->Misc.VirtualSize;
}else{
pNewSec->VirtualAddress = upSecHeader->VirtualAddress + upSecHeader->SizeOfRawData;
}

pNewSec->SizeOfRawData = 0x1000;//新增的节区的大小
pNewSec->PointerToRawData = upSecHeader->PointerToRawData + upSecHeader->SizeOfRawData;//文件中的偏移
pNewSec->Characteristics = 0x60000020;//修改属性(可执行)

//在新增节表后增加40个字节的空白区
memset(pNewSec+1, 0, 40);

//修改NT头属性

pFileHeader->NumberOfSections += 1;//修改NumberOfSection数量
pOptionalHeader->SizeOfImage += 0x1000;//修改SizeOfImage大小


LPVOID NewBuffer = malloc(pOptionalHeader->SizeOfImage);//申请内存
memset(NewBuffer, 0, pOptionalHeader->SizeOfImage);//初始化内存

memcpy(NewBuffer, pFileBuffer,pOptionalHeader->SizeOfImage);//复制内存


//定位导出表
DWORD ExportFoa = NULL;//导出表FOA
PDWORD AddressOfNames = NULL;//导出函数名称表
LPVOID AddressOfFunctions = NULL;//导出函数地址表
PWORD AddressOfNameOrdinals = NULL;//导出函数序号表

DWORD AddressOfNamesFOA = NULL;//导出函数名称表FOA
DWORD AddressOfFunctionsFOA = NULL;//导出函数地址表FOA
DWORD AddressOfNameOrdinalsFOA = NULL;//导出函数序号表FOA

ExportFoa = RVATOFOA(pOptionalHeader->DataDirectory[0].VirtualAddress,pFileBuffer);//获取导出表的地址FOA

pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + ExportFoa);//定位导出表

AddressOfNamesFOA = RVATOFOA(pExportDirectory->AddressOfNames,pFileBuffer);//获取导出函数名称表FOA
AddressOfFunctionsFOA = RVATOFOA(pExportDirectory->AddressOfFunctions,pFileBuffer);//获取到处函数地址表FOA
AddressOfNameOrdinalsFOA = RVATOFOA(pExportDirectory->AddressOfNameOrdinals,pFileBuffer);//获取导出函数序号表FOA

AddressOfNames = (PDWORD)((DWORD)pFileBuffer + AddressOfNamesFOA);//定位导出函数名称表
AddressOfFunctions = (LPVOID)((DWORD)pFileBuffer + AddressOfFunctionsFOA);//定位导出函数地址表
AddressOfNameOrdinals = (PWORD)((DWORD)pFileBuffer + AddressOfNameOrdinalsFOA);//定位导出函数序号表

////复制AddressOfFunctions表
LPVOID pNewSecAddr = (LPVOID)((DWORD)NewBuffer+pNewSec->PointerToRawData);//定位新节表的地址
memcpy(pNewSecAddr,AddressOfFunctions,(pExportDirectory->NumberOfFunctions * 4));

//复制AddressOfNameOrdinals表
pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfFunctions * 4));
memcpy(pNewSecAddr,AddressOfNameOrdinals,(pExportDirectory->NumberOfNames * 2));

//复制AddressOfNames
pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfNames * 2));
PDWORD NameAddr = (PDWORD)pNewSecAddr;//这里存储一下函数地址名称表的地址,以便后边移动名称的时候修改相应地址
memcpy(pNewSecAddr,AddressOfNames,(pExportDirectory->NumberOfNames * 4));


//复制函数名称表中的名称
pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfNames * 4));
//每复制一个函数名就要计算偏移添加到名字表的地址里

DWORD NameAddOffset = (DWORD)pNewSecAddr - (DWORD)NewBuffer;//函数名称偏移

for(size_t i=0;i < pExportDirectory->NumberOfNames;i++,AddressOfNames++)
{
DWORD NameOffset = (DWORD)*AddressOfNames;
PCHAR FName = (PCHAR)((DWORD)pFileBuffer + NameOffset);
size_t l = 0;

*NameAddr = FOATORVA(NameAddOffset,NewBuffer);//复制函数名称偏移到地址中
NameAddr = (PDWORD)((DWORD)NameAddr + 0x4);

while(FName[l] != '\0')
{
l += 1;
}

NameAddOffset += (l+1);
memcpy(pNewSecAddr,FName,(l+1));//复制函数名字符串到地址中

pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + l+1);
}

//复制IMAGE_EXPORT_DIRECTORY结构体
memcpy(pNewSecAddr,pExportDirectory,pOptionalHeader->DataDirectory[0].Size);
PIMAGE_EXPORT_DIRECTORY pNewExportDirectory = (PIMAGE_EXPORT_DIRECTORY)pNewSecAddr;//地址赋值给新的导出表结构体

//修改新的导出表的AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames地址,这里需要FOA->RVA
pNewExportDirectory->AddressOfFunctions = FOATORVA(pNewSec->PointerToRawData,NewBuffer);
pNewExportDirectory->AddressOfNameOrdinals = FOATORVA(pNewSec->PointerToRawData + pExportDirectory->NumberOfFunctions * 4,NewBuffer);
pNewExportDirectory->AddressOfNames = FOATORVA(pNewSec->PointerToRawData + pExportDirectory->NumberOfFunctions * 4 + pExportDirectory->NumberOfNames * 2,NewBuffer);


//修复目录项中的值,指向新的导出表的地址RVA
pDosHeader = (PIMAGE_DOS_HEADER)NewBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
pOptionalHeader->DataDirectory[0].VirtualAddress = FOATORVA((DWORD)pNewExportDirectory - (DWORD)NewBuffer,NewBuffer);

FILE* fp = fopen("C:\\testD.dll","wb+");
fwrite(NewBuffer,pOptionalHeader->SizeOfImage,1,fp);
fclose(fp);
return true;

}

查看移动后的效果

用工具打开,可以看到节表添加成功

1582601073918

导出表也可以正常解析

1582602113756

用十六进制编辑器打开我们移动后的Dll文件进行查看,可以看到我们移动的各个位置如下

1582602895748

在移动的过程中,如果遇到问题,也可以用十六进制编辑器打开我们移动后保存的文件,一步一步的对比调试。

文章首发于i春秋,版权所有,禁止转载!

Contents
  1. 1. 导出表
  2. 2. 移动导出表的步骤
  3. 3. 移动导出表