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

为什么要移动导出表

  1. 导出表由编译器生成, 导出表中存储了该PE文件有哪些导出函数以及函数的地址;
  2. 在程序启动时, 系统会根据导出表做初始工作; 将用到的Dll中的函数地址存储到IAT表中;
  3. 为了保护程序, 可以对.EXE的二进制代码进行加密操作;
    • 如果进行加密, 各种表的信息以及数据是混在一起的, 加密后会导致程序无法正常执行;
    • 因此需要新增一个节, 并将PE中的表移动到新节中, 然后将代码和数据进行加密;

移动导出表步骤

移动导出表步骤

  1. 读取Dll文件并在FileBuffer中新增一个节(.export);
  2. 复制AddressOfFunctions(函数地址表);
    • 长度: NumberOfFunctions * 4;
  3. 复制AddressOfNameOrdinals(函数序号表);
    • 长度: NumberOfNames * 2;
  4. 复制AddressOfNames(函数名称表);
    • 长度: NumberOfNames * 4;
  5. 复制所有函数名;
    • 长度不确定, 复制时直接修复AddressOfNames;
  6. 复制IMAGE_EXPORT_DIRECTORY结构;
  7. 修复IMAGE_EXPORT_DIRECTORY结构中的AddressOfFunctionsAddressOfNameOrdinalsAddressOfNames;
  8. 修复目录项中的值, 指向新的IMAGE_EXPORT_DIRECTORY;

RVA转FOA

将RVA转换成FOA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DWORD RVA2FOA(DWORD dwRVA, LPVOID lpBuffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
IMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
if(dwRVA < pSec[0].VirtualAddress)
{
return dwRVA;
}
for(size_t i=0; i<pNt->FileHeader.NumberOfSections; i++)
{
if(dwRVA >= pSec[i].VirtualAddress && dwRVA <= pSec[i].VirtualAddress + pSec[i].Misc.VirtualSize)
{
return dwRVA - pSec[i].VirtualAddress + pSec[i].PointerToRawData;
}
}
return dwRVA;
}

FOA转RVA

将FOA转换成RVA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DWORD FOA2RVA(DWORD dwFOA, LPVOID lpBuffer)
{
IMAGE_DOS_HEADER pDos = (IMAGE_DOS_HEADER)lpBuffer;
IMAGE_NT_HEADERS pNt = (IMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
IMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
if(dwFOA < pSec[0].PointerToRawData)
{
return dwFOA;
}
for(size_t i=0;i<pNt->FileHeader.NumberOfSections;i++)
{
if(dwFOA >= pSec[i].PointerToRawData && dwFOA < (pSec[i].PointerToRawData + pSec[i].SizeOfRawData))
{
return pSec[i].VirtualAddress + dwFOA - pSec[i].PointerToRawData;
}
}
return dwFOA;
}

移动导出表具体步骤

1. 将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
char* szFilePath = "xxx";
HANDLE hFile = CreateFileA(
szFilePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL
);
if(hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFileA Failed\n");
return 0;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("dwFileSize: %d\n", dwFileSize);
LPVOID lpData = new BYTE[dwFileSize];
if(lpData == NULL)
{
printf("申请内存失败\n");
return 0;
}
DWORD dwRead = 0;
if(FALSE == ReadFile(hFile, lpData, dwFileSize, &dwRead, NULL))
{
printf("ReadFile Failed\n");
return 0;
}
printf("lpData: %x\n", *(short*)lpData);

2. 新增节表

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
// 解析PE文件
IMAGE_DOS_HEADER pDos = (IMAGE_DOS_HEADER)lpData;
IMAGE_NT_HEADERS pNt = (IMAGE_NT_HAEDERS)(pDos->e_lfanew + (DWORD)lpData);
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);

// 新增节表
PIMAGE_SECTION_HEADER pNewSec = pSec + pNt->FileHeader.NumberOfSections;
if(((DWORD)lpData + pNt->OptionalHeader.SizeOfHeaders - (DWORD)pNewSec) < 80)
{
printf("空间不足新增节表失败\n");
return 0;
}

// 设置新接节表
strcpy((char*)(pNewSec->Name), ".export");
pNewSec->Misc.VirtualSize = 0x1000;
pNewSec->VirtualAddress = pNt->OptionalHeader.SizeOfImage;
pNewSec->SizeOfRawData = 0x1000;
PIMAGE_SECTION_HEADER pLastSec = pSec + (pNt->FileHeader.NumberOfSections - 1);
pNewSec->PointerToRawData = pLastSec->PointerToRawData + pLastSec->SizeOfRawData;
pNewSec->Characteristics = pSec[1].Characteristics;

// 设置全0节表
memset((LPVOID)(pNewSec + 1), 0, 40);

// 修正头部信息
pNt->FileHeader.NumberOfSections += 1;
pNt->OptionalHeader.SizeOfImage += 0x1000;

3. 复制导出表

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
// 申请内存
LPVOID lpSecMemory = new BYTE[0x1000];
if(lpSecMemory == NULL)
{
printf("申请内存失败\n");
return 0;
}

// 解析导出表
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT);
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(RVA2FOA(pDir->VirtualAddress, lpData) + (DWORD)lpData);
printf("NumberOfNames: %d\n", pExport->NumberOfNames);

