Основная загрузочная запись MBR Windows 7

Метки:  , , , , , , , , , , , , ,
Master Boot Record (MBR) - это так называемая главная загрузочная запись, то есть блок кода и данных, размещаемый на самом первом физическом секторе носителя информации (CHS=0:0:1, LBA=0), обеспечивающий начальный этап загрузки [операционной системы] и дополнительно описывающий схему разбиения носителя на разделы.

В алгоритме загрузки IBM PC-совместимых персональных компьютеров, использующих классический принцип загрузки (MBR/legacy), непосредственно после включения питания начинает исполняться код Базовой системы ввода-вывода (БИОС, BIOS). Почему я акцентировал внимание на термине "классический", дело в том, что в последнее время интенсивно разрабатывается спецификация Extensible Firmware Interface (EFI), которая призвана прийти на смену базовой системе ввода-вывода (BIOS). Однако в данной статье мы поведем рассказ именно о классическом методе загрузки (иначе называемом legacy boot), исторически сложившемся сценарии запуска посредством BIOS с использованием кода MBR-сектора. На финальной стадии работы BIOS, после инициализации и тестирования оборудования (POST), происходит выбор устройства, с которого предполагается дальнейшая загрузка "пользовательского" кода (операционной системы). Загрузочным устройством может служить любой тип накопителя, доступный для загрузки: гибкий диск, жесткий диск, флеш-диск, компакт-диск, карта памяти и некоторые другие. После того, как загрузочное устройство инициализировано (выбрано), код BIOS считывает с него первый физический сектор (цилиндр:головка:сектор (CHS) которого = 0:0:1 или адрес логического блока (LBA) = 0), в память по адресу 0000:7C00, затем проверяет сигнатуру 55AAh в последних двух байтах (смещение 1FEh), и если сигнатура присутствует, то управление передается по адресу 0000:7C00. Если же сигнатура не найдена, то управление получает собственный специализированный код BIOS, сигнализирующий об отсутствии операционной системы на носителе.

Каково назначение сигнатуры 55AAh? Представьте себе ситуацию, когда валидный код MBR в следствии какого-либо сбоя не был записан в первый сектор диска, или туда по каким-то причинам попал "мусор". После того, как BIOS передаст на подобный "код" управление, система с большой вероятностью зависнет!! Как раз для того, чтобы исключить подобные ситуации и была введена сигнатура MBR (55AAh), на основании которой код BIOS может хоть как-то идентифицировать сектор перед передачей управления. Если идентификатор не найдет, то диск считается не загрузочным.

Поскольку размер физического сектора равен 512 байт, а MBR в классическом понимании ограничивается одним сектором, то и размер MBR, соответственно, равен 512 байт. Кстати, никто не запрещает разработчику изменить структуру MBR таким образом, что он будет занимать более одного сектора последовательно (или не последовательно) на пространстве диска, что, в некоторых случаях, упрощает код да и алгоритм самой загрузки. В подобных ситуациях под MBR понимают весь загрузочный код, а первые 512 байт (то есть первый сектор) называют MBS (Master Boot Sector). Поскольку в загрузчиках Windows подобный подход не используется, термины MBS и MBR для нас абсолютно идентичны.
Теперь давайте перейдём к изучению функциональных особенностей сектора. В данной статье мы с вами будем исследовать загрузочный сектор одной из операционных систем линейки Windows - Microsoft Windows 7. С самого начала, немного забегая вперед, посмотрим на общую структуру размещения функциональных блоков кода в секторе MBR Windows 7:

Смещение Длина, байт Описание
0000h 355 Код загрузчика
0163h 80 Сообщения об ошибках
01B3h 2 Заполнители (00)
01B5h 3 Каждый байт из этих трех является указателем на сообщение об ошибке. Значение 0700h + значение байта определяет смещение сообщения в текущем сегменте данных: 0763h, 077Bh, 079Ah.
01B8h 4 Сигнатура диска (NT Disk Signature) или Идентификатор тома (Volume ID) или Серийный номер диска (Drive Serial Number). Используется самой системой Windows для однозначной идентификации носителя информации, а так же для сопоставления литеры (буквы) устройству. Так же может использоваться различного рода защитами с целью привязки устанавливаемого программного обеспечения к компьютеру.
01BCh 2 Заполнитель (обычно нули). Возможно зарезервировано для будущего использования.
01BEh 16 Таблица разделов (Partition Table, PT). Описывает каждый из четырех возможных для MBR-разметки разделов накопителя.
Раздел 1
01CEh 16 Раздел 2
01DEh 16 Раздел 3
01EEh 16 Раздел 4
01FEh 2 Сигнатура (55AAh). Последние 2 байта сектора.

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

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

