Вивчаємо Bootmgr. Частина 1 — інструментарій і основні принципи налагодження початкових етапів завантаження ОС Windows

Введення
Можливо, деякі читачі пам'ятають мою найпершу статтю на ресурсі, присвячену завантаженні Windows з VHD-образу. Можливо я б і не повернувся до цієї теми, якби не знайшлися люди, які спробували повторити дану технологію на своїх домашніх машинах. Природно, з реалізацією цього рішення виникли проблеми, що стосуються в основному тих помилок, які випльовує bootmgr в тих випадках, коли йому щось не подобається. Спроби інтерпретації помилок завантаження начебто 0xc03a0003 шляхом гугления до особливо цінних результатами не призводять, а документація Microsoft на цей рахунок зберігає багатозначне мовчання. Виникла ідея вивчити процес обробки VHD-образів, отримавши інформацію з перших рук, тобто від самого завантажувача.
Якщо звернеться до вже наявної в мережі інформації, то існує чудовий блог "Записки эникейщика про Windows" на сторінках якого (раз, два і три) розміщені, на мій погляд, найбільш цінні відомості, з питань влаштування bootmgr. Автор докладно розглянув процес завантаження, включаючи дослідження коду MBR і PBR, зупинившись на структурі bootmbr, коротко описавши відбуваються при його роботі процеси.
Ми ж підемо далі — опишемо інструментарій, який можна використовувати для вивчення пристрою завантажувача і спробуємо розібратися з деякими, що цікавлять нас алгоритмами. Якщо така пропозиція здалося комусь цікавим, ласкаво прошу під кат
1. Дістаємо код Bootmgr з системи
Завантажувач Bootmgr з'явився в операційних системах сімейства Windows, починаючи з Windows Vista. Причиною його розробки стало те, що старий добрий ntldr, що використовувався в лінійці NT не міг завантажувати систему, на комп'ютерах з материнськими платами оснащеними UEFI, в ті часи (2005 рік) мало поширеними серед широкого кола рядових користувачів.
За замовчуванням, при штатній установці, цей завантажувач поміщається в окремий прихований розділ, розташований на початку HDD, з розміром, достатнім для розміщення самого bootmgr а так само файлів конфігурації. У системах з MBR створення цього прихованого розділу можна уникнути, встановлюючи Windows на попередньо розмічений і відформатований HDD. У цьому випадку завантажувач поміщається в той же розділ, що і файли ОС. Системи з EFI + GPT спочатку вимагають наявності такого розділу, що має тип 0xef00 і відформатованого в FAT32.
Таким чином, перше наше завдання — добути bootmgr. Бажано взяти його з системи, яка буде виступати в ролі піддослідного. Для цього встановимо Windows на віртуальну машину. Це може бути і VirtualBox, VMware, і QEMU — все залежить від того, яким інструментарієм віртуалізації ви володієте. Я здебільшого працюю в ОС Linux, буду в основному орієнтуватися на інструменти застосовуються там, хоча приділю увагу і Windows.
Отже, припустимо, у нас є віртуальна машина (ВМ) з встановленою на ній Windows 7 (x86). Розмітка диска виконана на основі MBR, система встановлена в один розділ. Припустимо це QEMU, диск, на якому встановлена піддослідний має формат raw. тобто звичайний двійковий образ. Монтуємо образ
$ sudo modprobe -r loop
$ sudo modprobe loop max_part=15
$ sudo losetup -f win7.hdd
$ sudo mount /dev/loop0p1 ~/virt-win
$ ls -l ~/virt-win

