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

移动重定位表

Relocation(重定位)是一种将程序中的一些地址修正为运行时可用的实际地址的机制。在程序编译过程中,由于程序中使用了各种全局变量和函数,这些变量和函数的地址还没有确定,因此它们的地址只能暂时使用一个相对地址。当程序被加载到内存中运行时,这些相对地址需要被修正为实际的绝对地址,这个过程就是重定位;

在Windows操作系统中,程序被加载到内存中运行时,需要将程序中的各种内存地址进行重定位,以使程序能够正确地运行。Windows系统使用PE(Portable Executable)文件格式来存储可执行程序,其中包括重定位信息。当程序被加载到内存中时,系统会解析这些重定位信息,并将程序中的各种内存地址进行重定位;

重定位表一般出现在DLL中,因为DLL都是动态加载,所以地址不固定,DLL的入口点在整个执行过程中至少要执行2次,一次是在开始时执行初始化工作,一次则是在结束时做最后的收尾工作,重定位表则是解决DLL的地址问题;

移动重定位表步骤

  1. 读取文件并在FileBuffer中新增一个节(.rbase);
  2. 设置节表信息并修正PE头;
  3. 解析重定位表、复制重定位表、修正数据目录;
  4. 将数据写入新的文件;
  5. 验证是否移动成功;

ROA转FOA

将ROA转换成FOA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)lpBuffer);
PIMAGE_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
19
DWORD FOA2RVA(DWORD dwFOA, LPVOID lpBuffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
PIMAGE_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
// 读取文件
HANDLE hFile = CreateFileA(
FilePath_SRC,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL
);
if (INVALID_HANDLE_VALUE == hFile)
{
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);
  1. 新增节表

    新增.rbase节表

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
// 解析PE文件
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpData);
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
//printf("pDos: %x\n", pDos->e_magic);

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

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

// 修正PE头
pNt->FileHeader.NumberOfSections += 1;
pNt->OptionalHeader.SizeOfImage += 0x7000;
  1. 复制重定位表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LPVOID lpSecMemory = new BYTE[0x7000];
if (lpSecMemory == NULL)
{
printf("新节表申请内存失败\n");
return 0;
}
memset(lpSecMemory, 0, 0x7000);

// 解析重定位表
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)(RVA2FOA(pDir->VirtualAddress, lpData) + (DWORD)lpData);
printf("pDir->Size: %x\n", pDir->Size);

// 复制重定位表
memcpy(lpSecMemory, pReloc, pDir->Size);

// 修正数据目录
pDir->VirtualAddress = FOA2RVA(dwFileSize, lpData);
  1. 将数据写入新文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 打开新文件
    HANDLE hNewFile = CreateFileA(FilePath_DEST, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hNewFile)
    {
    printf("CreateFileA Failed\n");
    return 0;
    }
    // 写出第一部分
    DWORD lpNumberOfBytesWritten1 = 0;
    if (FALSE == WriteFile(hNewFile, lpData, dwFileSize, &lpNumberOfBytesWritten1, NULL))
    {
    printf("WriteFile One Failed\n");
    return 0;
    }
    // 写出新节
    DWORD lpNumberOfBytesWritten2 = 0;
    if (FALSE == WriteFile(hNewFile, lpSecMemory, 0x7000, &lpNumberOfBytesWritten2, NULL))
    {
    printf("WriteFile Two Failed\n");
    return 0;
    }
    CloseHandle(hNewFile);
    printf("WriteFile Success: %d %d\n", lpNumberOfBytesWritten1, lpNumberOfBytesWritten2);
  2. 调用新的Dll文件

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

运行成功

移动重定位表

完整代码

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
#include <iostream>
#include <Windows.h>

constexpr auto FilePath_SRC = "xxx\\Dll1.dll";
constexpr auto FilePath_DEST = "xxx\\Dll1_demo.dll";

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);
PIMAGE_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;
}

DWORD FOA2RVA(DWORD dwFOA, LPVOID lpBuffer)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
PIMAGE_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;
}

int main()
{
// 读取文件
HANDLE hFile = CreateFileA(
FilePath_SRC,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE,
NULL
);
if (INVALID_HANDLE_VALUE == hFile)
{
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);

// 解析PE文件
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpData);
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);
//printf("pDos: %x\n", pDos->e_magic);

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

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

// 修正PE头
pNt->FileHeader.NumberOfSections += 1;
pNt->OptionalHeader.SizeOfImage += 0x7000;

LPVOID lpSecMemory = new BYTE[0x7000];
if (lpSecMemory == NULL)
{
printf("新节表申请内存失败\n");
return 0;
}
memset(lpSecMemory, 0, 0x7000);

// 解析重定位表
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)(RVA2FOA(pDir->VirtualAddress, lpData) + (DWORD)lpData);
printf("pDir->Size: %x\n", pDir->Size);

// 复制重定位表
memcpy(lpSecMemory, pReloc, pDir->Size);

// 修正数据目录
pDir->VirtualAddress = FOA2RVA(dwFileSize, lpData);