Возможно, небольшие вопросы по структуре оставляют попытки использовать замысловатый триггер на сообщения об ошибках и размещение таблицы разделов непосредственно в MBR-секторе (устоявшаяся традиция), однако в остальном можно отдать должное лаконичности и логичности размещения кода в миниатюрном 512-байтовом пространстве, в таком пространстве особо не разбежишься, как говорится.
Не мешало бы тут же привести и общий алгоритм работы сектора:

  1. Код BIOS загружает 512 байт сектора MBR в память по адресу 0000:7C00.
  2. Код MBR копирует себя (собственный код) по адресу 0000:0600 и передает управление на этот адрес. Стек инициализируется в значение 0000:7C00.
  3. Парсим (разбираем) таблицу разделов. В таблице разделов ищется запись активного раздела. Если раздел найден, то запоминаем его. Если найдено несколько активных разделов, или активного раздела вовсе нет, то выводим ошибку.
  4. Проверяем наличие расширений прерывания int 13h.
  5. Грузим загрузочный сектор активного раздела (PBR) по адресу 0000:7C00.
  6. Включаем адресную линию A20.
  7. Проверяем наличие TPM версии 1.02.
  8. Уходим на дальнейшую загрузку ОС: передаем управление на уже загруженный к этому моменту код PBR по адресу 0000:7C00.

Теперь давайте взглянем на код внимательнее и попробуем углубиться в логику работы MBR Windows 7. В целях изучения с загрузочного диска нам необходимо этот самый MBR сектор каким-либо образом извлечь и записать в файл, а затем выполнить дизассемблирование кода MBR, то есть перевод его из шестнадцатеричного представления в понятный человеку язык Ассемблера. Для сохранения загрузочной записи MBR с диска я подумал использовать что-нибудь легковесное и, по возможности, не требующее инсталляции.. и инструмент был довольно быстро найден - им оказалась утилита DM Disk Editor and Data Recovery. Спасибо огромное автору за хороший и простой в освоении инструментарий.
У меня получилось выгрузить сектор MBR Windows 7 в виде .bin-файла, в итоге, после данной процедуры я получил 512 байт кода сектора. Теперь можно взглянуть на получившийся шестнадцатеричный дамп (блок кода/данных сектора, представленный в шестнадцатеричной системе счисления):

Собственно, как вы поняли, этот блок данных есть ни что иное, как шестнадцатеричное представление 512 байт сектора. Что же, дамп сектора у нас имеется, теперь самое время воспользоваться дизассемблером с целью трансляции кода сектора. На этом этапе мной был использован достаточно популярный и проверенный дизассемблер IDA, один из наиболее мощных в своем классе. С другой стороны, в коде MBR Windows 7 ничего сверхсложного нет, поэтому можно было бы использовать абсолютно любые доступные в сети дизассемблеры (в том числе онлайновые). После того, как исходный код MBR получен, мы попытаемся понять логику его работы.

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

Итак, BIOS, посредством прерывания 19h, производит загрузку первого сектора устройства в память, затем передает на только что загруженный код управление. Управление получено, и в этой точке мы имеем следующее состояние:

  • реальный режим (real mode) работы процессора;
  • значение регистра CS = 0;
  • значение регистра IP = 7C00;
  • значение регистра DL = номер "текущего" диска (с которого была выполнена загрузка сектора);
  • значение регистра DH = бит5 = 0: загрузочное устройство поддерживается INT 13h; иначе: (все 0) - не важно;
  • (для PnP BIOS): пара ES:DI = указатель на структуру PnP Installation check;
  • Прерывания запрещены.

Так уж сложилось, что любой код (функционирующий в реальном режиме) на старте производит подготовку собственного рабочего окружения: инициализацию сегментных регистров. Не далеко ушел от этого постулата и код сектора MBR Windows 7, тут мы видим инициализацию сегмента стека, данных, дополнительного сегмента. Затем производится копирование блока размером в 512 байт (самого себя) с адреса 0000:7C00 по адресу 0000:061C. Делается это по той причине, что впоследствии, на очередном этапе загрузки, код MBR по адресу 0000:7C00 будет перезаписан (затерт) кодом из PBR (загрузочного сектора раздела). После копирования кода управление передается по адресу 0000:061C.

  1. Обнуляем регистр AX. Этот регистр использоваться для дальнейшей инициализации сегментных регистров;
  2. Обнуляем сегмент стека SS;
  3. Устанавливаем указать стека SP в значение 7C00h. Не смотря на то, что по этому адресу у нас располагается первая инструкция, стек у нас не будет пересекаться с кодом, поскольку перед выполнением команда помещения в стек и SP уменьшается;
  4. Обнуляем сегментный регистр ES;
  5. Обнуляем сегментный регистр DS;
  6. Устанавливаем SI в значение 7C00h;
  7. Устанавливаем DI в значение 0600h. Значение 0600h будет использоваться как смещение адреса назначения для пересылки 512 байт далее по коду;
  8. Устанавливаем CX в значение 0200h. Это количество копируемых байт (512 в десятичной системе);
  9. Устанавливаем флаг направления cld. Направление "вперед" для инструкции movsb;
  10. Переносим 512 байт;
  11. Записываем в стек содержимое AX, который равен 0 (сегмент для перехода);
  12. Записываем в стек значение 061Ch (смещение для перехода);
  13. Командой retf делаем "дальний возврат" из процедуры. На самом деле, наш код никакой процедуры не вызывал, чтобы потом из нее "вернуться". Это просто распространенный прием, используемый для перехода по адресу, записанному в стеке (в нашем случае - 0000:061Ch), то есть ровно в то местоположение, в которое мы только что скопировали код.
Поскольку мы передали управление по адресу 0000:061C, с этого момента будет логичнее продолжить адресацию уже относительно нового адреса, куда мы, собственно и переместились после копирования участка кода.

