windows原理03

重定位表

重定位表用于在程序加载到内存中时,进行内存地址的修正。
为什么要进行内存地址的修正?我们举个例子来说:test.exe可执行程序需要三个动态链接库dll(a.dll,b.dll,c.dll),假设test.exe的ImageBase为400000H,而a.dll、b.dll、c.dll的基址ImageBase均为1000000H。这三个dll被加载到了同一个位置,这样肯定是不行的。重定位就是为了让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
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
// win原理Day003.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
//1 把文件读到内存中
char* ReadFileToMemory(char* pFilePath)
{
//1 获取文件句柄
HANDLE hFile = CreateFileA(pFilePath,
GENERIC_READ | GENERIC_WRITE,
FALSE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("文件打开失败\n");
return 0;
}
//2.获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
//3.申请内存空间
char* pBuf = new char[dwFileSize]{};
if (!pBuf)
{
CloseHandle(hFile);
printf("内存申请失败\n");
return 0;
}
//4.读取文件内容到内存空间
DWORD dwRead;
ReadFile(hFile, pBuf, dwFileSize, &dwRead, NULL);
//5. 返回内存地址
return pBuf;
}
//2 是否是PE文件
bool IsPeFile(char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是PE文件\n");
return false;
}
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)
(pDos->e_lfanew + pBuf);
if (pNt->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是PE文件\n");
return false;
}
return true;
}

//RVA to FOA
DWORD RVAtoFOA(DWORD dwRVA, char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pBuf);
//区段个数(文件头第二个字段)
DWORD dwCount = pNt->FileHeader.NumberOfSections;
//区段首地址(IMAGE_FIRST_SECTION)
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);

for (DWORD i = 0; i < dwCount; i++)
{
//FOA = RVA - 内存中区段首地址 + 文件中区段首地址
if (dwRVA >= pSec->VirtualAddress &&
dwRVA < pSec->VirtualAddress + pSec->SizeOfRawData)
{
return dwRVA - pSec->VirtualAddress + pSec->PointerToRawData;
}
//下一个区段表
pSec++;
}
return 0;
}

//解析重定位表
void ShowRelocTable(char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pBuf);
//找到重定位表(数据目录表第六项)
//解析
DWORD dwRelocRVA = pNt->OptionalHeader.DataDirectory[5].VirtualAddress;
//得到重定位表在文件中的地址
PIMAGE_BASE_RELOCATION pReloc =
(PIMAGE_BASE_RELOCATION)
(RVAtoFOA(dwRelocRVA, pBuf) + pBuf);

struct TypeOffset //重定位项结构
{
WORD offset : 12; //低12位表示相对于本块记录的内存页的偏移量
WORD type : 4; //高4位表示需要修正几个字节(0:不需要修正;3:修正4个字节;0x10:修正8个字节)
};
int n = 1;//重定位块个数
while (pReloc->SizeOfBlock)
{
printf("第%d块【0x%08X】\n", n++, pReloc->VirtualAddress);
//计算重定位项的个数
DWORD dwCount = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
//重定位项数组的首地址
TypeOffset* pOffset = (TypeOffset*)(pReloc + 1);
for (int i = 0; i < dwCount; i++)
{
if (pOffset->type == 3)
{
DWORD dwDataRVA = pOffset->offset + pReloc->VirtualAddress;
DWORD* pRelocData = (DWORD*)(RVAtoFOA(dwDataRVA, pBuf) + pBuf);
printf("\t需要修正的数据:0x%08X\n", *pRelocData);
//如果程序运行起来,修正的方式
//*pRelocData = *pRelocData - 默认加载基址 + 新的加载基址;
}
//下一个重定位项
pOffset++;
}

//下一个重定位块的地址
pReloc = (PIMAGE_BASE_RELOCATION)
((DWORD)pReloc + pReloc->SizeOfBlock);
}
}

int _tmain(int argc, _TCHAR* argv[])
{
char* pBuf = ReadFileToMemory("123.exe");
if (IsPeFile(pBuf))
{
ShowRelocTable(pBuf);
}
//释放内存
delete pBuf;
return 0;
}

修复重定位项

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

struct TyepOffset
{
WORD offset : 12;
WORD type : 4;
};

DWORD RVAtoFoa(PIMAGE_NT_HEADERS32 pnt, DWORD rva)
{
//得到区段表头的指针
auto pSection = IMAGE_FIRST_SECTION(pnt);
//有几个区段
int count = pnt->FileHeader.NumberOfSections;
//开始判断
for (int i = 0;i<count;i++)
{
if (rva >= pSection[i].VirtualAddress &&
rva < pSection[i].VirtualAddress + pSection[i].Misc.VirtualSize)
{
//说明rva在本区段内
return rva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
}
}
return -1;
}

