[Перевод] Руткиты на основе BIOS. Часть 2

mebnec5sfpjcvgxnbnwy-u3eau0.png


Привет, Хабровчане!
В конце августа в OTUS запускается 2 мощных курса по обратной разработке кода (реверс-инжиниринг). Смотрите запись трансляции Дня Открытых дверей, где Артур Пакулов (Ex-вирусный аналитик в Kaspersky Lab.) рассказывает подробнее о программах, особенностях онлайн-формата, навыках, компетенциях и перспективах, которые ждут выпускников после обучения. А также приглашаем вас принять участие в бесплатных открытых уроках: «Анализ буткита» и «Анализ банковского трояна».


Внедрение кода в BIOS

Код для доказательства концепции от Сакко и Ортега, который был протестирован ранее, было очень хрупким, и его функции не были теми действиями, которые должен выполнять руткит. Первым шагом в разработке нового руткита была разработка надежного метода, позволяющего BIOS выполнять дополнительный код.

Сакко и Ортега патчили модуль распаковки BIOS, поскольку он уже был распакован (чтобы можно было распаковывать все остальное) и вызывается при загрузке BIOS. Это обоснование было уместным, но методы перехвата нужно было изменить. Во время нормальной работы BIOS будет вызывать модуль распаковки один раз для каждого присутствующего сжатого модуля BIOS. В BIOS VMware включено 22 сжатых модуля, поэтому код распаковки вызывался 22 раза. Этот модуль перезапишет наш добавленный код, поскольку он находится в буферном пространстве, поэтому необходимо, чтобы наш добавленный код перемещался сам.

Процесс, который я использовал, включает следующие шаги:


  • Вставьте новый вызов в начале модуля распаковки в наш дополнительный код;
  • Скопируйте весь наш дополнительный код в новый раздел памяти;
  • Обновите вызов модуля распаковки, чтобы он указывал на новое место в памяти, где находится наш код;
  • Вернитесь к модулю распаковки и продолжите выполнение.

Этот процесс позволяет добавить в BIOS ROM значительное количество дополнительного кода и запускать этот код из надежного места в памяти после его перемещения. Вышеупомянутые четыре шага могут быть продемонстрированы на диаграмме:

_izyj2xu2ewogelq-lgtduc0xnc.png
(mspaint рулит)

Реализация этого кода на ассемблере была возможна несколькими различными способами, но целью было создать код, который был бы настолько независимым от системы, насколько это возможно. Для этого вся абсолютная адресация была удалена, и использовались только near вызовы или переходы. Исключениями из этого были любые ссылки на наше местоположение в свободной памяти, так как ожидалось, что это будет фиксированное местоположение, независимо от системы. Ниже приведен код ассемблера, который был использован для обработки перемещения кода:

start_mover:
; Следующие две push инструкции сохранят текущее состояние регистров в стек.
pusha
pushf

; Сегментные регистры очищаются, так как мы будем перемещать весь код в сегмент 0
xor ax, ax              ; (Это может не быть очевидным, но xor регистра устанавливает его в 0).
xor di, di
xor si, si
push cs; Push сегмента кода в сегмент данных, чтобы мы могли перезаписать код вызывающий адрес
pop ds; (CS перемещается в DS)
mov es, ax              ; Сегмент назначения (0x0000)
mov di, 0x8000              ; Смещение назначения, весь код запускается с 0x8000
mov cx, 0x4fff              ; Размер кода для копирования, соразмерный с дополнительным копированием, что не вызовет проблем

; Следующий вызов не меняет программный поток, а добавляет адрес вызова (т. е. откуда будет выполняться этот код) в стек. Это позволяет коду патчить себя независимо от того, где он может находиться в памяти. Если бы этот метод не использовался, знание того, где в памяти будет загружен модуль декомпрессии, потребовалось бы заранее (чтобы его можно было захардкодить), что не является хорошим решением, поскольку оно разнится для каждой системы.

call b

b:
pop si                  ; Это вытолкнет наш текущий адрес из стека (по сути, как копирование регистра EIP)
add si, 0x30                ; Насколько далеко вперед нам нужно скопировать наш код
rep movsw               ; Это будет повторять вызов команды movsw до тех пор, пока cx не уменьшится до 0. Когда эта команда будет завершена, наш код будет скопирован в 0x8000
mov ax, word [esp+0x12]         ; Это предоставит вызывающий адрес для патча оригинального хука
sub ax, 3               ; Возврат к началу вызывающего адреса, а не к тому месту, где он остановился
mov byte [eax], 0x9a            ; Вызывающую функцию нужно изменить на Call Far вместо Call Near

add ax, 1               ; Продвинуться вперед, чтобы установить новый адрес для вызова в будущем

mov word [eax], 0x8000          
; Новый адрес для вызова этого кода

mov word [eax+2], 0x0000        ; Новый сегмент (0)

; Теперь код перемещен, а вызывающая функция исправлена, чтобы все можно было восстановить, и мы могли вернуться.
popf
popa