Следующий блок кода MBR Windows 7 предназначен для организации поиска активного раздела на текущем (физическом) диске. Любой физический диск может быть разделен на блоки, называемые разделами (partitions). Разделы могут быть двух типов:

  • Основной (Primary) - (в первую очередь) используется для размещения файлов/каталогов ОС (тем не менее может размещать все что угодно) и может быть "активным", соответственно и "загрузочным";
  • Расширенный (Extended) - используется для размещения любой информации (в том числе и файлов/каталогов ОС), однако не может маркироваться в качестве "активного", соответственно загрузочным быть не может. Может разбиваться на логические (Logical) подразделы;

Для организации разбиения диска на разделы используется структура под названием таблица разделов, располагающаяся внутри MBR по смещению 01BEh от начала сектора. Активный (загрузочный) раздел требуется найти для того, чтобы считать (и в дальнейшем запустить) с него код уже [другого] загрузочного сектора раздела, носящего несколько имен: VBR (Volume Boot Record), PBR (Partition Boot Record) и даже PBS (Partition Boot Sector). Поскольку максимальное количество основных разделов равно 4, то таблица разделов у нас имеет 4 записи (по 16 байт каждая), описывающих каждый раздел на диске (итого размер таблицы - 64 байта). Признак загрузочного раздела - это наличие значения 80h в первом байте записи. Значение 00h указывает на незагрузочный раздел. Любое другое значение кроме 00h и 80h считается некорректным и игнорируется. Давайте рассмотрим структуру 16-байтовой записи таблицы разделов:

Смещение Длина Описание
00h 1 Признак активности раздела. 80h - активный (загрузочный) раздел. 00h - неактивный (незагрузочный) раздел.
01h 1 Начало раздела — головка. Для совместимости со старыми функциями int 13h, работающими в формате CHS.
02h 1 Начало раздела — сектор (биты 0—5), цилиндр (биты 6, 7). Для совместимости со старыми функциями int 13h, работающими в формате CHS.
03h 1 Начало раздела — цилиндр (старшие биты 8, 9 хранятся в байте номера сектора). Для совместимости со старыми функциями int 13h, работающими в формате CHS.
04h 1 [идентификатор] типа раздела. Фактически описывает используемую [на разделе] файловую систему.
05h 1 Конец раздела — головка. Для совместимости со старыми функциями int 13h, работающими в формате CHS.
06h 1 Конец раздела — сектор (биты 0—5), цилиндр (биты 6, 7). Для совместимости со старыми функциями int 13h, работающими в формате CHS.
07h 1 Конец раздела — цилиндр (старшие биты 8, 9 хранятся в байте номера сектора). Для совместимости со старыми функциями int 13h, работающими в формате CHS.
08h 4 Номер первого блока (читай: сектора) в формате LBA. С какого блока раздел начинается.
0Ch 4 Количество секторов раздела (длина раздела).

Ну а теперь, собственно, сам код поиска активного раздела:

  1. Разрешить асинхронные прерывания (аппаратные, от "внешних" устройств).
  2. Задаем счетчик записей (4) в регистре CX.
  3. задаем в BP смещение таблицы разделов (07BEh = 0600h + 01BEh). помните, мы переместили код по адресу 0000:0600h? значит теперь любые смещения у нас скорректированы относительно адреса 0600h.
  4. [метка]
  5. сравниваем первый байт первого раздела со значением 0.
  6. (знаковое) если меньше (то есть значение попадает в диапазон 80h-0FFh), то мы, предположительно, нашли загрузочную запись, и переходим на 0000:0634 для дальнейшей проверки.
  7. если не равно (с учетом первого сравнения jl - или больше, то есть значение попало в диапазон 01h-79h), то переходим на процедуру выдачи ошибки Invalid partition table.
  8. добавляем к BP значение 10h (16) для того, чтобы сместить указатель на следующую запись в таблице разделов;
  9. цикл на 0623h. то есть проверяем все четыре раздела, пока cx не будет равен 0;
  10. если мы проверили все 4 записи и не нашли среди них ни одной активной - то вызываем прерывание 18h. На многих современных BIOS просто выдается текст PRESS ANY KEY TO REBOOT, на очень старых моделях IBM PC вызывался встроенный интерпретатор языка BASIC.