// 复制函数地址表
// 长度 NumberOfFunctions * 4
LPVOID lpCopyDest = lpSecMemory;
LPDWORD lpNewFuncAddr = (LPDWORD)lpCopyDest;
LPVOID lpFuncAddr = (LPVOID)(RVA2FOA(pExport->AddressOfFunctions, lpData) + (DWORD)lpData);
memcpy(lpCopyDest, lpFuncAddr, pExport->NumberOfFunctions * 4);
printf("lpSecMemory => AddressOfFunctions: %x\n", *(int*)lpSecMemory);
printf("pExport->AddressOfFuntions: %x\n", *(int*)(RVA2FOA(pExport->AddressOfFunctions, lpData) + (DWORD)lpData));

// 复制函数序号表
// 长度 NumberOfNames * 2
lpCopyDest = (LPVOID)((DWORD)lpSecMemory + pExport->AddressOfFunctions * 4);
LPDWORD lpNewOrdAddr = (LPDWORD)lpCopyDest;
LPVOID lpOrdAddr = (LPVOID)(RVA2FOA(pExport->AddressOfNameOrdinals, lpData) + (DWORD)lpData);
memcpy(lpCopyDest, lpOrdAddr, pExport->NumberOfNames * 2);
printf("lpSecMemory => AddressOfNameOrdinals: %x\n", *(char*)((DWORD)lpSecMemory + pExport->NumberOfFunctions * 4));
printf("pExport->AddressOfNameOrdinals: %x\n", *(char*)(RVA2FOA(pExport->AddressOfNameOrdinals, lpData) + (DWORD)lpData));

// 复制函数名称表
// 长度 NumberOfNames * 4
lpCopyDest = (LPVOID)((DWORD)lpSecMemory + pExport->NumberOfNames * 2);
LPDWORD lpNewNameAddr = (LPDWORD)lpCopyDest;
LPDWORD lpNameAddr = (LPDWORD)(RVA2FOA(pExport->AddressOfNames, lpData) + (DWORD)lpData);
memcpy(lpCopyDest, 0, pExport->NumberOfNames * 4);

// 复制函数名称并修复名称表
lpCopyDest = (LPVOID)((DWORD)lpSecMemory + pExport->NumberOfNames * 4);
for(size_t i=0; i<pExoprt->NumberOfNames; i++)
{
LPSTR lpFuncName = (LPSTR)(RVA2FOA(lpNameAddr[i], lpData) + (DWORD)lpData);
printf("lpFuncName: %s\n", lpFuncName);
DWORD dwFuncLen = strlen(lpFuncName) + 1;
memcpy(lpCopyDest, lpFuncName, dwFuncLen);
*(lpNewFuncAddr + i) = FOA2RVA((dwFileSize + (DWORD)lpCopyDest - (DWORD)lpSecMemory), lpData);
lpCopyDest = (LPVOID)((DWORD)lpCopyDest + dwFuncLen);
}
printf("lpSecMemory => AddressOfNames: %s\n", ((DWORD)lpSecMemory + pExport->NumberOfFunctions * 4 + pExport->NumberOfNames * 2 + pExport->NumberOfNames * 4));
printf("pExport->AddressName: %s\n", (RVA2FOA(lpNewFuncAddr[0], lpData) + (DWORD)lpData));

// 复制导出表
memcpy(lpCopyDest, pExport, pDir->Size);
PIMAGE_EXPORT_DIRECTORY pNewExport = (PIMAGE_EXPORT_DIRECTORY)lpCopyDest;
pNewExport->AddressOfFunctions = FOA2RVA((dwFileSize + (DWORD)lpNewFuncAddr - (DWORD)lpSecMemory), lpData);
pNewExoprt->AddressOfNameOrdinals = FOA2RVA((dwFileSize + (DWORD)lpNewOrdAddr - (DWORD)lpSecMemory), lpData);
pNewExport->AddressOfNames = FOA2RVA((dwFileSize + (DWORD)lpNewNameAddr - (DWORD)lpSecMemory), lpData);

// 修复数据目录
pNt->OptionalHeader.DataDirectory[0].VirtualAddress = FOA2RVA((dwFileSize + (DWORD)pNewExport - (DWORD)lpSecMemory), lpData);

4. 将数据写入文件

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
FILE* newFile = fopen("xxx", "a+b");
if(!newFile)
{
printf("打开新文件失败\n");
return 0;
}
size_t m = fwrite(lpData, dwFileSize, 1, newFile);
if(!m)
{
printf("写出文件第一部分失败\n");
fclose(newFile);
return 0;
}
// 写出新节
size_t n = fwrite(lpSecMemory, 0x1000, 1 , newFile);
if(!n)
{
printf("写出文件第二部分失败\n");
fclose(newFile);
return 0;
}

// 关闭文件并返回
fclose(newFile);
printf("移动导出表成功\n");
return 0;

5. 调用新导出表

1
2
3
4
5
6
7
8
9
10
typedef void (*MyShowMessage)();
HMODULE hDll = LoadLibraryA("xxx");
if(hDll == NULL)
{
printf("LoadLibrary Failed\n");
return 0;
}
MyShowMessage myShowMessage = (MyShowMessage)GetProcAddress(hDll, "ShowMessage");
myShowMessage();
FreeLibrary(hDll);

运行成功

移动导出表

dllmain.cpp

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>

extern "C" __declspec(dllexport) void ShowMessage()
{
MessageBox(NULL, L"I`m DLL File", L"HELLO", MB_OK);
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


评论