手把手教你手撕NTFS文件系统

Markdown小崩修不好,凑合着看。

目标:定位H:\NtfsTest\520.exe。

第一步:计算$MFT偏移地址

NTFS数据存放格式为:引导扇区DBR、主文件表$MFT、系统文件、数据区。

看到全映像开头,各字段含义为:

偏移 长度(字节) 含义
0x00 3 跳转指令
0x03 8 “NTFS”
0x0B 2 每个扇区大小(字节)
0x0D 1 簇大小
0x0E 2 保留
0x10 3 0
0x13 1
0x14 2 每磁头扇区数
0x16 2 每柱面磁头数
0x18 2 MBR到DBE扇区总数
0x1A 2
0x1C 4
0x20 4 扇区总数/分区大小
0x24 4 $MFT开始簇号
0x28 8
0x30 8
0x38 8 $MFTMirr开始簇号
0x40 4 每个MFT记录簇数
0x44 4 每索引簇数
0x48 8 分区逻辑序列号

当0x0B处为0x0200、0x0D处为0x08时,$MFT开始的偏移地址为:
$$
0\mathrm x0C0000\times0\mathrm x08\times0\mathrm x0200=0\mathrm xC0000000
$$

第二步:找根目录的文件记录

从上面算出来的那个偏移开始,是各种NTFS元文件的文件记录:

序号 元文件 功能
0 $MFT 主文件表
1 $MFTMirr 主文件表部分镜像
2 $LogFile 事务型日志文件
3 $Volume 卷文件(卷标等)
4 $AttrDef 属性定义列表文件
5 $Root 根目录文件
6 $Bitmap 位图文件(分区簇的使用情况)
7 $Boot 引导文件
8 $BadClus 坏簇列表文件
9 $Quota 磁盘配额文件
10 $Secure 安全文件
11 $UpCase 大小写字符转换文件
12 $Extend Metadata Directory 扩展元数据目录
13 $Extend\$Reparse 重解析点文件
14 $Extend\$UsnJrnl 加密日志文件
15 $Extend\$Quota 配额管理文件
16 $Extend\$ObjId 对象ID文件

每个文件记录占俩扇区,找$Root文件属性的偏移地址:
$$
0\mathrm xC0000000+0\mathrm x05\times0\mathrm x02\times0\mathrm x0200=0\mathrm xC0001400
$$

第三步:读取NtfsTest目录索引项

先讲一些基本结构。

文件记录头结构:

偏移 长度 描述
0x00 4 “FILE”
0x04 2 更新序列号偏移
0x06 2 更新序列号和数组
0x08 2 日志文件序列号
0x10 8 序列号,记录文件重复使用次数
0x12 2 硬链接数
0x14 2 第一个属性偏移地址
0x16 2 标志 1使用中 2目录
0x18 4 文件记录实际大小
0x1C 4 文件记录分配的大小
0x20 8 对应基本文件记录的文件参考号
0x28 2 下一个自由ID号
0x2A 2 边界 本记录使用的最后两个扇区最后两字节
0x2C 4 本MFT记录号

文件记录中常用属性分为三种:“80H”属性为文件数据属性,“90H”属性为索引根属性,“A0H”是索引分配属性。

常驻属性头结构:

偏移 长度 含义
0x00 4 属性类型
0x04 4 属性长度,$8$的倍数
0x08 1 00常驻属性 01非常驻属性
0x09 1 属性名长
0x0A 2 属性值开始偏移
0x0C 2 标志
0x0E 2 标识
0x10 4 属性长度
0x14 2 属性体开始位置
0x16 1 索引标志
0x17 1 填充

非常驻属性头结构:

偏移 长度 含义
0x00 4 属性类型
0x04 4 属性长度
0x08 1 00常驻属性 01非常驻属性
0x09 1 属性名长
0x0A 2 属性名开始偏移
0x0C 2 压缩、加密、稀疏标志
0x0D 2 属性ID
0x10 8 起始虚拟簇号VCN
0x18 8 结束虚拟簇号VCN
0x20 2 Data Run偏移 通常0x48
0x22 2 压缩单位大小 $2^N$
0x24 4
0x28 8 属性分配大小
0x30 8 属性实际大小
0x38 8 属性原始大小
0x48 Data Run数据