Активный раздел найден. Но перед тем как перейти непосредственно к чтению с найденного раздела загрузочного сектора раздела (PBR), обеспечивающего дальнейшее выполнение загрузки, необходимо выбрать метод чтения. Часть кода MBR Windows 7, описанная ниже, предназначена для проверки факта поддержки BIOS так называемых "расширений прерывания 13h". Почему именно "расширения" и зачем они потребовались разработчикам из Microsoft? От чего же, к примеру, не использовать старые-добрые стандартные функции чтения секторов? Я постараюсь, в меру собственного понимания вопроса дать исчерпывающее пояснение. История этого события, как я полагаю, уходит корнями в то время, когда для дисков в BIOS существовало ограничение на количество адресуемых секторов - 16515072, поскольку в интерфейсе int 13h для номера цилиндра отводилось 10 бит (1024), для номера головки — 8 бит (256), для номера сектора — 6 бит (63), то есть всего 24 бита, что позволяло адресовать не более 8455716864 байт (8064 Мб или 7,875 Гб). Но диски неуклонно росли в "объемах", очевидно что требовалось что-то в данной ситуации менять. Вот именно тогда и были добавлены дополнительные (расширенные) функции, которые предусматривали использование так называемого режима LBA (Logical Block Addressing), который обеспечивал автоматическое изменение геометрии жестких дисков и позволял адресовать сектор уже как 64-битное целое число. LBA адресовал любой сектор на носителе линейным адресом, в отличии от старого неудобного стандарта CHS, в котором сектор адресовался посредством трех параметров - цилиндра, головки и номера сектора как такового. Этот режим LBA, судя по всему, не поддерживался стандартными (устаревшими) функциями прерывания int 13h, для его то поддержки и требуются "расширения".

  1. Регистр DL содержит номер текущего диска (мы не "портили" регистр DX в коде выше), который он получает от кода BIOS. Записываем его значение в память по адресу ds:[bp+0]. Соответственно, локальная переменная, адресуемая как [bp+0] - номер текущего диска. Если в системе один диск и загрузка происходит с него, то значение равно 80h, если же дисков много и загрузка происходит не с первого диска, то, вероятно, значение может быть иным.
  2. Сохраняем значение регистра BP в стек.
  3. [bp+11h] - локальная переменная, содержащая счетчик попыток чтения диска. Задаем 5 попыток.
  4. [bp+10h] - локальная переменная, указывающая на наличие/отсутствие расширений прерывания 13h.
  5. Записываем в аккумулятор AX значение 41h. Эта функция проверяет наличие расширений прерывания int 13h в BIOS.
  6. В регистре BX должно быть значение 55AAh.
  7. Вызываем прерывание.

Далее в MBR Windows 7 следует блок кода, который предназначается для обработки возвращенных функцией 41h прерывания 13h значений. На этом этапе мы и понимаем, если ли в BIOS расширения, или их нет. Не все BIOS (особенно старые реализации) поддерживают расширения, поэтому нам надо подобный вариант предусмотреть. Если возвращается сброшенный carry flag (CF=0) и значение регистра BX меняется на AA55h, то это означает наличие расширений. Версия возвращается в регистре AH. В регистре CX возвращается битовая карта поддерживаемого функционала.

  1. Восстанавливаем из стека регистр BP.
  2. Условие (беззнаковое) "если меньше", то есть если флаг CF=1 (установлен), то расширений нет и мы передаем управление по адресу 0000:0659.
  3. Еще одна проверка на отсутствие ошибки выполнения функции: проверяем BX на значение 0AA55h;
  4. Если нет - то передаем управление на 0000:0659.
  5. Заключительная проверка - тест значения в регистре CX на установленный нулевой бит;
  6. Если бит не взведен (0), то расширения не обнаружены и код прыгает на метку 0000:0659;
  7. Иначе, проверка прошла успешно, расширения есть. Увеличим на единицу содержимое ячейки [bp+10]. Вспомним, что она используется как внутренняя переменная, описывающая наличие или отсутствие расширений int 13h.
  8. [метка]
  9. Сохраняем все 32-битные регистры общего назначения в стек.
  10. Сравниваем локальную переменную [bp+10h] (которая определяет наличие расширений), с нулем;
  11. если 0 - то мы не можем использовать расширения и переходим под адресу 0000:0687. Иначе идем далее по коду.

Описанный далее код выполняет в случае присутствия расширений и использует функцию 42h прерывания int 13h под названием “Extended Read” (расширенное чтение), которая предназначается для считывания секторов с диска в режиме LBA. А считываем мы первый сектор активного раздела (PBR). Тут используется один не совсем стандартный прием: весь пакет DAP записывается в стек (то есть для задания параметров функции используется стек) и функции перед вызовом дается на этот блок данных указатель. Стоит помнить, что информация в стеке формируется в обратном порядке.

данные, формирующие блок DAP (Disk Address Packet, Адресный пакет диска) следующие:

смещение размер описание
00h 1 байт длина DAP. 10h или 18h (16 или 24 байта).
01h 1 байт зарезервировано, не используется. должно быть 0.
02h..03h 2 байта количество секторов для чтения (некоторые BIOS имеют ограничение).
04h..07h 4 байта сегмент:смещение буфера памяти для чтения секторов (в x86 сначала задается смещение, потом сегмент)
08h..0Fh 8 байт абсолютный номер стартового сектора для чтения (1 сектор диска имеет номер 0)
10h 8 байт Опционально. 64-битный адрес буфера трансфера. используется только тогда, когда двойное слово по смещению 04h DAP имеет значение FFFF:FFFF

* Поскольку у нас используется стандартный, 16-байтный пакет DAP, то последнее поле расширенного пакета DAP в нашем случае не задействовано.

  1. Поместим в стек старшие 4 байта номера стартового сектора для чтения (00000000);
  2. Поместим в стек младшие 4 байта номера стартового сектора для чтения сразу из переменной [bp+8]. Это у нас смещение в записи таблицы разделов?
  3. Поместим в стек сегмент буфера для чтения;
  4. Поместим в стек смещение буфера для чтения;
  5. Поместим в стек количество секторов для чтения (в нашем случае 1);
  6. Поместим в стек длину блока DAP (в нашем случае 10h = 16 байт);
  7. Инициализируем AH = 42h. (номер функции расширенного чтения);
  8. В DL поместим индекс (номер) диска. он у нас лежим в [bp+0];
  9. А теперь DS:SI приравняем к SS:SP, чтобы они указывали на блок DAP;
  10. Вызываем функцию прерывания;