На змонтованому розділі ми побачимо наступне вміст
разом 5504541
-rwxrwxrwx 1 root root 24 чер 2009 11 autoexec.bat
drwxrwxrwx 1 root root 4096 травень 21 09:08 Boot
-rwxrwxrwx 1 root root 391640 лип 21 2015 bootmgr
-rwxrwxrwx 1 root root 8192 травень 21 09:08 BOOTSECT.BAK
-rwxrwxrwx 1 root root 10 11 чер 2009 config.sys
lrwxrwxrwx 2 root root 60 14 лип 2009 'Documents and Settings' -> /home/maisvendoo/virt-win/Users
-rwxrwxrwx 1 root root 2415517696 травень 21 09:26 hiberfil.sys
-rwxrwxrwx 1 root root 3220692992 травень 21 09:26 pagefile.sys
drwxrwxrwx 1 root root 0 14 лип 2009 PerfLogs
drwxrwxrwx 1 root root 4096 травень 21 09:14 ProgramData
drwxrwxrwx 1 root root 4096 12 кві 2011 'Program Files'
drwxrwxrwx 1 root root 0 травень 21 09:14 Recovery
drwxrwxrwx 1 root root 0 травень 21 09:14 '$Recycle.Bin'
drwxrwxrwx 1 root root 4096 травень 21 09:09 'System Volume Information'
drwxrwxrwx 1 root root 4096 травень 21 09:14 Users
drwxrwxrwx 1 root root 16384 травень 21 09:09 Windows

Для нас представляє інтерес файл bootmgr. Однак, перш нам потрібен не зовсім він, а 32-розрядний образ завантажувача bootmgr.exe, який знаходиться в bootmgr в упакованому вигляді. Для його розпакування необхідно використовувати утиліту bmzip, яка написана в загальному-то для Windows (з наскоку зібрати її під Linux не вийшло), тому розпаковування виконаємо на віртуальній машині. Бінарну складання цієї утиліти, яка б працювала нормально виявилося досить важко знайти, незважаючи що тут дано посилання на неї. У підсумку, пакет був знайдений на якомусь з сайтів, присвячених кастомізації bootmgr. Для роботи bmzip виявилася необхідна бібліотека MSCompression.dll. Готовий до роботи пакет тепер завантажити тут.
Створимо на диску ВМ папку utils і скопіювати туди bmzip.exe разом з MSCompression.dll. Отмонтовуємо образ і запустимо ВМ. Запустимо командний рядок від імені адміністратора. Щоб випадково не зіпсувати завантажувач зробимо його копію
C:\ Windows\System32>cd c:\
C:\ xcopy bootmgr utils\bootmgr /h 

Файл завантажувача є прихованим і системним, тому знімемо з нього ці атрибути
C:\ cd utils
C:\ attrib -S -H /s

Розпаковуємо завантажувач
C:\ bmzip bootmgr bootmgr.exe

У підсумку отримуємо розпакований образ bootmgr.exe
image
Вимикаємо ВМ і знову монтуємо її диск в лінуксі. Створимо яку-небудь папку, де будемо потрошити завантажувач дизассемблером і скопіювати туди розпакований образ
$ mkdir -p ~/work/bootmgr/
$ cp ~/virt-win/utils/bootmgr.exe ~/work/bootmgr/

