系列文章目录
目录导读
前言
根据前面获取的 获取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字节
//! 开始读取每个文件记录
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属性
- 数据结构:
- C++结构体声明示例:
/*
* $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处的文件属性解释如下:
- 文件属性宏定义示例:
0x30 $FILE_NAME属性
- 数据结构:
- C++ 结构体的定义:
/*
* $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;
获取文件/目录相关内容 代码示例
//! 数据结构体
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项目源码。