Блок кода, описанный ниже предназначен для корректировки указателя стека, иными словами для "освобождения" стека от занесенных туда 20 байт параметров при вызове функции, но только так, чтобы не затронуть флаги. Если carry flag (CF) сброшен и AH=0, то функция завершилась удачно. В случае возникновения ошибки CF=1, а AH содержит код ошибки. В обеих случаях, поле счетчика блоков DAP содержит количество фактически считанных блоков.

  1. Сохраняем флаги в регистр AH.
  2. Оригинальным образом исключаем все данные блока DAP из стека просто "сдвигом" указателя стека на 10h (десятичное 20).
  3. Восстановим флаги из регистра AH. Таким образом, операция сложения не тронула флаги.
  4. Переход на метку по адресу 0000:069B. Там у нас в коде сектора MBR Windows 7 располагает код проверки успешности чтения.

Что мы видим далее? Приведенный ниже блок кода выполняется в том случае, если расширения int 13h не были найдены. В современных машинах трудно представить подобную ситуацию, однако разработчики, видимо, решили обеспечить совместимость со старым оборудованием, либо просто передрали код из старых версий :) Код считывает загрузочный сектор активного раздела (PBR) при помощи стандартной функции 02h прерывания int 13h. В регистре AL указывается количество секторов для чтения. DL = диск, DH = головка, CL = сектор, CH = цилиндр.

  1. Инициализируем AX. AL=1, значит читаем одни сектор;
  2. В BX = смещение буфера для чтения 7C00h;
  3. Инициализируем DL = [bp+0] - номер (индекс) диска;
  4. Инициализируем DH = [bp+1] - номер головки;
  5. Инициализируем CL = [bp+2] - номер сектора;
  6. Инициализируем CH = [bp+3] - номер цилиндра;
  7. Вызываем функцию прерывания.

Ну а далее, судя по всему, в секторе MBR у нас присутствует блок проверки на успешность считывания стандартной функцией чтения. Это часть большого цикла.

  1. Восстанавливаем все 32-битные регистры общего назначения из стека. Напомню, что незадолго до этого мы их сохраняли.
  2. Проверяем успешность выполнения функции 02h прерывания 13h. (беззнаковое) "если не меньше", то уходим на проверку считывания именно загрузочного сектора (0000:06BB).
  3. Уменьшаем счетчик попыток чтения сектора (байт по адресу [bp+11]).
  4. Если счетчик попыток не исчерпан еще (не 0), то передаем управление по адресу 0000:06B0 (сброс диска);
  5. Иначе сравниваем индекс текущего диска ([bp+0]) с 80h. первый диск?
  6. Если диск первый (+ с учетом всех вышеописанных условий) - то уходим на ошибку Error loading operating system;
  7. Загрузим в DL значение 80h (первый жесткий диск в системе);
  8. Возвращаемся на проверку расширений int 13h (0000:0634);
  9. [метка]
  10. Сохраняем BP. он у нас используется как база адресации внутренних переменных;
  11. Функция AH=0 - инициализация (сброс) диска;
  12. В DL поместим значение байта по адресу [bp+0], это у нас индекс (номер) диска;
  13. Вызываем функцию 0 прерывания 13h;
  14. Восстанавливаем значение регистра BP.
  15. Передаем управление код по адресу 0000:0659 (считывание сектора);
  16. [метка]
  17. Сюда мы перешли после считывания сектора стандартной функцией. Проверим на наличие в последних двух байтах считанного сектора (ds:7DFEh) значения AA55h (сигнатура MBR). А действительно ли мы считали PBR (VBR)?
  18. Если нет сигнатуры MBR, то вероятно, что по какой-то причине сектор не был считан, либо считан не тот сектор - уходим на ошибку Missing operating system;
  19. Сохраним индекс (номер) диска в стеке;

Следующий блок кода сектора Windows 7 MBR является подготовительным перед проверкой наличия TPM (подробнее о TPM ниже). Для проверки наличия TPM, сперва требуется включить адресную линию A20 (которая обеспечивает, помимо прочего, возможность передачи 21-го бита на адресной шине, соответственно открывая доступ к расширенной памяти). Управлением этой линией в IBM-PC-совместимых системах занимается контроллер клавиатуры (исторически?), поэтому с ним мы и будем работать напрямую через порты.

  1. Вызываем процедуру по адресу 0000:0756. Она является частью кода по включению линии A20 и ожидает готовности контроллера клавиатуры к приему очередного байта;
  2. Если функция вернула не 0 (то есть линия уже включена), то сразу уходим по адресу 0000:06E2;
  3. Маскируем прерывания;
  4. Инициализируем AL значением 0D1h. Это команда управления линией A20. выдачи данных в порт 2 контроллера, то есть мы говорим, что хотим записать байт статуса;
  5. Запишем в порт 64h это значение;
  6. Вызовем подпрограмму по адресу 0000:0756. Она предназначена для ожидания готовности;
  7. Инициализируем AL значением 0DFh. это данные для порта 2. мы хотим открыть линию A20;
  8. Запишем в порт 60h это значение;
  9. Вызовем подпрограмму по адресу 0000:0756. Ожидание готовности;
  10. Инициализируем AL значение 0FFh: сброс клавиатуры и запуск диагностики;
  11. Запишем в порт 64h это значение;
  12. Вызовем подпрограмму (процедуру) по адресу 0000:0756. Ожидание готовности;
  13. Разрешим прерывания.