2. Дизассеблируем bootmgr.exe
Тепер згодувати отриманий "экзешник" дізассемблер. Наприклад IDA Pro. Запустимо "йду" і відкриємо в ній видобутий файл.
image
IDA вірно ідентифікує файл як 32-розрядний виконуваний файл формату PE. Тиснемо ОК. Тепер, якщо в IDA Pro встановлений плагін для роботи з pdb-файлами, по ходу дизасеммблирования нам запропонують завантажити налагоджувальні символи, і не звідки-небудь, а сайту Microsoft.
image
Погоджуємося і отримуємо таку картину
image
Ага, зліва ми бачимо прототипи функцій, що містяться в досліджуваному файлі, завдяки тому що погодилися завантажити налагоджувальні символи. Це дуже сильно полегшить нам подальшу роботу. А поки визначимо точку входу в код завантажувача, і неважко здогадатися що це буде функція BmMain(). Однак, не приймаючи на віру тиснемо Ctrl + E
image
переконуючись що наша здогадка вірна — BmMain() є точкою входу, розташованої за адресою 0x401000. Тиснемо ОК і переміщаємося на початок коду
image
ми Бачимо заголовок функції BmMain() зі значним списком локальних змінних, і трохи нижче і сам код функції
image
Розібратися в мішанині асемблерного коду досить важко, та й не навіщо цього робити. Насамперед визначимося з тим, які функції завантажувача ми хочемо вивчити. Я щось там говорив про VHD? Ну так пошукаємо серед коду що-небудь, що стосується віртуальних дисків. Клацаємо правою кнопкою по списку функцій зліва і в вывалившемся контекстному меню вибираємо "Quick filter" (або перейшовши у вікно з прототипами тиснемо Ctrl + F). У рядку пошуку набираємо "vhd" і…
image
так, такі функції є в кількості 33 штук. Серед них VhdOpen() очевидно буде відповідати за відкриття віртуального диска, а ось наприклад VhdiVerifyVhdFooter() як пити дати відповідає за перевірку футера VHD-диска на коректність. Тобто ми приблизно уявляємо собі, куди будемо ставити точки зупинки у налагоджувач. До речі, поговорити про налагодження самий час
3. Налагодження Bootmgr на зв'язці QEMU + IDA Pro
Запускаємо віртуальну машину з ключами -s -S — це включає режим відладки
$ qemu-system-x86_64 ~/VM/qemu/win7-efi/win-x86.hdd -m 4096 -s -S

ВМ запускається і відразу ж стає на паузу, очікуючи підключення відладчика
Важливо! Ні в якому разі не використовуйте ключ -enable-kvm застосовує апаратну віртуалізацію. При її використанні налагодження в QEMU не працює.
Тепер на панелі інструментів у IDA вибираємо відладчик "Remote GDB debugger"
image
Відповівши "Так" на кілька заданих нам питань отримаємо віконце
image
де уб'ємо параметри з'єднання з ВМ: localhost на порту 1234. Тиснемо ОК. Нам повідомлять, що певний процес вже запущений і чекає підключення відладчика — не хочемо ми приєднається до нього? Звичайно ж ходимо!
image
Тому відповідаємо "Так" і...
image
ми встаємо на паузу десь у початку bios віртуальної машини. Чудово, але тепер ми повинні дістатися до того місця, де починає виконуватися bootmgr. Ставимо точку зупину на функції BmMain(). Натискаємо на панелі інструментів список точок зупину, тиснемо Insert на клавіатурі і вказуємо на якій адресі ми хочемо перервати виконання коду і перейти в налагодження
image
Вбиваємо адреса 0x401000. Якщо ж ми хочемо поставити бряк на потрібну нам функцію, то йдемо в головне меню і відкриваємо в сеанс налагодження список функцій: View -> Open subviews -> Functions. У списку правою кнопкою миші викликаємо контекстне меню і вибираємо Add breakpoint. Тепер тиснемо F9 і після недовгого очікування потрапляємо в самий початок коду завантажувача
image
Тепер ми можемо проходити код по кроках, дивитися значення регістрів і стека, відстежувати стек викликів і так далі. В якійсь мірі відладчик, вбудований в IDA зручний і інтуїтивно зрозумілий.
Можливо мене запитають — а чи можна використовувати GDB? Можна, запускаємо ВМ в режимі налагодження, запускаємо gdb в консолі
$ gdb -q

Підключаємося до віддаленого сесії ВМ
(gdb) target remote localhost:1234

Вмикаємо відображення дизассемблированных інструкцій
(gdb) display/4i $pc

Якщо вас не влаштовує синтаксис AT&T перемикаємося на інтел
(gdb) set disassembly-flavor intel

Ставимо точку зупину на BmMain() і запускаємо виконання
(gdb) b *0x401000
Breakpoint 1 at 0x401000
(gdb) c
Continuing.

