Намедни задался вопросом, при каких условиях появляется экран утилиты восстановления загрузки (Startup Repair). Восстановление загрузки это автоматизированное средство, которое обнаруживает большое количество общих проблем загрузки Windows и пытается их устранить в автоматическом режиме. Данная функциональная особенность последних версий Windows зачастую достаточно серьезно достает пользователей, поскольку в ожидании окончания процесса восстановления можно провести не один десяток минут, полностью выпадая из рабочего процесса на достаточно продолжительное время. Сама утилита восстановления конечно же интересна, но это тема отдельной статьи, а на тот момент мне просто хотелось выяснить по каким же условиям она стартует. Было выдвинуто предположение, что экран появляется лишь при внезапном выключении питания. Данная версия сразу была поставлена под сомнение, поскольку в памяти у меня всплыли примеры ситуаций, когда пользователи жаловались на автовосстановление загрузки даже при нормальном (с виду) завершении работы. С целью выяснить причины запуска средства восстановления загрузки решено было провести небольшой эксперимент. Хочу сразу оговориться, что рассматривать данный материал следует применительно к операционной системе Windows 7 SP1, поскольку все тесты проводились именно на этой версии, поэтому учтите, что в других версиях ситуация могла кардинально поменяться.
Начать поиски я решил в исходном коде распакованного файла bootmgr.exe, всё же как-никак самый что ни на есть начальный этап загрузки. Необходимой информации по теме в Сети я найти так и не смог, а с чего то надо было начинать! Памятуя, что в различных окнах английских версий системы я видел названия recovery tool и startup repair, я просто решил поискать по словам "recovery" (восстановление) и repair (исправление, ремонт), однако простым поиском по исходнику мне ничего подобного обнаружить не удалось, в файле не было упоминаний ни про какое восстановление. Далее я перешел к исходному коду дизассемблированного Winload.exe всё в той же IDA и просто решил поискать всё то же слово "recovery". В листинге файла мной была найдена переменная, автоименованная дизассемблером как aRecoveryTrue, которая имела значение recovery=true
и на которую в коде ссылались две функции: OslpDisplayAutoAdvMenu
и OslpDisplayAdvancedBootMenu
. Должно быть это опция в конфигурации загрузки, по которой код Winload.exe, судя по всему, должен производить некие действия по восстановлению работоспособности. Предположим что это ОНО! Опираясь на некоторые внутренние критерии я отбросил вторую функцию и сконцентрировался на функции с именем OslpDisplayAutoAdvMenu
(которая вызывалась из OslpDisplayAdvancedOptionsProcess
, а та, в свою очередь, вызывалась из основной функции OslpMain
).
OslpDisplayAdvancedOptionsProcess
интересна тем, что посредством неё происходит вызов функции OslpDisplayAdvancedBootMenu
, которая выводит дополнительные сервисные меню, в числе которых и меню восстановления. Просматривая её код, я наткнулся на запрос опций по кодовому обозначению BCD элемента 14000008h. Оказалось, что данный идентификатор носит имя BcdLibraryObjectList_RecoverySequence (BCDE_LIBRARY_TYPE_RECOVERY_SEQUENCE) или recoverysequence и содержит список идентификаторов GUID, указывающих на элементы загрузки среды восстановления. Запустив команду bcdedit /enum all я получил полный список идентификаторов, размещенных в хранилище данных конфигурации загрузки, среди которых мне встретилось и символическое имя recoverysequence и связанный с ним идентификатор 8be0cdd3-7c4b-11e3-b403-24be05175d79, который указывал на путь [C]:\Recovery\8be0cdd3-7c4b-11e3-b403-24be05175d79\.... По этому пути на основном системном диске располагается файл winre.wim, который и представляет собой среду восстановления. Теперь хотя бы понятно, где искать код восстановления.Но, вернемся к основной линии повествования. Так вот, чуть выше по коду в функции OslpMain
я обнаружил некие условия перехода, по которым либо функция OslpDisplayAdvancedOptionsProcess
получает управление, либо нет. Упомянутые условия являются результатом работы с выходными параметрами функции OslpGetBootStatusData
, которая, судя по моим догадкам и названию, получает и проверяет некий статус загрузки. В самой функции OslpGetBootStatusData
вызывается функция OslpGetSetBootStatusData
, которая, вероятно, и оперирует с неким источником статуса. Так что же является источником статуса? Начав просматривать код функции, я обнаружил указание на файл bootstat.dat, и, поскольку файл этот попадался мне довольно часто, то вспомнил, что знания мои о нем ограничивались лишь пониманием, что это файл, в котором хранится некий статус загрузки, более ничего о нем я не знал. В Сети исчерпывающего описания по нему я не встретил, поэтому попытаюсь предложить собственное определение:
Возможно что данные эти каким-то образом могут использоваться для обнаружения ошибок этапов загрузки/завершения операционной системы для предоставления пользователю вариантов восстановления на этапе загрузки или автоматического запуска среды восстановления. В дополнение ко всему, как будет показано далее, файл статуса загрузки содержит разнообразную дополнительную информацию. Если мне не изменяет память, файл появился еще в операционной системе Windows XP, однако значимость его для системы со временем могла меняться.
Я вспомнил, что Bootmgr тоже работает с данным файлом, во всяком случае у меня в памяти осталось стойкое на то ощущение. Вернувшись к коду Bootmgr и поискав файл bootstat.dat, я удостоверился, что к нему действительно происходит обращение через функцию BmpInitializeBootStatusDatalog
, то есть он начинает использоваться уже до этапа Winload.exe, на этапе Bootmgr. В коде функции BmpInitializeBootStatusDatalog
вызывается:
- функция
_BlGetBootOptionDevice@16
с идентификатором BCD элемента 11000043h, который указывает на устройство. - функция
_BlGetBootOptionString@12
с идентификатором BCD элемента 12000044h, который указывает на путь к файлу. - функция
_BlGetBootOptionBoolean@12
с идентификатором BCD элемента 16000045h, который указывает на то, сохранились ли записи BSD с предыдущей сессии или BSD обнуляется при каждой сессии.
Эту логику можно отследить по следующему фрагменту кода:
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 37 38 39 40 41 42 43 |
.text:00402038 mov edi, edi .text:0040203A push ebp .text:0040203B mov ebp, esp .text:0040203D sub esp, 10h .text:00402040 push ebx .text:00402041 push esi .text:00402042 lea eax, [ebp+var_8] .text:00402045 push eax .text:00402046 push 11000043h .text:0040204B push dword_495C34 .text:00402051 xor ebx, ebx .text:00402053 mov [ebp+var_8], ebx .text:00402056 mov [ebp+var_C], ebx .text:00402059 call _BlGetBootOptionDevice@16 ; BlGetBootOptionDevice(x,x,x,x) .text:0040205E mov esi, [ebp+var_8] .text:00402061 test eax, eax .text:00402063 jge short loc_40206B .text:00402065 mov esi, _BlpBootDevice .text:0040206B .text:0040206B loc_40206B: ; CODE XREF: BmpInitializeBootStatusDataLog()+2Bj .text:0040206B lea eax, [ebp+var_C] .text:0040206E push eax .text:0040206F push 12000044h .text:00402074 push dword_495C34 .text:0040207A call _BlGetBootOptionString@12 ; BlGetBootOptionString(x,x,x) .text:0040207F mov edx, [ebp+var_C] .text:00402082 test eax, eax .text:00402084 jge short loc_40208B .text:00402086 mov edx, offset aBootBootstat_d ; "\\Boot\\bootstat.dat" .text:0040208B .text:0040208B loc_40208B: ; CODE XREF: BmpInitializeBootStatusDataLog()+4Cj .text:0040208B lea eax, [ebp+var_1] .text:0040208E push eax .text:0040208F push 16000045h .text:00402094 push dword_495C34 .text:0040209A call _BlGetBootOptionBoolean@12 ; BlGetBootOptionBoolean(x,x,x) .text:0040209F test eax, eax .text:004020A1 jl short loc_4020AD .text:004020A3 cmp [ebp+var_1], bl .text:004020A6 jz short loc_4020AD .text:004020A8 xor eax, eax .text:004020AA inc eax .text:004020AB jmp short loc_4020AF |
Получается что-то вроде этого:
Элемент BCD | Формат | Значение |
---|---|---|
0x11000043 | Устройство | Устройство, на котором находится BSD файл. |
0x12000044 | Строка | Путь к файлу BSD. |
0x16000045 | Логическая переменная | Истина - сохранились BSD записи от предыдущей сессии; Ложь - BSD файл обнуляется при каждой сессии. |
Выходит, что записи о файле bootstat.dat хранятся в базе конфигурации загрузки (BCD).
Опять же, это может ни о чем не говорить, я просто мог ошибиться с выводами, поскольку глубина понимания кода у меня небольшая. И тем не менее, после обнаружения этой особенности в сознание закралось смутное подозрение на счет того, что коды различных стадий загрузки используют разные файлы bootstat.dat. После этого я решил поискать файл в других расположениях и нашел его в %SystemRoot%\bootstat.dat. Становится интереснее!! Требовалось подтверждение, с этой целью был использован Process Monitor с активированной опцией "Enable Boot Logging" и по результатам записи перезагрузки были отфильтрованы события с целевым значением "bootstat":
Глядя на скриншот можно было говорить что это определенно разные файлы :), на основе чего можно сделать два предположения:
- Файл %SystemDrive%\boot\bootstat.dat, размещенный на отдельном разделе System Reserved, используется только модулями стадий загрузки (bootmgr/winload) и служит для передачи сведений о состоянии между различными фазами загрузки.
- Файл %SystemRoot%\bootstat.dat используется другими компонентами операционной системы на различных этапах загрузки/завершения.
Опять же, даже не смотря на это, делать какие-либо дальнейшие выводы было преждевременно, поскольку при обнаружении второго файла, сначала причастность его к восстановлению системы надо доказать. А вдруг, не взирая на то, что он используется кодом некоторых системных компонентов на протяжении функционирования операционной системы, он не несет значимой нагрузки для среды восстановления?
Структура bootstat.dat
Мне наиболее интересен был "системный" а не "загрузочный" файл, поэтому изучение я начал именно с %SystemRoot%\bootstat.dat. Ну и первое что мы сделаем, так это уже наконец-то посмотрим на содержимое файла:
Вся прочая информация, которая содержится в файле - это нули. Да, информации в файле не густо, присутствует совсем небольшой блок в самом начале (предположительно заголовок) и где-то в теле блок поувесистее (вероятно некие записи). Внутренняя структура файла bootstat.dat не документирована, поэтому разобраться в том, какие именно параметры присутствуют в файле было проблематично. Однако на просторах Сети я обнаружил материал по структуре файла bootstat.dat и один из читателей предположил, что заголовок файла описывается структурой _BSD_FILE_HEADER
. Вся эта крайне полезная информация уже хоть как-то приоткрывает завесу неизвестности относительно внутреннего устройства объекта нашего изучения. Мне всё же показалось, что второе предположение имеет под собой больше оснований, требуется дополнительное изучение и позже я обновлю статью.
Заголовок
Первые 16 (10h) байт являются заголовком и описываются структурой _BSD_FILE_HEADER
:
1 2 3 4 5 6 7 8 |
typedef struct _BSD_FILE_HEADER { ULONG VersionNumber; NT_PRODUCT_TYPE ProductType; BOOLEAN AabEnabled; UCHAR AabTimeout; // 30 секунд BOOLEAN BootGood; BOOLEAN BootShutdown; } BSD_FILE_HEADER, PBSD_FILE_HEADER; |
который, фактически, описывается прототипом-перечислением RTL_BSD_ITEM_TYPE
:
1 2 3 4 5 6 7 8 9 |
enum RTL_BSD_ITEM_TYPE { RtlBsdItemVersionNumber = 0, RtlBsdItemProductType = 1, RtlBsdItemAabEnabled = 2, RtlBsdItemAabTimeout = 3, RtlBsdItemBootGood = 4, RtlBsdItemBootShutdown = 5, . . . }; |
Соответственно, значения заголовка таковы:
Смещение | Размер | Значение |
---|---|---|
0x00 | DWORD | Версия файла (VersionNumber):
|
0x04 | DWORD | Тип продукта (ProductType):
|
0x08 | BYTE | Автоматический выбор пункта загрузки (AabEnabled):
|
0x09 | BYTE | Задержка автовыбора (AabTimeout) - 30 секунд (значение 1Eh) |
0x0A | BYTE | Маркер успешной загрузки (BootGood)? |
0x0B | BYTE | Маркер (BootShutdown)? |
0x0C | DWORD | Размер актуальных данных файла Bootmgr (байт). Размер включает все данные вместе с заголовком и записями. Значение 00000000 - файл не содержит данных. Может отсутствовать в заголовке. |
Записи
За основным заголовком файла идут так называемые записи. Каждая запись, в свою очередь, имеет собственный заголовок, сразу за которым следуют данные записи.
Смещение | Размер | Значение |
---|---|---|
0x00 | DWORD | Метка времени (секунды). Предположительно время, прошедшее с момента запуска, либо с начала дня, в котором ОС начала загрузку. |
0x04 | DWORD | Всегда ноль. Неизвестно. |
0x08 | 16 байт | GUID владельца записи. Все нули - владельцем записи является Bootmgr; |
0x18 | DWORD | Размер записи (байт). Начиная от временной метки и заканчивая последним байтом данных текущей записи. |
0x1C | DWORD | Категория записи. 01 - информация, 03 - ошибка. |
0x20 | DWORD | Какой-то идентификатор. Встречаются значения 02 и 03. |
0x24 | DWORD | Идентификатор события. См. таблицу ниже. |
0x28 | варьируется | Дополнительные данные. Размер зависит от идентификатора события. |
Идентификатор события может принимать следующие значения:
Идентификатор | Событие | Следующие за идентификатором данные |
---|---|---|
0x01 | Инициализация приложения. | см. таблицу ниже. |
0x11 | Приложение запущено. | см. таблицу ниже. |
0x12 | Приложение завершено. | см. таблицу ниже. |
0x13 | Ошибка загрузки приложения стадии загрузки | Код NTSTATUS (4 байта), путь к приложению, завершающий 0. |
0x14 | Ошибка в файле BCD (данные конфигурации загрузки) | Код NTSTATUS (4 байта), путь к файлу BCD, завершающий 0. |
0x15 | Нет записей для приложения этапа загрузки в файле BCD. | Код NTSTATUS (4 байта), путь к файлу BCD, завершающий 0. |
0x16 | Общая ошибка | Код NTSTATUS (4 байта). |
0x31 | Завершение приложения. | Данные отсутствуют. Данные записи тут заканчиваются. |
Размерность NTSTATUS равна 4 байта. Путь к файлу записывается в кодировке UTF-8 и заканчивается 0. Для общей ошибки, тот же код NTSTATUS имеет размерность 4 байта.
События инициализации
Дополнительные данные для всех событий инициализации имеют следующий формат:
Смещение | Размерность | Значение |
---|---|---|
0x00 | 16 байт | Время (структура): 0x00 :: word :: год 0x02 :: word :: месяц 0x04 :: word :: день 0x06 :: word :: час 0x08 :: word :: минуты 0x0A :: word :: секунды 0x0C :: word :: всегда 0, неизвестно. 0x0E :: word :: всегда 7, неизвестно. |
0x10 | DWORD | всегда равно 1, назначение неизвестно. |
0x14 | DWORD | всегда равно 0, назначение неизвестно. |
События запуска
Данные записи для запуска приложения имеют следующую форму:
Смещение | Размерность | Значение |
---|---|---|
0x00 | 16 байт | GUID приложения стадии загрузки. |
0x10 | dword | Тип запуска.
|
0x14 | варьируется | Путь до приложения стадии загрузки, формат UTF-8, заканчивается 0. |
События завершения
Актуально только для приложений этапа загрузки, для обычных приложений я не рассматривал. Когда приложения завершилось (в результате успешной работы, сбоя или отмены) данные в записи следующие:
Смещение | Размерность | Значение |
---|---|---|
0x00 | 16 байт | GUID приложения этапа загрузки. |
0x10 | dword | всегда 0, назначение неизвестно. |
Основываясь на наших предположениях, в имеющемся у нас в наличии файле C:\Windows\bootstat.dat присутствуют две записи. Маловато как-то для глобального файла статуса загрузки/завершения, не находите?
Эксперимент
После того, как мы предположили внутреннюю структуру файла, хорошо бы попробовать на практике произвести какие-нибудь изменения в файле и посмотреть на результат.
- Я начал с файла, расположенного в C:\Windows\bootstat.dat и попытался изменить уже существующую запись (начинающуюся по смещению
00000810
), отредактировав её категорию на значение03
(ошибка), в дополнение к этому я изменил в заголовке значение размера актуальных байтов с нулевого на значение, включающее все существующие записи. Результат был отрицательным, изменения не произвели никакого эффекта, загрузка произошла в нормальном режиме. - Затем я решил создать новую записи после двух уже имеющихся, за эталон я взял уже существующую запись и изменил в ней время, категорию записи на 03 (ошибка), идентификатор события на 16 (общая ошибка), подправил длину записи. Перезагрузка привела к аналогичному отрицательному результату, загрузка прошла в нормальном режиме, никаких проблем система не обнаружила.
- Далее я вовсе решил удалить файл C:\Windows\bootstat.dat. Удивило меня то обстоятельство, что загрузка опять прошла в штатном режиме, а файл так и не появился. Выходит, его никто и не создал. Удаление я проводил на машине со стандартной PCAT (MBR) загрузкой. После этого я уже совсем распоясался и удалил для полноты ощущений еще и файл \boot\bootstat.dat из несмонтированного системного раздела. И тот же результат: загрузка прошла нормально, файлы не появились.
Я помню, что в своё время повреждение файла bootstat.dat доставляло множество проблем не только в Windows XP и Vista, но даже и в начальных версиях Windows 7? Неужели на каком то этапе разработки Windows 7 (SP1 версии 6.1.7601) разработчики пересмотрели функциональные характеристики данного файла и он более не играет ключевой роли? Но ведь код в системе в многочисленных модулях сохраняется. Это пока для меня загадка, вероятно я упустил некоторые особенности.
Делаем предварительные выводы:
- Файл не используется в качестве причины для запуска средства восстановления при загрузке.
- Файл %SystemDrive%\boot\bootstat.dat (наряду с файлом ntbtlog.txt/bootlog.txt) начинает использоваться при включении опции boot status logging (включается через msconfig.exe).
На самом деле заголовок файла bootstat.dat описывается следующей структурой, по крайней мере для XP:
typedef struct _BSD_FILE_HEADER
{
ULONG VersionNumber;
NT_PRODUCT_TYPE ProductType;
BOOLEAN AabEnabled;
UCHAR AabTimeout; // 30 секунд
BOOLEAN BootGood;
BOOLEAN BootShutdown;
} BSD_FILE_HEADER, PBSD_FILE_HEADER;
Которое по сути является более полным описанием перечисления RTL_BSD_ITEM_TYPE. Для последующих версий заголовок просто немного разбухает после добавления новых значений, например, в восьмёрке там от 9 до 10.
Размер же файла либо не входит в заголовок, либо идёт последним, так как оно записывается в других местах.
просто мегаценная для меня информация, спасибо вам огромное!! а есть источник на почитать? ну, какой-нибудь материал в Сети?
Я и есть источник, сам только что нашёл))
Вообще для реверса ядра рекомендую посматривать на WRK, отладочные сборки самой винды и ReactOS. В WRK в файле ntoswrk.lib предварительно собранные части. Их в отличии от ядра реверсить несколько проще, так как в нём во-первых все функции разбиты по объектным файлам, имена которых соответствуют имени файла в исходных кодах винды, а во-вторых, внешние символы в объектниках ещё не определены, и чётко видны в IDA. Конкретно эту информацию я отрыл в bootstatus, функция RtlCreateBootStatusDataFile. В отличии от кода РеактОС и релизных сборок винды, тут чётко видно, что по смещению 0х04 от начала заголовка записывается результат вызова RtlGetNtProductType. Посмотрев на файл от сервера, я там увидел закономерную двойку. Так же в функции RtlGetSetBootStatusData есть массив смещений и размеров структур, с которым идёт сверка при чтении/записи в этот файл. Сверив эти смещения с существующими определениями, где хоть как-то встречается упоминание ProductType, я и наткнулся на структуру RTL_BSD_ITEM_TYPE, которая идеально легла на смещения. А подтверждением послужило то, что по смещению AabTimeout в файле bootstat.dat находится значение 1E, то есть стандартные 30 секунд.
только я ненастоящий реверсер,
я маску нашёля лишь обложился всеми найденными исходниками винды и IDA.Заглядывайте в тред на дваче https://2ch.hk/pr/res/1015083.html я его веду. Хочу как-нибудь выложить результаты моего труда, но гитхаб, боюсь, такое нафиг снесёт ))
ну, WRK для меня вообще открытие :))) Как говорится, век живи - век учись!! Исходники ReactOS встречал и по ним как раз некоторые моменты стали понятны. Спасибо, как будет время может займусь осмыслением.
Кстати тут USHORT читать как UCHAR, заметил только после компиляции.