Затем у нас следует блок определения наличия интерфейса TPM (Trusted Platform Module) версии 1.2. Что это за интерфейс и почему разработчики вынуждены были включить его поддержку в и без того переполненные 512 байт сектора MBR Windows 7?

TPM (Доверяемый Платформенный Модуль, Trusted Platdorm Module) - криптографический процессор, предоставляющий инструментарий: генератор случайных чисел, генератор ключей RSA, генератор хешей SHA-1, подсистема шифрования, цифровой подписи. Физически это чип на материнской плате, в энергонезависимой/оперативной памяти которого хранятся корневые/производные ключи. Используется для организации "корня доверия" для технологии шифрования разделов Microsoft BitLocker Drive Encryption.

Именно по этой причине TPM нам требуется на столь раннем этапе загрузки, иначе мы не сможем в нужный момент расшифровать раздел, а соответственно и продолжить с него загрузку операционной системы. Функция BB00h прерывания int 1Ah (TCG_StatusCheck) возвращает в EAX=0, если TPM поддерживается, при этом регистр EBX должен содержать строку ‘TCPA’ (шестнадцатеричное: 54 43 50 41). Если значение не совпадает, то выходим из TCG-кода. Делаем проверку версии в регистре CX, если версия не 1.02, то так же выходим из TCG-кода. Если нужная версия найдена, то вызываем функцию TCG_CompactHashLogExtendEvent. При возврате из функции мы получаем имеем выходные данные в регистрах EAX и EDX.

  1. [метка]
  2. Теперь начинаем проверку. AX=BB00h. Функция TCG_StatusCheck;
  3. Вызываем функцию прерывания;
  4. Логическое "И" над EAX. Для проверки, вернула ли функция 0 в регистре EAX. Поддерживается ли TPM?
  5. Если не поддерживается, то уходим на 0000:0727;
  6. Продолжаем проверку. Сравниваем EBX со значением 41504354h (TCPA);
  7. Если не равно, то уходим на код по адресу 0000:0727;
  8. Продолжаем проверку. сравниваем CX со значением 102h. Версия 1.02 TPM?
  9. (беззнаковое) "если меньше", то уходим на код по адресу 0000:0727;
  10. TPM найдена нужной версии. готовим вызов функции TCG_CompactHashLogExtendEvent. Заносим параметры через стек. AH=0bbh, AL=07h.
  11. Занесем в стек значение 00000200h. 0200h - 512 в десятичном.
  12. Занесем в стек значение 00000008. старшее слово 0000, младшее слово 0008;
  13. Занесем в стек содержимое регистра EBX.
  14. Занесем в стек содержимое регистра EBX.
  15. Занесем в стек содержимое регистра EBP.
  16. Занесем в стек значение 00000000;
  17. Занесем в стек значение 00007C00;
  18. Восстановим из стека основные 32-разрядные регистры. Вот для чего мы заносили все эти значения в стек, чтобы потом одной командой popad инициализировать сразу все регистры, необходимые для вызова функции BB07h прерывания int 1Ah. получается AX = BB07h, DI = смещение буфера для хеширования (7C00h), ECX = длина буфера (0200h=512), EDX = номер PCR (Platform Configuration Registry) = 8, SI = значение для лога событий = 0, EBX = 41504354h;
  19. Занесем в стек значение 0000;
  20. Восстановим значение из стека в регистр ES, тем самым инициализировав его в значение 0000 (es=0000);
  21. Вызовем функцию BB07h прерывания 1Ah.

Нижеприведенная часть кода сектора является подготовительной. Она подготавливает регистр DL для последующего применения его уже кодом PBR (VBR) и выполняет переход на код PBR, который был загружен в коде MBR Windows 7 ранее. Помните?

  1. [метка]
  2. Восстанавливаем из стека слово в регистр DX. там у нас слово [bp+0], а это текущий диск. Обычно значение 80h;
  3. Обнуляем DH, ведь для нас важен только DL;
  4. Выполняем "длинный прыжок" по адресу 0000:7C00, куда мы считали загрузочный сектор активной партиции (Partition Boot Record);
  5. Вызов прерывания 18h. Похоже, что сюда никогда не передается управление и эта часть кода написана "на всякий случай"?

