Книга: Восстановление данных. Практическое руководство

Создаем MBR и пишем свой менеджер мультизагрузки

Создаем MBR и пишем свой менеджер мультизагрузки

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

Интерфейс INT 13h

Управлять дисками можно как через порты ввода/вывода, так и через BIOS. Порты намного более могущественны и интересны, однако BIOS программируется намного проще, к тому же она поддерживает большое количество разнокалиберных накопителей, абстрагируясь от конструктивных особенностей каждой конкретной модели. Поэтому будем действовать через нее, а точнее, через интерфейс прерывания INT 13h.

Попробуем прочитать сектор с диска в режиме CHS. Действовать нужно из самой MBR или из "голой" MS-DOS, иначе у нас ничего не получится, ведь Windows NT блокирует прямой доступ к диску даже из режима эмуляции MS-DOS.

Номер функции заносится в регистр AH. В случае чтения он равен двум. Регистр AL отвечает за количество обрабатываемых секторов. Так как мы собираемся читать по одному сектору за операцию, занесем сюда единицу. Регистр DH хранит номер головки, a DL — номер привода (80h — первый жесткий диск, 81h — второй и т.д.). Пять младших битов регистра CL задают номер сектора, оставшиеся биты регистра CL и восемь битов регистра CH определяют номер цилиндра, который мы хотим прочитать. Регистровая пара ES:BX указывает на адрес буфера-приемника. Вот, собственно говоря, и все. После выполнения команды INT 13h считываемые данные окажутся в буфере, а если произойдет ошибка (например, головка "споткнется" о BAD-сектор), то BIOS установит флаг переноса (carry flag), и мы будем вынуждены либо повторить попытку, либо вывести грустное сообщение на экран.

Код этой программы на языке ассемблера представлен в листинге 5.6.

Листинг 5.6. Код, считывающий загрузочный сектор или расширенную таблицу разделов

MOV SI, 1BEh ; Перейти к первому разделу
MOV AX, CS   ; Настраиваем ES
MOV ES, AX
MOV BX, buf  ; Смещение буфера
...
read_all_partitions:
 MOV AX, bud            ; Читать 1 сектор с диска
 MOV DL, 80h            ; Читать с первого диска
 MOV DH, [SI+1]         ; Стартовый номер головки
 MOV CX, [SI+2]         ; Стартовый сектор с цилиндром INT 13h
 JC error               ; Ошибка чтения
 ;Обрабатываем считанный boot-сектор или расширенную таблицу разделов
 ;===================================================================
 ;
 CMP byte [SI], 80h
 JZ LOAD_BOOT            ; Это загрузочный сектор
                         ; Передаем на него управление
 CMP byte [SI+4], 05h
 JZ LOAD_CHS_EXT         ; Это расширенная таблица разделов
                         ; в формате CHS
 CMP byte [SI+4], 0Fh
 JZ LOAD_LBA_EXT         ; Это расширенная таблица разделов
                         ; в формате LBA
 ADD SI, 10h             ; Переходим на следующий раздел
 CMP SI, 1EEh
 JNA read_all_partitions ; Читаем все разделы один за другим
...buf rb 512               ; Буфер на 512 байт

Запись сектора в режиме CHS происходит практически точно так же, только регистр AH равен не 02h, a 03h. С режимом LBA разобраться намного сложнее, но мы, как настоящие хакеры, его обязательно осилим.

Чтение сектора осуществляется функцией 42h(AH = 42h). В регистр DL, как и прежде, заносится номер привода, а вот регистровая пара DS:SI указывает на адресный пакет (disk address packet), представляющий собой продвинутую структуру формата, описанного в табл. 5.4.

Таблица 5.4. Формат адресного пакета, используемый для чтения и записи секторов в режиме LBA

Смещение Тип Описание
00h BYTE Размер пакета — 10h или 18h
01h BYTE Поле зарезервировано и должно быть равно нулю
02h WORD Сколько секторов читать
04h DWORD 32-разрядный адрес буфера-приемника в формате seg:offs
08h QWORD Стартовый номер сектора для чтения
10h QWORD 64-разрядный плоский адрес буфера-приемника. Используется только в случае, если 32-разрядный адрес равен FFFF:FFFF

Код, читающий сектор в режиме LBA, в общем случае выглядит так, как показано в листинге 5.7.

Листинг 5.7. Код, осуществляющий чтение сектора с диска в режиме LBA

MOV DI, 1BEh      ; Перейти к первому разделу
MOV AX, CS        ; Настраиваем...
MOV buf_seg       ; ...сегмент
MOV EAX, [DI+08h] ; Смещение partition относительно
                  ; начала раздела
ADD EAX, EDI      ; EDI должен содержать номер сектора
                  ; текущего MBR
MOV [X_SEC]       ;
...
read_all_partitions:
 MOV АН, 42h      ; Читать сектор в режиме LBA
 MOV DL, 80h      ; Читать с первого диска
 MOV SI, dap      ; Смещение адресного пакета INT 13h
 JC error         ; Ошибка чтения