数据运行(Data Run)列表构造:

偏移 大小 含义
0x00 0.5 文件内容起始簇号占用的字节数
0.5 文件内容簇数占用的字节数
0x01 2 文件内容簇数
0x03 3 文件内容起始簇号

“90H”属性是索引根属性,是实现NTFS的B+树索引的根节点,结构分别为:标准属性头、索引根、索引头、索引项。

索引根结构为:

偏移 长度 含义
0x00 4 属性类型
0x04 4 排序规则
0x08 4 索引项分配大小
0x0C 1 每个索引记录簇数
0x0D 3

索引头结构为:

偏移 长度 含义
0x00 4 第一个索引项偏移
0x04 4 索引项总大小
0x08 4 索引项分配的大小
0x0C 1 标志
0x0D 3

索引项字段结构为:

偏移 长度 含义
0x00 6 文件MFT参考号
0x08 2 索引项大小
0x0A 2 文件名偏移
0x0C 2 索引标志
0x0E 2
0x10 8 父目录MFT参考号
0x18 8 文件创建时间
0x20 8 最后修改时间
0x28 8 文件记录最后修改时间
0x30 8 最后访问时间
0x38 8 文件分配大小
0x40 8 文件实际大小
0x48 8 文件标志
0x50 1 文件名长度
0x51 1 文件命名空间
0x52 2F 文件名

“A0H”是索引分配属性,为一个索引基本结构,存储组成索引B+树目录索引子节点定位信息,结构有标准索引头和各个索引项。

标准索引头结构:

偏移 长度 含义
0x00 4 “INDX”
0x04 2 更新序列号偏移
0x06 2 更新序列号与数组 单位字
0x08 8 日志文件序列号
0x10 8 本索引缓存在索引分配中VCN
0x18 4 索引项偏移
0x1C 4 索引项大小
0x20 4 索引项分配的大小
0x24 1 非叶子节点为1
0x25 3
0x28 2 更新序列
0x2A 2S-2 更新序列数组

各索引项结构:

偏移 长度 含义
0x00 6 文件MFT参考号
0x08 2 索引项大小
0x0A 2 文件名偏移
0x0C 2 索引标志
0x0E 2
0x10 8 父目录MFT文件参考号
0x18 8 文件创建时间
0x20 8 最后修改时间
0x28 8 文件记录最后修改时间
0x30 8 最后访问时间
0x38 8 文件分配大小
0x40 8 文件实际大小
0x48 8 文件标志
0x50 1 文件名长度
0x51 1 文件命名空间
0x52 2F 文件名

开始干正事儿。

定位到“A0H”属性,在0x20处获取Data Run偏移地址,再获取Data Run数据,例如数据大小为0x01个簇、起始簇号0x2C(偏移地址在0x2C000)。跳转到0x2C000处找到偏移0x18获取文件名称偏移地址,去看看名称是否为“NtfsTest”文件夹,不是再获取下一索引项偏移地址。如果是,获取文件$MFT参考号,例如0x58E0,则偏移地址为:
$$
0\mathrm xC0000000+0\mathrm x58E0\times2\times0\mathrm x2000=0\mathrm xC1638000
$$
跳转到这个地址,依次查找“80H”、“90H”和“A0H”等属性,在“90H”属性索引项中获取520.exe文件名,得到该文件的$MFT参考号例如为0x5A0B,则520.exe偏移地址为:
$$
0\mathrm xC0000000+0\mathrm x5A0B\times2\times0\mathrm x0200=0\mathrm xC1682C00
$$
这个地址即为520.exe的文件记录,找到“80H”属性,在0x20偏移处获取Data Run偏移0x40,在偏移0x40处获取Data Run,例如Data Run数据大小为0x2E42簇、起始簇号为0x6485C7,偏移地址:
$$
0\mathrm x6485C7\times0\mathrm x08\times0\mathrm x0200=0\mathrm x6485C7000
$$
这里储存着H:\NtfsTest\520.exe的数据。

实例

