Анализ эксплоитов с помощью модифицированного VDB-отладчика

Анализ эксплоитов с помощью модифицированного VDB-отладчика

В данном посте я рассмотрю, как создать собственный отладчик на основе VDB, чтобы обнаружить эксплоит, проанализировать его и предотвратить запуск полезной нагрузки.

Автор: @darkrelativity

При неудачном выборе инструментов анализ эксплоита и особенностей его запуска может затянуться. Один из способов ускорить исследование эксплоита – использовать Vtrace и VDB. В данном посте я рассмотрю, как создать собственный отладчик на основе VDB, чтобы обнаружить эксплоит, проанализировать его и предотвратить запуск полезной нагрузки.

Сведения о Vtrace, VDB и исследуемом эксплоите

Vtrace – кроссплатформенный и кроссархитектурный прикладной программный отладочный интерфейс (API) для отладки приложений. VDB – кроссплатформенный и кроссархитектурный отладчик, использующий Vtrace. Оба инструмента можно найти здесь: http://visi.kenshoto.com.

Я покажу целесообразность создания собственного отладчика на основе VDB и Vtrace на примере эксплоита для драйверов NVIDIA, опубликованного 25 декабря 2012 года в списке рассылки Full Disclosure (эксплоит можно найти по адресу http://seclists.org/fulldisclosure/2012/Dec/261). Благодарю @peterwintrsmith за предоставление занимательного бага и хорошего примера, демонстрирующего некоторые возможности Vtrace и VDB.

Не вдаваясь в подробности эксплоита, отмечу, что пакет установки драйверов NVIDIA устанавливает службу с описанием "NVIDIA Driver Helper Service" и именем "NVSvc". Исполняемый файл службы находится по следующему пути: '%systemroot%\system32\nvvsvc.exe'. При запуске службы 'nvvsvc.exe' создает или использует существующий именованый канал '\\.\pipe\nvsr'. Далее служба ждет, пока клиент соединится с этим именованным каналом. После соединения с клиентом служба создает поток для чтения данных из канала.

Клиент может посылать в канал несколько типов сообщений. Тип сообщения определяется двумя первыми байтами сообщения. Эксплоит нацелен на машину состояний кода обработки сообщений, занимающегося разбором сообщений с опкодом 0x52. Используемый формат сообщения позволяет клиенту посылать имя и значение ключа реестра в кодировке Unicode. Эксплоит затрагивает следующие части сообщения:

  • Опкод (тип сообщения)
  • Имя ключа реестра
  • Размер значения ключа реестра
  • Значение ключа реестра

Служба 'NVSvc' имеет по меньшей мере две проблемы.

Первая проблема связана с разрешениями именованного канала, показанными на рисунке 1. Разрешение FILE_ALL_ACCESS означает, что сообщения в канал может посылать кто угодно.

Рисунок 1: вывод разрешений канала 'nvsr' утилитой accesschk

Просмотр дизассемблированного кода позволяет понять, почему канал 'nvsr' имеет такие разрешения: он создается с пустым списком доступа (NULL DACL), что позволяет всем читать канал и писать в него.

Вторая проблема – в том, как 'nvvsvc.exe' обрабатывает сообщения с опкодом 0x52. Псевдокод, иллюстрирующий эту проблему, показан на рисунке 2. Мы видим, как код обработки сообщений определяет тип сообщения по опкоду, использует функцию wcsnlen для получения длины имени ключа реестра, а затем, используя эту информацию, формирует индекс, необходимый для получения длины значения ключа. Далее в операции memmove используется значение ключа реестра, длина которого не сравнивается с длиной принимающего буфера. Операция memmove работает с двумя локальными буферами фиксированной длины, расположенными в стеке. После memmove данные, скопированные с локального буфера, снова без проверки длины записываются в канал. Как описано в приложении к электронному письму из списка рассылки Full Disclosure, размещение двух упомянутых буферов в памяти позволяет читать закрытые области памяти, динамически определять версию файла 'nvvsvc', динамически находить ROP-гаджеты и, в конечном счете, выполнять код.

Рисунок 2: псевдокод, иллюстрирующий вторую проблему

Создание собственного VDB-отладчика для обнаружения и анализа эксплоита

Цель создания собственного отладчика на основе VDB – продемонстрировать один из способов обнаружения и анализа эксплоитов. В данном разделе мы выясним, как обнаружить и проанализировать эксплоит для драйвера NVIDIA, опубликованный @peterwintrsmith.

Как можно обнаружить эксплуатацию описанных уязвимостей? В данном посте я предполагаю, что ВСЯКИЙ раз, когда программный счетчик переходит на область памяти, отображенную НЕ на файл – это "плохой знак". Поэтому, нужно уметь фиксировать тот факт, что программный счетчик оказывается в куче, стеке или другом регионе выделенной памяти, не отображенном на файл.

Другие приложения вроде OllyDbg позволяют пользователю останавливать выполнение, когда программный счетчик (регистр IP) находится вне или внутри определенной области памяти. Системы защиты игр также используют этот метод, чтобы помешать хакерам произвольно вызывать "защищенные" методы из внедренного кода [1]. Отличие данного подхода от того, что нужно нам в том, что он не учитывает, отображается ли память на файл.

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

  1. Установил пакет 64-битных драйверов NVIDIA (версии 310.70) на 64-битную систему.
  2. Запустил и распаковал пакет без последующей установки (так что, наличие видеокарты NVIDIA в тестовой системе было необязательным)
  3. Перешел в папку 'Display.Driver' и распаковал (например, с помощью 7zip) файл 'NvCplSetupInt.exe'
  4. В командной строке с правами администратора перешел в папку, в которую был извлечен 'NvCplSetupInt.exe', затем запустил команду 'nvvsvc.exe -install'
  5. Скопировал nvvsvc.exe в папку c:\windows\system32
  6. Использовал services.msc или net start nvsvc, чтобы запустить службу
  7. Загрузил эксплоит и заменил его шеллкод на свой, который состоял из последовательности NOP-ов (0x90) и прерывания INT 3 (0xcc) для создания точки останова, после чего скомпилировал эксплоит
  8. Запустил эксплоит и убедился, что он работает

Дальше я расширил подсистему stalker внутри Vtrace и VDB, чтобы реализовать обнаружение кода, выполняющегося в областях памяти, не отображенных на файл. На случай, если вам не приходилось использовать подсистему stalker, замечу, что она выполняет динамическое дизассемблирование в определенной пользователем точке входа и устанавливает новые точки останова на первую инструкцию каждого обнаруженного блока выполнения. В зависимости от типа инструкции, точка останова удаляется после первого попадания на нее. Кроме того, инструкции динамического ветвеления в базовых блоках получают "специальную" точку останова, называемую StalkerDynBreak. При попадании на такую точку останова вычисляются динамические ветви, и stalker устанавливает новые точки останова на цели этих ветвей. Это описание подсистемы stalker является неполным, но минимально необходимым для понимания оставшейся части поста. Wiki и код stalker можно найти на visi.kenshoto.com.

Возможно вы задаетесь вопросом, почему stalker сам не умеет обнаруживать выполнение в памяти, не отображенной на файл. Stalker был разработан, чтобы обслуживать 'правильно структурированный' код, а не код, который вручную перемешали со стеком для изменения потока управления. Проблема в том, что stalker не устанавливает точки останова на инструкции возврата, он полагает, что, если дизассемблированный базовый блок содержит инструкцию call, то в какой-то момент программный счетчик вернется на инструкцию, следующую за call и, в конечном счете, попадет в другой базовый блок, на котором точка останова уже выставлена. Эксплоит драйвера NVIDIA манипулирует стеком напрямую, чтобы косвенно изменять поток управления. Поэтому мне понадобилось заставить stalker считать все инструкции перехода и возврата своими динамическими точками останова.

Итак, мы создадим новый тип точки останова stalker – 'StalkerRetBreak'. Ниже показан код, относящийся к классу 'StalkerRetBreak'.

Рисунок 3: код для класса StalkerRetBreak

Точка останова 'StalkerRetBreak' считывает из стека адрес возврата и устанавливает на него новую точку останова. Таким образом, если в ходе выполнения функции адрес возврата будет изменен, stalker все равно сможет 'засечь' передачу потока управления. По сути, мы превратили инструкции возврата (return) в динамические точки останова. Аналогичное изменение понадобилось для инструкций перехода (jmp). На сей раз я напрямую модифицировал класс StalkerDynBreak. Посмотреть все изменения можно в архиве по ссылке [2].

Теперь нужно написать код для автоматизированного VDB-отладчика. Вот он:

Рисунок 4: код для автоматизированного отладчика

При запуске код, показанный на рисунке 4, перезапускает службу nvsvc, присоединяется к запущенному процессу, устанавливает точку останова stalker на начало функции CreateThread и 'запускает' отладчик. Cпециальная мета-переменная 'keepgoing' отвечает за 'продолжение выполнения, пока кто-нибудь не скажет остановиться'. Когда поступит сигнал остановиться, код выведет текущее положение программного счетчика и запустит скрипт 'disas_hits.py'.

Переменная 'keepgoing' устанавливается точкой останова StalkerRetBreak. Если в точке останова будет обнаружен переход в область памяти, не отображенную на файл, StalkerRetBreak изменит значение данной переменной и пошлет отладчику сигнал останова. В результате произойдет выход из цикла while.

Должно быть, вам интересно узнать, что делает 'disas_hits.py'. Этот скрипт пробегает все записанные попадания на точки останова stalker и дизассемблирует первые 16 байт в каждой точке или меньше, если наткнется на инструкцию возврата. Скрипт отвечает за форматированный вывод файла, в который отображается память, значения программного счетчика и фрагмента дизассемблированного кода/гаджета. Наверное вы удивлены, что я не включил код 'disas_hits.py' в автоматизированный отладчик. Я не сделал этого, чтобы его можно было запускать не только из моего отдельного автоматизированного отладчика, но и из графического интерфейса VDB PyQT. Исходный код 'disas_hits.py' можно найти в архиве [2].

Что выводит автоматизированный отладчик? Рисунок 5 показывает вывод, полученный в результате запуска автоматизированного отладчика ('c:\python27\python.exe mydebugger.py') и эксплоита:

Рисунок 5: вывод автоматизированного отладчика.

Отметим, что мы обнаружили вызов API-функции VirtualProtect (по адресу соответствующий гаджету), определенные в эксплоите гаджеты и созданную мной полезную нагрузку эксплоита. Гаджеты, определенные в исходном коде эксплоита, показаны на рисунке 6.

Рисунок 6: ROP-гаджеты в исходном коде эксплоита

"Полезная нагрузка" эксплоита не выполняется (это не касается ROP-гаджетов), так как было обнаружено, что поток управления передается в область памяти, отображенную не на файл. Отладчик просто вывел то, что должно было выполниться.

По ссылке [2] вы можете найти ZIP-архив, содержащий исходный код, и патч для релиза vdb_20121228.

Ссылки

[1] http://www.gamedeception.net/archive/index.php?t-18635.html
[2] https://sites.google.com/site/mvdbcode/

Ваша приватность умирает красиво, но мы можем спасти её.

Присоединяйтесь к нам!