; Следующие инструкции были перезаписаны патчем для модуля DECOMPC0.ROM, поэтому нам нужно запустить их сейчас, прежде чем мы вернемся.
mov bx,es
mov fs,bx
mov ds,ax
ret                 ; Обновлен до ближайшего возврата

После выполнения вышеуказанного кода он скопирует себя в смещение памяти 0×8000 и исправит инструкцию, которая первоначально вызывала его, чтобы указывала на 0×8000. Для первоначального тестирования этого кода перемещенный код был просто подпрограммой, которая отображала бы «W» на экране (см. Скриншот ниже). Однако конечная цель заключалась в том, чтобы вместо этого можно было вызывать наш код руткита, поэтому следующая модификация заключалась в интеграции этого кода.

wyvkzozmncsvqa3hgrz7skw4_ma.png

Как отмечалось в предыдущем разделе, «VBootkit» был определен как наиболее подходящий для того типа функциональности руткита, который можно загрузить из BIOS. VBootkit изначально был создан для запуска с загрузочного компакт-диска. Хотя эта отправная точка аналогична запуску из BIOS, существует ряд ключевых отличий. Эти различия в основном основаны на процессе загрузки, который показан ниже:

b7isi6ls32dqxzh7vj-olh9iuxy.png

Код нашего BIOS-руткита будет выполняться где-то между входом в BIOS и этапами загрузки BIOS. Буткит бы запускался на последнем этапе, начиная с 0×7C00 в памяти.

VBootkit был разработан так, чтобы он было загружен по адресу 0×7C00, после чего он переместился бы по адресу 0×9E000. Затем он перехватывает прерывание 0×13, а затем считывает первый сектор с жесткого диска (MBR) в 0×7C00, чтобы он мог работать так, как если бы буткита там никогда и не было. Этот процесс необходимо было изменить, чтобы все захардкоженные адреса были заменены (так как буткит больше не выполняется с 0×7C00). Кроме того, нет необходимости загружать MBR в память, поскольку BIOS сделает это самостоятельно.

VBootkit перехватывает прерывание 0×13, то есть заменяет адрес, на который обычно идет прерывание, своим собственным адресом, а затем вызывает прерывание после выполнения дополнительной обработки. Оказалось, что это требует дополнительной модификации, поскольку, когда код нашего BIOS-руткита вызывает прерывание 0×13, он еще не полностью инициализирован. Это было преодолено путем сохранения в памяти счетчика того, сколько раз был запущен модуль декомпрессии. Если он выполнялся более 22 раз (для 22 модулей), то BIOS был полностью инициализирован, и мы могли бы безопасно перехватить прерывание 0×13.

Vbootkit следует следующему алгоритму:


  • При первом вызове он перемещается в память 0×9E000 (аналогично нашему перемещению BIOS, сделанному ранее).
  • Затем он перехватывает прерывание 0×13, которое является прерыванием доступа к жесткому диску.
  • Вся активность жесткого диска будет проверена чтобы определить, какие данные читаются.
  • Если загрузчик Windows считывается с жесткого диска, код загрузчика будет изменен до его сохранения в памяти
  • Изменение, внесенное в загрузчик, приведет к изменению ядра Windows. Это, в свою очередь, позволит вводить произвольный код в ядро ​​Windows, предоставляя возможность повышения привилегий.

С нашей BIOS инъекцией плюс загруженным буткитом поток процесса происходит следующим образом:

nmox5jackqf74n1c0slq6x3608o.png

Результатом всех этих изменений является BIOS, который копирует буткит в память и выполняет его, загружает ОС с жесткого диска, а затем завершает работу ОС, которая была изменена, чтобы некоторые процессы запускались с дополнительными привилегиями. На следующем скриншоте показан код буткита, отображающий сообщение, как только он находит загрузчик и ядро ​​и успешно их исправляет:

zo-_p3qurse_mvho0pnd5lzhfm0.png

Код, использованный для этого руткита, был настроен на проверку любого процесса с именем «pwn.exe» и, если он найден, дает ему дополнительные привилегии. Это делается каждые 30 секунд, поэтому различия в привилегиях легко увидеть. Эту функцию можно увидеть в коде и на скриншоте ниже:

xor ecx,ecx
mov word cx, [CODEBASEKERNEL + Imagenameoffset]
cmp dword [eax+ecx], "PWN."         ; Проверяет, называется ли этот процесс PWN.exe.
je patchit
jne donotpatchtoken             ; jmp занимает 5 байтов, а это занимает 2 байта

patchit:
mov word cx, [CODEBASEKERNEL + SecurityTokenoffset]
mov dword [eax + ecx],ebx       ; Заменит его на токен services.exe, смещение для sec токена равно 200.

0lwpdbx1wovnvgp5vc6evo9c16c.png

Полученный BIOS-руткит определенно мог бы включать в себя больше функциональных возможностей (например, то, что включено в Vbootkit2), но даже в своем текущем состоянии действует как эффективный руткит.


Узнать подробнее о курсах: «Реверс-инжиниринг. Продвинутый», «Реверс-инжиниринг»


© Habrahabr.ru