全篇代码是ANSI编码的…自己改成Unicode吧。

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#include <Windows.h>
#include <tchar.h>
#include <cstdio>
/*
定位文件路径:F:\test3\iujklm684\test1.txt

1. 读取0x0B开始的2个字节,表示每个扇区的大小:0x200
2. 读取0x0D开始的1个字节,表示每个簇由几个扇区组成:0x8
3. 读取0x30开始的8个字节,表示$MFT的起始簇号:0xc0000

可以根据以上信息计算得出,$MFT元文件的偏移是:
0xc0000 * 0x8 * 0x200 = 0xc0000000

4. 每个文件目录的大小规定是1KB(0x400),即1024字节,而且
根目录与$MFT元文件偏移5个文件,即5号文件目录

可以根据以上信息计算得出,根目录的偏移是:
0xc0000000 + 0x400 * 5 = 0xc0001400

5. 由于文件目录的深度和存储数据的大小不同,获取定位使用的
属性也不同,要注意0x80、0x90、0xA0这三个属性;
6. 在元文件的目录头,偏移0x14开始的两个字节,是该目录第
一个属性的偏移;
7. 在属性中,前4个字节,表示属性;偏移0x4开始的4个字节,是该属性的大小

根据以上,我们可以遍历各个文件目录中的各个属性,
以此来根据不同的属性做出不同的处理

8. 属性读取的优先级是:0xA0 > 0x90 > 0x80;

0xA0读取最后8字节,
Data Run List:11 01 2c 00 00 00 00 00
(PS:Data Run可以有多个,但是记住之后的Data Run要加上之前的Data的簇大小偏移!!!)
上面的Data Run List表示数据的逻辑簇号是用1字节表示:2c
数据的长度是用1字节表示:01
计算出逻辑簇号0x2c的偏移:
0x2c * 0x8 * 0x200 = 0x2c000
来到INDX索引中,搜索UNICODE字符串,找到后,往上数5行,减去0x50得到文件号!
所在行的地址是:0x2c7c8,往上数5行后,0x2c7c8 - 0x50 = 0x2c778
读取0x2c778开始的6个字节,即可获得文件号,文件号都是以$MFT为开始的文件号,
每个文件大小都是1KB(0x400)
文件号是:2e 00 00 00 00 00
对应得文件或目录(test3目录)的偏移是:
0xc0000000 + 0x2e * 0x400 = 0xc000b800
来到新的目录下了,重复步骤8的操作;

这次读取0x90的属性,先扫描一边0x90中的常驻数据是否有我们要搜索的
文件或目录,使用UNICODE来搜索即可,若有,继续往上数5行,读取文件号即可;
若没有,则需要读取最后8字节的Data Run,继续在索引中去UNICODE搜索,重复操作;

扫描了一下,发现“iujklm684”UNICODE在0x90的属性中出现了,于是往上数5行,
获取文件号为:2f 00 00 00 00 00
计算文件的偏移:
0xc0000000 + 0x2f * 0x400 = 0xc000bc00
来到了新的目录下,继续重读步骤8的操作;

在0x90的属性中,搜索到“test1.txt”UNICODE,往上数5行,
读取前6字节,获取文件号是:35 00 00 00 00 00
计算文件的偏移:
0xc0000000 + 0x35 * 0x400 = 0xc000d400
来到了文件下了,可以根据0x30的属性来判断是否是目录还是文件!!!

9. 文件的数据存储在0x80的属性中,数据小的话,作为常驻数据直接在0x80属性中
获取;数据多的话,作为非常驻数据存储在Data Run的索引里!
*/
// 打开磁盘
BOOL OpenDisk(HANDLE& hFile, char* lpszFilePath) {
// 构造磁盘设备字符串
char szDrive[7] = { '\\', '\\', '.', '\\', 'C', ':', '\0' };
szDrive[4] = lpszFilePath[0];
// 打开磁盘
hFile = ::CreateFile(szDrive, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE == hFile) {
char szErr[MAX_PATH] = { 0 };
::wsprintf(szErr, "Create File Error!\nError Code Is:%d\n", ::GetLastError());
printf("%s", szErr);
system("pause");
return FALSE;
};
return TRUE;
};
// 从DBR中获取数据:每个扇区字节数、每个簇的扇区数、原文件$MFT的起始簇号
BOOL GetDataFromDBR(HANDLE hFile, WORD& wSizeOfSector, BYTE& bSizeOfCluster, LARGE_INTEGER& liClusterNumberOfMFT) {
// 获取扇区大小(2)、簇大小(1)、$MFT起始簇号(8)
BYTE bBuffer[512] = { 0 };
DWORD dwRead = 0;
// 注意:数据读取的大小最小单位是扇区!!!
::SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
::ReadFile(hFile, bBuffer, 512, &dwRead, NULL);
wSizeOfSector = MAKEWORD(bBuffer[0x0B], bBuffer[0x0C]);
bSizeOfCluster = bBuffer[0x0D];
liClusterNumberOfMFT.LowPart = MAKELONG(MAKEWORD(bBuffer[0x30], bBuffer[0x31]), MAKEWORD(bBuffer[0x32], bBuffer[0x33]));
liClusterNumberOfMFT.HighPart = MAKELONG(MAKEWORD(bBuffer[0x34], bBuffer[0x35]), MAKEWORD(bBuffer[0x36], bBuffer[0x37]));
return TRUE;
};
// 内存匹配
BOOL CompareMemory(BYTE* lpSrc, BYTE* lpDst, DWORD dwLen) {
if (0 == _memicmp(lpSrc, lpDst, dwLen))
return TRUE;
else {
// 此方法仅用于6个字符以上
if (12 >= dwLen)
return FALSE;
// 判断前后两个字符是否匹配
if ((lpSrc[0] != lpDst[0]) || (lpSrc[1] != lpDst[1]) || (lpSrc[dwLen - 2] != lpDst[dwLen - 2]) || (lpSrc[dwLen - 1] != lpDst[dwLen - 1]))
return FALSE;
// 前后字符匹配后,只允许2个字符不一样
DWORD dwCount = 0;
for (DWORD i = 0; i < dwLen; i++)
if (lpSrc[i] == lpDst[i])
dwCount++;
if (2 < (dwLen - dwCount))
return FALSE;
};
return TRUE;
};
// 0x90属性的处理
BOOL HandleAttribute_90(BYTE* lpBuffer, WORD wAttributeOffset, BYTE* lpUnicode, DWORD dwLen, LARGE_INTEGER liMFTOffset, LARGE_INTEGER& liRootOffset) {
// 先遍历判断0x90属性里是否有此目录或文件(UNICODE)
// 获取当前属性的大小
DWORD dwSizeOfAttribute = MAKELONG(MAKEWORD(lpBuffer[wAttributeOffset + 4], lpBuffer[wAttributeOffset + 5]), MAKEWORD(lpBuffer[wAttributeOffset + 6], lpBuffer[wAttributeOffset + 7]));
for (DWORD i = 0; i < dwSizeOfAttribute; i++)
if (CompareMemory(lpUnicode, (lpBuffer + wAttributeOffset + i), 2 * dwLen)) {
DWORD dwNameOffset = wAttributeOffset + i;
// 计算文件号
dwNameOffset = dwNameOffset / 8;
dwNameOffset = 8 * dwNameOffset;
dwNameOffset = dwNameOffset - 0x50;
// 获取文件号(6)
LARGE_INTEGER liNumberOfFile;
liNumberOfFile.LowPart = MAKELONG(MAKEWORD(lpBuffer[dwNameOffset], lpBuffer[dwNameOffset + 1]), MAKEWORD(lpBuffer[dwNameOffset + 2], lpBuffer[dwNameOffset + 3]));
liNumberOfFile.HighPart = MAKELONG(MAKEWORD(lpBuffer[dwNameOffset + 4], lpBuffer[dwNameOffset + 5]), 0);
// 计算文件号的偏移,文件号是相对$MFT为偏移说的
liRootOffset = liNumberOfFile;
liRootOffset.QuadPart = liMFTOffset.QuadPart + liRootOffset.QuadPart * 0x400;
return TRUE;
};
// 读取Data Run List,去到索引处INDX遍历UNICODE,获取文件号
return FALSE;
};
// 0xA0属性的处理
BOOL HandleAttribute_A0(HANDLE hFile, BYTE* lpBuffer, WORD wSizeOfSector, BYTE bSizeOfCluster, WORD wAttributeOffset, BYTE* lpUnicode, DWORD dwLen, LARGE_INTEGER liMFTOffset, LARGE_INTEGER& liRootOffset) {
// 读取Data Run List,去到索引处INDX遍历UNICODE,获取文件号
DWORD dwCount = 0;
DWORD dwClusterOffet = 0;
// 获取索引号的偏移
WORD wIndxOffset = MAKEWORD(lpBuffer[wAttributeOffset + 0x20], lpBuffer[wAttributeOffset + 0x21]);
// 读取Data Run List
while (TRUE) {
BYTE bTemp = lpBuffer[wAttributeOffset + wIndxOffset + dwCount];
// 读取Data Run List,分解并计算Data Run中的信息
BYTE bHi = bTemp >> 4;
BYTE bLo = bTemp & 0x0F;
if (0x0F == bHi || 0x0F == bLo || 0 == bHi || 0 == bLo)
break;
LARGE_INTEGER liDataRunSize, liDataRunOffset;
liDataRunSize.QuadPart = 0;
liDataRunOffset.QuadPart = 0;
for (DWORD i = bLo; i > 0; i--) {
liDataRunSize.QuadPart = liDataRunSize.QuadPart << 8;
liDataRunSize.QuadPart = liDataRunSize.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + i];
};
for (DWORD i = bHi; i > 0; i--) {
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
};
// 注意加上上一个Data Run的逻辑簇号
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + dwClusterOffet;
dwClusterOffet = dwClusterOffet + liDataRunOffset.LowPart;
// 去到索引处INDX遍历UNICODE,获取文件号
LARGE_INTEGER liIndxOffset, liIndxSize;
liIndxOffset.QuadPart = liDataRunOffset.QuadPart * bSizeOfCluster * wSizeOfSector;
liIndxSize.QuadPart = liDataRunSize.QuadPart * bSizeOfCluster * wSizeOfSector;
// 读取索引的数据,大小为1KB
BYTE* lpBuf = new BYTE[liIndxSize.QuadPart];
DWORD dwRead = 0;
::SetFilePointer(hFile, liIndxOffset.LowPart, &liIndxOffset.HighPart, FILE_BEGIN);
::ReadFile(hFile, lpBuf, liIndxSize.LowPart, &dwRead, NULL);
// 遍历Unicode数据
for (DWORD i = 0; i < liIndxSize.LowPart; i++)
if (CompareMemory(lpUnicode, (lpBuf + i), 2 * dwLen)) {
DWORD dwNameOffset = i;
// 计算文件号
dwNameOffset = dwNameOffset / 8;
dwNameOffset = 8 * dwNameOffset;
dwNameOffset = dwNameOffset - 0x50;
// 获取文件号(6)
LARGE_INTEGER liNumberOfFile;
liNumberOfFile.LowPart = MAKELONG(MAKEWORD(lpBuf[dwNameOffset], lpBuf[dwNameOffset + 1]), MAKEWORD(lpBuf[dwNameOffset + 2], lpBuf[dwNameOffset + 3]));
liNumberOfFile.HighPart = MAKELONG(MAKEWORD(lpBuf[dwNameOffset + 4], lpBuf[dwNameOffset + 5]), 0);
// 计算文件号的偏移,文件号是相对$MFT为偏移说的
liRootOffset = liNumberOfFile;
liRootOffset.QuadPart = liMFTOffset.QuadPart + liRootOffset.QuadPart * 0x400;
return TRUE;
};
delete[]lpBuf;
lpBuf = NULL;
// 计算下一个Data Run List偏移
dwCount = dwCount + bLo + bHi + 1;
};
return FALSE;
};
// 定位文件
BOOL LocationFile(HANDLE hFile, char* lpszFilePath, WORD wSizeOfSector, BYTE bSizeOfCluster, LARGE_INTEGER liMFTOffset, LARGE_INTEGER& liRootOffset) {
BYTE bBuffer[1024] = { 0 };
DWORD dwRead = 0;
// 分割文件路径
char szNewFile[MAX_PATH] = { 0 };
::lstrcpy(szNewFile, (lpszFilePath + 3));
char szDelim[] = "\\";
char* lpResult = strtok(szNewFile, szDelim);
BYTE bUnicode[MAX_PATH] = { 0 };
while (NULL != lpResult) {
BOOL bFlag = FALSE;
DWORD dwNameOffset = 0;
// 将分割的目录转换成2字节表示的Unicode数据
DWORD dwLen = ::lstrlen(lpResult);
::RtlZeroMemory(bUnicode, MAX_PATH);
for (DWORD i = 0, j = 0; i < dwLen; i++) {
bUnicode[j++] = lpResult[i];
bUnicode[j++] = 0;
};
// 读取目录的数据,大小为1KB
::SetFilePointer(hFile, liRootOffset.LowPart, &liRootOffset.HighPart, FILE_BEGIN);
::ReadFile(hFile, bBuffer, 1024, &dwRead, NULL);
// 获取第一个属性的偏移
WORD wAttributeOffset MAKEWORD(bBuffer[0x14], bBuffer[0x15]);
// 遍历文件目录的属性
DWORD dwAttribute = 0;
DWORD dwSizeOfAttribute = 0;
while (TRUE) {
if (bFlag)
break;
// 获取当前属性
dwAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset], bBuffer[wAttributeOffset + 1]), MAKEWORD(bBuffer[wAttributeOffset + 2], bBuffer[wAttributeOffset + 3]));
// 判断属性
if (0x90 == dwAttribute)
bFlag = HandleAttribute_90(bBuffer, wAttributeOffset, bUnicode, dwLen, liMFTOffset, liRootOffset);
else if (0xA0 == dwAttribute)
bFlag = HandleAttribute_A0(hFile, bBuffer, wSizeOfSector, bSizeOfCluster, wAttributeOffset, bUnicode, dwLen, liMFTOffset, liRootOffset);
else if (0xFFFFFFFF == dwAttribute) {
bFlag = TRUE;
break;
};
// 获取当前属性的大小
dwSizeOfAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset + 4], bBuffer[wAttributeOffset + 5]), MAKEWORD(bBuffer[wAttributeOffset + 6], bBuffer[wAttributeOffset + 7]));
// 计算下一属性的偏移
wAttributeOffset = wAttributeOffset + dwSizeOfAttribute;
};
// 继续分割目录
lpResult = strtok(NULL, szDelim);
};
return TRUE;
};
// 读取文件内容偏移
BOOL FileContentOffset(HANDLE hFile, WORD wSizeOfSector, BYTE bSizeOfCluster, LARGE_INTEGER liMFTOffset, LARGE_INTEGER liRootOffset) {
BYTE bBuffer[1024] = { 0 };
DWORD dwRead = 0;
LARGE_INTEGER liContenOffset = liRootOffset;
// 读取目录的数据,大小为1KB
::SetFilePointer(hFile, liRootOffset.LowPart, &liRootOffset.HighPart, FILE_BEGIN);
::ReadFile(hFile, bBuffer, 1024, &dwRead, NULL);
// 获取第一个属性的偏移
WORD wAttributeOffset MAKEWORD(bBuffer[0x14], bBuffer[0x15]);
// 遍历文件目录的属性
DWORD dwAttribute = 0;
DWORD dwSizeOfAttribute = 0;
while (TRUE) {
// 获取当前属性
dwAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset], bBuffer[wAttributeOffset + 1]), MAKEWORD(bBuffer[wAttributeOffset + 2], bBuffer[wAttributeOffset + 3]));
// 判断属性
if (0x80 == dwAttribute) {
// 读取偏移0x8出1字节,判断是否是常驻属性
BYTE bFlag = bBuffer[wAttributeOffset + 0x8];
if (0 == bFlag) { // 常驻
// 读取偏移0x14出2字节,即是内容的偏移
WORD wContenOffset = MAKEWORD(bBuffer[wAttributeOffset + 0x14], bBuffer[wAttributeOffset + 0x15]);
liContenOffset.QuadPart = liContenOffset.QuadPart + wAttributeOffset + wContenOffset;
printf("File Content Offset:0x%llx\n\n", liContenOffset.QuadPart);
}
else { // 非常驻
// 读取偏移0x20出2字节,即是数据运行列表偏移
DWORD dwCount = 0;
DWORD dwClusterOffet = 0;
// 获取索引号的偏移
WORD wIndxOffset = MAKEWORD(bBuffer[wAttributeOffset + 0x20], bBuffer[wAttributeOffset + 0x21]);
// 读取Data Run List
while (TRUE) {
BYTE bTemp = bBuffer[wAttributeOffset + wIndxOffset + dwCount];
// 读取Data Run List,分解并计算Data Run中的信息
BYTE bHi = bTemp >> 4;
BYTE bLo = bTemp & 0x0F;
if (0x0F == bHi || 0x0F == bLo || 0 == bHi || 0 == bLo)
break;
LARGE_INTEGER liDataRunSize, liDataRunOffset;
liDataRunSize.QuadPart = 0;
liDataRunOffset.QuadPart = 0;
for (DWORD i = bLo; i > 0; i--) {
liDataRunSize.QuadPart = liDataRunSize.QuadPart << 8;
liDataRunSize.QuadPart = liDataRunSize.QuadPart | bBuffer[wAttributeOffset + wIndxOffset + dwCount + i];
};
for (DWORD i = bHi; i > 0; i--) {
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | bBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
};
// 注意加上上一个Data Run的逻辑簇号
liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + dwClusterOffet;
dwClusterOffet = dwClusterOffet + liDataRunOffset.LowPart;
// 显示逻辑簇号和大小
liContenOffset.QuadPart = liDataRunOffset.QuadPart * wSizeOfSector * bSizeOfCluster;
printf("File Content Offset:0x%llx\nFile Content Size:0x%llx\n", liContenOffset.QuadPart, (liDataRunSize.QuadPart * wSizeOfSector * bSizeOfCluster));
// 计算下一个Data Run List偏移
dwCount = dwCount + bLo + bHi + 1;
};
};
}
else if (0xFFFFFFFF == dwAttribute)
break;
// 获取当前属性的大小
dwSizeOfAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset + 4], bBuffer[wAttributeOffset + 5]), MAKEWORD(bBuffer[wAttributeOffset + 6], bBuffer[wAttributeOffset + 7]));
// 计算下一属性的偏移
wAttributeOffset = wAttributeOffset + dwSizeOfAttribute;
};
return TRUE;
};
int _tmain(int argc, _TCHAR* argv[]) {
// 输入路径
printf("Input The File Path:\n");
char szFilePath[MAX_PATH] = { 0 };
fgets(szFilePath,sizeof(szFilePath),stdin);
// 打开磁盘
HANDLE hFile = NULL;
if (!OpenDisk(hFile, szFilePath))
return 1;
// 获取扇区大小(2)、簇大小(1)、$MFT起始簇号(8)
WORD wSizeOfSector = 0;
BYTE bSizeOfCluster = 0;
LARGE_INTEGER liClusterNumberOfMFT;
GetDataFromDBR(hFile, wSizeOfSector, bSizeOfCluster, liClusterNumberOfMFT);
// 计算$MFT元文件的字节偏移
LARGE_INTEGER liMFTOffset;
liMFTOffset.QuadPart = liClusterNumberOfMFT.QuadPart * bSizeOfCluster * wSizeOfSector;
// 计算根目录,与$MFT相距5个目录,每个目录大小固定为1KB(0x400)
LARGE_INTEGER liRootOffset;
liRootOffset.QuadPart = liMFTOffset.QuadPart + 5 * 0x400;
// 文件定位
LocationFile(hFile, szFilePath, wSizeOfSector, bSizeOfCluster, liMFTOffset, liRootOffset);
// 显示逻辑字节偏移和文件号
printf("Location File:0x%llx\n", liRootOffset.QuadPart);
// 80H属性 获取文件数据内容偏移
FileContentOffset(hFile, wSizeOfSector, bSizeOfCluster, liMFTOffset, liRootOffset);
printf("\n");
// 关闭文件并退出
::CloseHandle(hFile);
system("pause");
return 0;
};