PE结构(8)转移导出表

Last updated on May 7, 2024 am

转移导出表到自定义节是个有一点点挑战性的工作,但这确实是各种PE加密、解密的基础。

原因在于,对PE文件进行加密时,并不允许破坏PE本身的结构,否则会导致OS无法加载PE文件。因此一个常见做法,就是将PE中的各类表、表数据转移到加密者新增的一个节中,而后对其他节内的数据进行加密。

这样即可在不破坏PE数据结构的情况下,对用户数据进行加密操作。可以看到像是VMP这类加密工具,会在原文件中留下经典的vmp节表项,就是在做这个事情。

各类表的转移中,导出表算是个比较麻烦的家伙,想把它无损转移,大致上分为八个步骤:

  1. 新增一个自定义节
  2. 将函数表(AddressOfFunctions)拷贝到新节中
  3. 将序号表(AddressOfNameOrdinals)拷贝到新节中
  4. 将名称表(AddressOfNames)拷贝到新节中
  5. 将名称表引用的字符串拷贝到新节中,修复名称表内的地址
  6. 将导出表的目录结构(DATA_EXPORT_DIRECTORY)拷贝到新节中
  7. 修复导出表目录结构,关联在 2、3、4 步中拷贝的数据
  8. 修复PE头,使数据目录页数组中保留我们新的目录地址(RVA)

最后Dump存盘即可。

完整代码实现

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
#include "PE.h"
#include "PEStruct.h"
#include "InjectPE.h"

void work05() {
//将指定文件加载到内存,并解析PE信息到PEFILE结构体
PEFILE peFile{ 0 };
FILE* pFile = fopen("D:\\TestDLL.dll", "rb");
//此处的LoadFileToMemory方法做过特殊处理,会开辟额外的内存空间,方便后续追加节的操作
int fileLength = LoadFileToMemory(pFile, &peFile);
int r = fclose(pFile);

if (peFile.h_pe == nullptr) {
return;
}
DWORD bufferImageBase = (DWORD)peFile.h_dos;

//获取导出表
PDATA_EXPORT_DIRECTORY lpExportTable = nullptr;
GetExportTable(&lpExportTable, &peFile);
if (lpExportTable == nullptr) {
return;
}

//第一步,添加节,此处添加一个名为.crack,长度为4000字节的
lpPETable newTable = (lpPETable)AddSection(&peFile, (char*)".crack", 0x1000);
if (newTable == nullptr) {
return;
}
DWORD index = bufferImageBase + newTable->PointerToRawData;

//第二步,转移函数表
DWORD functionTableFOA = Rva2Foa(lpExportTable->AddressOfFunctions, &peFile);
void* functionTableAddress = (void*)(bufferImageBase + functionTableFOA);
DWORD functionTableSize = sizeof(DWORD) * lpExportTable->NumberOfFunctions;
memcpy((void*)index, functionTableAddress, functionTableSize);
index += functionTableSize;

//第三步,转移序号表
DWORD orderTableFOA = Rva2Foa(lpExportTable->AddressOfNameOrdinals, &peFile);
void* orderTableAddress = (void*)(bufferImageBase + orderTableFOA);
DWORD orderTableSize = sizeof(WORD) * lpExportTable->NumberOfNames;
memcpy((void*)index, orderTableAddress, orderTableSize);
index += orderTableSize;

//第四步,转移名称表
DWORD nameTableFOA = Rva2Foa(lpExportTable->AddressOfNames, &peFile);
void* nameTableAddress = (void*)(bufferImageBase + nameTableFOA);
DWORD nameTableSize = sizeof(DWORD) * lpExportTable->NumberOfNames;
memcpy((void*)index, nameTableAddress, nameTableSize);
//保留该地址,之后修复名称表用得到
DWORD* newNameTableItems = (DWORD*)index;
index += nameTableSize;

//第五步,修复名称表
for (int i = 0; i < lpExportTable->NumberOfNames; i++) {
DWORD nameRVA = ((DWORD*)nameTableAddress)[i];
DWORD nameFOA = Rva2Foa(nameRVA, &peFile);
char* nameItem = (char*)(nameFOA + bufferImageBase);
int nameSize = strlen(nameItem);
//勿要忽略\0
memcpy((void*)index, nameItem, nameSize + 1);
//新名称表字符串内存地址更新
newNameTableItems[i] = (DWORD)index;
index += nameSize + 1;
}

//第六步,拷贝导出表目录页
DWORD exportDirectorySize = sizeof(DATA_EXPORT_DIRECTORY);
PDATA_EXPORT_DIRECTORY lpNewExportTable = (PDATA_EXPORT_DIRECTORY)index;
memcpy((void*)index, lpExportTable, exportDirectorySize);
index += exportDirectorySize;

//第七步,修复导出表目录页
lpNewExportTable->AddressOfFunctions = Foa2Rva(functionTableFOA, &peFile);
lpNewExportTable->AddressOfNameOrdinals = Foa2Rva(orderTableFOA, &peFile);
lpNewExportTable->AddressOfNames = Foa2Rva(nameTableFOA, &peFile);

//第八步,修复PE头
DWORD NewExportTableFOA = (DWORD)lpNewExportTable - bufferImageBase;
DWORD NewExportTableRVA = Foa2Rva(NewExportTableFOA, &peFile);
lpPe_DATA_DIRECTORY directory = &peFile.h_op->DataDirectory[0];
directory->VirtualAddress = NewExportTableRVA;

pFile = fopen("D:\\TestDLL2.dll", "wb");
DumpFileBufferToFile(pFile, &peFile);
}

PE结构(8)转移导出表
http://dubhehub.github.io/blogs/2024050701250065136.html
Author
Sin
Posted on
May 7, 2024
Licensed under