Теперь в секторе следует блок кода, который отвечает у нас за вывод сообщений об ошибках. Ошибки критические, то есть после их вывода возвращаться в основную программу уже нет никакого смысла. Поэтому стоит отметить, что инструкции под адресам 0748, 0753, 0754 вводят машину в бесконечный цикл. Соответственно, потребуется ручная перезагрузка. Для вывода символов на экран используется функция 0Eh (телетайп) прерывания int 10h.

  1. [метка]
  2. Занесем в регистр AL смещение первого сообщения, которое размещается по адресу ds:07B7. Сообщение: Invalid partition table;
  3. Перейдем на процедуру подготовки вывода по адресу 0000:073E;
  4. [метка]
  5. Занесем в AL смещение второго сообщения, которое размещается по адресу ds:07B6. Сообщение: Error loading operating system;
  6. Перейдем на процедуру подготовки вывода по адресу 0000:073E;
  7. [метка]
  8. Занесем в AL смещение третьего сообщения, которое размещается по адресу ds:07B5. Сообщение: Missing operating system;
  9. [метка]
  10. Обнулим AH.
  11. Прибавим к ax значение 0700h, чтобы скорректировать смещение сообщений;
  12. SI = смещение сообщения;
  13. [метка]
  14. Загрузим в AL байт из ячейки по адресу DS:SI;
  15. AL=0? это конец строки?
  16. Если да, то переходим на метку 0000:0753;
  17. BX=7 - BH-страница, BL-цвет символа;
  18. AH=0Eh - функция 0Eh прерывания 10h. Вывод на экран в режиме телетайпа;
  19. Вызываем функцию прерывания;
  20. Тут организуется цикл с адресом 0000:0745, выход из цикла по условию достижения конца строки, чтобы допечатать строку по одному символу;
  21. [метка]
  22. Команда hlt - приостановка процессора;
  23. Короткий переход на 0753. Зацикливаем hlt. Требуется аппаратная перезагрузка.

Оставшаяся часть кода MBR Windows 7 - это дополнение для включения линии A20: процедура, ожидающая готовность контроллера клавиатуры к приему очередного байта. Выше, в коде открытия линии A20 мы постоянно вызывали её для ожидания готовности.

  1. [подпрограмма/процедура]
  2. Обнулим регистр CX;
  3. [метка]
  4. Считываем байт из порта 64h в регистр AL;
  5. Делаем небольшую паузу;
  6. Логическое И: проверка флага готовности;
  7. Если он не активен (бит выставлен) и счетчик в CX не равен 0, то выполняем очередную итерацию цикла ожидания;
  8. Иначе опять логическое И;
  9. Возврат из процедуры.

На этом код MBR заканчивается и далее в секторе располагаются разнообразные данные, как я уже и говорил ранее. Их можно увидеть на общем дампе MBR Windows 7, приведенном в начале статьи. Эти данные представляют из себя сообщения об ошибках, различные внутренние переменные и таблица партиций, а так же - завершающий сектор байт со значением AA55h - сигнатура MBR, записанная в обратном порядке (в силу особенностей архитектуры процессоров Intel x86).

