Bootkit своими руками — Часть 2

Bootkit своими руками — Часть 2

CyberLifes

Доброго времени суток! В предыдущей статье мы научились запускать свой код до загрузки операционной системы прямиком из MBR. Воспользовавшись этим, мы разработали простенькую систему защиты, требующую ввести пароль. В случае правильного ввода, мы позволяем ОС загрузиться дальше. Теперь, нам нужно установить контроль над ядром ОС, это можно сделать, если перехватить какую-либо функцию, которая вызывается самой ОС. Тогда, вместо этой самой функции выполнится наш код и дальше система продолжает свои дела. Так вот, нам сейчас нужно определиться с тем, какое место нас будет интересовать. Думаю, так как мы только изучаем bootkitы, давайте пойдём по стопам известного буткита «Sinowal» или «Alipop» и повторим за ними. Как только мы освоимся, тогда и начнём экспериментировать, но сейчас не будем осложнять себе жизнь :-).

8B F0 85 F6 74 21 80 3D
8B F0 85 F6 74 21 80 3D сигнатура в ntldr

Итак, эти буткиты на начальной стадии загрузки ОС заменяют несколько байт ntldr на свой код. Давайте взглянем на изменяемое место:

Как мы видим, модифицируются байты сразу после отработки функции _BlLoadBootDrivers. А именно модифицируются байтами 0xFF15xxxxxxxx. FF15 — опкод команды call dword ptr [addr]. Мы поступим точно также.

Чтобы получить возможность модифицировать ntldr, установим перехват на 2 и 42h функции тринадцатого прерывания BIOS. Эти функции производят чтение информации с секторов диска в память. Если это вторая функция, то прочитанные данные находятся по адресу es:bx. Количество секторов для чтения передаётся в регистре al. Во втором случае вся информация указывается в структуре Disk Address Packet (DAP).


Адрес DAP передаётся прерыванию в регистре esi. Как видно из таблицы, чтобы получить количество секторов, которое собирается прочитать прерывание, нужно прочитать память по адресу esi+2. Прочитанные данные будут находиться по этому указателю на память, находящемся по адресу esi + 4. Это нужно будет учесть, так как наш обработчик прерывания будет делать следующее: если выполняется нужная нам функция, то мы вызываем оригинальный обработчик, а затем начинаем поиск в прочитанных данных сигнатуры рассмотренного выше места в ntldr. Сигнатура будет такой: 8B F0 85 F6 74 21 80 3D. Если нашли нужное нам место, то переписываем его на call dword addr. Так как переход будет осуществляться не по &addr, а по *addr, то заведём специальную переменную, куда сохраним адрес на наш обработчик. Если всё произойдёт по плану, то, когда ось будет уже переключена из реального режима в защищённый и выполнит функцию _BlLoadBootDrivers в ntldr, сразу после неё она встретит call на наш обработчик и выполнит его! Из этого выходит, что наш обработчик должен быть уже не 16 разрядным, а 32. В нашем обработчике будет основной код буткита, его мы рассмотрим в следующей части, а сейчас пока попытаемся сделать так, чтобы система хотя бы грузилась. Так как мы затёрли нужный оси код, мы должны его вернуть повторить. В нашем обработчике пока будет выполняться единственный функционал — выполнение заменённых инструкций. Таким образом система будет думать, что всё так и задумано))). В этот раз мы будем компилировать код на nasm, так как очень удобно реализовать код в двух разных по разрядности сегментов, просто вставив use32 или use16 в нужное место. Итак, вот что получилось:


Немного пояснений. Для экономии времени, мы не прописываем MBR виртуальной системы на наш код, а вместо этого настраиваем первую загрузку системы со съёмного носителя — дискеты. Таким образом, после компиляции, можно сразу загружать ось и смотреть что получилось, иначе, пришлось бы дожидаться загрузки гостевой оси, затем переносить скомпилированный код и им прошивать MBR. Это очень долго. В самом начале кода, у нас на экран выводится 16 букв ‘N’, после чего система ожидает нажатия клавиши. Только после этого наш загрузчик начнёт выполняться. Это сделано для того, что во время тестирования, иногда забываешь закрыть образ дискеты из hex редактора и, поэтому, ось не может получить доступ к дискете и грузится со следующего загрузочного носителя в списке. В нашем случае с диска. Это может ввести в заблуждение, можно подумать, что наш код отработал и система успешно загрузилась, а на самом деле всё не так. Я много потратил времени из-за таких глупых ошибок. Но, когда система ожидает нажатие клавиши, то можно быть уверенным, что мы загрузились именно с нашего загрузчика. Два раза в коде встречается место, когда мы рассчитываем полный физический адрес памяти имея в своём распоряжении сегмент и смещение. В реальном режиме сделать это проще простого. Достаточно умножить номер сегмента на его размер и прибавить смещение. Как мы знаем, размер сегмента в реальном режиме равен 65536 байт или 2 ^ 16. В 17 строке, мы вычитаем 1 из переменной, где хранится общий объём оперативной памяти ПК в килобайтах, тем самым резервируя для себя место. Дальше мы переносим свою тушу в это «тёпленькое» местечко, где будет располагаться обработчик 13h прерывания и код, который будет выполняться системой в защищённом режиме. В 21 строке мы определяем номер сегмента нашего местечка, так как это требует операция movsb. По идее нужно было бы сделать это в два шага. Первый — перевести общий размер памяти из килобайт в байты, умножив полученное значение на 1024, или сдвинув в лево на 10 разрядов, второй — разделить полученное значение на размер одного сегмента — на 65536 или сдвинуть вправо на 4 разряда. Таким образом, мы var413h * 2^10/2^4, что можно сделать одной операцией — var413h * 2 ^(10-4) или var413h*2^6. Так будет определённо быстрее.

Также необходимо помнить, что перед вызовом оригинального обработчика прерывания с возвратом в наш обработчик, необходимо предварительно сохранить регистр флагов! И это обязательно, иначе не загрузимся! Если возвращаться в наш обработчик не нужно, то сохранять ничего не надо. Начиная со строки 74, мы занимаемся непосредственно поиском нашей сигнатуры. Перед этим мы перенастроили адреса прочитанных данных так, чтобы на них указывала пара es: bx. У двух функций 13h прерывания они получаются по-разному. И так, в 96 строке мы заменяем первые байты сигнатуры (8b F0) на FF 15 — опкод команды call. Дальше мы должны заменить оставшиеся байты сигнатуры на адрес переменной с полным физическим адресом нашего кода, который вызовется в защищённом режиме. Этот адрес получается с 97 строки. Так как мы в обработчике прерывания, код которого был перенесён в верхние адреса памяти, то мы берём сегментный регистр cs и умножаем на 65536, тем самым получаем физический адрес нашего сегмента кода. Дальше к нему нужно прибавить смещение до переменной, содержащей адрес 32-разрядного кода. Это делается так: прибавляем смещение метки StartCODE32 и вычитаем 4, так как эта переменная находится сразу перед этой метки. Всё! Дальше ось грузится сама по себе и наткнётся на наш код, который съэмулирует затёртые байты и всё будет ок)



Report Page