int main()
{
// 打开文件
HANDLE hFile = CreateFileA("123.exe", GENERIC_ALL, FILE_SHARE_READ, NULL,
OPEN_EXISTING, NULL, NULL);

//判断打开是否成功
if (INVALID_HANDLE_VALUE == hFile)
{
printf("文件打开失败\n");
return -1;
}
//获取文件大小
DWORD size = GetFileSize(hFile, NULL);
//开辟空间
PCHAR lpBase = new char[size] {};
//读取文件
DWORD RealRead = 0;
ReadFile(hFile, lpBase, size, &RealRead,NULL);

//解析PE文件
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBase;
PIMAGE_NT_HEADERS32 pNT = (PIMAGE_NT_HEADERS32)(lpBase + pDos->e_lfanew);

//得到重定位表的RVA
DWORD relocationRVA = pNT->OptionalHeader.DataDirectory[5].VirtualAddress;
//得到重定位表的FOA
DWORD relocationFOA = RVAtoFoa(pNT, relocationRVA);
//得到在当前内存中的位置
auto pReloc = (PIMAGE_BASE_RELOCATION)(relocationFOA + lpBase);


//遍历
int index = 1;
while (pReloc->SizeOfBlock != 0)
{
//输出当前页的RVA
printf("%d ,%08X\n", index,pReloc->VirtualAddress);
//当前页有多少个需要重定位的数据
int count = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
//
TyepOffset* item = (TyepOffset*)(pReloc + 1);
for (int i = 0;i<count;i++)
{
//判断重定位的类型是否是我们想修复的类型
if (item[i].type == 3)
{
//得到一个基于VirtualAddress的偏移
DWORD offset = item[i].offset;
//得到这一项的foa
DWORD itemFOA = RVAtoFoa(pNT, pReloc->VirtualAddress + offset);
//得到需要重定位数据的地址
DWORD addr = *(DWORD*)(itemFOA + lpBase);
//修复
// newaddr = addr - imagebase + newImaegBase
DWORD newAddr = addr - pNT->OptionalHeader.ImageBase + 0x500000;
//写回内存中才能完成修复
*(DWORD*)(itemFOA + lpBase) = newAddr;

}
}
//下一个重定位页
index++;
pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock);
}
//修改默认加载基址
pNT->OptionalHeader.ImageBase = 0x500000;
//写回文件中
HANDLE hNewFIle = CreateFileA("New123.exe", GENERIC_ALL, FILE_SHARE_READ, NULL,
CREATE_ALWAYS, NULL, NULL);
if (INVALID_HANDLE_VALUE == hNewFIle)
{
printf("创建文件失败");
return -1;
}
WriteFile(hNewFIle, lpBase, size, &RealRead,NULL);


//关闭文件
CloseHandle(hNewFIle);
CloseHandle(hFile);
}

资源表

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// win原理Day003.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
//1 把文件读到内存中
char* ReadFileToMemory(char* pFilePath)
{
//1 获取文件句柄
HANDLE hFile = CreateFileA(pFilePath,
GENERIC_READ | GENERIC_WRITE,
FALSE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("文件打开失败\n");
return 0;
}
//2.获取文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);
//3.申请内存空间
char* pBuf = new char[dwFileSize]{};
if (!pBuf)
{
CloseHandle(hFile);
printf("内存申请失败\n");
return 0;
}
//4.读取文件内容到内存空间
DWORD dwRead;
ReadFile(hFile, pBuf, dwFileSize, &dwRead, NULL);
//5. 返回内存地址
return pBuf;
}
//2 是否是PE文件
bool IsPeFile(char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE)
{
printf("不是PE文件\n");
return false;
}
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)
(pDos->e_lfanew + pBuf);
if (pNt->Signature != IMAGE_NT_SIGNATURE)
{
printf("不是PE文件\n");
return false;
}
return true;
}

//RVA to FOA
DWORD RVAtoFOA(DWORD dwRVA, char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pBuf);
//区段个数(文件头第二个字段)
DWORD dwCount = pNt->FileHeader.NumberOfSections;
//区段首地址(IMAGE_FIRST_SECTION)
PIMAGE_SECTION_HEADER pSec = IMAGE_FIRST_SECTION(pNt);

for (DWORD i = 0; i < dwCount; i++)
{
//FOA = RVA - 内存中区段首地址 + 文件中区段首地址
if (dwRVA >= pSec->VirtualAddress &&
dwRVA < pSec->VirtualAddress + pSec->SizeOfRawData)
{
return dwRVA - pSec->VirtualAddress + pSec->PointerToRawData;
}
//下一个区段表
pSec++;
}
return 0;
}
/*
//低16位是其ID
char* arryResType[] = { "", "鼠标指针(Cursor)", "位图(Bitmap)", "图标(Icon)", "菜单(Menu)"
, "对话框(Dialog)", "字符串列表(String Table)", "字体目录(Font Directory)", "字体(Font)", "快捷键(Accelerators)"
, "非格式化资源(Unformatted)", "消息列表(Message Table)", "鼠标指针组(Croup Cursor)", "", "图标组(Group Icon)", ""
, "版本信息(Version Information)" };
*/