// 打开新文件
HANDLE hNewFile = CreateFileA(FilePath_DEST, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hNewFile)
{
printf("CreateFileA Failed\n");
return 0;
}
// 写出第一部分
DWORD lpNumberOfBytesWritten1 = 0;
if (FALSE == WriteFile(hNewFile, lpData, dwFileSize, &lpNumberOfBytesWritten1, NULL))
{
printf("WriteFile One Failed\n");
return 0;
}
// 写出新节
DWORD lpNumberOfBytesWritten2 = 0;
if (FALSE == WriteFile(hNewFile, lpSecMemory, 0x7000, &lpNumberOfBytesWritten2, NULL))
{
printf("WriteFile Two Failed\n");
return 0;
}
CloseHandle(hNewFile);
printf("WriteFile Success: %d %d\n", lpNumberOfBytesWritten1, lpNumberOfBytesWritten2);

typedef void (*MyShowMessage)();
HMODULE hDll = LoadLibraryA(FilePath_DEST);
if (hDll == NULL)
{
printf("LoadLibraryA Failed\n");
return 0;
}
MyShowMessage myShowMessage = (MyShowMessage)GetProcAddress(hDll, "ShowMessage");
myShowMessage();
}

重建重定位结构

重定位表的修复原理与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
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
//Modify_ImageBase_Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//
#include <iostream>
#include <windows.h>

const auto FilePath_SRC = "xxx\\Dll1.dll";
const auto FilePath_DEST = "xxx\\Dll1_Demo.dll";

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);
PIMAGE_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;
}

int main()
{
HANDLE hFile = CreateFileA(
(LPSTR)FilePath_SRC,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile Failed\n");
return 0;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("dwFileSize: %d\n", dwFileSize);
LPVOID lpData = new BYTE[dwFileSize];
if (NULL == lpData)
{
printf("申请内存失败\n");
return 0;
}
DWORD lpNumberOfBytesRead = 0;
if (FALSE == ReadFile(hFile, lpData, dwFileSize, &lpNumberOfBytesRead, NULL))
{
printf("ReadFile Failed\n");
return 0;
}
CloseHandle(hFile);
printf("lpData: %x\n", *(short*)lpData);

//解析PE
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpData;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpData);
//定位重定位位置
PIMAGE_DATA_DIRECTORY pDataDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC);
//获取重定位表
PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)(RVA2FOA(pDataDir->VirtualAddress, lpData) + (DWORD)lpData);

//判断是否有重定位表, 数据目录表不存在时, VirtualAddress为0, 也就是指向映像基址
if ((LPVOID)pLoc == lpData)
{
return FALSE;
}
//循环重定位表, 重定位表VirtualAddress和SizeOfBlock都为0表示重定位表结束
while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0)
{
//重定位数据, 位于IMAGE_BASE_RELOCATION表开头8字节之后
PWORD pLocData = (PWORD)((PBYTE)pLoc + sizeof(IMAGE_BASE_RELOCATION));
//计算本节需要修正的重定位项(地址)的数目, 每个数据都是16字节(4+12字节, 高4位表示重定位类型, 低12位为RVA)
//SizeOfBlock的值包括了SizeOfBlock和VirtualAddress的大小, 8字节需要减去
DWORD dwNumOfpLoc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (size_t i = 0; i < dwNumOfpLoc; i++)
{
//高位为3表示有效重定位
if ((DWORD)(pLocData[i] & 0x0000F000) == 0x00003000)
{
/*需要修正的数据
修正重定位数据, 重定位表记录的是存在硬编码的地址, 以基址+偏移的形式
存在硬编码的地址 = 重定位基址 + 重定位表数据偏移
= 基址 + 重定位地址 + 重定位数据(数据后12位)*/
PDWORD pAddress = (PDWORD)((PBYTE)pDos + RVA2FOA(pLoc->VirtualAddress + (pLocData[i] & 0x0FFF), lpData));
//重定位地址 = 硬编码地址 - ImageBase + 实际基地址
// = 实际基地址 - ImageBase + 硬编码地址
*pAddress = *pAddress - pNt->OptionalHeader.ImageBase + 0x1500000;
}
}
//循环下一个重定位区段
pLoc = (PIMAGE_BASE_RELOCATION)((PBYTE)pLoc + pLoc->SizeOfBlock);
}

//修改ImageBase
pNt->OptionalHeader.ImageBase = 0x1500000;

// 打开新文件
HANDLE hNewFile = CreateFileA(FilePath_DEST, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == hNewFile)
{
printf("CreateFileA Failed\n");
return 0;
}
DWORD lpNumberOfBytesWritten = 0;
if (FALSE == WriteFile(hNewFile, lpData, dwFileSize, &lpNumberOfBytesWritten, NULL))
{
printf("WriteFile Failed\n");
return 0;
}
CloseHandle(hNewFile);
printf("WriteFile Success\n");

typedef void (*MyShowMessage)();
HMODULE hDll = LoadLibraryA(FilePath_DEST);
if (hDll == NULL)
{
printf("LoadLibraryA Failed\n");
return 0;
}
MyShowMessage myShowMessage = (MyShowMessage)GetProcAddress(hDll, "ShowMessage");
myShowMessage();

return 0;
}

运行成功

重建重定位表

评论