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

Метки:  , , , , , , ,

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

Представьте себе ситуацию, когда валидный код 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.
01BEh 16 Таблица разделов
Раздел 1
01CEh 16 Раздел 2
01DEh 16 Раздел 3
01EEh 16 Раздел 4
01FEh 2 Сигнатура (55AAh)

Вполне логичная структура, не находите? Как раз по этой структуре четко можно проследить, что помимо загрузочного кода в 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 байт кода сектора. Теперь можно взглянуть на получившийся шестнадцатеричный дамп:

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

  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 предназначена для поиска активного раздела на текущем диске. Для поиска используется таблица разделов, которая размещена внутри 512 байт MBR. А необходимо это для того, что в дальнейшем, для продолжения загрузки ОС, нам необходимо будет считать и запустить из активного раздела код загрузочного сектора активного раздела, который имеет несколько названий: VBR (Volume Boot Record), PBR (Partition Boot Record) и даже PBS (Partition Boot Sector). Признак загрузочного раздела - это наличие значения 80h в первом байте 16-байтовой записи, которая описывает каждый раздел в таблице разделов. Значение 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. сравниваем первый байт первого раздела со значением 0.
  5. (знаковое) если меньше (то есть значение попадает в диапазон 80h-0FFh), то мы, предположительно, нашли загрузочную запись, и переходим на 0000:0634 для дальнейшей проверки.
  6. если не равно (с учетом первого сравнения jl - или больше, то есть значение попало в диапазон 01h-79h), то переходим на процедуру выдачи ошибки Invalid partition table.
  7. добавляем к bp значение 10h (16) для того, чтобы сместить указатель на следующую запись в таблице разделов;
  8. цикл на 0623h. то есть проверяем все четыре раздела, пока cx не будет равен 0;
  9. если мы проверили все 4 записи и не нашли среди них ни одной активной - то вызываем прерывание 18h. На многих современных BIOS просто выдается текст PRESS ANY KEY TO REBOOT. На старых машинах IBM PC вызывался встроенный интерпретатор языка BASIC.

Активный раздел найден. Перед тем как перейти к считыванию с этого раздела загрузочного сектора раздела PBR, который обеспечивает дальнейшее выполнение загрузки, необходимо выбрать метод чтения. Часть кода MBR Windows 7, описанная ниже, предназначена для проверки факта поддержки BIOS так называемых "расширений прерывания 13h". Почему именно "расширения" и зачем они потребовались разработчикам из Microsoft? Почему, к примеру, не использовать старые-добрые стандартные функции чтения секторов? Я постараюсь, в меру собственного понимания вопроса, дать краткое пояснение. История этого события, как я полагаю, уходит корнями в то время, когда для дисков в BIOS существовало ограничение на количество адресуемых секторов - 16 515 072, поскольку в интерфейсе int 13h для номера цилиндра отводилось 10 бит, для номера головки — 8 бит, для номера сектора — 6 бит, то есть всего 24 бита. Но поскольку диски росли в объемах, а логика позволяла адресовать не более 8 455 716 864 байт (~8 гигабайт), требовалось что-то в данной ситуации менять. Вот именно тогда и были добавлены дополнительные (расширенные) функции, которые предусматривали использование так называемого режима 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. Сохраняем все 32-битные регистры в стек.
  9. Сравниваем нашу внутреннюю переменную ([bp+10h]) наличия расширений с 0;
  10. если 0 - то мы не можем использовать расширения и переходим под адресу 0000:0687. Иначе идем далее по коду.

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

Перед вызовом функции в стек записываются следующие данные, формируется блок 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. Вызываем функцию прерывания;

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

  1. Сохраняем флаги в регистр ah.
  2. Оригинальным образом исключаем все данные блока DAP из стека просто переносом указателя стека на 10h.
  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. Сохраняем bp. он у нас используется как база адресации внутренних переменных;
  10. Функция ah=0 - инициализация (сброс) диска;
  11. В dl поместим значение байта по адресу [bp+0], это у нас индекс (номер) диска;
  12. Вызываем функцию 0 прерывания 13h;
  13. Восстанавливаем значение регистра bp.
  14. Передаем управление код по адресу 0000:0659 (считывание сектора);
  15. Сюда мы перешли после считывания сектора стандартной функцией. Проверим на наличие в последних двух байтах считанного сектора (ds:7DFEh) значения AA55h (сигнатура MBR). А действительно ли мы считали PBR (VBR)?
  16. Если нет сигнатуры MBR, то вероятно, что по какой-то причине сектор не был считан, либо считан не тот сектор - уходим на ошибку Missing operating system;
  17. Сохраним индекс (номер) диска в стеке;

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

  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. Что это за интерфейс и почему разработчики вынуждены были включить его поддержку в миниатюрный код сектора MBR Windows 7?