Breakpoint 1, 0x00401000 in ?? ()
1: x/4i $pc
=> 0x401000: mov edi,edi
0x401002: push ebp
0x401003: mov ebp,esp
0x401005: and esp,0xfffffff8
(gdb)

будь Ласка, ми бачимо майже теж саме, що бачили в IDA, володіючи при цьому всією міццю GDB. Майже, тому що тут ми не зможемо використовувати символи відлагодження від Microsoft, бо GDB їх не розуміє. Але можливості GDB не в приклад більш широкі, ніж можливості IDA саме в плані процесу налагодження та його автоматизації.
Однак, існує ще одна можливість налагодження, повз яку пройти не можна
3. Налагодження на зв'язці WinDbg + VirtualBox
Ті, хто займається розробкою драйверів для ОС Windows безумовно знайомі з цим чудовим відладчиком. Чудовий він тим, що має можливості порівнянні з можливостями лінуксового GDB. Єдиним його недоліком є страшний спосіб настроювання інтерфейсу. Але ми опустимо ці моменти, а звернемося до можливостей даного налагоджувач для розв'язуваної нами завдання.
Отже, нехай на у нас є ВМ на основі VirtualBox. Створимо для цієї ВМ COM-порт з наступними параметрами
image
Це віртуальний COM-порт, пробрасываемый в именованый канал. Для налагодження через послідовний порт віртуальну машину слід налаштувати відповідним чином. Завантажуємо її і запускаємо консоль з адміністративними правами. З нею вводимо команди установки завантажувача для налагодження
c:\ Windows\system32> bcdedit /bootdebug {bootmgr} on

Ця команда включить можливість налагодження завантажувача. Далі налаштуємо порт для налагодження
c:\ Windows\system32> bcdedit /dbgsettings serial debugport:1 baudrate:115200

Вказуємо, що ми використовуємо COM1 зі швидкістю 115200 бод. Відмінно, вимикаємо ВМ і запускаємо відладчик.
Відладчик WinDbg можна скачати офіційно з сайту Microsoft разом з комплектом для розробки драйверів. Однак у цієї збірки відладчика є проблема — глюк з відображенням значень регістрів. Тому я використовую збірку, яка гойдається з того ж сайту редмодовцев, на яку ведепосилання з твітера якогось Домініка Вонга. У цій збірці даний баг відсутня. Запускаємо WinDbg наступною командою
c:\Wingdbx86> windbg -b -k com:pipe,port=\\.\pipe\com1,resets=0,reconnect

Відкриємо налаштування інтерфейсу (File — > Open Workspace in File) в який серед інших параметрів збережений шлях http://msdl.microsoft.com/download/symbols для завантаження налагодження символів з серверів Microsoft. У мене цей шлях заздалегідь вбито в налаштування (File -> Symbol File Path) і збережений в темі для WinDbg. Така настройка дозволить нам автоматично отримати налагоджувальну інформацію для завантажувача.
Тепер запустимо ВМ. Практично відразу вона встане на паузу, а у вікні налагоджувача ми побачимо наступну картину
image
Ага, відладчик підключився до ВМ і встав на точці, люб'язно наданої нам майкрософтом. Ну що ж, тепер нам доступні всі можливості налагодження з використанням windbg.
Проте ми не зупиняємося на самому початку коду завантажувача, а трохи далі. Як показує покрокова налагодження ми знаходимося якраз за функцією BlInitializeLibrary() яка забезпечує початкову ініціалізацію обладнання
image
і, при налагодженні за допомогою IDA ми сюди просто не потрапляємо. Таким чином, при налагодженні з WinDbg від нас вислизає частина дій bootmgr відразу після його запуску. У цьому полягає недолік використання стандартних засобів налагодження, наданих Microsoft. Однак, недоступний код ми завжди зможемо досліджувати окремо з допомогою IDA.
Тепер, в якості прикладу, подивимося на те, як bootmgr працює з образами VHD фіксованого розміру.
4. Налагоджує завантаження з VHD
Все нижче наступне розглядається на налагоджувач WinDbg, підключеному до ВМ на VirtualBox, але в рівній мірі справедливо і для інших методів налагодження, з урахуванням їх особливостей. ВМ, використовувана в даному прикладі має дві системи: одна встановлена на HDD, інша на VHD образ. Поставимо точку зупину на функції VhdOpen()
kd> bp VhdOpen

