Marylin
27.11.2021, 01:10
FAT датируется аж 1977-годом и пришла к нам из проприетарного DOS. Благодаря простой реализации, сейчас это способ хранения данных на таких носителях как USB-брелки, карты-памяти телефонов и ЦФК, в EFI для хранения кода загрузчика, в системах реального времени RTOS, и многое другое. В данной статье рассматриваются гендерные признаки зоопарка FAT, и в какую сущность превратила эволюция конкретно взятую FAT32. Детальный её разбор позволит понять алгоритм восстановления удалённых файлов, а так-же подтолкнёт на создание своих утилит форматирования, с широким спектром логической геометрии. Из инструментов понадобится любой (способный работать с дисками) HEX-редактор типа WinHex или HxD, штатный калькулятор Win, испека на FATот Microsoft.
https://forum.antichat.xyz/attachments/4914108/img_5d0ea65b56.png
Оглавление:
1. Вводная часть;
2. Загрузочная запись и блок BPB – Bios Parametr Block;
3. Таблицы FAT – File Allocation Table;
4. Корневой каталог "RootDir";
5. Практика – разбор служебных структур;
6. Заключение.
1. Вводная часть
В основе проектирования операционных систем лежит работа с информацией. Здесь перед инженерами встают три задачи: во-первых определиться с типом накопителей (HDD, SSD, Flash), чтобы предоставить системе соответствующий драйвер. Второй момент – это способ хранения данных в виде файловых систем Ext, HPFS, FAT, NTFS и прочие. Но что ещё немаловажно, это предусмотреть возможность восстановления инфы в случае критических сбоев. Последний вопрос стоит ребром, что побудило Microsoft создать до блеска отшлифованную NTFS (New-Technology-File-System). Её мы не будем пока трогать, и отправив на скамейку запасных, рассмотрим только FAT.
1.1. Иерархия: диск[сектор] + том[кластер]
Для начала разберём геометрию накопителей.
• На нижнем уровне, производитель делит всю поверхность на 512-байтные сектора (отсчёт с единицы). Но утилиты и ОС могут предоставлять нам секторы > 512-байт, однако они по-любому будут кратны 512, например 1024-байт. Сектор – мин.порция обмена с диском, мы не сможем прочитать с него кол-во байт не кратное 512. Это нужно учитывать при вызове функций Read/WriteFile().
Самый первый сектор всегда отводится под MBR (MasterBootRecord), где указывается объём накопителя, и смещение к его логическому тому. Вместо устаревшей трехмерной CHS с лимитом в 8 ГБ, в современном мире используется уже линейный подход LBA48 (48-bit Logical Block Address), в которой секторы нумеруются с нуля, а не как прежде с единицы. LBA позволяет адресовать диск размером 128 ПетаБайт, при размере блока 512.
• ОС не может хранить файлы на дисках без логических разделов (том, volume, макс=26 с литерами от А: до Z. Значит нужно создать этот том, и установить на него какую-нибудь файловую систему (далее FS), чтобы ОС знала о порядке расположения файлов – эта операция известна как форматирование. Бинарные файлы можно сбрасывать и на "сырой" RAW-том, только за доступ к ним будем отвечать уже сами. Такой изврат применяется редко, например при работе с дисками без поддержки их со-стороны ОС (см.прерывание BIOS int-13h).
• Допустим, имеем первый раздел C:\. Его смещение от начала диска указывается в секторе(1) MBR. У всех накопителей, первый раздел начинается как-минимум с сектора(63). Если учесть, что непосредственно данные и код MBR занимают всего 1-сектор, следовательно перед началом тома в загашнике остаётся аж 62 свободных, а это: 62*512=31 КБ пространства, где можно скрыть от FS конфиденциальную инфу. Важно понять, что FS устанавливается не на диск, а на его логический том, поэтому драйвер считает началом тома LBA(0), хотя в глазах биоса это сектор(63).
Такой расклад создаёт путаницу, но достаточно запомнить, что функцией CreateFile() можно открыть как физ.диск (тогда LBA(0) указывает на его начало), а можно запросить и логический том диска, например по букве C:\. В этом случае LBA(0) считается уже указателем на начало раздела. То-есть всё зависит от того, какой объект мы открываем.
• Каждая запись в таблице FAT указывает на определённый блок-информации в области данных. Если указателями адресовать мелкие секторы диска, то во-первых нужно будет увеличить разрядность этих указателей до 64-бит (т.к. диски могут быть исполинских размеров), а во-вторых потребуется огромное кол-во самих линков, что повлияет на размер таблицы. Файлы до 512-байт встречаются редко, а значит нет смысла искать их в каждом секторе. По этой причине, несколько секторов всегда собираются в "кластер", что позволяет увеличить полезное пространство, за счёт уменьшения таблицы FAT.
C дефолтными настройками, все FS создают кластеры размером 4096-байт, а это 8-секторов. В служебной таблице FAT лежат теперь указатели не на секторы, а на кластеры, и соответственно размер её сокращается в 8-раз! Но помимо плюсов есть и минусы.. Если в txt-файле у нас 11-байтная строка "Hello World", на диске этот файл будет занимать 4 КБ, т.к. мин.блоком хранения данных уже получается кластер. Зайдите в свойства любого файла и посмотрите, какой он имеет реальный размер, и сколько ему выделено на диске:
https://forum.antichat.xyz/attachments/4914108/img_45c7360524.png
Как видим – картина удручающая, и при кластерном распределении у нас улетает в трубу огромная часть пространства. Но говорят, "случaйнaя удaчa принocит бoльшe рaдocти, чeм зaкoнoмeрный уcпex", и видимо инженеры с этим согласны. Здесь они вынуждены сесть на шпагат и только надеяться, что большинство файлов юзера будут размером больше 4КБ. Исключительно в этом случае экономия на размере таблицы оправдает себя.
Чтобы предотвратить суицид крайне впечатлительных юзверей, инженеры построили себе хату-с-краю, и предложили нам самостоятельно выбирать кол-во секторов в кластере – данный параметр имеется у большинства приличных утилит форматирования и называется "SectorPerCluster". Если у вас много мелких файлов и вы считаете 4КБ для хранения каждого из них слишком расточительным, то всегда можете изменить размер кластера в диапазоне от 512-байт, до 64КБ. Вот скрин одной из таких утилит "Fat32format":
https://forum.antichat.xyz/attachments/4914108/img_0bddc6a9e8.png
1.2. ОрганизацияFAT– вид сверху
На рис.ниже представлены основные отличия FAT12/16, от более совершенной FAT32.
Здесь видно, что раньше, в системной области четыре служебные структуры располагались плотно друг-к-другу: это блок параметров BPB с адресом LBA(0) (не путать с MBR), далее основная таблица FAT, следом её резервная копия FAT2, и в хвосте корневой каталог. Размер таблицы зависит от общего кол-ва секторов в накопителе. Очевидно, что чем больше размер диска, тем больше в нём кластеров, и соответственно больше указателей на кластеры в таблице FAT.
Теперь посмотрим на организацию FAT32 справа..
Корневой каталог уже съехал с насиженных мест и может находится где угодно. Драйвер FS считает его обычным файлом с доступом на запись, что даёт возможность каталогу RootDir расширяться динамически. Кроме того, блок BPB разбух с одного до трёх секторов, хотя в FSInfo нет ничего интересного, а последний отправлен в резерв для наших потомков. Причём имеется и зеркало ВРВ, всегда по адресу LBA(6). В доке на FAT32 эту область назвали "Резервные сектора", сразу после которых расположилась уже таблица FAT со-своей копией. Благодаря такой организации, драйвер FS может восстанавливать теперь не только таблицу FAT, но и загрузочный сектор ОС в блоке параметров BPB.
https://forum.antichat.xyz/attachments/4914108/img_74fbb6c1d8.png
2. Загрузочная запись и блокBPB – Bios ParametrBlock
В самом начале тома с адресом LBA(0), во всех версиях FAT лежит блок параметров биос ВРВ. Он занимает ровно один 512-байтный сектор и содержит в себе базовые параметры файловой системы, а так-же код загрузчика ОС с сигнатурой 0x55AA. Запустим HEX-редактор и через меню "Инструменты" откроем в нём свою флэш – вот как это выглядит у меня:
https://forum.antichat.xyz/attachments/4914108/img_3a17b57723.png
Загрузчик нас не интересует, ..более того, если Flash не загрузочная, Boot-block вообще забит нулями, и это часто практикуется (например на microSD телефонов). Зато ВРВ критически важен для драйвера, посколько именно в нём зарыта инфа о расположении оставшихся трёх/системных структур – это парочка FAT, и корневой каталог RootDir. Для программного доступа к этому блоку я создал инклуд, и поместил в него структуру ВРВ с описанием каждого поля. Здесь-же перечислю только те поля, которые откроют нам доступ к FAT и корневому каталогу:
https://forum.antichat.xyz/attachments/4914108/img_14ed5888e1.png
В спеке на FAT32 от Microsoft говорится, что положение корневого каталога "RootDir" строго не регламентируется, и он может лежать в произвольном кластере области данных. Однако на практике, Root в моих тестах всегда расположен сразу после двух таблиц FAT, т.е. адрес его фиксирован. Не знаю, может и есть в природе отбившиеся от стада утилиты форматирования, но лично мне таковые ещё не встречались.
В любом случае, найти каталог можно по приведённой выше формуле, и если это штатный форматер Win (как и большинство других), то вторая часть формулы
(RootCluster-2) * SectorPerCluster
будет всегда возвращать нуль. Тогда получается, что каталог лежит сразу после резервных секторов + размеры двух таблиц FAT (см.первый скрин FAT16). А вообще, жить без идеологии и надеяться на случай не есть гуд, а потому лучше использовать полную формулу, тем-более что мягкие уже грозно предупредили нас об этом.
3. ТаблицаFAT – File AllocationTable
Теперь выйдем из матрицы, и поговорим о более насущном..
Организация любой файловой системы – невероятно сложный механизм. Однако в случае с FAT, это утверждение сильно притянуто за-уши. Именно благодаря свой простоте она дожила до наших дней и не собирается уходить со-сцены, а наоборот мутировала в 64-битную ExFAT.
Значит чтобы получить указатель на таблицу, нужно прочитать поле "RsvdSectorCount" структуры ВРВ. В данном случае там прописан сектор(1000), и если умножить его на 512, получим смещение в байтах от начала тома = 0x0007D000. Далее, читаем из той-же ВРВ поле "FATSz32" с размером, и передаём оба полученных значения функции ReadFile(). Так, в приёмном буфере функции получим дамп таблицы FAT, для её детального разбора.
Таблица – это массив указателей на кластеры, без каких-либо служебных полей. Отсчёт кластеров в массиве начинается с нуля, а разрядность указателей 32-бит (отсюда и название FAT). Старшие 4-бита отправлены в резерв, остаётся 28. Тогда получается, что FAT32 может адресовать всего:
2^28=268.435.456
кластеров, и если размер каждого из них 4096-байт, то макс.объём диска = 1ТБ. Однако это теоретический предел, ведь всегда можно увеличить размер кластера в 2,4,8 и даже в 16-раз, при этом оставив 28-битные линки. Так-что на практике становится доступным диск 16ТБ, а если требуется больше, нужно переходить на ExFAT, в которой разрядность указателей расширена до 64-бит.
На рисунке ниже представлен фрагмент таблицы FAT (для удобства, в меню "Вид" сгруппируйте отображение по 4-байта).
• Два/первых элемента массива всегда равны
0x0FFFFFF8
+
0xFFFFFFFF
, и не используются в качестве указателей на кластеры – это просто сигнатура таблицы, а байт(F8) определяет "MediaType" (варианты: F0=Floppy, F8=Hard, FA=Ram). Когда блок ВРВ и его резервная копия безвозвратно утеряны (бэд-сектор или вирь), по данной сигнатуре можно будет найти таблицу, чтобы попытаться спасти информацию.
https://forum.antichat.xyz/attachments/4914108/img_c42ebeb18c.png
• 32-битные элементы начиная со-смещения(8), отображают уже состояние соответствующих кластеров в области-данных диска. Он может быть свободным(0), выделенным(2), зарезервированным(F6) или повреждённым(F7). Элемент с макс.значением
0x0FFFFFFF
является маркером последнего кластера файла EOC (EndOfCluster). Если файл большой и занимает несколько кластеров, указатели связываются в цепочку до тех пор, пока не встретится этот маркер (выделены красным).
• Древовидная структура расположения файлов на диске, начинается с корневого каталога – мы не видим его среди привычных нам папок, но он присутствует всегда. Ему назначается имя в виде "метки тома", которую запрашивает у нас система, при форматировании раздела. Именно поэтому второй элемент таблицы по смещению(8) имеет маркер окончания
0x0FFFFFFF
, т.к. под корневой каталог отводится всего один кластер(2) (см.структуру ВРВ-->RootCluster).
Чтобы продемонстрировать организацию таблицы FAT, я форматнул свою флэш и сбросил на неё всего два файла. Первый размером 7.4КБ, поэтому занимает на диске два 4К-байтных кластера (выделен зелёным), а второй размером 20.2КБ и как видим, загребает 5 (выделенных синим) кластеров. Далее следуют null-элементы, которые информируют о свободном пространстве.
Обратите внимание, что после кластера(2), следующий элемент таблицы указывает сразу на кластер
0х00000004
. Это-же касается и записи после маркера EOC по смещению(14h) – перепрыгнув через один, она линкует сразу на кластер(6). Здесь возникает вопрос: куда подевались кластеры 3 и 5, ведь значения
0х0FFFFFFF
это не указатели, а просто маркеры окончания цепочки?
Дело в том, что в таблице FAT хранятся указатели на следующие кластеры файла, а первый – прописывается в корневом каталоге RootDir, где лежит полный паспорт файла. То-есть когда файл меньше 4096-байт, выделенный под него первый кластер указывается в корневом каталоге, а в таблице получим лишь связанный с ним EOC. Но если файл выходит за границы одного кластера, то следующие указываются уже в таблице FAT, пока не встретится маркер окончания.
4. Корневой каталог "RootDirectory"
Файловая система требует базы, где хранилась-бы информация о всех, существующих на диске, файлах. Для FAT32(16) такой базой является "корневой каталог". Чтобы описать свойства одного файла, инженерам потребовался блок размером в 32-байта, где можно найти: имя файла/каталога в формате 8.3, его размер, дату создания, атрибуты, а так-же номер первого кластера, с которого начинается данный файл. Если-же хвост файла торчит снаружи выделенного кластера, номер следующего указывается уже в таблице FAT. Иначе, в таблице будет прописан только маркер окончания EOC. Таким образом, таблица и корневой-каталог логически связаны между собой, и потеря любого из них приведёт к краху всей FS.
В доках, 32-байтную запись каталога назвали SFN, что подразумевает "Short File Name". Короткая (short) она потому, что хранит имя в досовском формате(8.3). Но в современном мире мы уже отвыкли от всевозможных рамок, и предпочитаем для файлов более длинные имена, которые Win всё-же ограничивает до 256-символов. Поэтому в FAT предусмотрена родственная к SFN структура LFN (Long) – она имеет такой-же размер 32-байт, но является не самостоятельной, а дополнением к SFN. Если в имени файла меньше 8-ми символов, структура LFN может вообще отсутствовать, но SFN в наличии всегда.
Нужно признать, что организация длинных имён в FAT выполнена через известное место. Так, каждая структура LFN несёт в себе макс. 13-символов имени файла, а если их больше, то добавляется ещё одна структура и получаем 26-символов. Если-же и этого мало, то подключается сл.структура и т.д. Причём имя задаётся в Unicode (где на каждый символ расходуются по 2-байта) и внутри одной структуры разбросано по трём/разным полям. Более того, порядок следования цепочки структур обратный – т.е. сначала идёт последняя LFN(x), после неё LFN(x-1) и далее в том-же духе. Общее кол-во структур указывается в первом байте "Ordinal". Здесь становится очевидным, что разработчик явно был под веществами.
Выше упоминалось, что LBA каталога можно найти по формуле из ВРВ:
RsvdSecCount + (FATSz*2) + ((RootCluster-2) * SecPerCluster)
Для своей флэш я получил LBA=16384, и прыгнув на него в редакторе, попал в начало каталога.
Проясним некоторые моменты..
1. Первая структура SFN (синий блок) будет всегда описывать корневую папку диска. Это единственная структура, которой может быть присвоен атрибут "VolumeID" со-значением 08h. Атрибуты файлов/каталогов лежат по смещению(0Вh) от начала структур, и я заключил их в синий овал. Все остальные поля первой структуры SFN считаются не действительными!
2. На моей флэш всего два файла с именами "dCrypt.asm" и "usbVIDbase.txt". Чем руководствуется драйвер FAT при добавлении к SFN структуры LFN остаётся загадкой, поскольку имя первого файла меньше 8-ми символов, но дров всё-равно вставил LFN (см.красный блок). А вот имя второго файла уже 10-символов, и для него выделено две LFN в зелёном блоке. Обратите внимание, что у первой в поле-ординала лежит значение(1) (+40h маска), а у второй LFN ординал равен(2). Эти поля я выделил чёрным, и они определяют кол-во структур LFN для текущего файла. Записи LFN можно обнаружить по атрибуту(0F).
https://forum.antichat.xyz/attachments/4914108/img_dc4df09040.png
Из чёрной таблицы с расшифровкой полей видно, что номер первого кластера разделён на две части Hi/Low. Первый файл "dCrypt.asm" размером 7.4КБ и начинается с кластера(3). Но поскольку он требует два 4К-байтных, следующий указывается уже в таблице FAT (см.зелёный dword на предыдущем скрине). Здесь нужно отметить, что номера кластеров представляются относительно начала области-данных, которая совпадает с началом рассматриваемого корневого-каталога RootDir.
Так как-же найти злополучный файл на диске?
Для этого в спеке FAT32 приведена формула такого вида:
FileFirstCluster = RootDir + (SFN.FstClus - RootCluster)
Если учесть, что в стандартной реализации FAT32 поле "RootCluster" в структуре ВРВ всегда равно 2, то для первого файла получаем следующий сектор LBA:
Код:
RootDir = сектор 16384
SFN.FstClus = кластер(3)
Формула: 3-2 = кластер(1) = 8 секторов
-----------------------------------------
FileFirstClus = 16384 + 8 = сектор(16392)
https://forum.antichat.xyz/attachments/4914108/img_3105408fdf.png
4.1. Алгоритм поиска удалённых файлов
Когда мы удаляем файл с диска FAT, он фактически остаётся на своём месте и так-же занимает свой кластер. Просто в соответствующей структуре SFN, первому байту присваивается маркер(Е5). Аналогичную ситуацию наблюдаем и в случае "Быстрого форматирования" тома. Такой расклад способствует восстановлению удалённых файлов. Чтобы создать их список, достаточно в корневом-каталоге с шагом в 32, проверять каждый/первый байт на маркер(Е5).
В качестве доказательства, я удалил со-своей флэш "usbVIDbase.txt", а второй файл оставил. В результате, SFN удалённого файла(и две предшествующие ей LFN) приобрели вид как на скрине ниже, а причастные к файлу элементы таблицы FAT сбросились в нуль. т.е. кластеры освободились для последующей записи. Таким образом, информация в кластерах удалённых файлов будет доступна нам до тех пор, пока драйвер FS не перезапишет их новыми данными.
https://forum.antichat.xyz/attachments/4914108/img_ca849818c2.png
5. Практика – разбор служебных структур
Теперь попробуем автоматизировать весь изложенный процесс..
Код ниже покажет основную информацию об USB-Flash или карте-памяти, если они имею формат FAT32.
Здесь есть нюанс, на который советую обратить внимание..
Значит получаем букву диска, и проверяем его на съёмный носитель "Removable". Если ок, то открываем раздел диска через CreateFile(), и пытаемся прочитать с него сектор ВРВ. Суть в том, что читать рекомендуется в асинхронном режиме, поскольку том может быть занят кем-то другим, например антивирусом. Если функцией ReadFile() читать синхронно, то функция может вернуть ошибку, мол диск недоступен. Поэтому мы используем асинхронный режим, в котором запрос ставится в очередь, а нам после вызова необходимо выждать пару секунд при помощи Sleep(). Этот способ требует в параметрах ReadFile() указатель на структуру "OVERLAPPED" и никогда не даёт осечек.
C-подобный:
format pe console
include
'win32ax.inc'
include
'equates\fat.inc'
entry start
;
//----------
.
data
fatBuff rb
512
*
3
ntName db
'\\?\GLOBALROOT'
dosName db
32
dup
(
0
)
usbHndl dd
0
align
16
fpuRes0 dq
0
fpuRes1 dq
0
fpuRes2 dq
0
secSize dd
0
clsSize dd
0
kByte dd
1024
mByte dd
1024
*
1024
gByte dd
1024
*
1024
*
1024
ol OVERLAPPED
rootDir dd
0
dumpByte dd
16
dumpLen db
8
dumpStr db
16
dup
(
'.'
)
,
0
buff db
0
;
//----------
.
code
start
:
invoke SetConsoleTitle
,
;
//---- Проверим наличие клиента в портах USB
invoke GetLogicalDriveStringsA
,
64
,
buff
mov esi
,
buff
@@
:
push esi
invoke GetDriveType
,
esi
;
// запрашиваем тип драйва..
pop esi
cmp eax
,
DRIVE_REMOVABLE
;
// извлекаемое устройство?
je @ok
add esi
,
4
cmp byte
[
esi
]
,
0
;
// последнее в списке?
jne @b
cinvoke printf
,
jmp @exit
;
//---- Получаем имя и открываем девайс на асинх.чтение
@ok
:
push esi
cinvoke printf
,
,
esi
pop esi
mov byte
[
esi
+
2
]
,
0
invoke QueryDosDevice
,
esi
,
dosName
,
32
cinvoke printf
,
,
ntName
invoke CreateFile
,
ntName
,
GENERIC_READ
+
GENERIC_WRITE
,
\
FILE_SHARE_READ
+
FILE_SHARE_WRITE
,
\
0
,
OPEN_EXISTING
,
\
FILE_FLAG_OVERLAPPED
+
FILE_ATTRIBUTE_NORMAL
,
0
mov
[
usbHndl
]
,
eax
;
//---- Читаем структуру "Bios Parametr Block"
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
;
//---- Проверим ф.систему на FAT32, иначе ошибка
mov esi
,
fatBuff
cmp word
[
esi
+
BPB
.
FATSz16
]
,
0
je @f
cinvoke printf
,
jmp @exit
;
//---- Есть FAT32! - собираем инфу..
@@
:
mov eax
,
BPB_32
.
FilSysType
add eax
,
esi
movzx ebx
,
[
esi
+
BPB_32
.
Header
.
BytePerSector
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
SecPerCluster
]
mov edx
,
ecx
imul ecx
,
ebx
mov
[
secSize
]
,
ebx
mov
[
clsSize
]
,
ecx
shr ecx
,
10
cinvoke printf
,
,
\
eax
,
ebx
,
edx
,
ecx
;
//------------------------
mov esi
,
fatBuff
mov eax
,
[
esi
+
BPB_32
.
Header
.
HiddSec
]
mov ebx
,
[
esi
+
BPB_32
.
Header
.
TotSec32
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
mov edx
,
ecx
shl edx
,
9
shr edx
,
10
push
0
ebx
fild qword
[
esp
]
fimul
[
secSize
]
fidiv
[
gByte
]
fstp
[
fpuRes0
]
add esp
,
8
cinvoke printf
,
,
\
eax
,
ebx
,
dword
[
fpuRes0
]
,
dword
[
fpuRes0
+
4
]
,
ecx
,
edx
;
//------------------------
mov esi
,
fatBuff
movzx eax
,
[
esi
+
BPB_32
.
Header
.
NumFats
]
movzx ebx
,
[
esi
+
BPB_32
.
BkBootSec
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
mov edx
,
[
esi
+
BPB_32
.
FATSz32
]
imul edx
,
[
secSize
]
shr edx
,
10
cinvoke printf
,
,
\
eax
,
ebx
,
ecx
,
[
esi
+
BPB_32
.
FATSz32
]
,
edx
;
//------------------------
;
// RootDir = (RsvdSecCount + (FATSz32 * NumFats)) + (RootCluster - 2) * SecPerCluster
mov esi
,
fatBuff
mov eax
,
[
esi
+
BPB_32
.
FATSz32
]
movzx ebx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
shl eax
,
1
add eax
,
ebx
;
// RsvdSecCount + (FATSz32 * NumFats)
mov ebx
,
[
esi
+
BPB_32
.
RootCluster
]
sub ebx
,
2
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
SecPerCluster
]
imul ebx
,
ecx
;
// (RootCluster - 2) * SecPerCluster
add eax
,
ebx
mov
[
rootDir
]
,
eax
cinvoke printf
,
,
\
[
esi
+
BPB_32
.
RootCluster
]
,
eax
;
//------------------------
mov esi
,
fatBuff
mov eax
,
BPB_32
.
Header
.
OemName
mov ebx
,
BPB_32
.
VolLabel
mov ecx
,
[
esi
+
BPB_32
.
VolID
]
movzx edx
,
cx
shr ecx
,
16
add eax
,
esi
add ebx
,
esi
cinvoke printf
,
,
eax
,
ebx
,
ecx
,
edx
;
//---- Читаем таблицу FAT, и выводим фрагмент её дампа
xor al
,
al
mov ecx
,
sizeof
.
OVERLAPPED
mov edi
,
ol
rep stosb
mov esi
,
fatBuff
movzx eax
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
shl eax
,
9
mov
[
ol
.
Offset
]
,
eax
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
cinvoke printf
,
call PrintDump
;
//---- Читаем корневой каталог, и так-же дампим фрагмент
xor al
,
al
mov ecx
,
sizeof
.
OVERLAPPED
mov edi
,
ol
rep stosb
mov eax
,
[
rootDir
]
shl eax
,
9
mov
[
ol
.
Offset
]
,
eax
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
cinvoke printf
,
call PrintDump
@exit
:
invoke CloseHandle
,
[
usbHndl
]
cinvoke _getch
cinvoke exit
,
0
;
//------------------------------------------
;
// Вспомогательная процедура - выводит дамп
;
//------------------------------------------
PrintDump
:
mov esi
,
fatBuff
@next
:
mov edi
,
dumpStr
xor ebx
,
ebx
@prn
:
xor eax
,
eax
lodsb
push ebx eax
cinvoke printf
,
,
eax
pop eax
cmp al
,
20
h
jb @f
mov byte
[
edi
]
,
al
@@
:
inc edi
pop ebx
inc ebx
cmp ebx
,
4
jb @f
cinvoke printf
,
xor ebx
,
ebx
@@
:
dec
[
dumpByte
]
jnz @prn
push esi
mov
[
dumpByte
]
,
16
invoke CharToOem
,
dumpStr
,
dumpStr
cinvoke printf
,
,
dumpStr
mov eax
,
'....'
mov edi
,
dumpStr
mov ecx
,
4
rep stosd
cinvoke printf
,
pop esi
dec
[
dumpLen
]
jnz @next
mov
[
dumpLen
]
,
8
ret
;
//----------
section
'.idata'
import data readable
library msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
,
user32
,
'user32.dll'
include
'api\msvcrt.inc'
include
'api\kernel32.inc'
include
'api\user32.inc'
https://forum.antichat.xyz/attachments/4914108/img_84f5aceafb.png
Во-втором случае я решил поинтересоваться картой-памяти своего телефона и обнаружил, что размер кластера у неё 32КБ, поэтому и таблица FAT намного меньшего размера: была 7.692 секторов в длину, а стала 951. Более того, и сам том начинается не как обычно с физ.сектора(63), а съехал аж к сектору(8192), о чём говорит поле "Volume First LBA". В такой организации пространства есть смысл, т.к. нам заранее известны ориентировочные размеры файлов – это фотки, мультимедиа и прочие, т.е. больше стандартного кластера 4КБ:
https://forum.antichat.xyz/attachments/4914108/img_bf48b08ad1.png
6. Заключение
Если не брать во-внимание детали оформления служебных структур, основным недостатком FAT32 является полное отсутствие "инстинкта самосохранения" при критических сбоях. Хорошо хоть инженеры предусмотрели некую пародию, в виде резервных блоков ВРВ и таблицы FAT2. Зато в NTFS всё продумано до мелочей, и в этом большое её преимущество. Например метафайл NTFS под названием "$LogFile" представляет собой журнал последней удачной конфигурации. Если случается крах, то при следующей перезагрузке, все параметры считываются уже из этого журнала, который драйвер NTFS обновляет при обычном выключении компьютера. Однако выбор у пользователей должен быть всегда, и FAT как-раз нам его предоставляет.
В скрепке лежит инклуд, и ехе-файл для тестов. Увидимся теперь на пороге Нового года, пока!
https://forum.antichat.xyz/attachments/4914108/img_5d0ea65b56.png
Оглавление:
1. Вводная часть;
2. Загрузочная запись и блок BPB – Bios Parametr Block;
3. Таблицы FAT – File Allocation Table;
4. Корневой каталог "RootDir";
5. Практика – разбор служебных структур;
6. Заключение.
1. Вводная часть
В основе проектирования операционных систем лежит работа с информацией. Здесь перед инженерами встают три задачи: во-первых определиться с типом накопителей (HDD, SSD, Flash), чтобы предоставить системе соответствующий драйвер. Второй момент – это способ хранения данных в виде файловых систем Ext, HPFS, FAT, NTFS и прочие. Но что ещё немаловажно, это предусмотреть возможность восстановления инфы в случае критических сбоев. Последний вопрос стоит ребром, что побудило Microsoft создать до блеска отшлифованную NTFS (New-Technology-File-System). Её мы не будем пока трогать, и отправив на скамейку запасных, рассмотрим только FAT.
1.1. Иерархия: диск[сектор] + том[кластер]
Для начала разберём геометрию накопителей.
• На нижнем уровне, производитель делит всю поверхность на 512-байтные сектора (отсчёт с единицы). Но утилиты и ОС могут предоставлять нам секторы > 512-байт, однако они по-любому будут кратны 512, например 1024-байт. Сектор – мин.порция обмена с диском, мы не сможем прочитать с него кол-во байт не кратное 512. Это нужно учитывать при вызове функций Read/WriteFile().
Самый первый сектор всегда отводится под MBR (MasterBootRecord), где указывается объём накопителя, и смещение к его логическому тому. Вместо устаревшей трехмерной CHS с лимитом в 8 ГБ, в современном мире используется уже линейный подход LBA48 (48-bit Logical Block Address), в которой секторы нумеруются с нуля, а не как прежде с единицы. LBA позволяет адресовать диск размером 128 ПетаБайт, при размере блока 512.
• ОС не может хранить файлы на дисках без логических разделов (том, volume, макс=26 с литерами от А: до Z. Значит нужно создать этот том, и установить на него какую-нибудь файловую систему (далее FS), чтобы ОС знала о порядке расположения файлов – эта операция известна как форматирование. Бинарные файлы можно сбрасывать и на "сырой" RAW-том, только за доступ к ним будем отвечать уже сами. Такой изврат применяется редко, например при работе с дисками без поддержки их со-стороны ОС (см.прерывание BIOS int-13h).
• Допустим, имеем первый раздел C:\. Его смещение от начала диска указывается в секторе(1) MBR. У всех накопителей, первый раздел начинается как-минимум с сектора(63). Если учесть, что непосредственно данные и код MBR занимают всего 1-сектор, следовательно перед началом тома в загашнике остаётся аж 62 свободных, а это: 62*512=31 КБ пространства, где можно скрыть от FS конфиденциальную инфу. Важно понять, что FS устанавливается не на диск, а на его логический том, поэтому драйвер считает началом тома LBA(0), хотя в глазах биоса это сектор(63).
Такой расклад создаёт путаницу, но достаточно запомнить, что функцией CreateFile() можно открыть как физ.диск (тогда LBA(0) указывает на его начало), а можно запросить и логический том диска, например по букве C:\. В этом случае LBA(0) считается уже указателем на начало раздела. То-есть всё зависит от того, какой объект мы открываем.
• Каждая запись в таблице FAT указывает на определённый блок-информации в области данных. Если указателями адресовать мелкие секторы диска, то во-первых нужно будет увеличить разрядность этих указателей до 64-бит (т.к. диски могут быть исполинских размеров), а во-вторых потребуется огромное кол-во самих линков, что повлияет на размер таблицы. Файлы до 512-байт встречаются редко, а значит нет смысла искать их в каждом секторе. По этой причине, несколько секторов всегда собираются в "кластер", что позволяет увеличить полезное пространство, за счёт уменьшения таблицы FAT.
C дефолтными настройками, все FS создают кластеры размером 4096-байт, а это 8-секторов. В служебной таблице FAT лежат теперь указатели не на секторы, а на кластеры, и соответственно размер её сокращается в 8-раз! Но помимо плюсов есть и минусы.. Если в txt-файле у нас 11-байтная строка "Hello World", на диске этот файл будет занимать 4 КБ, т.к. мин.блоком хранения данных уже получается кластер. Зайдите в свойства любого файла и посмотрите, какой он имеет реальный размер, и сколько ему выделено на диске:
https://forum.antichat.xyz/attachments/4914108/img_45c7360524.png
Как видим – картина удручающая, и при кластерном распределении у нас улетает в трубу огромная часть пространства. Но говорят, "случaйнaя удaчa принocит бoльшe рaдocти, чeм зaкoнoмeрный уcпex", и видимо инженеры с этим согласны. Здесь они вынуждены сесть на шпагат и только надеяться, что большинство файлов юзера будут размером больше 4КБ. Исключительно в этом случае экономия на размере таблицы оправдает себя.
Чтобы предотвратить суицид крайне впечатлительных юзверей, инженеры построили себе хату-с-краю, и предложили нам самостоятельно выбирать кол-во секторов в кластере – данный параметр имеется у большинства приличных утилит форматирования и называется "SectorPerCluster". Если у вас много мелких файлов и вы считаете 4КБ для хранения каждого из них слишком расточительным, то всегда можете изменить размер кластера в диапазоне от 512-байт, до 64КБ. Вот скрин одной из таких утилит "Fat32format":
https://forum.antichat.xyz/attachments/4914108/img_0bddc6a9e8.png
1.2. ОрганизацияFAT– вид сверху
На рис.ниже представлены основные отличия FAT12/16, от более совершенной FAT32.
Здесь видно, что раньше, в системной области четыре служебные структуры располагались плотно друг-к-другу: это блок параметров BPB с адресом LBA(0) (не путать с MBR), далее основная таблица FAT, следом её резервная копия FAT2, и в хвосте корневой каталог. Размер таблицы зависит от общего кол-ва секторов в накопителе. Очевидно, что чем больше размер диска, тем больше в нём кластеров, и соответственно больше указателей на кластеры в таблице FAT.
Теперь посмотрим на организацию FAT32 справа..
Корневой каталог уже съехал с насиженных мест и может находится где угодно. Драйвер FS считает его обычным файлом с доступом на запись, что даёт возможность каталогу RootDir расширяться динамически. Кроме того, блок BPB разбух с одного до трёх секторов, хотя в FSInfo нет ничего интересного, а последний отправлен в резерв для наших потомков. Причём имеется и зеркало ВРВ, всегда по адресу LBA(6). В доке на FAT32 эту область назвали "Резервные сектора", сразу после которых расположилась уже таблица FAT со-своей копией. Благодаря такой организации, драйвер FS может восстанавливать теперь не только таблицу FAT, но и загрузочный сектор ОС в блоке параметров BPB.
https://forum.antichat.xyz/attachments/4914108/img_74fbb6c1d8.png
2. Загрузочная запись и блокBPB – Bios ParametrBlock
В самом начале тома с адресом LBA(0), во всех версиях FAT лежит блок параметров биос ВРВ. Он занимает ровно один 512-байтный сектор и содержит в себе базовые параметры файловой системы, а так-же код загрузчика ОС с сигнатурой 0x55AA. Запустим HEX-редактор и через меню "Инструменты" откроем в нём свою флэш – вот как это выглядит у меня:
https://forum.antichat.xyz/attachments/4914108/img_3a17b57723.png
Загрузчик нас не интересует, ..более того, если Flash не загрузочная, Boot-block вообще забит нулями, и это часто практикуется (например на microSD телефонов). Зато ВРВ критически важен для драйвера, посколько именно в нём зарыта инфа о расположении оставшихся трёх/системных структур – это парочка FAT, и корневой каталог RootDir. Для программного доступа к этому блоку я создал инклуд, и поместил в него структуру ВРВ с описанием каждого поля. Здесь-же перечислю только те поля, которые откроют нам доступ к FAT и корневому каталогу:
https://forum.antichat.xyz/attachments/4914108/img_14ed5888e1.png
В спеке на FAT32 от Microsoft говорится, что положение корневого каталога "RootDir" строго не регламентируется, и он может лежать в произвольном кластере области данных. Однако на практике, Root в моих тестах всегда расположен сразу после двух таблиц FAT, т.е. адрес его фиксирован. Не знаю, может и есть в природе отбившиеся от стада утилиты форматирования, но лично мне таковые ещё не встречались.
В любом случае, найти каталог можно по приведённой выше формуле, и если это штатный форматер Win (как и большинство других), то вторая часть формулы
(RootCluster-2) * SectorPerCluster
будет всегда возвращать нуль. Тогда получается, что каталог лежит сразу после резервных секторов + размеры двух таблиц FAT (см.первый скрин FAT16). А вообще, жить без идеологии и надеяться на случай не есть гуд, а потому лучше использовать полную формулу, тем-более что мягкие уже грозно предупредили нас об этом.
3. ТаблицаFAT – File AllocationTable
Теперь выйдем из матрицы, и поговорим о более насущном..
Организация любой файловой системы – невероятно сложный механизм. Однако в случае с FAT, это утверждение сильно притянуто за-уши. Именно благодаря свой простоте она дожила до наших дней и не собирается уходить со-сцены, а наоборот мутировала в 64-битную ExFAT.
Значит чтобы получить указатель на таблицу, нужно прочитать поле "RsvdSectorCount" структуры ВРВ. В данном случае там прописан сектор(1000), и если умножить его на 512, получим смещение в байтах от начала тома = 0x0007D000. Далее, читаем из той-же ВРВ поле "FATSz32" с размером, и передаём оба полученных значения функции ReadFile(). Так, в приёмном буфере функции получим дамп таблицы FAT, для её детального разбора.
Таблица – это массив указателей на кластеры, без каких-либо служебных полей. Отсчёт кластеров в массиве начинается с нуля, а разрядность указателей 32-бит (отсюда и название FAT). Старшие 4-бита отправлены в резерв, остаётся 28. Тогда получается, что FAT32 может адресовать всего:
2^28=268.435.456
кластеров, и если размер каждого из них 4096-байт, то макс.объём диска = 1ТБ. Однако это теоретический предел, ведь всегда можно увеличить размер кластера в 2,4,8 и даже в 16-раз, при этом оставив 28-битные линки. Так-что на практике становится доступным диск 16ТБ, а если требуется больше, нужно переходить на ExFAT, в которой разрядность указателей расширена до 64-бит.
На рисунке ниже представлен фрагмент таблицы FAT (для удобства, в меню "Вид" сгруппируйте отображение по 4-байта).
• Два/первых элемента массива всегда равны
0x0FFFFFF8
+
0xFFFFFFFF
, и не используются в качестве указателей на кластеры – это просто сигнатура таблицы, а байт(F8) определяет "MediaType" (варианты: F0=Floppy, F8=Hard, FA=Ram). Когда блок ВРВ и его резервная копия безвозвратно утеряны (бэд-сектор или вирь), по данной сигнатуре можно будет найти таблицу, чтобы попытаться спасти информацию.
https://forum.antichat.xyz/attachments/4914108/img_c42ebeb18c.png
• 32-битные элементы начиная со-смещения(8), отображают уже состояние соответствующих кластеров в области-данных диска. Он может быть свободным(0), выделенным(2), зарезервированным(F6) или повреждённым(F7). Элемент с макс.значением
0x0FFFFFFF
является маркером последнего кластера файла EOC (EndOfCluster). Если файл большой и занимает несколько кластеров, указатели связываются в цепочку до тех пор, пока не встретится этот маркер (выделены красным).
• Древовидная структура расположения файлов на диске, начинается с корневого каталога – мы не видим его среди привычных нам папок, но он присутствует всегда. Ему назначается имя в виде "метки тома", которую запрашивает у нас система, при форматировании раздела. Именно поэтому второй элемент таблицы по смещению(8) имеет маркер окончания
0x0FFFFFFF
, т.к. под корневой каталог отводится всего один кластер(2) (см.структуру ВРВ-->RootCluster).
Чтобы продемонстрировать организацию таблицы FAT, я форматнул свою флэш и сбросил на неё всего два файла. Первый размером 7.4КБ, поэтому занимает на диске два 4К-байтных кластера (выделен зелёным), а второй размером 20.2КБ и как видим, загребает 5 (выделенных синим) кластеров. Далее следуют null-элементы, которые информируют о свободном пространстве.
Обратите внимание, что после кластера(2), следующий элемент таблицы указывает сразу на кластер
0х00000004
. Это-же касается и записи после маркера EOC по смещению(14h) – перепрыгнув через один, она линкует сразу на кластер(6). Здесь возникает вопрос: куда подевались кластеры 3 и 5, ведь значения
0х0FFFFFFF
это не указатели, а просто маркеры окончания цепочки?
Дело в том, что в таблице FAT хранятся указатели на следующие кластеры файла, а первый – прописывается в корневом каталоге RootDir, где лежит полный паспорт файла. То-есть когда файл меньше 4096-байт, выделенный под него первый кластер указывается в корневом каталоге, а в таблице получим лишь связанный с ним EOC. Но если файл выходит за границы одного кластера, то следующие указываются уже в таблице FAT, пока не встретится маркер окончания.
4. Корневой каталог "RootDirectory"
Файловая система требует базы, где хранилась-бы информация о всех, существующих на диске, файлах. Для FAT32(16) такой базой является "корневой каталог". Чтобы описать свойства одного файла, инженерам потребовался блок размером в 32-байта, где можно найти: имя файла/каталога в формате 8.3, его размер, дату создания, атрибуты, а так-же номер первого кластера, с которого начинается данный файл. Если-же хвост файла торчит снаружи выделенного кластера, номер следующего указывается уже в таблице FAT. Иначе, в таблице будет прописан только маркер окончания EOC. Таким образом, таблица и корневой-каталог логически связаны между собой, и потеря любого из них приведёт к краху всей FS.
В доках, 32-байтную запись каталога назвали SFN, что подразумевает "Short File Name". Короткая (short) она потому, что хранит имя в досовском формате(8.3). Но в современном мире мы уже отвыкли от всевозможных рамок, и предпочитаем для файлов более длинные имена, которые Win всё-же ограничивает до 256-символов. Поэтому в FAT предусмотрена родственная к SFN структура LFN (Long) – она имеет такой-же размер 32-байт, но является не самостоятельной, а дополнением к SFN. Если в имени файла меньше 8-ми символов, структура LFN может вообще отсутствовать, но SFN в наличии всегда.
Нужно признать, что организация длинных имён в FAT выполнена через известное место. Так, каждая структура LFN несёт в себе макс. 13-символов имени файла, а если их больше, то добавляется ещё одна структура и получаем 26-символов. Если-же и этого мало, то подключается сл.структура и т.д. Причём имя задаётся в Unicode (где на каждый символ расходуются по 2-байта) и внутри одной структуры разбросано по трём/разным полям. Более того, порядок следования цепочки структур обратный – т.е. сначала идёт последняя LFN(x), после неё LFN(x-1) и далее в том-же духе. Общее кол-во структур указывается в первом байте "Ordinal". Здесь становится очевидным, что разработчик явно был под веществами.
Выше упоминалось, что LBA каталога можно найти по формуле из ВРВ:
RsvdSecCount + (FATSz*2) + ((RootCluster-2) * SecPerCluster)
Для своей флэш я получил LBA=16384, и прыгнув на него в редакторе, попал в начало каталога.
Проясним некоторые моменты..
1. Первая структура SFN (синий блок) будет всегда описывать корневую папку диска. Это единственная структура, которой может быть присвоен атрибут "VolumeID" со-значением 08h. Атрибуты файлов/каталогов лежат по смещению(0Вh) от начала структур, и я заключил их в синий овал. Все остальные поля первой структуры SFN считаются не действительными!
2. На моей флэш всего два файла с именами "dCrypt.asm" и "usbVIDbase.txt". Чем руководствуется драйвер FAT при добавлении к SFN структуры LFN остаётся загадкой, поскольку имя первого файла меньше 8-ми символов, но дров всё-равно вставил LFN (см.красный блок). А вот имя второго файла уже 10-символов, и для него выделено две LFN в зелёном блоке. Обратите внимание, что у первой в поле-ординала лежит значение(1) (+40h маска), а у второй LFN ординал равен(2). Эти поля я выделил чёрным, и они определяют кол-во структур LFN для текущего файла. Записи LFN можно обнаружить по атрибуту(0F).
https://forum.antichat.xyz/attachments/4914108/img_dc4df09040.png
Из чёрной таблицы с расшифровкой полей видно, что номер первого кластера разделён на две части Hi/Low. Первый файл "dCrypt.asm" размером 7.4КБ и начинается с кластера(3). Но поскольку он требует два 4К-байтных, следующий указывается уже в таблице FAT (см.зелёный dword на предыдущем скрине). Здесь нужно отметить, что номера кластеров представляются относительно начала области-данных, которая совпадает с началом рассматриваемого корневого-каталога RootDir.
Так как-же найти злополучный файл на диске?
Для этого в спеке FAT32 приведена формула такого вида:
FileFirstCluster = RootDir + (SFN.FstClus - RootCluster)
Если учесть, что в стандартной реализации FAT32 поле "RootCluster" в структуре ВРВ всегда равно 2, то для первого файла получаем следующий сектор LBA:
Код:
RootDir = сектор 16384
SFN.FstClus = кластер(3)
Формула: 3-2 = кластер(1) = 8 секторов
-----------------------------------------
FileFirstClus = 16384 + 8 = сектор(16392)
https://forum.antichat.xyz/attachments/4914108/img_3105408fdf.png
4.1. Алгоритм поиска удалённых файлов
Когда мы удаляем файл с диска FAT, он фактически остаётся на своём месте и так-же занимает свой кластер. Просто в соответствующей структуре SFN, первому байту присваивается маркер(Е5). Аналогичную ситуацию наблюдаем и в случае "Быстрого форматирования" тома. Такой расклад способствует восстановлению удалённых файлов. Чтобы создать их список, достаточно в корневом-каталоге с шагом в 32, проверять каждый/первый байт на маркер(Е5).
В качестве доказательства, я удалил со-своей флэш "usbVIDbase.txt", а второй файл оставил. В результате, SFN удалённого файла(и две предшествующие ей LFN) приобрели вид как на скрине ниже, а причастные к файлу элементы таблицы FAT сбросились в нуль. т.е. кластеры освободились для последующей записи. Таким образом, информация в кластерах удалённых файлов будет доступна нам до тех пор, пока драйвер FS не перезапишет их новыми данными.
https://forum.antichat.xyz/attachments/4914108/img_ca849818c2.png
5. Практика – разбор служебных структур
Теперь попробуем автоматизировать весь изложенный процесс..
Код ниже покажет основную информацию об USB-Flash или карте-памяти, если они имею формат FAT32.
Здесь есть нюанс, на который советую обратить внимание..
Значит получаем букву диска, и проверяем его на съёмный носитель "Removable". Если ок, то открываем раздел диска через CreateFile(), и пытаемся прочитать с него сектор ВРВ. Суть в том, что читать рекомендуется в асинхронном режиме, поскольку том может быть занят кем-то другим, например антивирусом. Если функцией ReadFile() читать синхронно, то функция может вернуть ошибку, мол диск недоступен. Поэтому мы используем асинхронный режим, в котором запрос ставится в очередь, а нам после вызова необходимо выждать пару секунд при помощи Sleep(). Этот способ требует в параметрах ReadFile() указатель на структуру "OVERLAPPED" и никогда не даёт осечек.
C-подобный:
format pe console
include
'win32ax.inc'
include
'equates\fat.inc'
entry start
;
//----------
.
data
fatBuff rb
512
*
3
ntName db
'\\?\GLOBALROOT'
dosName db
32
dup
(
0
)
usbHndl dd
0
align
16
fpuRes0 dq
0
fpuRes1 dq
0
fpuRes2 dq
0
secSize dd
0
clsSize dd
0
kByte dd
1024
mByte dd
1024
*
1024
gByte dd
1024
*
1024
*
1024
ol OVERLAPPED
rootDir dd
0
dumpByte dd
16
dumpLen db
8
dumpStr db
16
dup
(
'.'
)
,
0
buff db
0
;
//----------
.
code
start
:
invoke SetConsoleTitle
,
;
//---- Проверим наличие клиента в портах USB
invoke GetLogicalDriveStringsA
,
64
,
buff
mov esi
,
buff
@@
:
push esi
invoke GetDriveType
,
esi
;
// запрашиваем тип драйва..
pop esi
cmp eax
,
DRIVE_REMOVABLE
;
// извлекаемое устройство?
je @ok
add esi
,
4
cmp byte
[
esi
]
,
0
;
// последнее в списке?
jne @b
cinvoke printf
,
jmp @exit
;
//---- Получаем имя и открываем девайс на асинх.чтение
@ok
:
push esi
cinvoke printf
,
,
esi
pop esi
mov byte
[
esi
+
2
]
,
0
invoke QueryDosDevice
,
esi
,
dosName
,
32
cinvoke printf
,
,
ntName
invoke CreateFile
,
ntName
,
GENERIC_READ
+
GENERIC_WRITE
,
\
FILE_SHARE_READ
+
FILE_SHARE_WRITE
,
\
0
,
OPEN_EXISTING
,
\
FILE_FLAG_OVERLAPPED
+
FILE_ATTRIBUTE_NORMAL
,
0
mov
[
usbHndl
]
,
eax
;
//---- Читаем структуру "Bios Parametr Block"
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
;
//---- Проверим ф.систему на FAT32, иначе ошибка
mov esi
,
fatBuff
cmp word
[
esi
+
BPB
.
FATSz16
]
,
0
je @f
cinvoke printf
,
jmp @exit
;
//---- Есть FAT32! - собираем инфу..
@@
:
mov eax
,
BPB_32
.
FilSysType
add eax
,
esi
movzx ebx
,
[
esi
+
BPB_32
.
Header
.
BytePerSector
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
SecPerCluster
]
mov edx
,
ecx
imul ecx
,
ebx
mov
[
secSize
]
,
ebx
mov
[
clsSize
]
,
ecx
shr ecx
,
10
cinvoke printf
,
,
\
eax
,
ebx
,
edx
,
ecx
;
//------------------------
mov esi
,
fatBuff
mov eax
,
[
esi
+
BPB_32
.
Header
.
HiddSec
]
mov ebx
,
[
esi
+
BPB_32
.
Header
.
TotSec32
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
mov edx
,
ecx
shl edx
,
9
shr edx
,
10
push
0
ebx
fild qword
[
esp
]
fimul
[
secSize
]
fidiv
[
gByte
]
fstp
[
fpuRes0
]
add esp
,
8
cinvoke printf
,
,
\
eax
,
ebx
,
dword
[
fpuRes0
]
,
dword
[
fpuRes0
+
4
]
,
ecx
,
edx
;
//------------------------
mov esi
,
fatBuff
movzx eax
,
[
esi
+
BPB_32
.
Header
.
NumFats
]
movzx ebx
,
[
esi
+
BPB_32
.
BkBootSec
]
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
mov edx
,
[
esi
+
BPB_32
.
FATSz32
]
imul edx
,
[
secSize
]
shr edx
,
10
cinvoke printf
,
,
\
eax
,
ebx
,
ecx
,
[
esi
+
BPB_32
.
FATSz32
]
,
edx
;
//------------------------
;
// RootDir = (RsvdSecCount + (FATSz32 * NumFats)) + (RootCluster - 2) * SecPerCluster
mov esi
,
fatBuff
mov eax
,
[
esi
+
BPB_32
.
FATSz32
]
movzx ebx
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
shl eax
,
1
add eax
,
ebx
;
// RsvdSecCount + (FATSz32 * NumFats)
mov ebx
,
[
esi
+
BPB_32
.
RootCluster
]
sub ebx
,
2
movzx ecx
,
[
esi
+
BPB_32
.
Header
.
SecPerCluster
]
imul ebx
,
ecx
;
// (RootCluster - 2) * SecPerCluster
add eax
,
ebx
mov
[
rootDir
]
,
eax
cinvoke printf
,
,
\
[
esi
+
BPB_32
.
RootCluster
]
,
eax
;
//------------------------
mov esi
,
fatBuff
mov eax
,
BPB_32
.
Header
.
OemName
mov ebx
,
BPB_32
.
VolLabel
mov ecx
,
[
esi
+
BPB_32
.
VolID
]
movzx edx
,
cx
shr ecx
,
16
add eax
,
esi
add ebx
,
esi
cinvoke printf
,
,
eax
,
ebx
,
ecx
,
edx
;
//---- Читаем таблицу FAT, и выводим фрагмент её дампа
xor al
,
al
mov ecx
,
sizeof
.
OVERLAPPED
mov edi
,
ol
rep stosb
mov esi
,
fatBuff
movzx eax
,
[
esi
+
BPB_32
.
Header
.
RsvdSecCounter
]
shl eax
,
9
mov
[
ol
.
Offset
]
,
eax
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
cinvoke printf
,
call PrintDump
;
//---- Читаем корневой каталог, и так-же дампим фрагмент
xor al
,
al
mov ecx
,
sizeof
.
OVERLAPPED
mov edi
,
ol
rep stosb
mov eax
,
[
rootDir
]
shl eax
,
9
mov
[
ol
.
Offset
]
,
eax
invoke ReadFile
,
[
usbHndl
]
,
fatBuff
,
512
,
0
,
ol
invoke Sleep
,
1000
cinvoke printf
,
call PrintDump
@exit
:
invoke CloseHandle
,
[
usbHndl
]
cinvoke _getch
cinvoke exit
,
0
;
//------------------------------------------
;
// Вспомогательная процедура - выводит дамп
;
//------------------------------------------
PrintDump
:
mov esi
,
fatBuff
@next
:
mov edi
,
dumpStr
xor ebx
,
ebx
@prn
:
xor eax
,
eax
lodsb
push ebx eax
cinvoke printf
,
,
eax
pop eax
cmp al
,
20
h
jb @f
mov byte
[
edi
]
,
al
@@
:
inc edi
pop ebx
inc ebx
cmp ebx
,
4
jb @f
cinvoke printf
,
xor ebx
,
ebx
@@
:
dec
[
dumpByte
]
jnz @prn
push esi
mov
[
dumpByte
]
,
16
invoke CharToOem
,
dumpStr
,
dumpStr
cinvoke printf
,
,
dumpStr
mov eax
,
'....'
mov edi
,
dumpStr
mov ecx
,
4
rep stosd
cinvoke printf
,
pop esi
dec
[
dumpLen
]
jnz @next
mov
[
dumpLen
]
,
8
ret
;
//----------
section
'.idata'
import data readable
library msvcrt
,
'msvcrt.dll'
,
kernel32
,
'kernel32.dll'
,
user32
,
'user32.dll'
include
'api\msvcrt.inc'
include
'api\kernel32.inc'
include
'api\user32.inc'
https://forum.antichat.xyz/attachments/4914108/img_84f5aceafb.png
Во-втором случае я решил поинтересоваться картой-памяти своего телефона и обнаружил, что размер кластера у неё 32КБ, поэтому и таблица FAT намного меньшего размера: была 7.692 секторов в длину, а стала 951. Более того, и сам том начинается не как обычно с физ.сектора(63), а съехал аж к сектору(8192), о чём говорит поле "Volume First LBA". В такой организации пространства есть смысл, т.к. нам заранее известны ориентировочные размеры файлов – это фотки, мультимедиа и прочие, т.е. больше стандартного кластера 4КБ:
https://forum.antichat.xyz/attachments/4914108/img_bf48b08ad1.png
6. Заключение
Если не брать во-внимание детали оформления служебных структур, основным недостатком FAT32 является полное отсутствие "инстинкта самосохранения" при критических сбоях. Хорошо хоть инженеры предусмотрели некую пародию, в виде резервных блоков ВРВ и таблицы FAT2. Зато в NTFS всё продумано до мелочей, и в этом большое её преимущество. Например метафайл NTFS под названием "$LogFile" представляет собой журнал последней удачной конфигурации. Если случается крах, то при следующей перезагрузке, все параметры считываются уже из этого журнала, который драйвер NTFS обновляет при обычном выключении компьютера. Однако выбор у пользователей должен быть всегда, и FAT как-раз нам его предоставляет.
В скрепке лежит инклуд, и ехе-файл для тестов. Увидимся теперь на пороге Нового года, пока!