//解析资源表
void ShowResouceTable(char* pBuf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pBuf);
//找到资源表(数据目录表的第三项)
DWORD dwResRVA = pNt->OptionalHeader.DataDirectory[2].VirtualAddress;
//资源表在文件中的具体文件地址
DWORD dwResRoot = (DWORD)(RVAtoFOA(dwResRVA, pBuf) + pBuf);
//开始解析
//第一层地址
PIMAGE_RESOURCE_DIRECTORY pRes1 = (PIMAGE_RESOURCE_DIRECTORY)dwResRoot;
//第一层总个数
DWORD dwCount_1 = pRes1->NumberOfIdEntries + pRes1->NumberOfNamedEntries;
//第一层资源项的起始地址
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry1 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes1 + 1);
for (DWORD i = 0; i < dwCount_1; i++)
{
//判断资源命名方式
if (pEntry1->NameIsString)
{
//字符串命名
PIMAGE_RESOURCE_DIR_STRING_U pName =
(PIMAGE_RESOURCE_DIR_STRING_U)
(pEntry1->NameOffset + dwResRoot);
//资源字符是WCHAR类型
//在文件中并不是以0为结尾存储的
WCHAR * wcName = new WCHAR[pName->Length+1]{};
memcpy(wcName, pName->NameString, pName->Length * 2);
printf("资源种类名称:【%S】\n", wcName);
delete wcName;
}
else
{
//ID命名
//低16位是其ID
char* arryResType[] = { "", "鼠标指针(Cursor)", "位图(Bitmap)", "图标(Icon)", "菜单(Menu)"
, "对话框(Dialog)", "字符串列表(String Table)", "字体目录(Font Directory)", "字体(Font)", "快捷键(Accelerators)"
, "非格式化资源(Unformatted)", "消息列表(Message Table)", "鼠标指针组(Croup Cursor)", "", "图标组(Group Icon)", ""
, "版本信息(Version Information)" };
if (pEntry1->Id > 17)
{
printf("资源种类ID:【%d】\n", pEntry1->Id);
}
else
{
printf("资源种类ID:【%s】\n", arryResType[pEntry1->Id]);
}
}
//判断是否有下一层
if (pEntry1->DataIsDirectory)
{
//第二层起始地址
PIMAGE_RESOURCE_DIRECTORY pRes2 =
(PIMAGE_RESOURCE_DIRECTORY)
(pEntry1->OffsetToDirectory + dwResRoot);
//第二层资源个数
DWORD dwCount2 = pRes2->NumberOfIdEntries + pRes2->NumberOfNamedEntries;
//第二层资源项的起始地址
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry2 =
(PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes2 + 1);
for (DWORD i2 = 0; i2 < dwCount2; i2++)
{
//判断资源命名方式
if (pEntry2->NameIsString)
{
//字符串命名
PIMAGE_RESOURCE_DIR_STRING_U pName =
(PIMAGE_RESOURCE_DIR_STRING_U)
(pEntry2->NameOffset + dwResRoot);
//资源字符是WCHAR类型
WCHAR * wcName = new WCHAR[pName->Length + 1]{};
memcpy(wcName, pName->NameString, pName->Length * 2);
printf("\t资源名称:【%S】\n", wcName);
delete wcName;
}
else
{
//ID命名
printf("\t资源ID:【%d】\n", pEntry2->Id);
}

//判断是否有下一层
if (pEntry2->DataIsDirectory)
{
//第三层
PIMAGE_RESOURCE_DIRECTORY pRes3 =
(PIMAGE_RESOURCE_DIRECTORY)
(pEntry2->OffsetToDirectory + dwResRoot);
//资源个数
DWORD dwCount3 = pRes3->NumberOfIdEntries + pRes3->NumberOfNamedEntries;
PIMAGE_RESOURCE_DIRECTORY_ENTRY pEntry3 = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(pRes3 + 1);

//资源类别(英文的或者中文的等等)
//pEntry3->Name;
PIMAGE_RESOURCE_DATA_ENTRY pData = (PIMAGE_RESOURCE_DATA_ENTRY)(pEntry3->OffsetToData + dwResRoot);
//资源的数据大小
DWORD dwDataSize = pData->Size;
//pData->OffsetToData,资源数据的RVA
DWORD dwDataFOA = RVAtoFOA(pData->OffsetToData, pBuf);
PBYTE pByteData = (PBYTE)(dwDataFOA + pBuf);
//这里输出前10个字节
printf("\t\t");
for (int n = 0; n < 10; n++)
{
printf("%02X ", pByteData[n]);
}
printf("\n");
}
//下一个资源
pEntry2++;
}
}
//下一种资源
pEntry1++;
}

}

int _tmain(int argc, _TCHAR* argv[])
{
char* pBuf = ReadFileToMemory("123.exe");
if (IsPeFile(pBuf))
{
ShowResouceTable(pBuf);
}
//释放内存
delete pBuf;
return 0;
}