і натиснемо клавішу F5. Відладчик встане на зазначеній функції
image
Причому, увагу — ми ще взагалі не заходили в меню завантаження і не вибирали завантаження з VHD. А це означає, що перевірка VHD відбувається задовго до появи меню. Таку ж поведінку ми спостерігаємо, наприклад, якщо подсунем bootmgr порожній VHD. Меню завантаження нам взагалі не покажуть, а покажуть помилку з кодом 0xc000000F.
Проходимо трохи далі, натискаючи F10 або вводячи в комадной рядку p і дійдемо до виклику VhdiAllocateVhdData() — очевидно це створення в пам'яті деяких структур для роботи з чином
image
Трохи нижче розташований виклик VhdiVerifyAndInitializeVhd() — очевидно перевірка коректності образу. Це здалося мені цікавим і я пішов всередину (F11)
image
Нижче, після деяких підготовчих операцій завантажувач читає останні 512 байт образу, у яких міститься так званий "футер" образу, викликаючи функцію VhdiReadVhdInformation(). Не треба ходити до ворожки, щоб зрозуміти — функція повертає покажчик на структуру, що містить дані футера. Як мені вдалося з'ясувати, цей покажчик, після виклику VhdiReadVhdInformation() виявляється в регістрі ecx. Його значення дорівнює 0x110098. Подивимося на пам'ять з того адресою
kd> db 0x110098

Команда читає пам'ять за вказаною адресою, виводячи її у вікно відладчика у вигляді послідовності байт
00110098 63 6f 6e 65 63 74 69 78-00 00 00 02 00 00 01 00 conectix........
001100a8 ff ff ff ff ff ff ff ff-70 5e d3 1e 77 69 6e 20 ........p^..win 
001100b8 00 06 00 01 57 69 32 6b-00 00 00 40 06 00 00 00 ....Wi2k...@....
001100c8 00 00 00 40 06 00 00 00-cb 2c 10 3f 02 00 00 00 ...@.....,.?....
001100d8 83 e6 ff ff 75 11 0a 5a-eb 03 c6 43 b9 c9 d6 df ....u..Z C.......
001100e8 24 b6 76 57 00 00 00 00-00 00 00 00 00 00 00 00 $.vW............
001100f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00110108 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................

