Windows - статьи

         

Атрибуты


После нахождения записи mft, самой важной задачей является поиск необходимого атрибута, например с данными. Атрибуты бывают двух видов: резидентные (resident) и нерезидентные (nonresident). Резидентный атрибут умещается в записи MFT, а нерезидентный нет. У обоих атрибутов есть общий заголовок, куда входят поля типа, длины, и пр. и далее свой собственный заголовок (либо для резидентного либо для нерезидентного). Атрибуты идентифицируются типом, но также могут быть и именованными, если поле name_length не нулевое. В общем, заголовок обоих атрибутов описывается структурой.

typedef struct _ATTR_RECORD { /*0x00*/ ATTR_TYPES type; //тип атрибута /*0x04*/ USHORT length; //длина заголовка; используется для перехода к //следующему атрибуту /*0x06*/ USHORT Reserved; /*0x08*/ UCHAR non_resident; //1 если атрибут нерезидентный, 0 - резидентный /*0x09*/ UCHAR name_length; //длина имени атрибута, в символах /*0x0A*/ USHORT name_offset; //смещение имени атрибута, относительно заголовка //атрибута /*0x0C*/ USHORT flags; //флаги, перечислены в ATTR_FLAGS /*0x0E*/ USHORT instance;

union { //Резидентный атрибут struct { /*0x10*/ ULONG value_length; //размер, в байтах, тела атрибута /*0x14*/ USHORT value_offset; //байтовое смещение тела, относительно заголовка //атрибута /*0x16*/ UCHAR resident_flags; //флаги, перечислены в RESIDENT_ATTR_FLAGS /*0x17*/ UCHAR reserved; } r; //Нерезидентный атрибут struct { /*0x10*/ ULARGE_INTEGER lowest_vcn; /*0x18*/ ULARGE_INTEGER highest_vcn; /*0x20*/ USHORT mapping_pairs_offset;//смещение списка отрезков /*0x22*/ UCHAR compression_unit; /*0x23*/ UCHAR reserved1[5]; /*0x28*/ ULARGE_INTEGER allocated_size; //размер дискового пространства, //которое было выделено под тело //атрибута /*0x30*/ ULARGE_INTEGER data_size; //реальный размер атрибута /*0x38*/ ULARGE_INTEGER initialized_size; } nr; } u; } ATTR_RECORD, *PATTR_RECORD;

Флаги атрибута описываются структурой. typedef enum { ATTR_IS_COMPRESSED = 0x1, //атрибут сжат (compressed) ATTR_IS_ENCRYPTED = 0x4000, //атрибут зашифрован (encrypted) ATTR_IS_SPARSE = 0x8000 //атрибут разрежен (sparse) } ATTR_FLAGS;




Важные типы атрибутов. typedef enum { AT_STANDARD_INFORMATION = 0x10, AT_ATTRIBUTE_LIST = 0x20, AT_FILE_NAME = 0x30, AT_OBJECT_ID = 0x40, AT_SECURITY_DESCRIPTOR = 0x50, AT_VOLUME_NAME = 0x60, AT_VOLUME_INFORMATION = 0x70, AT_DATA = 0x80, AT_INDEX_ROOT = 0x90, AT_INDEX_ALLOCATION = 0xa0, AT_BITMAP = 0xb0, AT_REPARSE_POINT = 0xc0, AT_END = 0xffffffff } ATTR_TYPES;

В заголовке MFT записи хранится байтовое смещение первого атрибута, относительно самой записи. Прибавляя это смещение к смещению записи, мы получим смещение первого атрибута. Для перехода к следующему элементу нужно прибавить к этому смещению значение поля length заголовка и так для всех последующих заголовков. Концом списка считается значение AT_END, считанное с начала атрибута. Важное замечание: в исходниках Linux NTFS, поле length имеет тип ULONG, однако при исследовании выяснилось, что для корректного обхода атрибутов следует сделать это поле как USHORT, а следующее за ним просто зарезервировать; бывают ситуации, когда в этих старших байтах хранился «мусор». К тому же этих двух байт вполне хватит для адресации смещения внутри MFT записи, размер которой обычно составляет 1 или 4 КБ.

Важное значение имеют флаги атрибутов, которые перечислены в ATTR_FLAGS. Они содержат информацию о характеристиках данного атрибута, например, является ли он зашифрованным или сжатым.

Если все атрибуты для файла не вмещаются в одну MFT запись, тогда для файла создаются расширенные записи (extra records). В таком случае основная (первичная) запись называется базовой и хранит атрибут $ATTRIBUTE_LIST, в котором хранятся ссылки на расширенные файловые записи.

Пользовательский файл обычно содержит атрибуты: $STANDART_INFORMATION – информация о файле (время создания, атрибуты), $FILE_NAME – имя файла, $SECURITY_DESCRIPTOR – дескриптор защиты, $DATA – данные. Описание этих атрибутов, а также их структуры можно найти у Кэрриэ в «Криминалистическом анализе файловых систем».

Для исследования NTFS на диске лучше всего использовать Runtime DiskExplorer for NTFS, поскольку она показывает очень много полезной информации и подробно структуры самой ФС.



Рис. 1. Список атрибутов и файлов у корневого каталога.

Резидентные атрибуты. Как уже упоминалось, такие атрибуты хранят свое тело в записи MFT и для них флаг non_resident в заголовке установлен в ноль. Для считывания данных такого атрибута достаточно определить смещение тела как сумму смещений заголовка атрибута и поля r.value_offset, а затем считать r.value_length байт в память.

Нерезидентные атрибуты. Для таких атрибутов флаг non_resident установлен в 1 и их тела хранятся в отдельных кластерах, на которые указывают отрезки. Отрезок (run) хранит цепочки кластеров, в которых находится содержимое атрибута. Массив отрезков называется списком отрезков (run list). Если атрибут имеет один отрезок, то он не фрагментирован и, соответственно, все кластеры, которые содержат данные являются смежными. Смещение списка отрезков определяется суммой смещения заголовка атрибута и поля nr.mapping_pairs_offset.

Отрезки находятся в сжатом виде и содержат сопоставления LCN-VCN для кластеров. Набор отрезков, описывающих кластеры для атрибута называется списком отрезков (run list). Поле mapping_pairs_offset в заголовке нерезидентного атрибута содержит смещение списка отрезков от начала заголовка атрибута. Фактически, список отрезков это массив структур переменного размера. Размер каждого из полей структуры указывается в предыдущем байте. Первый элемент структуры содержит размер отрезка в кластерах, а второй номер кластера. Байт, описывающий размеры полей размера и номера кластеров условимся называть байтом длин. Младший полубайт байта длин содержит длину поля размера, а старший длину поля номера кластера. Данные в полях структуры хранятся в формате Intel, т. е. младший байт по младшему адресу.

Рассмотрим пример. Нерезидентный атрибут имеет список отрезков вида. 32 90 3A 00 00 0C | 32 30 0F DA A7 1B | 32 A0 36 5E 89 05 | 00

Первый байт – байт длин описывает длины полей первого отрезка. Младший полубайт равен 2, значит на поле длины приходится два байта и длина отрезка равна 0x3A90.




Далее, старший полубайт байта длин равен трем и стартовый кластер равен 0xC0000. Получаем первый отрезок начинается с кластера 0xC0000 и заканчивается границей 0xC0000 + 0x3A90 = C3A90. Для перехода к следующему элементу следует прибавить размеры полей, и единицу для байта длин, т. е. 3 + 2 + 1. Для второго отрезка по байту длин видим, что размер полей такой же как в предыдущем отрезке, т. е. младший полубайт равен двойке, значит размер поля длины два и равен 0xF30, старший полубайт равен трем и стартовый VCN равен 0x1BA7DA. Для преобразования VCN в LCN, складываем первый LCN - 0xC0000 и VCN - 0x1BA7DA. Получаем 0xC0000 + 0x1BA7DA = 0x27A7DA. Получаем, второй отрезок начинается с кластера 0x27A7DA и продолжается до 0x27A7DA+0xF30 = 0x27B70A. Для перехода к следующему отрезку добавляем размеры полей плюс единицу для самого байта длин. Третий отрезок начинается с байта длин - 32 и размер в кластерах отрезка равен 0x36A0, VCN равен 0x5895E. Для преобразования VCN-LCN складываем предыдущий стартовый LCN с данным VCN, т. е. 0x27A7DA + 0x5895E = 0x2D3138. Получаем третий отрезок 0x2D3138 - 2D67D8. Следующий байт за отрезком нулевой, следовательно список отрезков закончен. Итак исходный атрибут размещается в кластерах 0xC0000 – 0xC3A90, 0x27A7DA - 0x27B70A, 0x2D3138 – 0x2D67D8 (не включая последний кластеры).

На практике возможны «нестандартные» цепочки кластеров, когда вышеприведенное правило сопоставления относительных смещений в LCN работать не будет, поэтому следует после распаковки списка отрезков сверять полученный из отрезков размер файла с тем, который указывается в заголовке нерезидентного атрибута. В «нормальном» случае эти значения должны совпадать. Кроме того следует выполнять проверку не является ли значение длины этого отрезка больше самого смещения этого отрезка. Если это так, то от распаковки такого отрезка лучше отказаться. Также таким способом нельзя распаковать отрезки разряженного файла.

Например, следующая функция из загрузчика ReactOS для NTFS распаковывает отрезок.



PUCHAR // возвращает указатель на следующий отрезок в списке NtfsDecodeRun( PUCHAR DataRun, //на входе, указатель на отрезок для распаковки LONGLONG *DataRunOffset, //на выходе, распакованное значение кластерного смещения ULONGLONG *DataRunLength //на выходе, распакованное значение числа кластеров ) { UCHAR DataRunOffsetSize; //размер поля смещения UCHAR DataRunLengthSize; //размер поля длины CHAR i;

//из старшего полубайта считаем размер поля смещения DataRunOffsetSize = (*DataRun >> 4) & 0xF; //из младшего размер поля смещения DataRunLengthSize = *DataRun & 0xF;

*DataRunOffset = 0; *DataRunLength = 0;

//указатель на сами данные DataRun++;

//цикл распаковки длины отрезка, с каждой итерацией значение сдвигается на i-байт и //прибавляется с длиной for (i = 0; i < DataRunLengthSize; i++) { *DataRunLength += *DataRun << (i << 3); DataRun++; }

/* NTFS 3+ sparse files, если файл разряжен */ if (DataRunOffsetSize == 0) { *DataRunOffset = -1; } else { //цикл распаковки смещения for (i = 0; i < DataRunOffsetSize - 1; i++) { *DataRunOffset += *DataRun << (i << 3); DataRun++; } //последний байт может быть знаковым, поэтому он обрабатывается отдельно *DataRunOffset = ((CHAR)(*(DataRun++)) << (i << 3)) + *DataRunOffset; }

//возвращаем указатель на следующий отрезок return DataRun; }

Рис. 2. DiskExplorer for NTFS показывает список отрезков для атрибута $BITMAP у файла $MFT. Кроме того, отображает другую полезную информацию об атрибутах, включая длину, резидентен или нет.

Рис. 3. У файла $MFT атрибут дата имеет всего один отрезок, что свидетельствует о том, что он не фрагментирован и его можно индексировать как обычный массив. Данные атрибута начинаются с кластера 0xC0000 и имеют длину 0xE7A8 кластеров.


Каталоги


Каталог представляется записью MFT и содержит ссылки на файлы и другие каталоги. NTFS умеет быстро находить определенный файл в каталоге, потому что каталог хранит информацию о содержащихся в нем файлах в формате дерева. Поиск файла по упорядоченному дереву занимает намного меньше времени, чем при линейном поиске. В свою очередь, это накладывает дополнительные, незначительные временные издержки, при добавлении файлов/каталогов в дерево, на поддержании дерева в упорядоченном виде.

В NTFS используется понятие индекса, которое пришло из баз данных. Индекс - это коллекция элементов (атрибутов), хранящихся в отсортированном порядке. NTFS использует B+ деревья для организации индекса. Для таких деревьев в одном узле дерева может содержаться несколько значений. В NTFS эти значения называются индексными элементами. В качестве индексного элемента может выступать любой атрибут, по которому будет производиться индексация. Для индексов каталогов это всегда атрибут $FILE_NAME, для каждого файла/каталога, содержащегося в нем. Т. о. на каждый файл/каталог приходится как минимум две структуры FILE_NAME, первый как атрибут у файла, а второй, используемый для индекса. Узел дерева хранит последовательность атрибутов $FILE_NAME. Для хранения узлов дерева используются два типа атрибутов $INDEX_ROOT, который присутствует всегда для любой директории и $INDEX_ALLOCATION, который может и не присутствовать для небольших каталогов.

Индексы также используются для метафайла $Secure, который содержит дескрипторы защиты для файлов.


Рис. 4. Дерево каталога NTFS.

Индексный узел наделен заголовком INDEX_HEADER.

typedef struct _INDEX_HEADER //заголовок узла { /*0x00*/ ULONG entries_offset; //байтовое смещение первого индексного элемента, //относительно заголовка узла /*0x04*/ ULONG index_length; //размер узла в байтах /*0x08*/ ULONG allocated_size; //выделенный размер узла /*0x0C*/ ULONG flags; } INDEX_HEADER, *PINDEX_HEADER;

Индексные узлы хранятся в атрибутах $INDEX_ROOT и $INDEX_ALLOCATION.


Первый является резидентным и, следовательно, может хранить только один индексный узел, а второй является нерезидентным и хранить неограниченное число узлов.

Индексный элемент каталога наделен заголовком INDEX_ENTRY_HEADER_DIR.

typedef enum _INDEX_ENTRY_FLAGS { INDEX_ENTRY_NODE = 1, INDEX_ENTRY_END = 2 //последний элемент в узле } INDEX_ENTRY_FLAGS;

typedef struct _INDEX_ENTRY_HEADER_DIR //заголовок индексного элемента { /*0x00*/ MFT_REF indexed_file; //адрес MFT файла /*0x08*/ USHORT length; //смещение следующего элемента, относительно текущего /*0x0A*/ USHORT key_length; //длина атрибута $FILE_NAME /*0x0C*/ INDEX_ENTRY_FLAGS flags; //флаги /*0x10*/ FILE_NAME_ATTR file_name;//сам атрибут $FILE_NAME, если key_length //больше нуля. } INDEX_ENTRY_HEADER_DIR, *PINDEX_ENTRY_HEADER_DIR;

Теперь рассмотрим атрибут $INDEX_ROOT. В самом начале его тела располагается заголовок INDEX_ROOT.

typedef struct _INDEX_ROOT //заголовок $INDEX_ROOT { /*0x00*/ ATTR_TYPES type; //тип индексируемого атрибута /*0x04*/ ULONG collation_rule; //правило упорядочения в дереве /*0x08*/ ULONG index_block_size; //размер индексной записи в байтах /*0x0C*/ UCHAR clusters_per_index_block; //size of each index block (record) in clusters //либо логарифм размера /*0x0D*/ UCHAR reserved[3]; //unused /*0x10*/ INDEX_HEADER index; //заголовок индексного узла } INDEX_ROOT, *PINDEX_ROOT;

Наглядно, INDEX_ROOT представляется следующим образом.



Рис. 5. Тело атрибута $INDEX_ROOT.

Для перечисления всех индексных элементов в узле, необходимо вычислить адрес начального индексного элемента как сумму смещения заголовка INDEX_HEADER, и значения поля entries_offset заголовка узла, а затем перебрать все индексные элементы, пока в очередном элементе не будет встречен флаг INDEX_ENTRY_END. Для перехода от одного элемента к другому следует к адресу индексного элемента добавлять поле length.

Если индексные элементы не вмещаются в атрибуте $INDEX_ROOT, то для их хранения выделяется атрибут $INDEX_ALLOCATION, который по своему строению отличается от $INDEX_ROOT.



Тело атрибута $INDEX_ALLOCATION хранит индексные элементы в индексных записях, каждая из которых обладает заголовком. Индексная запись имеет статический размер и представляет один узел дерева. Ее размер указывается либо в поле index_block_size в INDEX_ROOT либо в поле загрузочного сектора, где может быть байтовый размер записи или двоичный логарифм размера.

Каждая индексная запись обладает заголовком INDEX_ALLOCATION.

typedef struct _INDEX_ALLOCATION //заголовок индексной записи { /*0x00*/ ULONG magic; //сигнатура "INDX" /*0x04*/ USHORT usa_ofs; /*0x06*/ USHORT usa_count; /*0x08*/ ULARGE_INTEGER lsn; /*0x10*/ ULARGE_INTEGER index_block_vcn; //VCN индексной записи /*0x18*/ INDEX_HEADER index; //заголовок узла } INDEX_ALLOCATION, *PINDEX_ALLOCATION;

Атрибут $INDEX_ALLOCATION представляет собой последовательность индексных записей, количество которых может быть вычислено делением размера атрибута на размер индексной записи (AttrRec->u.nr.data_size.QuadPart / IndexRootAttr->index_block_size). Зная количество индексных записей, мы можем их обойти как линейный массив элементов.

Индексные записи могут быть выделены для хранения узлов, а могут и нет. Для определения статуса выделения нужно при проходе учитывать атрибут $BITMAP, который хранит в своем теле последовательность бит, каждый из которых соответствует номеру индексной записи (начиная с нуля). Атрибут $BITMAP делится на байты и хранит состояние выделения индексных записей по следующей схеме: если старший бит байта N содержал состояние выделения индексной записи X, тогда младший бит следующего байта N+1 содержит состояние выделения записи X + 1. Рассмотрим пример. ff 5f 70 ff 05 11111111 01011111 01110000 11111111 00000101

Все индексные записи, начиная с нуля и заканчивая номером 12 выделены, т. к. первый байт содержит все единицы (анализ байтов происходит слева направо, а битов справа налево), следовательно, индексные записи с номерами 0-7 выделены. Переходим к следующему байту, видим, что первые пять бит равны единице, следовательно записи 8-12 выделены для использования.


Следующая запись, 13 не выделена.

Для определения байта, в котором хранится бит, характеризующий выделение, нужно индекс записи разделить нацело на восемь. Чтобы рассчитать позицию бита в байте, нужно взять остаток от этого деления.

Если PUCHAR StartBitmap – содержит адрес считанного в память битмапа, тогда X = StartBitmap[N / 8], f = (X >> (N % 8)) & 1,

переменная f будет содержать статус выделения индексной записи N.

Пусть для примера N = 20. Разделим целочисленно 20 на 8, 20/8 = 2. Получаем второй байт - 01110000. Возьмем остаток 20 % 8 = 4. Сдвигая байт на 4 и обнуляя все байты, кроме первого получаем 1. Следовательно, индексная запись N выделена.

Как показывает практика, свободная индексная запись будет содержать нормальный заголовок индекса, но в первом индексном элементе будет установлен флаг INDEX_ENTRY_END и поле key_length будет обнулено.



Рис. 6. Организация индексных записей в атрибуте INDEX_ALLOCATION.

Мы рассмотрели механизм, с помощью которого NTFS проходится по дереву каталогов, например, когда нужно открыть конкретный файл. В начале просматривается содержимое первого каталога как дерева и там ищется имя следующего каталога, затем для него выполняется та же операция, пока не будет найден искомый файл.

Индексные записи удобно просматривать с помощью той же DiskExplorer. Для этого нужно выбрать каталог и поставить внизу галочку на пункте Details. В таком случае для директории отобразятся все ее атрибуты. Чтобы просмотреть содержимое атрибута $INDEX_ALLOCATION в бинарном виде, нужно перейти по кластеру с которого начинаются его данные и нажать F3 для просмотра их в бинарном виде.

Рис. 7. Заголовок атрибута $INDEX_ALLOCATION, чтобы перейти к его содержимому, нужно кликнуть на подсвеченном стартовом кластере – x0131439F.

Рис. 8. Содержимое $INDEX_ALLOCATION в бинарном виде. Для перехода к просмотру в виде дерева – F5. Байт перед именем файла, есть тип имени, 3 – DOS и Win32 имя. Например, перед $AttrDef стоит тройка по смещению 0xA9, а по 0xAA уже первый буква имени файла.Также и для $BadClus, по смещению 0x111 стоит 3, т. е. DOS | Win32 имя.

Тема индексов очень хорошо рассмотрена у того же Кэрриэ в «Криминалистическом анализе».


Общие концепции NTFS


Метафайлы NTFS – служебные файлы, используемые NTFS для поддержания своей внутренней структуры. Том (volume) NTFS – раздел, отформатированный под NTFS. Главная загрузочная запись (Master Boot Record) – первый сектор жесткого диска, который содержит загрузочный код (bootstrap code) и таблицу разделов (partition table). Загрузочный код читает таблицу разделов, ищет активный (флаг 0x80) раздел и передает управление на его первый сектор (загрузочный сектор). Загрузочный сектор (boot sector) – первый сектор тома, в котором хранятся параметры ФС. Также в загрузочный сектор входит код, который отвечает за чтение ntldr в память. Такой код несет смысл только если том является системным (т. е. только для диска C). В процессе монтирования тома, ntfs.sys проводит валидацию загрузочного сектора и признает его своим в случае совпадение необходимых параметров. Системный том (system volume) – том, на котором располагается ntldr. Для NT системным томом может быть только первый том – С:. Загрузочный том (boot volume) – том, на котором располагается папка windows (winnt).

Формат загрузочного сектора NTFS описывается следующей таблицей (как и все остальные описываемые структуры, наилучшим руководством по ним может служить книга Брайана Кэрриэ «Криминалистический анализ файловых систем»).

Смещение

Размер в байтах

Описание

0x0 3 Команда перехода на загрузочный код.
0x3 8 Сигнатура ‘NTFS    ’.
0xB 2 Количество байт на сектор
0xD 1 Количество секторов в кластере.
0xE 2 Должно равняться нулю.
0x10 1 Должно равняться нулю.
0x11 2 Должно равняться нулю.
0x13 2 Должно равняться нулю.
0x15 1 Тип носителя
0x16 2 Должно равняться нулю.
0x18 2 Должно равняться нулю.
0x1A 2 Должно равняться нулю.
0x1C 4 Не используется.
0x20 4 Должно равняться нулю.
0x24 4 Не используется.
0x28 8 Число секторов в томе.
0x30 8 Стартовый кластер MFT.
0x38 8 Стартовый кластер копии MFT.
0x40 1 Кластеров на запись MFT.
0x41 3 Не используется.
0x44 1 Кластеров на индексную запись.
0x45 3 Не используется.
0x48 8 Серийный номер тома. Уникален. Создается в процессе форматирования.
0x50 4 Не используется.
0x54 426 Загрузочный код.
0x1FE 2 Маркер конца. Равен 0xAA55.
<


Соответствующие структуры имеют вид (определения взяты из исходников Linux- NTFS Project, там же можно найти очень много информации о структурах ntfs). Так же как и FAT, NTFS хранит часть информации в блоке, называемом BIOS Parameter Block, который входит в состав boot sector.

typedef struct _BIOS_PARAMETER_BLOCK { /*0x0b*/USHORT bytes_per_sector; /* Размер сектора, в байтах */ /*0x0d*/UCHAR sectors_per_cluster; /* Секторов в кластере */ /*0x0e*/USHORT reserved_sectors; /* должен быть ноль */ /*0x10*/UCHAR fats; /* должен быть ноль */ /*0x11*/USHORT root_entries; /* должен быть ноль */ /*0x13*/USHORT sectors; /* должен быть ноль */ /*0x15*/UCHAR media_type; /* тип носителя, 0xf8 = hard disk */ /*0x16*/USHORT sectors_per_fat; /* должен быть ноль */ /*0x18*/USHORT sectors_per_track; /* не используется */ /*0x1a*/USHORT heads; /* не используется */ /*0x1c*/ULONG hidden_sectors; /* не используется */ /*0x20*/ULONG large_sectors; /* должен быть ноль */ /* sizeof() = 25 (0x19) bytes */ } BIOS_PARAMETER_BLOCK, *PBIOS_PARAMETER_BLOCK;

typedef struct _NTFS_BOOT_SECTOR { /*0x00*/UCHAR jump[3]; /* переход на загрузочный код */ /*0x03*/ULARGE_INTEGER oem_id; /* сигнатура "NTFS ". */ /*0x0b*/BIOS_PARAMETER_BLOCK bpb; /*0x24*/UCHAR physical_drive; /* не используется */ /*0x25*/UCHAR current_head; /* не используется */ /*0x26*/UCHAR extended_boot_signature; /* не используется */ /*0x27*/UCHAR reserved2; /* не используется */ /*0x28*/ULARGE_INTEGER number_of_sectors; /* Количество секторов на томе. */ /*0x30*/ULARGE_INTEGER mft_lcn; /* Стартовый кластер MFT. */ /*0x38*/ULARGE_INTEGER mftmirr_lcn;/* Стартовый кластер копии MFT */ /*0x40*/CHAR clusters_per_mft_record; /* Размер MFT записи в кластерах. */ /*0x41*/UCHAR reserved0[3]; /* зарезервировано */ /*0x44*/CHAR clusters_per_index_record;/* Размер индексной записи в кластерах. */ /*0x45*/UCHAR reserved1[3]; /* зарезервировано */ /*0x48*/ULARGE_INTEGER volume_serial_number; /* уникальный серийный номер тома */ /*0x50*/ULONG checksum; /* не используется */ /*0x54*/UCHAR bootstrap[426]; /* загрузочный-код */ /*0x1fe*/USHORT end_of_sector_marker; /* конец загрузочного сектора, сигнатура 0xaa55 */ /* sizeof() = 512 (0x200) bytes */ } NTFS_BOOT_SECTOR, *PNTFS_BOOT_SECTOR;

При проверке факта, что том является NTFS, необходимо прежде всего проверить сигнатуру «NTFS ».

if( NtfsBootSector->oem_id.QuadPart != 0x202020205346544E ) return FALSE;

Потом некоторые параметры бут сектора, как например кол-во байт в секторе и количество секторов в кластере на кратность двойки в степени.

if( NtfsBootSector->bpb.bytes_per_sector < 0x100 NtfsBootSector->bpb.bytes_per_sector > 0x1000 ) return FALSE;

//check sectors per cluster switch( NtfsBootSector->bpb.sectors_per_cluster ) { case 1: case 2: case 4: case 8: case 16: case 32: case 64: case 128: break; default: return FALSE; }

Все проверки в функции ntfs_boot_sector_is_ntfs из Linux-NTFS Project.


Общие сведения


NTFS (NT File System) – файловая система, которая разрабатывалась Microsoft специально для Windows NT. Написание NTFS шло параллельно с разработкой самой этой ОС. Известно, что NT зарождалась не в вакууме; в ее основу были положены идеи серверной системы VMS «high-end» класса компании DEC (в 1998 она была куплена Compaq, которая затем была поглощена Hewlett-Packard). ОС VMS работала на железе DEC, включая 32-битные компьютеры VAX и 64-битные машины на основе процессора Alpha. Каким-то образом команда, работавшая над созданием VMS, перекочевала в Microsoft для создания NT. Во главе этой команды находился Дэйв Катлер (Dave Cutler), крестный отец NT, разработчик ядра системы. Основная версия причины перехода Катлера в Microsoft – закрытие проекта разработки нового RISC-процессора и соответствующей ОС, в котором он работал вместе со своей командой инженеров. В результате, не без участия Билла Гейтса, Дэйв со своей командой оказался в Microsoft. Кстати, Гейтс был очень заинтересован в создании новой ОС, потому что у Microsoft на тот момент была только Windows, пригодная для домашних пользователей, а выхода на рынок серверов не было.

Архитекторами NTFS были Том Миллер (Tom Miller) и Гэри Кимура (Gary Kimura), которые работали программистами в команде Катлера в DEC. Так же, как NT влила в себя идеи VMS, NTFS основывалась на идеях файловой системы VMS – Files-11, в которой, в частности, поддерживались списки контроля доступом (ACL), а также ввод/вывод, ориентированный на записи (record-oriented I/O) .

NTFS разрабатывалась, прежде всего, как ФС для ОС корпоративного уровня, какой и являлась NT. Предыдущие файловые системы FAT и HPFS (ФС для OS/2) были способны удовлетворить потребностям рядовых пользователей, но не имели достаточных возможностей для корпоративной среды, таких как защищенность файлов, отказоустойчивость и восстанавливаемость. В связи с этим Microsoft выработала следующие требования к новой ФС класса «high end». (Эти требования были перечислены у Хелен Кастер в «Основах Windows NT и NTFS», первой книге, которая проливала свет на внутреннее устройство NT).




Восстанавливаемость. В случае сбоя диска, NTFS должна привести себя в рабочее, целостное состояние. В лучшем случае, NTFS вернется в состояние, которое непосредственно предшествовало сбою, в худшем какая-то пользовательская информация может быть потеряна, но том останется в рабочем состоянии. Восстанавливаемость реализуется через модель обработки транзакций. Транзакция не что иное, как операция I/O, которая изменяет структуры NTFS. Если такая операция не завершается полностью, то NTFS откатывает свои структуры данных до состояния перед выполнением транзакции. Защита от несанкционированного доступа. Файлы и каталоги NTFS обладают дескрипторами защиты, что позволяет контролировать доступ к ним в рамках общей модели безопасности NT. Подобными дескрипторами защиты обладают все объекты NT (процессы, потоки, разделы и пр.). Отказоустойчивость. Если восстанавливаемость гарантирует целостность тома после сбоя, то отказоустойчивость может гарантировать восстановление пользовательских данных. Это реализуется за счет избыточности данных и RAID. Дублирование данных осуществляется на уровне диспетчера томов, который копирует записываемые данные на другом диске. Диски и файлы большого объема. В NTFS реализуется очень эффективная поддержка больших дисков и файлов. Для хранения номера кластера используется 64 бита или 8 байт. Т. о. NTFS позволяет адресовывать 2^64 кластеров, при стандартном размере кластера в 4KB и максимальном 64KB. Под размер файлов также выделено 8 байт, что позволяет адресовывать очень большие файлы. Поддержка POSIX. Также как NT, NTFS полностью соответствует стандарту POSIX 1003.1. Главная особенность формата заключается в назначении имен файлам с учетом регистра символов, а также реализация жестких связей (hard links). Жесткие связи позволяют файлам из разных каталогов ссылаться на одни и те же данные. Жесткая связь создается для существующего файла и указывает на его данные, хотя для пользователя такой объект выглядит как файл.

Разработка новой файловой системы, которая должна удовлетворять вышеперечисленным требованиям очень сложный процесс и здесь разработчики учли опыт MS в разработке FAT и HPFS.


Том Миллер так описывает процесс разработки новой ФС: Работа над файловой системой – это, пожалуй, самое ужасное [при разработке операционной системы]. Если обнаружилась в ядре системы, или что-нибудь странное происходит с дисплеем, то можно просто перезагрузить машину и продолжать работу. Но если что-то случилось с жестким диском, то часто дальнейшая работа вообще невозможна. Всех страшно раздражают ошибки в файловой системе. Никому не хочется, чтобы его постоянная память стала не постоянной.

NTFS имеет следующие версии.

Версия 1.0. Была выпущена вместе с NT 3.1 в середине 1993. Включает самый основной функционал ФС. Версия 1.1. Была выпущена вместе с NT 3.5 в 1994. Версия 1.2. Была написана для NT 3.51 (середина 1995) и для NT 4 (середина 1996); последняя, также известна как «NTFS 4.0». Добавлена поддержка сжатия файлов, именованных потоков, защита файлов на основе списков ACL. Версия 3.0. Поставляется с Windows 2000 (известна как «NTFS V5.0»). Добавлена поддержка дисковых квот, шифрования, разреженных файлов (sparse files), точек повторного разбора (reparse points), служебный метакаталог $Extend и его файлы Версия 3.1. Поставляется с Windows XP (осень 2001, «NTFS V5.1»), Windows 2003 (весна 2003, «NTFS V5.2»), Windows Vista (середина 2005, «NTFS V6.0»).


Распространенные атрибуты


Ниже даны характеристики основных атрибутов, которые используются в NTFS для описания файлов и каталогов. Обратите внимание, что это формат тел атрибутов соответствующих типов.

Атрибут $FILE_NAME.


Идентификатор 0x30.

Смещение

Тип

Описание

0x0 MFT_REF Ссылка на родительский каталог (в котором хранится файл).
0x8 ULARGE_INTEGER Время создания.
0x10 ULARGE_INTEGER Время последней модификации атрибута с данными.
0x18 ULARGE_INTEGER Время последней модификации данной записи.
0x20 ULARGE_INTEGER Время последнего доступа к записи.
0x28 ULARGE_INTEGER Размер выделенного дискового пространства для основного атрибута $DATA (кратно размеру кластера).
0x30 ULARGE_INTEGER Реальный размер основного атрибута $DATA.
0x38 ULONG Атрибуты файла.
0x3C ULONG Тип точки разбора.
0x40 UCHAR Длина имени файла в символах.
0x41 UCHAR Пространство имен (0 – POSIX, 1 - Win32, 2 - DOS).
0x42+ WCHAR Имя файла.

Атрибут $FILE_NAME всегда резидентен. Он служит для двух функций. Во-первых, хранит имя файла в записи MFT, причем атрибутов $FILE_NAME у файла может быть несколько (NT эмулирует различные подсистемы DOS и POSIX, в том числе и на уровне файлов). Для MS-DOS атрибут будет хранить имя в формате 8.3, для POSIX символы имени файла с учетом регистра и стандартное имя для Win32 подсистемы. Один атрибут может содержать имя сразу для двух подсистем (обычно, 3 – Win32|DOS, если имя влезает в формат 8.3). Во-вторых, атрибут используется для организации индекса в структуре каталогов, где он выступает как индексный элемент. Заметьте, ошибочным является утверждение, что индексы дублируют только один $FILE_NAME для файла, как раз наоборот, если файл имеет в записи MFT один $FILE_NAME для DOS и другой для Win32, то оба они будут дублироваться и в индексах.

Атрибут $STANDART_INFORMATION


Идентификатор 0x10.

Смещение

Тип

Описание

0x0 ULARGE_INTEGER Время создания.
0x8 ULARGE_INTEGER Время последней модификации атрибута с данными.
0x10 ULARGE_INTEGER Время последней модификации данной записи.
0x18 ULARGE_INTEGER Время последнего доступа к записи.
0x20 ULONG Атрибуты файла (см. следующую таблицу).
0x24 ULONG Максимальное количество версий.
0x28 ULONG Номер версии.
0x2C ULONG Идентификатор класса.
0x30 ULONG Идентификатор владельца. (начиная с w2k)
0x34 ULONG Идентификатор безопасности. (начиная с w2k)
0x38 ULARGE_INTEGER Изменение квоты. (начиная с w2k)
0x40 ULARGE_INTEGER Номер USN. (начиная с w2k)
<


Возможны следующие атрибуты файла: Флаг

Описание

0x1 Только чтение
0x2 Скрытый
0x4 Системный
0x20 Архивный
0x80 Устройство
0x100 Временный
0x200 Разреженный
0x400 Точка подключения
0x800 Сжатый
0x1000 Автономный
0x2000 Содержимое не индексируется
0x4000 Зашифрованный
Атрибут $ STANDART_INFORMATION всегда резидентен. Атрибут содержит основную информацию о файле и существует для каждого файла и каталога. Обычно $STANDART_INFORMATION располагается первым атрибутом в списке. Поля временных штампов присутствуют также и в атрибуте $FILE_NAME. Идентификатор безопасности используется как индекс в файле $Secure.

Атрибут $DATA.

Атрибут $DATA (идентификатор 0x80) в своем теле содержит данные файла и может быть как резидентным, если атрибут умещается в MFT-записи, так и нерезидентным. На уровне атрибута $DATA реализуются Alternate Data Streams или альтернативные потоки данных. Поток в NTFS – это данные файла. По умолчанию, все записываемые данные в файл попадают в безымянный поток $DATA. Альтернативные потоки – это дополнительные атрибуты $DATA для файла, в которых также могут храниться данные. Главный безымянный поток обычно следует последним в списке атрибутов, а все последующие альтернативные потоки будут добавляться за ним. NTFS адресует потоки (атрибуты $DATA) по их именам (имена атрибутов). Смещение имени потока вычисляется также как смещение имени любого атрибута, как сумма смещения заголовка атрибута со смещением имени, которое указывается в заголовке (при присутствии значения длины имени, при его отсутствии мы имеем дело в неименованным потоком).

Жесткие связи.

Жесткие связи (hard links) – механизм POSIX, который позволяет из разных каталогов обращаться к одному файлу. На уровне NTFS, жесткая ссылка – это дополнительный атрибут $FILE_NAME в MFT-записи файла и такой же атрибут в родительской директории самой ссылки (в этом случае структура INDEX_ENTRY_HEADER_DIR в поле ссылки на файл содержит ссылку на MFT запись для самого файла).


Жесткие связи создаются на уже существующий файл и их количество указывается в заголовке MFT записи файла в поле link_count. Если на файл нет жестких связей, это поле равно единице. Если link_count больше единицы тогда количество связей определяется как link_count минус 1. Отличие атрибута $FILE_NAME, который описывает жесткую ссылку в MFT записи, от атрибута, который описывает одно из имен файла, в том, что поле родительской директории содержит ссылку на родительскую директорию самой ссылки, а не ссылку на директорию, которой принадлежит файл.

Для перечисления всех жестких ссылок на файл нужно получить их количество как MFT_RECORD.link_count – 1 и ссылку на родительский каталог файла. Если кол-во ссылок больше нуля, тогда пройтись по всем атрибутам $FILE_NAME и сравнить значения полей родительских ссылок с ссылкой на родительский каталог файла. В случае несоответствия, мы имеем дело с жесткой ссылкой.

Метафайлы.

Первые 11 записей MFT описывают основные метафайлы NTFS. За ними идет не используемая область из нескольких записей и далее пользовательские файлы и каталоги. Структура и описание метафайлов даны в следующей таблице. Индекс MFT

Имя файла

Описание

0 $Mft Описывает местоположение MFT файла. При поиске записей через их индексы считывается атрибут $DATA. Стартовый адрес тела атрибута $DATA совпадает с местоположением самого $Mft. В случае, если MFT занимает один отрезок, т.е. не фрагментирован, его можно индексировать как линейный массив, но как правило, MFT фрагментирован, поэтому для поиска записи по индексу нужно искать соответствующий отрезок.
1 $MftMirr Копия первых четырех записей MFT.
2 $LogFile Файл журнала транзакций. Содержит информацию для восстановления NTFS после сбоя.
3 $Volume Содержит информацию о томе, такую как метка тома и версия тома.
4 $AttrDef Определяет имена и идентификаторы атрибутов.
5 . Корневой каталог. Все метафайлы содержатся в корневом каталоге.
6 $Bitmap Файл распределения кластеров на томе. В каждом бите тела содержит статус выделения кластера. Читается также как атрибут $BITMAP для каталогов, но в роли индекса индексной записи выступает номер кластера.
7 $Boot Описывает загрузочный сектор. Стартовый адрес тела нерезидентного атрибута $DATA равен нулю, что соответствует началу тома, где бут сектор, собственно, и расположен.
8 $BadClus Содержит информацию о плохих кластерах тома.
9 $Secure Содержит дескрипторы защиты для всех файлов тома.
10 $Upcase Предназначен для сопоставления имен с буквами в верхнем регистре.
11 $Extend Каталог расширенных метаданных, таких как квоты, точки разбора и идентификаторы объектов.
12 - 15 Записи пустые.
16 - 24 Записи не используются.
Пользовательские файлы и каталоги.
Метафайлы в NTFS могут быть и дополнительными или необязательными. Такие файлы хранятся в директории $Extend и за ними строго не закреплены индексы в MFT. К дополнительным метафайлам относятся $Quota – информация о дисковых квотах, $Reparse – используется для точек разбора (reparse point), $ObjId – позволяет связывать идентификатор объекта файла с записью MFT.

Метафайлы $Secure, $Extend, а также все дополнительные появились начиная с NTFS v3.0, т. е. с Windows 2000.


Ссылки


Zachary, G. Pascal (1994). Showstopper! The Breakneck Race to Create Windows NT and the Next Generation at Microsoft. Warner Books. ISBN 0-02-935671-7. Кастер, Хелен (1996). Основы Windows NT и NTFS. Русская редакция. ISBN 5-7502-0023-X.



Таблица 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.