TPM (Trusted Platdorm Module) - это "доверяемый платформенный модуль", интерфейс, который предоставляет дополнительный функционал для технологии шифрования разделов Microsoft BitLocker Drive Encryption. А если проще, то это чип на материнской плате, представляющий собой своеобразную смарт-карту, на которой хранятся криптографические ключи, используемые для шифрования разделов при помощи BitLocker'а.

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

  1. Теперь начинаем проверку. ax=BB00h. Функция TCG_StatusCheck;
  2. Вызываем функцию прерывания;
  3. Логическое "И" над eax. Для проверки, вернула ли функция 0 в регистре eax. Поддерживается ли TPM?
  4. Если не поддерживается, то уходим на 0000:0727;
  5. Продолжаем проверку. Сравниваем ebx со значением 41504354h (TCPA);
  6. Если не равно, то уходим на код по адресу 0000:0727;
  7. Продолжаем проверку. сравниваем cx со значением 102h. Версия 1.02 TPM?
  8. Условие (беззнаковое) "если меньше", то уходим на код по адресу 0000:0727;
  9. TPM найдена нужной версии. готовим вызов функции TCG_CompactHashLogExtendEvent. Заносим параметры через стек. ah=0bbh, al=07h.
  10. Занесем в стек значение 00000200h. 0200h - 512 в десятичном.
  11. Занесем в стек значение 00000008. старшее слово 0000, младшее слово 0008;
  12. Занесем в стек ebx.
  13. Занесем в стек ebx.
  14. Занесем в стек ebp.
  15. Занесем в стек значение 00000000;
  16. Занесем в стек значение 00007C00;
  17. Восстановим из стека основные 32-разрядные регистры. Вот для чего мы заносили все эти значения в стек, чтобы потом одной командой popad инициализировать сразу все регистры, необходимые для вызова функции BB07h прерывания int 1Ah. получается ax = BB07h, di = смещение буфера для хеширования (7C00h), ecx = длина буфера (0200h=512), edx = номер PCR (Platform Configuration Registry) = 8, si = значение для лога событий = 0, ebx = 41504354h;
  18. Занесем в стек значение 0000;
  19. Восстановим значение из стека в регистр es, тем самым инициализировав его в значение 0000 (es=0000);
  20. Вызовем функцию BB07h прерывания 1Ah.

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

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

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

  1. Занесем в регистр al смещение первого сообщения, которое размещается по адресу ds:07B7. Сообщение: Invalid partition table;
  2. Перейдем на процедуру подготовки вывода по адресу 0000:073E;
  3. Занесем в al смещение второго сообщения, которое размещается по адресу ds:07B6. Сообщение: Error loading operating system;
  4. Перейдем на процедуру подготовки вывода по адресу 0000:073E;
  5. Занесем в al смещение третьего сообщения, которое размещается по адресу ds:07B5. Сообщение: Missing operating system;
  6. Обнулим ah.
  7. Прибавим к ax значение 0700h, чтобы скорректировать смещение сообщений;
  8. si = смещение сообщения;
  9. Загрузим в al байт из ds:si;
  10. al=0? это конец строки?
  11. Если да, то переходим по адресу 0000:0753;
  12. bx=7 - bh-страница, bl-цвет символа;
  13. ah=0Eh - функция 0Eh прерывания 10h. Вывод на экран в режиме телетайпа;
  14. Вызываем функцию прерывания;
  15. Тут организуется цикл с адресом 0000:0745, выход из цикла по условию достижения конца строки, чтобы допечатать строку по одному символу;
  16. hlt = останов;
  17. Прыжок "на месте". Другими словами это бесконечный цикл. Требуется аппаратная перезагрузка.

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

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

  • Поделиться:

11 комментариев:

  1. виктор

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

    • einaare

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

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

  2. Евгений

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

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

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

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

  3. Igor

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

    • 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-редакторе такого нет. Их надо как-то самостоятельно высчитывать?

    • 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-ти верхних значений.

    • einaare

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

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

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