Ага, ми бачимо знайоме слово — conectix. Це поле передує футер VHD образу, носить назву cookie і зберігає пам'ять про те, що Microsoft придбала технологію VHD у фірми Conectix, яка розробила цей формат віртуальних дисків для старих комп'ютерів Macintosh, Це безсумнівно футер VHD, ми можемо бачити тут сигнатуру операційної системи в якій він був створений (Wi2k) а так само послідовність win вказує на те, що VHD створений засобами Windows. Так, все так і було. Пройшовши трохи далі ми натикаємося на виклик VhdiVerifyVhdFooter() перевіряючий формат футера. В якості параметра він отримує покажчик на вищезазначену структуру, чомусь через регістр esi (???)
image
Цю ділянку коду цікавив мене найбільше, тому десь з допомогою IDA Pro, десь руками, я перетворив його в псевдокод на C
signed int __usercall VhdiVerifyVhdFooter(int footer)
{
signed int error_code; // Error code
int cur_checksum; // Actual checksum, writed in VHD
int calc_checksum; // Calculated checksum
int disk_type; // Disk type
int creator_host_os; // Creator host OS

// Error code
error_code = -1069940733; // 0xc03a0003

// Check cookie
if ( RtlCompareMemory((const void *)footer, "conectix", 8) == 8 )
{
// Store actual checksumm
cur_checksum = *(_DWORD *)(footer + 64);
// Write to zero checksum in footer structure
*(_DWORD *)(footer + 64) = 0;
// Calculate check summ
calc_checksum = BlUtlCheckSum(0x40001, 0, footer, 0x200);
// Restore checsum in footer
*(_DWORD *)(footer + 64) = cur_checksum;

// Checksum verify
if ( calc_checksum == cur_checksum )
{
// File type verify
if ( *(_WORD *)(footer + 14) == 1 )
{
// Check disk type
disk_type = *(_DWORD *)(footer + 60);
if ( disk_type == 2 || disk_type == 3 || disk_type == 4 )
{
// Check creator host OS
creator_host_os = *(_DWORD *)(footer + 36);
if ( creator_host_os != 1798465879 && creator_host_os )
{
error_code = -1073741637; // 0xc00000bb

} // Check disk size (by integer sectors count)
else if ( *(_DWORD *)(footer + 48) & 0x1FF || *(_DWORD *)(footer + 40) & 0x1FF )
{
error_code = -1069940718; // 0xc03a0012
}
else
{
error_code = 0;
}
}
else
{
error_code = -1069940732; // 0xc03a0004
}
}
else
{
error_code = -1069940731; // 0xc03a0005
}
}
else
{
error_code = -1069940734; // 0xc03a0002
}
}
return error_code;
}

Футер VHD можна представити у вигляді наступної структури (в коментарях вказані зміщення від її початку).
//-----------------------------------------------------------------------------
// VHD foother's data
//-----------------------------------------------------------------------------
struct vhd_footer_t
{
char cookie[8]; // +0
uint32_t features; // +8
uint32_t file_format_version; // +12
uint64_t data_offset; // +16
uint32_t time_stamp; // +24
char creator_application[4]; // +28
uint32_t creator_version; // +32
char creator_host_os[4]; // +36
uint64_t original_size; // +40
uint64_t current_size; // +48
vhd_disk_geometry_t disk_geometry; // +56
uint32_t disk_type; // +60
uint32_t checksum; // +64
vhd_uuid_t unique_id; // +68
uint8_t saved_state; // +84
uint8_t reserved[427];
};

Користуючись цими даними можна зробити висновок про те, які поля футера перевіряє bootmgr і які помилки він викидає. При коректному VHD образі дана функція повертає нуль, в інших випадках розклад такий
0xc03a0003 - Невірний cookie
0xc03a0002 - Неправильна контрольна сума футера
0xc03a0005 - Невірна версія формату файла
0xc03a0004 - Невірний тип віртуального диска
0xc00000bb - Віртуальний диск створений не в Windows
0xc0300012 - Розмір диска не кратний 512 (розмір сектора в VHD)

Отримана мною інформація вирішила спір виник з колегою по форуму, на якому обговорювалася методика завантаження Windows з VHD. Я його програв, вважаючи, що образи, створені VirtualBox не будуть вантажиться з допомогою bootmgr. VirtualBox, створюючи такі образи пише всі поля у відповідності зі специфікацією Microsoft, крім поля creator_application, куди записано win в оригінальному образі і vbox у випадку з виртуалбоксом. Але це поле не перевіряється bootmgr-му, так що диски працюють, а у мене вони не працювали за зовсім іншої причини, яка є предметом зовсім іншої історії.
Висновок
Можливо, стаття дещо сумбурна. Але, вона говорить про те, що не боги горщики обпалюють, а налагодження низькорівневого коду Windows лише справа техніки. Цікаву для вас інформацію завжди можна отримати, доклавши до цього голову і руки. У цьому тексті я спробував узагальнити розрізнену інформацію, зібрану мною в мережі з питань налагодження bootmgr. Сподіваюся, що у мене це вийшло, дякую всім читачам за увагу і...
продовження буде!
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.