Комментарии: 23

  1. виктор

    Для Window7 x64 дамп MBR совершенно другой.

    1. einaare

      Странно, я дамп брал со своей Windows 7 x64 версии. Windows 7 Professional SP1 русская. 6.1.7601.

      UPD: Сейчас специально проверил еще раз сектор LBA0. Дамп соответствует. Какая у Вас версия?

    2. nic

      Дамп может и другой.Дизассемблируйте код как описано в статье.И посмотрите.Существенных различий нет.

  2. Евгений

    Код поиска активного раздела:
    >sti

    Комментарии:
    "1: Запрещаем немаскируемые прерывания."
    -------------------------------------------------------------------
    Тут какая-то ошибка. Sti их наоборот, разрешает. Раньше в секции инициализации в начале было cli, а сразу перед "самокопированием" - sti. Здесь, может, cli из дампа выпало?

    А в целом - огромное спасибо за проделанную работу. Чёткий, грамотный, пошаговый разбор кусочков кода (это тоже хороший приём, т.к. не приходится глазами по общему листингу шарить). Всё супер!

    P.S. Ещё бы ссылочку на скачивание чистого дампа в bin-формате и файлы lst и asm. То же и к PBR относится.

    1. einaare

      точно :) вот ведь ошибка то :) сейчас проверю. cli в коде не было, кстати.

    2. einaare

      поправил. спасибо. над файлами подумаю.

  3. Igor

    Будьте добры подскажите, а какой код запускает драйвер ntfs.sys? Наверно загрузочная запись, которая в PBR?

    1. einaare

      Честно говоря пока не разбирался с этим вопросом. Но, судя по всему, код поддержки NTFS (в виде серии функций) присутствует уже на стадиях PBR и Bootmgr, но полноценным драйвером не является. Полноценный драйвер \Windows\System32\Drivers\Ntfs.sys загружается на стадии функционирования Winload, перед передачей управления ntoskrnl.exe.

  4. Maxel

    Такой вопрос, подскажите начинающему, а что за сигнатура AA55h и адрес 01FE? Это я про структуру сектора mbr, я вот открыл hex-редактор. Сектор 512 байт . Кончается он слева строчкой 0000001F0 , по центру. Да , в самом конце этой строчки есть 55 AA, AA идет под буквой F, если смотреть сверху. А откуда берется h? В книге тоже одной прочитал , сигнатура AA 55 по адресу 01FE-01FF. Откуда эти адреса берутся, в hex-редакторе такого нет. Их надо как-то самостоятельно высчитывать?

    1. einaare

      01FE - адрес последних двух байт 512 байтового сектора, обычно не показывается в hex-редакторе, поскольку он отображает только адреса начала каждой строки, а конец надо высчитывать самостоятельно. 55AA - два байта 55 и AA, расположенные по этому смещению (01FE) относительно начала сектора. в типовом hex-редакторе каждая строка 16 байт, поэтому высчитать смещение конкретного байта от начала строки не сложно. и 01FE и 55AA - это шестнадцатеричные числа. просто первое - это смещение от начала сектора, а второе - непосредственно значение (2 байта), хранящееся по данному смещению. h - обозначение шестнадцатеричного числа, может быть опущено.

  5. Maxel

    Ага, спасибо, понял, что h - это просто обозначение системы счисления. А адрес видимо получается так - последняя строчка 0000001F0 , мы ноль в конце заменяем на E(55) получается как раз 01FE, ну а если AA, то заменяем на F(АА), получается 01FF. Т.е этот ноль в конце строчки для того, что потом вместо него поставить одно из 16-ти верхних значений.

    1. einaare

      ну да, если в вашем hex-редакторе есть вертикальные обозначения смещения в строке, то актуальное смещение вычисляет так, как вы и описали. 0 в конце шестнадцатеричного числа означает, что число кратно 16, собственно строка у нас и есть размером 16 байт.

  6. Влад

    очень хорошо, серьёзная работа, а чему могут помочь такие знания?

    1. einaare

      я думаю многому.. например, написанию mbr-загрузчика для собственной ОС или пониманию внутренней организации загрузки Windows, пониманию, на каком этапе с каким железом взаимодействует ОС, с какой целью и какие именно функции задействованы. С точки зрения обучения ассемблеру :) пониманию принципов разбиения на разделы... ну и со всеми вытекающими смежными областями - огромное количество механизмов и алгоритмов :) для общего развития, одним словом :))

  7. Юзер

    задаюсь вопросом - для кого эта статья?
    - те, кто как рыба в воде в этих знаниях, им это не нужно.
    Кто разбирается и занимается восстановлением дисков - это давно съели и ... Программистам на это на чихать. Остаются юзеры вроде меня :(
    Чтобы понять то, что здесь написал автор, как он выматерился - нужно выучить Ассемблер. Вы в своём уме? предлагать такое самоубийство юзеру.
    В таком случае, нужно было тогда посоветовать узерам изучить мат. часть, железо, Булеву алгебру со всеми системами исчисления ну про BIOS не забыть вдогонку.

    Статья мне понравилась еще бы картинок цветных типа конек-горбунок, Иван царевич и лягушка болотная и т.п.
    Но начать сначала не мешало бы с того: Что, при включении ПК после процедуры тестирования которая прошита в BIOS (да, ешо как сам BIOS сапускается ) и удачном отклике всех прицепленных железок к MB (motherboard) - BIOS начинает рыскать MBR на диске Мастер первого слота который задается в параметрах самого окна BIOS, а потом уже MBR выбор диска, системы ОС и т.д.
    Иль я чёт не так понимаю.
    Не мешало бы вкратце объяснить, что за абракадабра в окне утилита DM Disk Editor and Data Recovery - а то, сразу дизассемблировать код MBR.
    Да я может и дизассемблировать не буду, а попросту гыляну что там матерное написано про разделы внесу поправки и забуду это может на всю жизнь.

    Нет, так дело не попрет - статью нужно полностью переписать. Не понимаю, за что платят деньги блин, ученным :(

    1. einaare

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

    2. Геннадий

      Судя по тому что статья 3-я в гугле по запросу "mbr pbr" статья много кому нужна :)))
      Отличная статья, спасибо.

  8. Геннадий

    В таблице "общая структура сектора MBR Windows 7" после "01B8h - 4 - Сигнатура диска (NT Disk Signature)" по моему должно идти "01BCh - 2 - заполнители (00)". Иначе 2 байта выпадают

    1. einaare

      да, выпадают, спасибо :) но по сути там все верно, посмотрите еще раз таблицу, подправил!!

  9. Геннадий

    И оффтоп конечно. Но подскажите как сделать в IDA, чтобы с начало кода сегмент начинался с 0000:7C00, а потом оно менялось на 0000:0600. А то у меня получается только целиком сегмент смещать (или 7С00 или 0600)?

  10. Андрей

    А почему используется ещё и PBR? Почему MBR не может сразу загрузить загрузчик Windows?

    1. einaare

      Хороший вопрос!! Казалось бы, зачем усложнять, вводя еще один (промежуточный) этап? Грузи сразу загрузчик NTLDR/Bootmgr и упрощай структуру.. На устройствах, имеющих разделы, первым сектором диска является MBR (как независимый от операционной системы этап загрузки), а на устройствах, не имеющих разделы в первом секторе размещается PBR (начинает уже загрузку непосредственно установленной на разделе операционной системы). Так вот, дабы дать возможность загружаться разнообразным операционным системам после MBR, и ввели еще один промежуточный этап в виде PBR. Подробнее читайте в статье про PBR на этом ресурсе.

  11. Серёга

    Ребят, привет.Мож кто подскажет, где что поменять в загрузчике установочной флэшки Win7, сделаной руфусом 2.0, чтоб загрузка пошла. Дело в том что я решил ушустрить установку, провёл несколько тестов с форматированием с разным размером кластера, определился на одном значении, но загрузка с флэшки идёт только со стандартным размером кластера. Пока выдаёт ошибку " disk read error". с ассемблером имел дело уже 20 лет назад и то поверхностно, но оч. надо. MBR PBR могу скинуть.
    ???

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *