26.12.2012

PHDays CTF Quals: BINARY 500, или как спрятать флаг ниже плинтуса

image

15—17 декабря 2012 года прошли отборочные соревнования под названием PHDays CTF Quals. Более 300 команд боролись за право участия в конкурсе PHDays III CTF, который состоится в мае 2013 года в рамках международного форума PHDays III.

Григорьев Максим, Ковалев Сергей
Исследовательский центр Positive Technologies

Введение

15—17 декабря 2012 года прошли отборочные соревнования под названием PHDays CTF Quals. Более 300 команд боролись за право участия в конкурсе PHDays III CTF, который состоится в мае 2013 года в рамках международного форума PHDays III. В течение последних двух месяцев наша команда усиленно разрабатывала задания для отборочных соревнований, и эту статью мы решили посвятить разбору одного из них – Binary 500. Данное приложение весьма необычно, поэтому ни одна команда не смогла достать флаг, спрятанный в ее недрах.

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

Рисунок 1. Предупреждение и лицензионное соглашение

Дроппер

Начнем с обзора дроппера, устанавливающего задание на компьютер. Главная задача данного модуля достаточно тривиальна. Она заключается в записи файлов, извлеченных из секции ресурсов, в самодельную скрытую файловую систему, а также перезаписи оригинального загрузчика (MBR) своим собственным. Кроме того, дроппер создает копию оригинальной MBR и сохраняет ее в файловой системе.

Модуль использует несколько приемов, усложняющих его анализ. Во-первых, он написан на С++ с использованием STL, ООП и виртуальных функций. Поэтому все вызовы функций являются неявными и используют таблицы виртуальных методов.

Рисунок 2. Вызовы виртуальных методов в IDA Pro

Второй подход, который усложняет анализ данного файла, – чтение и запись файлов. Все операции с жестким диском проводятся напрямую через SCSI-контроллер. Вместо вызовов стандартных ReadFile/WriteFile мы использовали функцию DeviceIoControl с контрольным кодом SCSI_PASS_THROUGH_DIRECT, которая позволяет взаимодействовать с жестким диском на более низком уровне.

Также все файлы, находящиеся в ресурсах, зашифрованы с использованием 256-битного ключа.

Теперь перейдем к описанию скрытой файловой системы. Ее структура достаточно проста. Рост системы происходит с конца и записывается за 2 сектора до конца жесткого диска. Первый DWORD содержит количество файлов в файловой системе, сложенное операцией XOR с константой 0x8FC54ED2. Далее идет директория с описанием файлов:

struct MiniFsFileEntry
{
DWORD fileIndex;
DWORD fileOffset;
DWORD fileSize;
};

Индекс файла – это просто некоторая константа, представляющая данный файл в файловой системе (вместо имени). Смещение до файла измеряется в байтах относительно начала файловой системы.

Рисунок 3. Структура файловой системы MiniFs

MBR

После того как дроппер завершил свою работу, становится понятно, что в операционной системе нам больше делать нечего, нужно перезагружаться и отлаживать модифицированную загрузочную запись. Отлаживать MBR можно несколькими способами. Конечно, это можно делать на реальной машине при помощи аппаратного отладчика, но это неудобно и дорого. В связи с этим мы предлагаем использовать виртуальную машину VMWare (нужно прописать некоторые настройки виртуальной машины), подключившись к ней при помощи отладчика GDB (у этого способа есть значительные недостатки, о которых будет рассказано далее), либо эмулятор Bochs. Преимущество этих двух методов состоит в том, что можно производить анализ с использованием отладчика IDA Pro. Это очень удобно, хотя и тут мы столкнулись с большими проблемами.

Выяснив, какими инструментами мы можем отлаживать данный код, принимаемся за дело. Первая часть MBR очень проста, и ее анализ не должен вызвать никаких проблем. Единственное, что она делает, – читает вторую часть нашей MBR (Extended MBR) с жесткого диска вызовом функции 0х42 обработчика прерывания 0х13 BIOS и записывает ее по адресу 0x7e00 (сразу после первой части загрузчика). Это действие необходимо, поскольку BIOS копирует в память всего 512 байт загрузчика, а наш код превышает этот размер.

Попав на расширенную часть загрузчика, опытный специалист сразу заподозрит неладное, а именно то, что загрузчик обфусцирован.

Рисунок 4. Сравнение исходников загрузчика с результатом анализа IDA Pro

Вся сложность обфускации состоит в вызовах функций, так как они происходят неявно. С самого начала в регистр AX заносится адрес функции, которая ищет вызываемую функцию в специальной таблице, сопоставляющей индекс функции (двухбайтовая константа) с ее смещением относительно этого поля таблицы. Данная функция берет адрес возврата и читает WORD, который отвечает за индекс функции. Далее производится поиск смещения в таблице и передается управление на эту функцию. В самом конце происходит возврат управления за константу с индексом вызываемой функции (адрес возврата + 2).

Рисунок 5. Таблица функций в MBR

Рисунок 6. Схематичное представление алгоритма обфускации MBR

Сам код загрузочного сектора достаточно простой:

  1. получение параметров жесткого диска;
  2. считывание в память оригинальной загрузочной записи из скрытой файловой системы;
  3. замена нашей загрузочной записи на оригинальную по адресу 0x7c00;
  4. считывание из файловой системы и расшифровка загрузчика гипервизора;
  5. считывание из файловой системы и расшифровка тела гипервизора;
  6. подготовка параметров и передача управления загрузчику гипервизора.

Стоит упомянуть, что для шифрования загрузчика и тела гипервизора использовался набор байт из BIOS эмулятора Bochs. Таким образом, задание получилось заточенным под данный эмулятор. Это было сделано по нескольким причинам. Во-первых, отладка аппаратной виртуализации Intel VT-x возможна только на реальной машине или на Bochs (начиная с версии 2.4.5. Первый способ в рамках PHDays CTF Quals очень затруднителен. Таким образом, мы с самого начала были привязаны к использованию данного эмулятора. Во-вторых, нам не хотелось, чтобы данное задание можно было исследовать только при помощи статического анализа, поэтому было решено, что шифрование с использованием ключа из BIOS будет принуждать игроков применять динамический анализ. В-третьих, мы решили подстраховаться: в случае случайного запуска программы на реальной машине загрузчик не был бы расшифрован, и управление передалось бы на оригинальную загрузочную запись, что не позволило бы повредить систему.

Для облегчения работы игроков мы заранее выложили информацию о том, что для решения одного из заданий им понадобится сборка Bochs, а также рабочий образ операционной системы.

Загрузчик гипервизора

Аппаратная виртуализация на сегодняшний день не является новым понятием. Широкое распространение она получила в 2006—2007 годах, когда производители самых известных процессоров на рынке рабочих станций AMD и Intel стали выпускать процессоры, поддерживающие соответствующие функции. Подробнее о мониторе виртуальных машин (он же гипервизор) будет рассказано в следующем разделе. В этом разделе будет кратко рассмотрена проверка наличия аппаратной виртуализации на тестовой системе.

Как упоминалось ранее, отладка приложения, использующего аппаратную виртуализацию Intel VT-x, может быть осуществлена только на реальной машине или на эмуляторе Bochs (начиная с версии 2.4.5), но на этом проблемы не заканчиваются. Стандартная сборка эмулятора не поддерживает аппаратную виртуализацию. Именно поэтому нами была скомпилирована специальная сборка Bochs, ссылку на которую мы дали в первой подсказке к заданию. 

Основная задача загрузчика гипервизора — перенести тело гипервизора выше первого мегабайта и передать ему управление. Однако по пути он делает несколько нетривиальных операций, о которых стоит рассказать отдельно.

На вход загрузчик гипервизора принимает несколько параметров. Среди них адрес, на который его загрузили. Этот адрес используется в качестве базы сегмента кода, которая устанавливается дальним переходом (far jump).

Далее инструкцией CPUID проверяется, что код исполняется на Intel-системе (функция 0), и что данная система поддерживает аппаратную виртуализацию (функция 1). Проверка происходит следующим образом: если при вызове CPUID в регистре EAX лежит значение 1, то на выходе в бите 5 регистра ECX будет находиться флаг VMX. Если флаг взведен, то виртуализация поддерживается. Чтобы проверить, заблокирована ли виртуализация на ранних этапах загрузки (BIOS), необходимо прочитать регистр MSR с номером 0x3A. Если на выходе инструкции RDMSR в регистре EAX установлен бит 0 и сброшен бит 2, то виртуализация заблокирована.

На следующем этапе вызывается функция чтения карты памяти системы. Это достигается путем вызова в цикле прерывания 0x15 с параметром 0xE820 в регистре EAX. При этом в буфере сохраняется набор записей, описывающих области памяти: база, длина, тип, дополнительный тип (если поддерживается BIOS). Дальше полученная карта изучается на наличие участка свободной памяти для тела монитора. Сам монитор на данный момент занимает около 20КБ пространства, но сохраняется 2МБ (для удобства работы с памятью). Если такая память найдена, то она помечается как занятая.

Для того чтобы перенести тело монитора выше первого мегабайта, необходимо перейти из реального режима работы в защищенный или длинный. Поскольку в дальнейшем монитор должен работать в длинном режиме (в документации говорится, что монитор может оставаться и в защищенном режиме, но большого смысла в этом нет), происходит переход в длинный режим. Для этого необходимо выполнить несколько условий: подготовить страничные структуры (PML4, PDPT, некоторое количество PD для страниц размером 2МБ), взвести бит PAE в регистре CR4, записать в регистр CR3 адрес таблицы PML4, установить GDTR с дескрипторами сегментов длинного режима, взвести бит LMA в регистре MSR EFER, взвести биты PG и PE в регистре CR0. Если после этого выполнить инструкцию дальнего перехода, то произойдет переключение из реального режима в длинный.

Было замечено, что дизассемблер IDA 6.1 неправильно работает с эмулятором Bochs и после перехода в длинный режим начинает выдавать странные значения (в IDA 6.3 данная ошибка исправлена). Возможно, он самостоятельно вычисляет значения регистров и не обращается к Bochs за соответствующими сервисами. При этом он не способен правильно обработать прямое переключение из реального режима в длинный.

Далее гипервизор копируется на адрес назначения, и ему передается управление.

Гипервизор

Специально для данного задания был написан тонкий гипервизор, который:
  1. выполняет вход в режим VMX root;
  2. настраивает структуру VMCS для запуска гостевой системы в реальном режиме с адреса 0x7C00;
  3. устанавливает обработчики выходов из гостя;
  4. запускает гостя инструкцией VMLAUNCH.

Главная задача, которая стоит перед исследователем этого кода, — найти адрес, с которого запускаются обработчики выходов из гостевой системы.

Флаг

Добравшись до обработчика выходов виртуальной машины, участник выходил на финишную прямую, и до получения заветного флага оставалось решить небольшую задачку.

Из кода обработчика легко увидеть, что если был совершен выход по инструкции CPUID, а регистр EIP равен определенному значению, то начинается обработка некоторого события. Из значений регистров EAX, ECX, EDX, EBX, ESI, EDI, ESP и EBP происходит заполнение вектора (32 байта), а далее этот вектор проверяется на валидность. Проверка заключается в подстановке вектора (x_0,…,x_31 ) в систему уравнений следующего вида:

Если равенство выполняется, то введенный вектор верен и используется в качестве ключа для расшифровки буфера. Таким образом, игроку нужно решить систему уравнений из 32 уравнений с 32 неизвестными и получить ключ шифрования. Единственное усложнение данной проверки заключается в том, что она написана с использованием инструкций математического сопроцессора (FPU).

В зашифрованном буфере находится еще одна MBR, в которой в открытом виде лежит флаг. Такой загрузчик прописывается на место оригинального, и вызывается принудительная перезагрузка, после чего MBR выводит флаг на экран.

Рисунок 7. Пример выведенного на экран флага

Тестовое приложение

Специально для тестирования нами было написано небольшое приложение, которое выделяет память по заданному адресу, записывает по нужному смещению (адрес + смещение = необходимое значение регистра EIP) команды CPUID и еще несколько инструкций, заполняет регистры общего назначения необходимыми значениями и передает управление по нужному адресу. Таким образом, при выполнении инструкции CPUID гипервизор перехватывает управление, проверяет флаги и перезагружает систему, выводя флаг на экран.
 

Рисунок 8. Пример тестовой программы

Заключение

Создавая данное приложение, мы хотели разработать нечто нестандартное, программу, которую было бы интересно анализировать в команде, ведь для решения данного задания необходимо знать обратный анализ Win32-приложений, анализ загрузчика, работающего в реальном режиме процессора, разбор принципов обфускации и шифрования. Для решения необходимо сочетать как статический, так и динамический анализ, а также изучить базовые принципы аппаратной виртуализации, ассемблера х86-64 и применить начальные математические знания для получения флага.

Искренне надеемся, что у нас получилось заинтересовать как участников, так и тех, кто читал данный обзор!

От авторов

Мы решили написать это задание за три недели до начала отборочных соревнований и были уверены, что закончим его достаточно быстро, но все оказалось очень неожиданно. Мы сдали задание всего за несколько часов до начала отборочных соревнований, не успев его оттестировать. На момент сдачи мы были уверены только в том, что у участников была возможность получить флаг. Однако система, которая запускалась в виртуальной среде, постоянно выдавала ошибки и периодически падала в синий экран смерти, после чего вообще отказывалась загружаться. Во время написания данной статьи у нас было немного времени, для того чтобы исправить ряд недочетов и сделать более стабильную версию задания. И все же на полную стабилизацию работы ОС времени не хватило, так что делимся новой версией задания, а также видео, демонстрируюим работу задания и тестовой программы. Всем спасибо!

Архив с заданием

Видео

или введите имя

CAPTCHA