Хакер - Воскрешению подлежит! Восстанавливаем файлы в NTFS с использованием PowerShell
hacker_freiАнтон Кузнецов
Содержание статьи
- Теория
- Резидентные и нерезидентные
файлыатрибуты - Время и атрибуты $STANDARD_INFO и $FILE_NAME
- Восстановление файлов, удаленных с использованием del или erase
- Восстановление файлов, удаленных через корзину
- Выводы
Существует множество способов вернуть утраченные или удаленные с накопителей файлы. В статье мы с тобой освежим в памяти теоретические основы восстановления удаленной информации в файловой системе NTFS v3.1 и подробно рассмотрим практический способ ручного восстановления файлов с использованием PowerShell.
INFO
Хочу отдать должное Крису Касперски за его труды на эту тему и подробное описание теории и практики в книге «Восстановление данных. Практическое руководство», а заодно поблагодарить Валентина Холмогорова и Ксению Кирилову за обновление и переиздание книги в 2021 году.
Сегодня создано предостаточно программных решений для восстановления удаленных файлов с различных файловых систем. Однако, если перефразировать известное изречение, практика без теории мертва: чтобы максимально эффективно использовать инструменты, нужно понимать, как они работают. Если перед специалистом стоит задача не просто вернуть утраченные файлы, а разобраться в причинах инцидента, знания об устройстве файловых систем просто необходимы.
ТЕОРИЯ
Подробно рассматривать структуру NTFS я не вижу смысла, описание можно найти в книге Криса «Восстановление данных. Практическое руководство» и статье «Файловая система NTFS извне и изнутри» (в двух частях). Однако, чтобы наши действия при восстановлении файлов были осмысленными, нам все же придется освежить в памяти некоторые ключевые особенности файловой системы NTFS 3.1, а заодно вспомнить, как хранятся и удаляются файлы.
WARNING
Не храни важные файлы на одном томе с операционной системой (обычно это диск С): на этом томе в NTFS чаще всего происходят изменения даже без прямого участия пользователя, что может повлиять на восстановление утраченной информации. Данные после удаления на этом дисковом разделе могут быть перезаписаны системными файлами.
В файловой системе NTFS работает принцип «все есть файл». Файл же имеет определенный набор атрибутов. Мы не будем разбирать все атрибуты файлов, затронем только самые важные: $STANDARD_INFO
, $FILE_NAME
, $DATA
.
В атрибутах $STANDARD_INFO
и $FILE_NAME
записаны метки времени, подробнее о них мы поговорим позже, а в атрибуте $DATA
хранится содержимое резидентного файла. В случае с нерезидентными файлами там располагаются «ссылки» на разбросанное по секторам содержимое файла, называемое data runs или отрезками. Отрезки представляют собой последовательность кластеров, которые хранят содержимое нерезидентного файла, а в качестве упомянутых «ссылок» в атрибуте $DATA
используется номер начального кластера и количество идущих следом кластеров. Как видишь, математика простая: если файл нерезидентный (больше 720 байт), нам нужно найти кластер, с которого он начинается, и собрать все кластеры воедино в верном порядке, а после записать содержимое кластеров в новый файл.
Все файловые атрибуты хранятся в файловой записи (File Record) таблицы Master File Table ($mft
). Это наиважнейший служебный файл (оттого он имеет резервную копию $mftmirr
), так как содержит в себе информацию обо всех файлах и директориях на томе. Имея на руках только файл $mft
, можно выстроить хронологию связанных с файлами событий, а в некоторых случаях — восстановить файлы.
Еще один важный служебный файл — карта свободного пространства $BitMap
. С ее помощью отслеживаются все используемые и неиспользуемые кластеры, что, конечно же, напрямую связано с созданием и удалением файлов или директорий. Кластер же в NTFS — минимальная единица дискового пространства, доступного для размещения файлов и директорий в файловой системе NTFS. По умолчанию размер кластера равен 4096 Kбайт, но при создании тома можно задать иной размер.
Процесс удаления файла подробно описан в книге Криса, также информацию можно найти в статье «Разгребаем руины. Как восстановить удаленные файлы на разделах NTFS».
РЕЗИДЕНТНЫЕ И НЕРЕЗИДЕНТНЫЕ ФАЙЛЫ АТРИБУТЫ
Некоторые материалы, посвященные файловой системе NTFS v3.1, упоминают о резидентных и нерезидентных файлах. Резидентными файлами считаются те, размер которых меньше или равен 720 байт (это количество байтов было получено исключительно опытным путем в NTFS v3.1 в Windows 10, хотя в других источниках читатель может найти иную информацию о максимальном размере резидентного файла: он варьируется от 700 байт до 1 Кбайт). Такие файлы хранят все содержимое в $mft
(Master File Table), и в этом случае флаг нерезидентности (Non-resident flag) у них равен 00h
. Для нерезидентных файлов (строго больше 720 байт) флаг Non-resident flag равен 01h
.
На самом деле флаг резидентности присущ каждому атрибуту файла в отдельности (в том числе $FN
, $SI
и другим атрибутам), а не самому файлу как таковому. Несмотря на наличие такого флага у каждого атрибута, существуют исключительно резидентные атрибуты, которые хранят свои данные только в файле $mft
(например, $FILE_NAME
, $STANDARD_INFO
, $VOLUME_NAME
). Есть атрибуты, которые могут быть как резидентными, так и нерезидентными (это справедливо по отношению к атрибутам $DATA
, $EA
и другим). Наконец, существуют файловые атрибуты, которые хранят свои данные исключительно за пределами $mft
, то есть являются строго нерезидентными ($BITMAP
, $REPARSE_POINT
, $SECURITY_DESCRIPTOR
и прочие).
Время и атрибуты $STANDARD_INFO и $FILE_NAME
При создании файла на томе, перемещении между томами и практически любых манипуляциях с файлами (переименование, локальное перемещение, доступ внутри одного тома, изменение) у файла изменяются атрибуты $STANDARD_INFO
(далее — $SI
) и $FILE_NAME
(далее — $FN
). В них указано время, когда произошло действие с файлом.
В следующих таблицах показано, какие атрибуты файлов $SI
и $FN
меняются и при каких действиях с файлом.
Здесь:
- Modification (изменение атрибутов
$Data
и$INDEX
); - Accessed (обращение к содержимому файла);
- Change MFT (запись в таблицу MFT — не отображается в Windows);
- BornTime (или birthday of file — создание файла).
Важно, что при удалении через del или erase атрибуты $FILE_NAME
и $STANDARD_INFO
не изменяются.
Зная метки времени определенных файлов и правила изменения атрибутов, специалист по расследованию инцидентов способен выстроить верную хронологию событий. Время, в которое произошли те или иные действия с файлами, при проведении криминалистического расследования может стать одним из важнейших артефактов.
ВОССТАНОВЛЕНИЕ ФАЙЛОВ, УДАЛЕННЫХ С ИСПОЛЬЗОВАНИЕМ DEL ИЛИ ERASE
Настало время приступить к практической части, чтобы закрепить теорию и поупражняться в восстановлении удаленных файлов. Но для начала следует выделить два способа удаления файлов в Windows:
- удаление c использованием системных утилит del (Shift-Del) или erase;
- удаление через корзину.
Такое разделение связано с разницей в механизме удаления и некотором отличии при восстановлении файлов.
WARNING
Никогда не восстанавливай удаленные файлы на тот же том, с которого они были удалены. Лучше всего использовать отдельный носитель.
В первую очередь разберемся с восстановлением нерезидентных файлов, а дальше посмотрим, как восстанавливать резидентные. Итак, практиковаться мы будем с использованием PowerShell-модуля PowerForensics авторства Джареда Эткинсона. Этот модуль прост в установке и содержит полезные командлеты для работы с файловыми системами NTFS и FAT. Он позволяет работать с системными файлами Windows и парсить атрибуты файлов NTFS, конвертировать метки времени из байтового представления в человеко‑читаемое (что необходимо в форензике). Подробнее узнать о возможностях модуля можно из его описания.
Для начала установим этот модуль (нужно запускать PowerShell от имени администратора, поскольку при чтении служебных файлов требуются привилегии локального администратора):
Import-Module PowerForensics
Проверим, что все прошло успешно, для этого выполним командлет
Get-Command - Module PowerForensics
Установка модуля завершена, теперь давай найдем все удаленные файлы на томе. В главной файловой таблице они имеют флаг удаления 00h
по смещению 16 байт от начала файловой записи.
Как уже было сказано в теоретической части, в Windows постоянно происходят изменения, какие‑то файлы удаляются, а какие‑то, наоборот, создаются (например, начиная с Windows 8 количество файлов Prefetch ограничено 1024, поэтому старые удаляются системой, а новые создаются при запуске исполняемых файлов).
Для чистоты эксперимента создадим том F (запускаем diskmgmt.msc
, затем сжимаем имеющийся том и отдаем для нового 5 Гбайт с дефолтным размером кластера 4096 Кбайт). Поместим туда файл (в роли подопытного будет выступать калькулятор, но читатель может использовать любой другой файл) calc.exe
и удалим его через erase (то же самое будет при удалении через del или Shift-Del).
Теперь для поиска удаленного файла на томе F
воспользуемся командлетом Get-ForensicsFileRecord
и отфильтруем вывод по флагу True
для атрибута Deleted
:
Get-ForensicFileRecord -VolumeName F: | Where-Object {$_.Deleted}
Здесь мы видим основные файловые атрибуты и их значения, однако для восстановления нам потребуется значение файловой записи RecordNumber
. После того как файл будет найден, для дальнейших действий лучше пользоваться номером файловой записи, так как имя файла может изменяться в процессе удаления (далее мы разберем этот случай). Имея номер файловой записи, мы сможем отыскать недостающие элементы, если файл нерезидентный, и собрать его воедино по кусочкам, после чего записать содержимое в новый файл.
Для этого запишем в переменную $file_record
объект файла со свойствами (атрибутами файла):
$file_record = Get-ForensicFileRecord -VolumeName F: -Index 43
Теперь мы можем обратиться к отдельному атрибуту и узнать интересующую нас информацию. Для восстановления нам нужно определить номер начального кластера и общее количество занимаемых файлом кластеров. Для этого в переменную $file_descriptor
запишем содержимое атрибута $DATA
:
$file_descriptor=$file_record.Attribute | Where-Object {$_.name -eq 'DATA'}`
После чего мы можем получить значения начального кластера и количества кластеров, которые занимает наш файл. В переменную $start_cluster
сохраним эти данные:
$start_cluster = $file_descriptor.DataRun | select *
На рисунке справа мы видим содержимое этой переменной в консоли PowerShell, а слева — атрибут $Data
с его полями для нашего удаленного файла.
Теперь дело за малым: нужно записать содержимое по «адресу» выше (номер начального кластера и количество кластеров) в восстановленный файл на томе.
Будем записывать на том С:
, чтобы не затереть удаленные данные на томе F:
. Запись можно выполнить при помощи командлета Invoke-ForensicDD
из модуля PowerForensics или использовать WriteAllBytes
из библиотеки System.IO.File
:
Invoke-ForensicDD -InFile \\.\F: -Offset ($st_cl.StartCluster*4096) -BlockSize ($st_cl.ClusterLength*4096) -Count 1 -OutFile C:\calc.exe
Здесь мы указываем в параметре InFile
том и расположение файла, который будет скопирован (в нашем случае это том F и расположение удаленного файла). В качестве смещения на диске указываем адрес начального кластера, умноженный на 4096, и количество кластеров, также умноженных на 4096, ведь размер кластера на нашем томе равен 4096 Кбайт. После чего указываем количество файлов на диске и путь с именем файла, куда будет восстановлен наш файл.
В результате мы получаем наш рабочий калькулятор в корне тома C:\calc.exe
.
Вот только есть одно но: несмотря на то что калькулятор запускается и работает без нареканий, хеш‑суммы оригинального файла (до удаления) и файла после восстановления не бьются…
Причина тому — разница в размере файла после восстановления. Размер файла до удаления равен 27 648 байт, а после восстановления — 28 672 (что при делении на 4096 дает нам 7 — те самые семь кластеров в сумме). То есть при восстановлении мы заботливо дозаписали нулями 1024 байт, это нужно и важно учитывать при получении оригинального файла. Значения реального размера файла (RealSize=27 648
) и выделенного под хранение файла пространства (AllocationSize=28 672
) хранятся в атрибуте $DATA
. Вся информация у нас на руках, давай наведем красоту. Для этого, прежде чем сохранить содержимое кластеров в файл, запишем массив байтов в переменную $bytearray
:
$bytearray = Invoke-ForensicDD -InFile \\.\F: -Offset ($st_cl.StartCluster*4096) -BlockSize ($st_cl.ClusterLength*4096) -Count 1
После чего, используя функцию WriteAllBytes
системной библиотеки System.IO.File
, запишем содержимое в файл, указав верхнюю границу массива для записи [0..($bytearray.Length - (1024 + 1))]
:
[System.IO.File]::WriteAllBytes("C:\calc.exe",$bytearray[0..($bytearray.Length - (1024 + 1))])
Теперь посчитаем хеш‑суммы и сравним полученные значения.
Как и ожидалось, хеш‑суммы бьются, значит, после восстановления содержимое файла изменено не было. Если с восстановлением удаленных с использованием del (Shift-Del) или erase файлов все понятно, то что происходит в системе, когда ты перемещаешь файл в корзину?
ВОССТАНОВЛЕНИЕ ФАЙЛОВ, УДАЛЕННЫХ ЧЕРЕЗ КОРЗИНУ
Сразу следует отметить, что перемещение файла в корзину не равно удалению: файл удаляется после очистки корзины (хоть это и очевидно, тем не менее упомянуть об этом стоит).
После перемещения файла в корзину в директории C:\$Recycle.Bin\<User’s SID>\
создается два файла c именами, которые начинаются с $I
и $R
и заканчиваются оригинальным расширением перемещенного в корзину файла. Даже с отображением скрытых файлов в директории нам не удастся посмотреть на созданные после перемещения в корзину файлы. Чтобы до них добраться, содержимое директории C:\$Recycle.Bin\<User’s SID>\
нужно переместить в специально созданную для исследования директорию (можно воспользоваться PowerShell-командлетом Copy-Item). После перемещения нам станут доступны следующие файлы:
$I<random>.original_extension
(далее —$I
) — файл с метаданными, создается, как только файл перемещается в корзину. Он используется для восстановления файла из корзины средствами Microsoft, подробнее разбор файла будет приведен ниже.$R<random>.original_extension
(далее —$R
) — файл, который создается после перемещения в корзину. Это копия перемещенного в корзину файла, о чем говорят одинаковое содержимое и хеш‑суммы (если сравнить оригинал до удаления и перемещенный файл). Изменяется только имя файла.
Разбор содержимого популярных файлов и файловых записей, а также структур таблиц MFT, MBR, GPT и файловых атрибутов встречается в публикациях на GitHub. Однако мне не удалось найти описание файла $I
с метаданными, поэтому давай вместе разберемся, что полезного можно обнаружить в таком файле.
В роли подопытного будет по‑прежнему выступать файл calc.exe
. Переместим его копию в корзину, после чего скопируем все содержимое из директории C:\$Recycle.Bin\<User’s SID>\*
в папку для исследования.
Ниже представлен разбор всех полей файла $IOWX1VN.exe
(был создан при перемещении калькулятора в корзину).
Здесь:
- желтый цвет — File Header, заголовок файла
$I
; - синий цвет — File Size (Bytes) — размер файла;
- красный цвет — атрибут
$SI ChangedTime
— время удаления, точнее, перемещения файла в корзину в формате UTC; - бирюзовый цвет — FileName Length — размер имени файла в байтах;
- пурпурный цвет — File Path — полный путь до файла, который был удален (перемещен в корзину).
У каждого пользователя своя $Recycle.Bin
(корзина). Более того, на каждом томе такая корзина создается по умолчанию. Грубо говоря, корзина из тома С пробрасывается в том F и другие созданные тома. Поэтому перед именем удаленного файла мы видим SID пользователя — зачастую это полезная информация о том, какой пользователь в системе переместил файл в корзину и выполнил удаление. Например, для файла calc.exe
полное имя файла будет выглядеть так:
F:\$RECYCLE.BIN\S-1-5-21-3457051395-4168275294-665325124-1000\$ROWX1VN.exe
либо
F:\$RECYCLE.BIN\S-1-5-21-3457051395-4168275294-665325124-1000\$IOWX1VN.exe
Кроме информации о том, кто удалил файл, мы получаем информацию о метках времени и о моменте, когда файл был перемещен в корзину. Также нам становится известен путь, по которому хранился файл до перемещения. Как изменяются метки времени, я рассказывал в теоретической части.
Теперь давай поговорим о восстановлении файла, который был удален через корзину. По алгоритму оно похоже на восстановление файлов, удаленных с помощью del или erase, поскольку для файловой системы нет разницы между файлами, удаленными из корзины или какой‑либо другой директории. По сути, система удаляет файлы так же, как при применении утилит del или erase, только предварительно переименовав файлы в $R<random>.extension и $I<random>.extension
— именно такие записи хранятся в главной файловой таблице $mft
.
Вероятно, у читателя возник вполне логичный вопрос: какой из двух файлов восстанавливать — $I
или $R
? Ведь при очистке корзины удалятся оба этих файла и их записи какое‑то время будут храниться в $mft
(до перезаписи другими данными).
Для восстановления удаленного файла нам нужны оба: и $R
, и $I
. Файл $R
требуется для восстановления содержимого, а файл $I
— для получения оригинального имени файла.
Единственный недостающий элемент при восстановлении — оригинальное имя, которое можно отыскать только в файле $I
. Но файл $I
резидентный, а это значит, что пришло время разобраться, как восстанавливать резидентные файлы.
Для начала мы также найдем ID файловой записи для файла $I
:
Get-ForensicFileRecord -VolumeName F:| where {$_.Deleted}
$file_record = Get-ForensicFileRecord -VolumeName F: -Index 44
После чего обратимся к атрибуту $DATA
по указанному ID и запишем содержимое атрибута в массив байтов:
$file_descriptor=$file_record.Attribute | Where-Object {$_.name -eq 'DATA'}
$Byte_Array = $file_descriptor.RawData
В $Byte_Array
хранится содержимое резидентного файла.
Структуру файла $I
мы уже разобрали и помним, что байты с 0 до 26 отводятся на файловый заголовок, размер файла в байтах, атрибут $SI Changed Time
и размер файлового имени. А начиная с индекса 27 до конца файла хранится полное имя файла (путь + имя). Зная эту информацию, мы можем получить недостающий элемент для восстановления нашего калькулятора.
Для этого мы декодируем значения массива с индекса 27 до конца массива в UTF8 и воспользуемся командлетом Split-Path, чтобы получить только имя файла:
$filename = Split-Path -Path ([System.Text.Encoding]::UTF8.GetString($Byte_Array[27..$Byte_Array.Length])) -Leaf
Все данные для восстановления нерезидентного файла $ROWX1VN.exe
у нас есть, и способ мы уже изучили, поэтому дело за малым. Готовый сценарий для восстановления удаленных через корзину файлов можно найти на GitHub.
ВЫВОДЫ
Восстановление удаленных файлов — задача довольно кропотливая и интересная. Хотя автоматизированных средств для этого предостаточно, лучше не доводить дело до потери данных. Чтобы обезопасить себя от утраты ценной информации, не забывай создавать бэкапы и хранить их по принципу 3-2-1.
Думаю, что с полученными знаниями читателю не составит труда разработать собственную программу для автоматизации процесса восстановления. Ведь, как писал Крис в своей книге, «настоящий хакер сам разрабатывает свой инструментарий».
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei