В предыдущей статье я рассказывал о логике работы загрузочного сектора MBR Windows 7. Основной целью работы MBR при традиционном способе загрузки (BIOS/POST→MBR), является нахождение, загрузка и передача управления коду загрузочного сектора раздела (PBR). В продолжении цикла статей о процессе загрузки операционной системы Windows, данная публикация освещает очередной этап загрузки ОС и посвящена изучению логики работы загрузочного сектора раздела PBR Windows 7.
Многие задаются вопросом: зачем нам создавать такую, казалось бы, на первый взгляд неудобную и очевидно избыточную цепочку загрузки, не проще ли вовсе отказаться от этапа PBR и грузить загрузчик следующего этапа сразу из кода MBR? Технически это, безусловно, возможно, если бы не принятое в своё время соглашение, что код MBR считается независимым от какой-либо операционной системы, фактически универсальным [общим] этапом загрузки, а вот PBR уже относится к конкретной реализации операционной системы (фактически входит в её состав) и обеспечивает дальнейшую её загрузку. По этой же причине не совсем корректно называть этап PBR вторым (после MBR) условным этапом загрузки операционной системы Windows, логичнее было бы тогда считать его первым. Сектор PBR может размещаться на:
- Устройствах хранения, имеющих разделы (жесткие/твердотельные диски) -- в качестве первого сектора раздела (партиции) - рассматриваемая нами ситуация;
- Устройствах хранения, [формально] не имеющих разделов (гибкие диски, флешки) -- в качестве первого сектора диска (подменяет собой MBR);
поэтому, код PBR может вызываться (в зависимости от сценария использования):
- напрямую :: кодом BIOS (по аналогии MBR);
- косвенно :: кодом MBR (случай, описываемый в данной статье);
- косвенно :: кодом менеджера загрузки (для Windows: NTLDR/Bootmgr);
Последний пункт лишний раз подчеркивает основное назначение сектора PBR - обеспечение загрузки любой операционной системы (или иного кода), особенно в условиях установки нескольких операционных систем на одну станцию (иначе: мультизагрузка). В этом случае любой менеджер загрузки, в зависимости от выбранного пользователем пункта [меню] загрузки, подгружает код сектора PBR раздела и передает ему управление, что обеспечивает дальнейшую загрузку выбранной ОС.
Часто, в тех или иных источниках, можно встретить и альтернативное название загрузочной записи раздела - Volume Boot Record (VBR, загрузочная запись тома), реже встречается именование Partition Boot Sector (PBS, загрузочный сектор раздела).
Код PBR загружается по уже хорошо нам знакомому (идентичному с MBR) стартовому адресу 0000:7C00
. В операционной системе Windows 7 загрузочная запись раздела занимает аж целых 12 физических секторов (сами понимаете, что в других версиях операционной системы величина эта может варьироваться). На этот счет тоже имеются разночтения:
- встречается мнение, что по аналогии с MBR, к PBR относятся все секторы (12), а первый сектор в цепочке секторов PBR носит название PBS.
- есть и другая точка зрения, что PBR/PBS это только один сектор, а все остальные (идущие за ним) 11 секторов относятся уже к коду менеджера BOOTMGR.
Эта особенность для нас не столь уж значима и мы условимся, что для PBR у раздела, на котором он размещается, резервируются последовательно-идущие сектора.
Теперь воспользуемся специализированной утилитой DMDE и сохраним дамп сектора (фактически копия 512 байт, размещенных в нулевом секторе диска) PBR Windows 7 во внешний файл. Посмотрим, что же он из себя представляет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
0000000000 EB 52 90 4E 54 46 53 20 20 20 20 00 02 08 00 00 лRђNTFS .◘ 0000000010 00 00 00 00 00 F8 00 00 3F 00 FF 00 00 08 00 00 ш ? я ◘ 0000000020 00 00 00 00 80 00 80 00 FF 1F 03 00 00 00 00 00 Ђ Ђ я.. 0000000030 55 21 00 00 00 00 00 00 02 00 00 00 00 00 00 00 U! ☻ 0000000040 F6 00 00 00 01 00 00 00 1A AA 3B C8 C2 3B C8 CC ц . →Є;ИВ;ИМ 0000000050 00 00 00 00 FA 33 C0 8E D0 BC 00 7C FB 68 C0 07 ъ3АЋРј |ыhА• 0000000060 1F 1E 68 66 00 CB 88 16 0E 00 66 81 3E 03 00 4E ▼▲hf Л€▬. fЃ>. N 0000000070 54 46 53 75 15 B4 41 BB AA 55 CD 13 72 0C 81 FB TFSu§ґA»ЄUН.r.Ѓы 0000000080 55 AA 75 06 F7 C1 01 00 75 03 E9 DD 00 1E 83 EC UЄu.чБ. u.йЭ ▲ѓм 0000000090 18 68 1A 00 B4 48 8A 16 0E 00 8B F4 16 1F CD 13 ↑h→ ґHЉ▬♫ ‹ф▬▼Н. 00000000A0 9F 83 C4 18 9E 58 1F 72 E1 3B 06 0B 00 75 DB A3 џѓД↑ћX▼rб;.. uЫЈ 00000000B0 0F 00 C1 2E 0F 00 04 1E 5A 33 DB B9 00 20 2B C8 ☼ Б.☼ .▲Z3Ы№ +И 00000000C0 66 FF 06 11 00 03 16 0F 00 8E C2 FF 06 16 00 E8 fя.◄ .▬☼ ЋВя.▬ и 00000000D0 4B 00 2B C8 77 EF B8 00 BB CD 1A 66 23 C0 75 2D K +Иwпё »Н→f#Аu- 00000000E0 66 81 FB 54 43 50 41 75 24 81 F9 02 01 72 1E 16 fЃыTCPAu$Ѓщ☻.r▲▬ 00000000F0 68 07 BB 16 68 70 0E 16 68 09 00 66 53 66 53 66 h•»▬hp♫▬h○ fSfSf 0000000100 55 16 16 16 68 B8 01 66 61 0E 07 CD 1A 33 C0 BF U▬▬▬hё.fa♫•Н→3Аї 0000000110 28 10 B9 D8 0F FC F3 AA E9 5F 01 90 90 66 60 1E (►№Ш☼ьуЄй_.ђђf`▲ 0000000120 06 66 A1 11 00 66 03 06 1C 00 1E 66 68 00 00 00 .fЎ◄ f..∟ ▲fh 0000000130 00 66 50 06 53 68 01 00 68 10 00 B4 42 8A 16 0E fP.Sh. h► ґBЉ▬♫ 0000000140 00 16 1F 8B F4 CD 13 66 59 5B 5A 66 59 66 59 1F ▬▼‹фН.fY[ZfYfY▼ 0000000150 0F 82 16 00 66 FF 06 11 00 03 16 0F 00 8E C2 FF ☼‚▬ fя.◄ .▬☼ ЋВя 0000000160 0E 16 00 75 BC 07 1F 66 61 C3 A0 F8 01 E8 09 00 ♫▬ uј•▼faГ ш.и○ 0000000170 A0 FB 01 E8 03 00 F4 EB FD B4 01 8B F0 AC 3C 00 ы.и. флэґ.‹р¬< 0000000180 74 09 B4 0E BB 07 00 CD 10 EB F2 C3 0D 0A 41 20 t○ґ♫»• Н►лтГ♪◙A 0000000190 64 69 73 6B 20 72 65 61 64 20 65 72 72 6F 72 20 disk read error 00000001A0 6F 63 63 75 72 72 65 64 00 0D 0A 42 4F 4F 54 4D occurred ♪◙BOOTM 00000001B0 47 52 20 69 73 20 6D 69 73 73 69 6E 67 00 0D 0A GR is missing ♪◙ 00000001C0 42 4F 4F 54 4D 47 52 20 69 73 20 63 6F 6D 70 72 BOOTMGR is compr 00000001D0 65 73 73 65 64 00 0D 0A 50 72 65 73 73 20 43 74 essed ♪◙Press Ct 00000001E0 72 6C 2B 41 6C 74 2B 44 65 6C 20 74 6F 20 72 65 rl+Alt+Del to re 00000001F0 73 74 61 72 74 0D 0A 00 8C A9 BE D6 00 00 55 AA start♪◙ Њ©ѕЦ UЄ |
Первые 2 байта (шестнадцатеричное: EB 52) занимает инструкция перехода jmp на 82 байта вперед по коду. Третий байт (90) представляет из себя инструкцию nop (No Op, нет операции), которая ничего не делает и фактически играет роль заполнителя. Спрашивается, зачем нам прыжок в самом начале кода? Причина подобного явления заключается в том, что здесь по соглашению располагаются служебные данные: сразу за указанными инструкциями перехода и заполнителя (jmp+nop) идет сигнатура (OEM ID
) NTFS раздела (загрузочные разделы Windows 7 работают исключительно с файловой системой NTFS), занимающая 8 байт. Уже за сигнатурой в секторе размещается блок данных, носящий название BIOS Parameter Block (BPB).
BPB
) - структура, описывающая физическое расположение данных раздела (иначе: ключевых параметров файловой системы). Актуальна для разделов, содержащих файловые системы FAT12, FAT16, FAT32, HPFS, NTFS.BPB бывает стандартным (в старых реализациях ОС) и расширенным, дополненным (современные реализации ОС). Расширенный блок параметров BIOS (BPB NTFS) занимает 73 байта и располагается в области со смещением 000B-0053
. Рассмотрим подробнее его структуру:
Смещение | Размер | Значение | Описание |
---|---|---|---|
000B | 2 байта | 00 02 (512) | Размер сектора в байтах. 512. Не рекомендуется использоваться другие значения, поскольку в мире уже огромное количество кода использует именно это значение. |
000D | байт | 08 | Секторов на кластер. Размер кластера = 512*8 = 4Кб. |
000E | 2 байта | 00 00 | Зарезервированные сектора. |
0010 | 1 байт | 00 | Для NTFS разделов всегда = 0. Количество таблиц FAT для FAT-раздела. |
0011 | 2 байта | 00 00 | Для NTFS разделов всегда = 0. Максимальное количество записей в корневой директории для FAT12/16. |
0013 | 2 байта | 00 00 | Для NTFS разделов всегда = 0, поскольку параметр на используется в NTFS. Общее количество секторов раздела. |
0015 | байт | F8 | Дескриптор носителя. F8 = Fixed Disk. |
0016 | 2 байта | 00 00 | Для NTFS разделов всегда = 0. FAT: секторов на таблицу FAT. |
0018 | 2 байта | 3F 00 | Геометрия диска. Секторов на дорожку. |
001A | 2 байта | FF 00 | Геометрия диска. Количество головок. |
001C | 4 байта | 00 08 00 00 (2048) | Количество "скрытых секторов". Количество скрытых секторов ОС Windows7/Vista было увеличено до значения 2048. |
0020 | 4 байта | 00 00 00 00 | в NTFS не используется. Всегда = 0. FAT - старшая часть слова количества секторов. |
0024 | 4 байта | 80 00 80 00 | в NTFS не используется. Всегда = 80008000. FAT: первый байт - номер диска (80h), второй байт - флаги, третий байт - сигнатура, четвертый байт - зарезервирован. |
0028 | 8 байт | FF 1F 03 00 00 00 00 00 | Всего секторов в разделе. |
0030 | 8 байт | 55 21 00 00 00 00 00 00 | Номер стартового кластера для $MFT файла раздела. |
0038 | 8 байт | 02 00 00 00 00 00 00 00 | Номер стартового кластера для $MFTMirror файла раздела. |
0040 | знаковое двойное слово | F6 00 00 00 | Кластеров на каждую файловую запись. Значение знаковое. Поэтому, если старший бит первого байта равен 1, то длина записи равна степени двойки отрицательного значения. Например, если байт = F6, то длина записи равна 210 (потому как 10==-F6). Последние 3 байта всегда 0 для NTFS, не используются. |
0044 | двойное слово | 01 00 00 00 | Кластеров на каждый блок индекса. Алгоритм идентичен описанному выше. Последние 3 байта всегда 0 для NTFS, не используются. |
0048 | 8 байт | 1A AA 3B C8 C2 3B C8 CC | Серийный номер раздела. На самом деле, по команде DIR мы видим не все байты номера. |
0050 | 4 байта | 00 00 00 00 | Контрольная сумма. Почему всегда 0? |
Теперь, что бы углубиться в логику работы, нам необходимо получить исходный код сектора. Я воспользуюсь привычным нам уже методом и дизассемблирую полученный ранее дамп сектора в виде .bin
-файла с помощью дизассемблера IDA. После получения исходного кода приступим к его изучению.
В самом начале сектора PBR Windows 7, как мы уже и говорили, имеется переход по смещению 0000:7C54
, это реализовано с целью "перепрыгнуть" описанный выше BPB.
1 2 |
0000:7C00 jmp short loc_7C54 0000:7C02 nop |
- переход по адресу
0000:7C54
- пустой операнд (ничего не делаем)
Сразу за этим командами следует блок данных BPB NTFS. Его мы уже приводили и увидеть его можно на общем дампе сектора PBR Windows 7, размещенном выше в статье. Следующий фрагмент кода сектора PBR демонстрирует хорошо уже нам знакомую по предыдущей статье технику инициализации сегментов реального режима процессора:
1 2 3 4 5 6 |
0000:7C54 loc_7C54: 0000:7C54 cli 0000:7C55 xor ax, ax 0000:7C57 mov ss, ax 0000:7C59 mov sp, 7C00h 0000:7C5C sti |
- [метка]
- Запрещаем асинхронные аппаратные прерывания (все прерывания кроме немаскируемых);
- Обнуляем регистр
AX
; - Обнуляем сегмент стека
SS
при помощиAX
; - Указатель стека
SP
инициализируем в значение 7С00h; - Разрешаем асинхронные аппаратные прерывания.
Затем мы наблюдаем блок кода, который при помощи команды возврата из процедуры retf, выполняет переход по адресу 07C0:0066
(он же 0000:7C66). Поэтому, дальнейшая адресация, после данного блока, пойдет у нас уже относительно указанного адреса.
1 2 3 4 5 |
0000:7C5D push 07C0h 0000:7C60 pop ds 0000:7C61 push ds 0000:7C62 push 0066h 0000:7C65 retf |
- Занесем в стек значение 07C0h;
- Восстановим его в
DS
. то есть DS=07C0h; - Сохраним
DS
(07C0h); - Занесем в стек значение 0066h;
- Команда возврата, извлекающая из стека значения для перехода. Прыгаем по адресу
07C0:0066
.
Итак, ранее мы уже условились, что в начиная с текущего блока, код PBR Windows 7 будет адресоваться относительно сегмента и смещения 07C0:0066
. Нижеприведенная часть кода проверяет наличие расширений прерывания int 13h. Подобная проверка уже имелась в коде MBR, из чего можно заключить, что использования общих блоков кода на начальном этапе загрузки операционной системы у PBR и MBR не наблюдается, возможно в каких-то сценариях код PBR выполняется без предварительного исполнения кода MBR?
1 2 3 4 5 6 |
07C0:0066 mov ds:000Eh, dl 07C0:006A cmp dword ptr ds:0003, 5346544Eh 07C0:0073 jnz short loc_008A 07C0:0075 mov ah, 41h 07C0:0077 mov bx, 55AAh 07C0:007A int 13h |
- Запишем значение регистра
DL
в память по смещению 000Eh в сегменте данных. В DL у нас содержится номер диска. Остался от MBR, либо содержится в DL после передачи управления от BIOS; - Проверяем двойное слово по смещению 0003 на значение "NTFS". Действительно ли это загрузочная запись NTFS?
- Если нет - то уходим на вывод ошибки A disk read error occured;
- Запишем в регистр
AH
значение 41h. Это номер функции; - Запишем в регистр
BX
значение 55AAh. Необходимый параметр для функции 41h; - Вызываем функцию прерывания. Определяем наличие расширений.
Код, приведенный далее, проверяет возвращенные функцией 41h прерывания 13h значения и отвечает на вопрос: доступны ли на компьютере расширения прерывания работы с диском (int 13h)? Для этого проверяется три условия.
1 2 3 4 5 6 7 |
07C0:007C jb short loc_008A 07C0:007E cmp bx, 0AA55h 07C0:0082 jnz short loc_008A 07C0:0084 test cx, 1 07C0:0088 jnz short loc_008D 07C0:008A loc_008A: 07C0:008A jmp loc_016A |
- Если carry flag (CF, флаг переноса) не сброшен, то переходим на вывод ошибки A disk read error occured;
- Сравним слово в регистре
BX
со значением AA55h. Сменилось ли значение с 55AA на AA55? Это один из признаков успешного выполнения функции; - Если не сменилось, то опять уходим на вывод ошибки A disk read error occured;
- Проверим нулевой бит регистра
CX
. Это второе условие наличия расширений; - Если нулевой бит регистра
CX
не сброшен, то продолжаем выполняться и переходим по адресу07C0:008D
. Фактически проверяется условиеCX
> 0? - [метка]
- Если же сброшен, то произошла ошибка и снова переходим на процедуру вывода ошибок по адресу
07C0:016A
.
Что мы видим в идущем далее фрагменте? Управление на этот участок кода переходит только в случае обнаружения расширений прерывания 13h. Здесь код получает у нас расширенные параметры текущего накопителя для использования этих данных в дальнейшем. Делается это с помощью функции 48h прерывания 13h. Итогом является блок параметров или результирующий буфер, занимающий 30 байт и содержащий искомые параметры накопителя. Обратите внимание, что в этом фрагменте кода проводятся хитроумные манипуляции со стеком. Для лучшего понимания логики работы этого блока стоит обратить внимание на так называемые "внутренние переменные", которые активно используются кодом на данном этапе. Эти переменные адресуются непосредственно через регистр сегмента данных DS
и в нашем коде имеют вид DS:XXXX:
ds:000E
- номер диска.ds:000B
- размер сектора в байтах.ds:000F
- размер сектора в байтах, деленный на 16. Используется для приращения сегментного регистра ES в цикле.ds:0011
- содержит абсолютный номер сектора для чтения через функцию 42h.ds:0016
- счетчик количества циклов внутри процедуры sub_011D.ds:001C
- количество скрытых секторов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
07C0:008D loc_008D: 07C0:008D push ds 07C0:008E sub sp, 18h 07C0:0091 push 001Ah 07C0:0094 mov ah, 48h 07C0:0096 mov dl, ds:000Eh 07C0:009A mov si, sp 07C0:009C push ss 07C0:009D pop ds 07C0:009E int 13h 07C0:00A0 lahf 07C0:00A1 add sp, 18h 07C0:00A4 sahf 07C0:00A5 pop ax 07C0:00A6 pop ds 07C0:00A7 jb short loc_008A 07C0:00A9 cmp ax, ds:000Bh 07C0:00AD jnz short loc_008A 07C0:00AF mov ds:000Fh, ax 07C0:00B2 shr word ptr ds:000Fh, 4 07C0:00B7 push ds 07C0:00B8 pop dx 07C0:00B9 xor bx, bx 07C0:00BB mov cx, 2000h 07C0:00BE sub cx, ax 07C0:00C0 inc dword ptr ds:0011h 07C0:00C5 loc_00C5: 07C0:00C5 add dx, ds:000Fh 07C0:00C9 mov es, dx 07C0:00CB inc word ptr ds:0016h 07C0:00CF call sub_011D 07C0:00D2 sub cx, ax 07C0:00D4 ja short loc_00C5 |
Для начала, я приведу сам блок параметров:
Блок параметров | ||
---|---|---|
Смещение | Размер | Описание |
00h | 2 байта | Размер буфера = 30 = 1Eh |
02h | 2 байта | Информационные флаги |
04h | 4 байта | Количество цилиндров диска (нумерация начинается с 0, поэтому реальных на 1 больше) |
08h | 4 байта | Количество головок диска (нумерация начинается с 0, поэтому реальных на 1 больше) |
0Ch | 4 байта | Секторов на дорожке (нумерация начинается с 1) |
10h | 8 байт | Общее количество секторов на диске (нумерация начинается с 0, поэтому реальных на 1 больше) |
18h | 2 байта | Байт на сектор |
1Ah | 4 байта | (Опционально) Указатель на параметры расширенной конфигурацию диска (Enchanced Disk Drive, EDD) |
- [метка]
- Сохраним
DS
в стек; - Вычтем из текущего значения регистра SP значение 18h. Тем самым мы подготовим регистр
SP
как базу для задания смещения блока в регистреSI
; - Запишем в стек значение 1Ah. Тем самым смещаем указатель стека
SP
еще на 2 байта в минус; - Зададим номер функции (48h) получения расширенного блока параметров диска;
DL
= индекс диска. Ранее мы его сохраняли во внутренней переменной ds:000Eh. Теперь восстанавливаем для использования;SI
= SP. То естьSI
= 7BE4.SI
задает смещение буфера, куда будет помещен блок параметров диска. Таким образом блок размером в 30 байт у нас будет лежать по адресуds:7BE4
;- Сохраним
SS
в стеке; - Восстановим
DS
из стека. Таким способом мы приравнялиDS
=SS
; - Вызываем функцию прерывания. Считываем блок параметров;
- Загружаем младший байт регистра флагов в AH;
- Добавляем к
SP
значение 18h. Тем самым мы выставили значениеSP
= 7BFC, и не спроста. По данному адресу в стеке у нас лежит параметр "байт на сектор"; - Загружаем содержимое
AH
в младший байт регистра флагов; - Восстановим
AX
. Теперь вAX
у нас получается слово "байт на сектор"; - Восстановим
DS
; - Если carry flag (CF) после вызова прерывания int 13h не сброшен, то произошла ошибка и мы переходим на вывод ошибки A disk read error occured;
- Сравним значение
AX
(байт на сектор, которое вернула функция 48h) со значением слова по смещениюds:000Bh
(07C0:000B
, или0000:7C0B
), то есть байт на сектор в BPB. Параметры диска совпадают? - Если не равно, то переход на код вывода ошибки A disk read error occured;
- Сохраним содержимое регистра
AX
по адресу07С0:000Fh
(0000:7C0F). То есть мы записываем младший байт параметра "Reserved Sectors" и параметр "Количество таблиц FAT для раздела FAT". Видимо, просто трюк, поскольку последний параметр не нужен и всегда равен нулю для NTFS, его можно и подпортить? - Логический сдвиг вправо слова по адресу
ds:000Fh
на 4 бита. Тем самым мы делим значение на16
. Зачем? Для того, чтобы далее по коду использовать в приращении значения сегментного регистраES
, который у нас отвечает за сегментную часть адреса считывания сектора в память; - Сохраним значение сегментного регистра
DS
в стеке; - Восстановим это значение в регистр
DX
.DX
=DS
; - Обнулим регистр
BX
; - Присвоим
CX
значение 2000h (десятичное 8192). Вероятно,CX
у нас будет использоваться как счетчик считываемых байтов. Будем читать сразу 16 секторов. Что называется "с запасом", поскольку нам то нужно всего 8? - Вычтем из
CX
значениеAX
. Это сделано для корректирования размера считываемого блока; - Увеличим слово по адресу
ds:0011h
на единицу. Это незначащее поле BPB для NTFS, у нас используется как абсолютный номер сектора для чтения через функцию 42h. Поскольку начальное значение было нулевым, теперь там 1, тем самым указывая на второй от начала сектор раздела. Ведь первым является сам PBR, поэтому начинаем читать со следующего после него; - [метка]
- Прибавляем к значению регистра DX значение слова по адресу
ds:000Fh
; ES
=DX
. Зачем нам в цикле каждый раз менять значение сегментаES
? Дело в том, что внутри процедуры чтения сектора sub_011D мы используем сегментES
для задания сегментной части у целевого адреса, куда помещается считанный сектор;- Увеличим на единицу слово по адресу
ds:0016h
. Это незначащее поле BPB для NTFS, вероятно используется как счетчик циклов внутри процедуры sub_011D. После увеличения приобретает значение 1, поскольку изначально поле было нулевым; - Вызываем подпрограмму sub_011D, которая у нас отвечает за чтение секторов;
- Вычтем из
CX
(начальное значение =8192), значениеAX
(=512). Таким образом каждую итерацию цикла уменьшаем счетчик; - Переход на
cs:00C5
, еслиCX
большеAX
. Тем самым образуется цикл чтения секторов по условию.
Следующий далее участок кода, по аналогии с кодом из MBR, предназначен для определения наличия TPM версии 1.2. Это действительно странно, поскольку практически аналогичную проверку мы уже проводили в коде MBR, однако разработчики решили определить наличие TPM и тут? Возможно предположения относительно "автономности" кода PBR не лишены основания? Как и ранее, возникает предположение, что это сделано для того, что бы в каком-то гипотетическом сценарии загрузки PBR мог грузиться независимо от MBR.
1 2 3 4 5 6 7 8 |
07C0:00D6 mov ax, 0BB00h 07C0:00D9 int 1Ah 07C0:00DB and eax, eax 07C0:00DE jnz short loc_010D 07C0:00E0 cmp ebx, 41504354h 07C0:00E7 jnz short loc_010D 07C0:00E9 cmp cx, 102h 07C0:00ED jb short loc_010D |
- Загружаем в регистр
AX
номер функции BB00h (TCG_StatusCheck); - Вызываем прерывание
1Ah
; - Сравним значение регистра
EAX
с нулем. Одно из условий; - Если не ноль, то BIOS не поддерживаем спецификацию TCG, выходим из кода TCG и переходим по адресу
010D
; - Иначе, если поддержка есть - проверим регистр
EBX
на значение 41504354h (TCPA), значения расположены в обратном порядке; - Если значение не совпадает, то выход из кода TCG, переход по адресу
010D
; - Сравним значение регистра
CX
с 102h. Версия 1.02? - Если нет, то выход из кода TCG - переход по смещению
010D
.
Идущий далее блок кода PBR Windows 7 у нас выполняется в случае поддержки TPM оборудованием и в случае наличия необходимой разработчикам версии TPM - 1.02. Вызывается функция TCG_CompactHashLogExtendEvent. Более подробно аналогичный участок кода описан в предыдущей статье, посвященной MBR.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
07C0:00EF push ss 07C0:00F0 push 0BB07h 07C0:00F3 push ss 07C0:00F4 push 0E70h 07C0:00F7 push ss 07C0:00F8 push 9 07C0:00FB push ebx 07C0:00FD push ebx 07C0:00FF push ebp 07C0:0101 push ss 07C0:0102 push ss 07C0:0103 push ss 07C0:0104 push 1B8h 07C0:0107 popad 07C0:0109 push cs 07C0:010A pop es 07C0:010B int 1Ah |
Нижележащий блок кода предназначен для заполнения нулями блока данных по адресу ES:1028h
.
1 2 3 4 5 6 7 8 9 |
07C0:010D loc_010D: 07C0:010D xor ax, ax 07C0:010F mov di, 1028h 07C0:0112 mov cx, 0FD8h 07C0:0115 cld 07C0:0116 rep stosb 07C0:0118 jmp near ptr 027Ah 07C0:011B nop 07C0:011C nop |
- [метка]
- Обнулим регистр
AX
. Ноль будет использоваться как заполнитель; - Инициализируем регистр
DI
значением 1028h. Начиная с адресаES
:DI
(ES:1028h) у нас пойдет циклическая запись нулей; - В регистр
CX
запишем счетчик байтов - 0FD8h (4056); - Сбросим флаг направления (DF), то есть направление - вперед, регистр
DI
будет увеличиваться каждый раз на единицу; - Выполнить команду инициализации строки байт. Область памяти будет заполнена нулями;
- Переход по адресу
cs:027Ah
. Сюда мы ранее считали весь оставшийся код PBR, размещенный на диске начиная со второго сектора раздела и (скорее всего) представляет собой простейший парсер NTFS, предназначенный для нахождения и загрузки файла Bootmgr. Всё, мы ушли! - Нет операции. Простой заполнитель. Резерв.
- Нет операции. Простой заполнитель. Резерв.
В конце кода первого сектора PBR Windows 7 у нас размещена подпрограмма считывания секторов диска, выполняющая "расширенное" чтение. Для этого используется функция 42h прерывания 13h. Процедура за один проход читает по одному сектору. Заметьте, в отличие от кода MBR, код PBR выполняет чтение только с помощью "расширенной" функции чтения. В процедуре используется несколько внутренних переменных:
ds:0011h
- содержит абсолютный номер сектора для чтения через функцию 42h. начальное значение =1, то есть начинаем отсчет со второго сектора раздела.ds:001Ch
- количество скрытых секторов. прибавляем это значение для определения [начального] номера сектора для чтения.ds:000Eh
- номер диска.ds:000Fh
- размер сектора в байтах, деленный на 16. Используется для увеличения сегментного регистра ES.ds:0016h
- счетчик количества внутренних циклов. всегда равно 1 при входе в процедуру.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
0000:011D sub_011D proc near 0000:011D pushad 0000:011F push ds 0000:0120 push es 0000:0121 loc_0121: 0000:0121 mov eax, ds:11h 0000:0125 add eax, ds:1Ch 0000:012A push ds 0000:012B push large 0 0000:0131 push eax 0000:0133 push es 0000:0134 push bx 0000:0135 push 1 0000:0138 push 10h 0000:013B mov ah, 42h 0000:013D mov dl, ds:000Eh 0000:0141 push ss 0000:0142 pop ds 0000:0143 mov si, sp 0000:0145 int 13h 0000:0147 pop ecx 0000:0149 pop bx 0000:014A pop dx 0000:014B pop ecx 0000:014D pop ecx 0000:014F pop ds 0000:0150 jb loc_016A 0000:0154 inc dword ptr ds:0011h 0000:0159 add dx, ds:000Fh 0000:015D mov es, dx 0000:015F dec word ptr ds:0016h 0000:0163 jnz short loc_0121 0000:0165 pop es 0000:0166 pop ds 0000:0167 popad 0000:0169 retn |
Финальная часть кода PBR Windows 7 представляет собой блок вывода сообщений об ошибках, которые могут возникать в процессе исполнения кода. Сам вывод происходит довольно распространенным методом, при помощи функции 0Eh прерывания 10h. После вывода сообщений происходит приостановка процессора при помощи исполнения инструкции hlt (до момента возникновения внешнего прерывания). Следом располагается "короткий" переход на адрес 0176
, который "зацикливает" hlt.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
07C0:016A loc_016A: 07C0:016A mov al, ds:01F8h 07C0:016D call sub_0179 07C0:0170 mov al, ds:01FBh 07C0:0173 call sub_0179 07C0:0176 hlt 07C0:0177 jmp short loc_0176 07C0:0179 sub_0179 proc near 07C0:0179 mov ah, 1 07C0:017B mov si, ax 07C0:017D loc_017D: 07C0:017D lodsb 07C0:017E cmp al, 0 07C0:0180 jz loc_018B 07C0:0182 mov ah, 0Eh 07C0:0184 mov bx, 7 07C0:0187 int 10h 07C0:0189 jmp short loc_017D 07C0:018B retn |
- [метка]
- Загружаем указатель на строку (8Ch) из ячейки памяти по адресу
ds:01F8h
в регистрAL
; - Вызываем процедуру вывода;
- Загружаем указатель на строку (D6h) из ячейки памяти по адресу
ds:01FBh
в регистрAL
; - Вызываем процедуру вывода;
- После вывода всех ошибок следует останов инструкцией hlt;
- На всякий случай, если hlt отработала некорректно (?)- выполняем прыжок по адресу
0176
(вечный цикл); - [подпрограмма/процедура]
- Сама процедура вывода строки. Тут у нас вычисляется реальный адрес выводимой строки. Делается это следующим образом: заносим в ah значение 1, а в
AL
у нас уже есть значение указателя. Тем самым параAH
+AL
(или, другими словами регистрAX
) примут вид 1XXh, то есть либо 18Ch для первого и 1D6h для второго вызовов процедуры. А эти значения и есть реальные смещения сообщений об ошибках. Стоит заметить так же, что строки начинаются с преамбулы в два байта ODh, 0Ah, "возврат каретки" и "перевод строки" соответственно. Это распространенная техника перевода строки при использовании функций прерывания 10h; SI
=AX
;- [метка]
- Загрузим байт в
AL
изDS
:SI
; - Сравним
AL
с нулем, не конец ли строки? после каждой строки у нас располагается классический нуль-терминатор, знаменуя её окончание; - Если достигнут конец строки - выход из процедуры;
AH
= 0Eh - функция вывода на экран (в режиме телетайпа);BX
= 7 - атрибут вывода;- Вызов функции 0Eh прерывания 10h. Собственно, вывод [одного] символа на экран;
- Цикл по адресу
017D
, пока не выведем на экран всю строку (по одному байту); - Возврат из процедуры печати (вывода) символов.
То есть, в коде, который представлен первым сектором PBR, мы видим вывод всего двух сообщений A disk read error occured и Press Ctrl+Alt+Del to restart. Все остальные сообщения, которые мы могли наблюдать на дампе сектора, вероятнее всего выводятся уже кодом из последующих секторов PBR, изучение которых мы будем проводить в будущем.
Заключение
На этом анализ кода первого сектора PBR Windows 7 завершен. Стоит отметить, что логика работы не всех частей кода PBR была мною раскрыта должным образом, однако, надеюсь устранить данный недостаток в будущем, когда пройдет затяжной приступ лени. По сути, что сделал у нас код первого сектора PBR Windows 7? Фактически, он загрузил код следующих за ним 8 секторов, которые все вместе составляют код PBR, с носителя в память и передал этому коду управление. На этапе PBR все операции выполняются в реальном, 16-битном режиме работы процессора.
приглядитесь к команде
0000:0121 mov eax, ds:11h
по-моему он читает сектора не со следующего после VBR а со следующего после зарезервированных секторов после VBR
вернее приглядитесь к следующей команде
0000:0125 add eax, ds:1Ch
да, если учесть что в ds:1Ch - количество скрытых секторов, то так оно и есть. действительно была неточность, исправил.
Очень крутая статья! Спасибо за материал!