Таблица MFT
Об MFT слышал, наверное, каждый и информация по ней есть и у Руссиновича и в статьях, например у Касперски в «NTFS изнутри и снаружи», все же я продублирую и обобщу ее.
В NTFS ключевым местом для хранения информации о файлах является таблица MFT (Master File Table). В ней содержится информацию обо всех файлах в системе. MFT состоит из записей (mft record) фиксированного размера, размер записи указывается в параметре clusters_per_mft_record структуры бут сектора.
Ключевые моменты NTFS:
- Любой объект файловой системы представляется хотя бы одной записью в MFT и, по сути, является файлом. Например, бут сектор - файл $Boot, сам MFT – $Mft.
- Файл представляет собой набор атрибутов, в которых хранятся все данные. Например, атрибут $STANDART_INFORMATION хранит информацию о файле. Атрибутом также являются и данные. Если файл имеет альтернативные потоки (streams), то таких атрибутов несколько, по числу потоков.
- LCN – смещение в кластерах относительно тома, VCN – смещение в кластерах относительно файла. Для перевода LCN в смещение на томе следует использовать формулу offs = lcn * bytes_per_sector * sectors_per_cluster, соответствующие значения хранятся в BIOS_PARAMETER_BLOCK в бут секторе.
MFT состоит из заголовка записи MFT_RECORD, за которым следуют данные записи – атрибуты.
typedef struct _MFT_RECORD { /*0x00*/ ULONG signature; //сигнатура 'FILE' /*0x04*/ USHORT usa_offs; /*0x06*/ USHORT usa_count; /*0x08*/ ULARGE_INTEGER lsn; /*0x10*/ USHORT sequence_number; /*0x12*/ USHORT link_count; /*0x14*/ USHORT attrs_offset; /*0x16*/ USHORT flags;//флаги, см. MFT_RECORD_FLAGS /*0x18*/ ULONG bytes_in_use; /*0x1C*/ ULONG bytes_allocated; /*0x20*/ ULARGE_INTEGER base_mft_record; //адрес базовой MFT-записи /*0x28*/ USHORT next_attr_instance; /*0x2A*/ USHORT reserved; /*0x2C*/ ULONG mft_record_number; //size - 48 b } MFT_RECORD, *PMFT_RECORD;
Начало записи MFT идентифицируется сигнатурой “FILE”. Флаги указывают, является ли запись каталогом, или файлом и используется ли запись вообще.
typedef enum { MFT_RECORD_NOT_USED = 0, //запись не используется MFT_RECORD_IN_USE = 1, //запись используется MFT_RECORD_IS_DIRECTORY = 2 //запись описывает каталог } MFT_RECORD_FLAGS;
Если файловая запись используется и описывает каталог, тогда поле flags равно MFT_RECORD_IN_USE | MFT_RECORD_IS_DIRECTORY (3). Для файла, flags равен MFT_RECORD_IN_USE.
Записи адресуются через структуру struct MFT_REF { unsigned __int64 index : 48; //индекс элемента в таблице unsigned __int64 ordinal : 16; //порядковый номер };
Для адресации записей используется младшее поле index, а ordinal нужно для определения несоответствий внутри самой файловой системы.
Смещение первого элемента MFT (в кластерах), т. е. его начало хранится в поле mft_lcn. Размер записи хранится в поле clusters_per_mft_record. Особенность этого поля заключается в том, что оно может хранить отрицательное значение, в таком случае это указание на то, что размер записи задается не в кластерах, а в байтах, причем задается не количество байт, а отрицательное значение степени двойки (логарифм двойки). Для получения количества байт нужно сделать инверсию значения в положительное и возвести двойку в степень этого значения. Например, так.
if( boot_sect.clusters_per_mft_record > 0 ) bpmftrec = bps * spc * boot_sect.clusters_per_mft_record; else bpmftrec = 2 << ~boot_sect.clusters_per_mft_record;
Задача с поиском элемента в MFT осложняется тем, что сам файл MFT может быть фрагментирован и тогда линейная индексация его как массива не представляется возможной. Для последующего чтения записей в MFT, лучше сразу скэшировать информацию об отрезках для файла MFT.
Первые записи MFT стандартизированы и описывают служебные файлы самой NTFS.
Утилита Руссиновича nfi позволяет сдампить элементы в MFT и просмотреть их атрибуты, например, nfi C.