系列文章目录
整个专栏系列是根据GitHub开源项目NTFS-File-Search获取分区所有文件/目录列表的思路。
具体的如下:
- Qt/C++ 了解NTFS文件系统,了解MFT(Master File Table)主文件表(一)
介绍NTFS文件系统,对比通过MFT(Master File Table)主文件表获取数据的优劣,简单介绍开源项目NTFS-File-Search,以及了解借鉴NTFS系统中所参考的所有文章。- Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
读取Boot引导分区扇区数据,获取单个簇大小(4096字节),MFT元数据大小(1024字节)和MFT元数据的起始簇号,计算出MFT元数据在磁盘的偏移地址。- Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址
解析MFT元数据结构,根据MFT元数据文件记录头获取属性的偏移地址,用于后面解析0x80 $Data属性,获取Run List数据列表- Qt/C++ 了解NTFS文件系统,解析MFT主文件表中的常驻属性与非常驻属性
简单介绍$MFT元数据中的常驻属性与非常驻属性结构.- Qt/C++ 了解NTFS文件系统,解析0x80 $Data属性,获取Run List数据列表
根据0x80 Data属性,找到存放所有MFT元数据的区间列表(Run List数据列表)- **Qt/C++ 了解NTFS文件系统,遍历Run Lists数据列表,读取0x30 FILE_NAME属性,获取所有文件/目录数据** 根据前面获取的 获取Run List数据列表, 遍历所有Run List数据列表读取所有MFT元数据,并解析 FILE_NAME属性和STANDARD_INFORMATION属性获取文件或目录的实际大小,名称,磁盘分配大小,记录号 ,文件属性等信息
目录导读
- 系列文章目录
- 前言
- [遍历Run Lists数据列表](#遍历Run Lists数据列表)
- [MFT元数据 属性结构解析](#MFT元数据 属性结构解析)
-
- [0x10 $STANDARD_INFORMATION属性](#0x10 $STANDARD_INFORMATION属性)
- [0x30 $FILE_NAME属性](#0x30 $FILE_NAME属性)
- [获取文件/目录相关内容 代码示例](#获取文件/目录相关内容 代码示例)
- 总结
前言
根据前面获取的 获取Run List数据列表 遍历读取所有MFT元数据,再根据0x30 $FILE_NAME属性 和0x10 $STANDARD_INFORMATION属性获取NTFS系统中的所有文件和目录数据。
遍历Run Lists数据列表
以开源项目NTFS-File-Search中的方法为例:
已知一个簇包含8个扇区,每个扇区512字节,
一个簇8*512=4096个字节,
每个MFT表元数据1024字节(前面根据$boot表得到的数据);
一个簇包含4个MFT元数据
遍历每个Run Lists数据:
定义每次读取 #define FILE_RECORDS_PER_FILE_BUF 65536
个簇 的磁盘数据,不足 65536个簇 的读取剩余字节数据,并按 一个MFT元数据大小(1024字节) 循环解析读取的磁盘数据。
直到读取完这一个Run List(MFT_DATARUN )数据
再读取下一个Run List(MFT_DATARUN )数据,
直到所有数据全部读取完.
- 代码示例
m_ullRecordSize ☞每个MFT表大小 - 1024字节
m_ullClusterSize☞ 每个簇大小 -8*512字节
cpp
//! 开始读取每个文件记录
DWORD cbFileRecordBuffer = FILE_RECORDS_PER_FILE_BUF * m_ullRecordSize;
UINT64 ullVolumeOffset=0;
pbFileRecordBuffer=new BYTE[cbFileRecordBuffer];
for (size_t i = 0; i < m_rgDataRuns.size(); ++i)
{
//UINT64 nRemainingFiles = RunLength * m_pVolume->ClusterSize() / m_pVolume->RecordSize();
//! 当前run list 列表长度
VCN_t RunLength = m_rgDataRuns[i].Length;
//! 偏移量 -以簇为单位
ullVolumeOffset += m_rgDataRuns[i].Offset;
qDebug()<<"";
qDebug()<<"[i] "<<QString::number(i,10)<<" [Offset] "<<QString::number(ullVolumeOffset,10)<<" [Length] "<<QString::number(m_rgDataRuns[i].Length,10);
/* Read the files in chunks */
//! 读取(当前run list 列表长度* 簇大小)字节后结束
for (UINT64 nChunkOffset = 0; nChunkOffset < RunLength * m_ullClusterSize;)
{
/* Determine the number of files to read */
//! 每次读取的大小 -固定65536* 4096字节大小
UINT64 cbReadLength = cbFileRecordBuffer;
if (RunLength * m_ullClusterSize - nChunkOffset < cbReadLength) {
//! 读取几次固定65536* 4096字节后,剩下的数据
cbReadLength = RunLength * m_ullClusterSize - nChunkOffset;
}
qDebug()<<"[cbReadLength]: "<<QString::number(cbReadLength,16)<<" -> "<<QString::number(cbReadLength,10);
/* Read the file records from the volume */
//! ullVolumeOffset * m_ullClusterSize + nChunkOffset 相对偏移量
//! 开始读取磁盘数据
DWORD cbBytesRead = ReadBytes(m_hVolume,pbFileRecordBuffer, cbReadLength, ullVolumeOffset * m_ullClusterSize + nChunkOffset);
if (!cbBytesRead) {
break;
}
//! 计算此次读取了多少字节,算作偏移量
nChunkOffset += cbReadLength;
//! 解析MFT元数据
ParseRecordChunk(pbFileRecordBuffer,cbReadLength);
}
}
MFT元数据 属性结构解析
参考 :
NTFS文件系统详解(三)之NTFS元文件解析 : 分析30H属性和
属性-$文件名(0x30)
两篇文章,解析 STANDARD_INFORMATION** 属性和 **FILE_NAME属性获取文件/目录相关内容
0x10 $STANDARD_INFORMATION属性
0x10 $STANDARD_INFORMATION 属性存储标准信息,包括一些基本文件属性,如只读,系统,存档;时间属性,如文件的创建和最后修改时间;有多少目录指向该文件(即其硬链接)
在旧版本的NTFS中,这个属性只包含DOS文件权限和文件时间。
Windows 2000引入了四个新字段,用于引用配额、安全性、文件大小和日志记录信息。
如$AttrDef,此属性的中所定义,最小大小为48字节,最大大小为72字节。
- 数据结构:
- C++结构体声明示例:
cpp
/*
* $MFT Standard Information Attribute - $MFT标准信息属性
* http://inform.pucp.edu.pe/~inf232/Ntfs/ntfs_doc_v0.5/attributes/standard_information.html
* https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html
* 部分字段不一致,但是属性前面字段是一样的。
*/
typedef struct MFT_STANDARD_INFORMATION_ATTRIBUTE_HDR
{
//! 8字节
LONGLONG CreationTime; //C Time - File Creation
//! 8字节
LONGLONG ChangeTime; //A Time - File Altered
//! 8字节
LONGLONG LastWriteTime; //M Time - MFT Changed
//! 8字节
LONGLONG LastAccessTime; //R Time - File Read
//! 4字节
ULONG FileAttributes; //DOS File Permissions(Also called attributes in DOS terminology.)
//! 4字节
ULONG Unknown[3];
//! 4字节
ULONG QuotaId;
//! 4字节
ULONG SecurityId;
//! 8字节
ULONGLONG QuotaChange;
//! 8字节
USN Usn;
}*PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR;
偏移0×20处的文件属性解释如下:
标志 | 常用字母缩写 (WizTree软件中的文件属性就是这个缩写) | 名称 | 描述 |
---|---|---|---|
0x0001 | R | 只读(Read-only) | 表示文件只能被读取,不能被修改或删除。 |
0x0002 | H | 隐藏(Hidden) | 表示文件或文件夹是隐藏的,通常不会在文件浏览器中显示。 |
0x0004 | S | 系统(System) | 表示文件或文件夹是系统文件或系统文件夹,这些通常是操作系统的一部分。 |
0x0020 | A | 存档(Archive) | 表示文件可以被备份程序标记为需要备份 |
0x0040 | 设备文件 | 设备文件 | |
0x0080 | 常规文件 | 常规文件 | |
0x0100 | 临时文件 | 临时文件 | |
0x0200 | L | 稀疏文件(Sparse File) | 表示文件具有稀疏性,即文件中存在大量的空白区域,这些空白区域在磁盘上并不实际占用空间。 |
0x0400 | O | 重解析点(Reparse Point) | 表示文件是一个重解析点,如符号链接或快捷方式等。 |
0x0800 | C | 压缩(Compressed) | 表示文件已经被压缩以节省磁盘空间。 |
0x1000 | 脱机文件 | 脱机文件 | |
0x2000 | I | 未编入索引 孤立(Indexed) | 表示文件不包含任何索引信息,主要用于NTFS文件系统中,影响文件的搜索速度。 |
0x4000 | E | 加密(Encrypted) | 表示文件使用了EFS(加密文件系统)进行了加密。 |
- 文件属性宏定义示例:
// MFT File 属性- $STANDARD_INFORMATION (MFT_FILERECORD_ATTR_STANDARD_INFO)
// https://flatcap.github.io/linux-ntfs/ntfs/attributes/standard_information.html
#define MFT_STANDARD_INFORMATION_ATTR_R 0x0001
#define MFT_STANDARD_INFORMATION_ATTR_H 0x0002
#define MFT_STANDARD_INFORMATION_ATTR_S 0x0004
#define MFT_STANDARD_INFORMATION_ATTR_A 0x0020
#define MFT_STANDARD_INFORMATION_ATTR_L 0x0200
#define MFT_STANDARD_INFORMATION_ATTR_O 0x0400
#define MFT_STANDARD_INFORMATION_ATTR_C 0x0800
#define MFT_STANDARD_INFORMATION_ATTR_I 0x2000
#define MFT_STANDARD_INFORMATION_ATTR_E 0x4000
0x30 $FILE_NAME属性
0x30 $FILE_NAME 属性 用于存储文件名 ,用Unicode字符表示的文件名,由于MS DOS 不能识别长文件名,所以NTFS系统会自动生成一个8.3文件名,
并且始终是常驻属性 。
正如在$AttrDef中定义的那样,这个属性的最小大小为68字节,最大大小为578字节。这相当于文件名的最大长度为255个Unicode字符。
- 数据结构:
- C++ 结构体的定义:
cpp
/*
* $FILE_NAME Attribute Header Layout - 属性头布局
* https://flatcap.github.io/linux-ntfs/ntfs/attributes/file_name.html
*/
typedef struct MFT_FILENAME_ATTRIBUTE_HDR
{
MFT_FILE_ID ParentReference; // File reference to the parent directory -对父目录的文件引用
ULONGLONG CreationTime; // File creation time - 文件创建时间
ULONGLONG ModiciationTime; // File altered time - 文件修改时间
ULONGLONG MFTTime; // MFT changed time - MFT改变时间
ULONGLONG ReadTime; // File read time - 文件读取时间
ULONGLONG AllocatedSize; // Allocated size of the file -已分配的文件大小
ULONGLONG RealSize; // Real size of the file - 文件的实际大小
DWORD Flags; // Flags - 标志
DWORD ER;
BYTE NameLength; // Filename length in characters - 文件名长度(以字符为单位)
BYTE NameSpaceType; // File namespace type - 文件命名空间类型
WCHAR Name[1]; // Filename - 文件名
}*PMFT_FILENAME_ATTRIBUTE_HDR;
获取文件/目录相关内容 代码示例
参考开源项目NTFS-File-Search的示例:
在每次解析MFT元数据时,都需要先执行ApplyFixup 方法修正数据。
在开源项目NTFS-File-Search是直接声明了一整块内存供数据处理使用
我这里为了方便,每条数据都是单独处理的,由系统声明内存,尤其是文件名的读取直接复制的内存,
这里是将文件名同样保存到了两个字段。
cpp
//! 数据结构体
typedef struct NTFS_FILE_ENTRYW
{
qint64 AllocatedSize; // File size on disk (bytes)
qint64 RealSize; // File size on disk (bytes)
qint64 FileAttributes;
qint64 NextEntryOffset;
ULONGLONG FileUpdateTime;
qint64 NumberFolders; // 文件夹数量
qint64 NumberDocuments; // 文件数量
MFT_FILE_ID MFTFileId; // $MFT Record Number
UINT64 ParentDirectoryRecord; // Parent Directory $MFT-Record Number
BOOL IsDirectory;
LPWSTR Name; // filename/directory nane
LPWSTR lpszFileName; // Full path of file/directory
}*PNTFS_FILE_ENTRYW;
//按照1024字节解析 MFT元数据
void ParseRecordChunk(PBYTE pbRecordChunk, UINT64 cbRecordChunk)
{
for (UINT64 iRecord = 0; iRecord < cbRecordChunk / m_ullRecordSize; ++iRecord)
{
PMFT_FILE_RECORD_HEADER m_pFileRecord=POINTER_ADD(PMFT_FILE_RECORD_HEADER, pbRecordChunk, iRecord * m_ullRecordSize);
//! 文件是否在使用
if (!(m_pFileRecord->Flags & MFT_FILERECORD_FLAG_IN_USE))
{
continue;
}
ApplyFixup(m_pFileRecord);
RecordAttrMultiMap mpAttributes = GetAttributes(m_pFileRecord);
auto FileNamesList = mpAttributes.equal_range(MFT_FILERECORD_ATTR_FILENAME);
/* Find a valid (non-DOS file-name namespace) $FILE_NAME attribute */
for (auto i = FileNamesList.first; i != FileNamesList.second; ++i)
{
PMFT_FILENAME_ATTRIBUTE_HDR pFileName = POINTER_ADD(PMFT_FILENAME_ATTRIBUTE_HDR,
i->second,
i->second->Resdient.AttributeOffset
);
if (pFileName && pFileName->NameSpaceType != FILENAME_NAMESPACE_DOS)
{
NTFS_FILE_ENTRY FileEntry;
//
// Set the file entry info
//
FileEntry.AllocatedSize = pFileName->AllocatedSize;
FileEntry.RealSize = pFileName->RealSize;
FileEntry.NumberFolders = 0;
FileEntry.NumberDocuments = 0;
FileEntry.FileUpdateTime = pFileName->ModiciationTime;
FileEntry.FileAttributes = 0;
FileEntry.MFTFileId.MftRecordIndex = m_pFileRecord->RecordNumber;
FileEntry.MFTFileId.SequenceNumber = m_pFileRecord->SequenceNumber;
FileEntry.IsDirectory = (m_pFileRecord->Flags & MFT_FILERECORD_FLAG_IS_DIRECTORY) ? TRUE : FALSE;
FileEntry.ParentDirectoryRecord = pFileName->ParentReference.MftRecordIndex;
FileEntry.Name =new WCHAR[pFileName->NameLength];
CopyMemory(FileEntry.Name , pFileName->Name, pFileName->NameLength * sizeof(WCHAR));
FileEntry.Name[pFileName->NameLength] = L'\0';
FileEntry.lpszFileName =new WCHAR[pFileName->NameLength];
CopyMemory(FileEntry.lpszFileName , pFileName->Name, pFileName->NameLength * sizeof(WCHAR));
FileEntry.lpszFileName[pFileName->NameLength] = L'\0';
if (FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO))
{
PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR pStandardInfo = POINTER_ADD(
PMFT_STANDARD_INFORMATION_ATTRIBUTE_HDR,
FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO),
FindAttribute(m_pFileRecord,MFT_FILERECORD_ATTR_STANDARD_INFO)->Resdient.AttributeOffset
);
FileEntry.FileAttributes = pStandardInfo->FileAttributes;
}
/* Check if file should be skipped */
if (QString::fromWCharArray(FileEntry.lpszFileName)=="")
{
break;
}
/* Add entry to map */
if (FileEntry.IsDirectory) {
m_DirectoryMap[m_pFileRecord->RecordNumber] = FileEntry;
NumberFolders++;
}
else {
m_FileMap[m_pFileRecord->RecordNumber] = FileEntry;
NumberDocuments++;
}
break;
}
}
}
}
总结
通过开源项目NTFS-File-Search
获取到的数据会出现数据大小不一致,会多出/缺少部分文件的问题.
参考 「NTFS:让你的硬盘更安全、更高效!」NTFS文件系统详解,一文中遍历分区文件列表的思路,
发现是没有读取索引属性(IndexEntries列表 )不知道是不是这个原因,后面看NTFS-File-Search的源码作者会不会再次优化。
整个了解NTFS文件系统示例到此完毕。
后面还有不了解的内容建议直接参考开源NTFS-File-Search项目源码。