...
dap:
packet_size db 10h ; размер пакета 10h байт
reserved    db 00h ; "Заначка" для будущих расширений
N_SEC       dw 01h ; Читаем один сектор
buf_seg     dw 00h ; Сюда будет занесен сегмент буфера-приемника
buf_off     dw buf ; Смещение буфера-приемника
X_SEC       dd 0   ; Сюда будет занесен номер сектора для чтения
dd 0               ; Реально не используемый хвост
                   ; 64-битного адреса
buf rb 512         ; Буфер на 512 байт

Запись осуществляется аналогично чтению, только регистр AH содержит не 42h, a 43h. Регистр AL определяет режим: если бит 0 равен 1, BIOS выполняет не запись, а ее эмуляцию. Бит 2, будучи взведенным, задействует запись с проверкой. Если регистр AL равен 0, выполняется обыкновенная запись по умолчанию.

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

Создаем код загрузчика

Лучше всего загрузчики программируются на FASM. С точки зрения ассемблера загрузчик представляет собой обыкновенный двоичный файл, предельно допустимый объем которого составляет 1BBh (443) байт. Немного? Но не будем спешить с выводами. Всякий раздел всегда начинается с начала цилиндра, а это значит, что между концом MBR и началом раздела имеется, по меньшей мере, n свободных секторов, где n == sectors per track. Практически все современные винчестеры имеют по 64 сектора на дорожку, что дает нам: 443 + 63*512 == 32 699 байт или приблизительно 32 Кбайт. Да в этот объем даже графический интерфейс с мышью уместить можно! Однако делать этого мы не будем. Настоящие хакеры работают в текстовом режиме с командной строкой.

Как уже говорилось, BIOS загружает MBR по адресу 7C00h, поэтому в начале ассемблерного кода должна стоять директива ORG 7C00h, а еще — USE16, ведь загрузчик выполняется в 16-разрядном реальном режиме. Позже, при желании, он может перейти в защищенный режим, но это будет уже потом. Не будем лезть в такие дебри.

Обнаружив загрузочный раздел (а обнаружить это можно по флагу 80h, находящемуся по нулевому смещению от начала раздела), загрузчик должен считать первый сектор этого раздела, поместив его в памяти по адресу 0000:7C00h, то есть в точности поверх своего собственного тела. А вот это уже нехорошо! И чтобы не вызвать крах системы, загрузчик должен заблаговременно перенести свое тело по другому адресу, что обычно осуществляется командой MOVSB. Копироваться можно по любому из адресов памяти — от 0080:0067h до 9FE00h. Память, расположенную ниже 0080:0067h, лучше не трогать, так как здесь находятся вектора прерываний и системные переменные BIOS, а от A000h и выше начинается область отображения ПЗУ, так что предельно доступный адрес равен A000h - 200h (размер сектора) == 9FE00h.

Не забывайте, что трогать регистр DL ни в коем случае нельзя, поскольку в нем передается номер загрузочного привода. Некоторые загрузчики содержат ошибку, всегда загружаясь с первого жесткого диска, и это в то время, как BIOS уже больше 10 лет как позволяют менять порядок загрузки, и потому загрузочным может быть любой привод.

По правде говоря, FASM — это единственный известный мне ассемблер, "переваривающий" команду дальнего вызова JMP 0000:7C00h напрямую. Все остальные ассемблеры заставляют извращаться приблизительно так: PUSH offset_of_target/PUSH segment_of_target/RETF. Здесь мы заталкиваем в стек сегмент и смещение целевого адреса и выполняем дальний RETF, переносящий нас на нужное место. Еще можно воспользоваться самомодифицирующимся кодом, собрав команду JMP FAR "вручную", или просто расположить целевой адрес в одном сегменте с исходным адресом (например, 0000:7C00h ? 0000:7E00h). Однако эти подходы муторны и утомительны.

В общем, скелет нашего загрузчика будет выглядеть так, как показано в листинге 5.8.

Листинг 5.8. Скелет простейшего загрузчика, написанный на FASM

use16
ORG 7C00h
CLD            ; Копируем слева направо
               ; (в сторону увеличения адресов)
MOV SI,7C00h   ; Откуда копировать
MOV DI,7E00h   ; Куда копировать
MOV CX,200h    ; Длина сектора
REP MOVSB      ; Копируем
; // Выбираем раздел, который мы хотим загрузить,
; // считываем его в память по адресу 0000:7C00h
; // (см. листинги 5.7 и 5.6)
JMP 0000:7C00h ; Передаём управление на загрузочный сектор

Записываем загрузчик в главную загрузочную запись

Под старушкой MS-DOS записать свой загрузчик в MBR было просто. Для этого достаточно дернуть прерывание INT 13h, функция 03h (запись сектора). Но под Windows NT этот прием уже не работает, и приходится прибегать к услугам функции CreateFile. Если вместо имени открываемого файла указать название устройства, например, .PHYSICALDRIVE0 (первый физический диск), мы сможем свободно читать и записывать его сектора вызовами ReadFile и WriteFile соответственно. При этом флаг dwCreationDisposition должен быть установлен на значение OPEN_EXISTING, а флаг dwShareMode — на значение FILE_SHARE_WRITE. Еще потребуются права системного администратора, иначе ничего не получится.

Законченный пример вызова CreateFile выглядит, как показано в листинге 5.9.

Листинг 5.9. Открытие непосредственного доступа к жесткому диску под Windows NT

XOR EAX,EAX
PUSH EAX                                   ; hTemplateFile
PUSH dword FILE_ATTRIBUTE_NORMAL           ; dwFlagsAndAttributes
PUSH dword OPEN_EXISTING                   ; dwCreationDisposition
PUSH EAX                                   ; lpSecurityAttributes
PUSH dword FILE_SHARE_WRITE                ; dwShareMode
PUSH dword (GENERIC_WRITE OR GENERIC_READ) ; dwDesiredAccess
PUSH DEVICE_NAME                           ; Имя устройства
CALL CreateFile                            ; Открываем устройство
INC EAX
TEST EAX,EAX
JZ error
DEC EAX
...
DEVICE_NAME db ".PHYSICALDRIVE0",0
BUF RB 512 ; Буфер

Открыв физический диск и убедившись в успешности этой операции, мы должны прочитать оригинальный MBR-сектор в буфер, перезаписать первые 1BBh байт, ни в коем случае не трогая таблицу разделов и сигнатуру 55h AAh (мы ведь не хотим, чтобы диск перестал загружаться, верно?). Теперь остается записать обновленный код MBR на место и закрыть дескриптор устройства. После перезагрузки все изменения вступят в силу.

Примечание

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

Под Windows 9x, разумеется, трюк с CreateFile не работает. Но там можно воспользоваться симуляцией прерываний из DMPI или обратиться к драйверу ASPI. Оба способа были подробно описаны в моей книге "Техника защиты компакт-дисков от копирования". Однако, хотя в ней речь идет о CD, а не о HDD, жесткие диски программируются аналогично.

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

? Ge2000.asm — тщательно прокомментированный Stealth-вирус, подменяющий системный загрузчик своим собственным. Хоть это и вирус, но он не опасен и может быть использован в учебных целях.

? Mbr.asm — предельно простой, но полнофункциональный загрузчик с поддержкой разделов свыше 8 Гбайт.

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

Отладка кода загрузчика

Отлаживать код загрузчика невероятно трудно. Загрузчик получает управление задолго до запуска операционной системы, когда никакие отладчики еще не работают. Несколько лет назад это представляло огромную проблему, и при разработке "навороченных" загрузчиков приходилось либо встраивать в них интегрированный мини-отладчик, либо выискивать ошибки вручную, изучая листинги с карандашом в руке. С появлением эмуляторов все изменилось. Достаточно запустить такой эмулятор, как BOCHS (рис. 5.5), и вы сможете отлаживать загрузчик как и любую другую программу!


Рис. 5.5. Внешний вид эмулятора BOCHS в процессе отладки загрузочного сектора

Программирование загрузчиков — одна из тех немногих областей, в которых применение ассемблера действительно обоснованно. Языки высокого уровня для этого слишком абстрагированы от оборудования. Кроме того, они недостаточно гибки. Вот почему хакеры так любят возиться с загрузчиками, добавляя сюда множество новых возможностей, включая автоматическую загрузку с CD-ROM или дисков SCSI, противодействие вирусам, парольную защиту с шифрованием данных и т.д. Здесь действительно есть, где развернуться, и есть на чем показать все свои возможности. В качестве дополнительного чтения я рекомендовал бы вам несколько весьма интересных источников. Вот они:

MBR and OS Boot Records — масса интересного материала по MBR (на английском языке): http://thestarman.narod.ru/asm/mbr/MBR_in_detail.htm;

BOCHS — отличный эмулятор со встроенным отладчиком, значительно облегчающий процесс "пуско-наладки" загрузочных секторов. Бесплатен, распространяется с исходными текстами: http://bochs.sourceforge.net;

http://www.koders.com (рис. 5.6) — отличный поисковик, нацеленный на поиск исходных кодов, по ключевому слову MBR выдает огромное количество загрузчиков на любой вкус;


Рис. 5.6. Поиск исходных текстов загрузчиков MBR на сайте Koders

Ralf Brown Interrupt List (рис. 5.7) — знаменитый "Список прерываний" (Interrupt List) Ральфа Брауна (Ralf Brown), описывающий все прерывания, включая недокументированные (на английском языке): http://www.pobox.com/~ralf;


Рис. 5.7. Просмотр легендарного "Списка прерываний" Ральфа Брауна

? OpenBIOS — проект "Открытого BIOS", распространяемого в исходных текстах. Помогает понять некоторые неочевидные моменты обработки системного загрузчика: http://www.openbios.info/docs/index.html.

Оглавление книги


Генерация: 1.087. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз