Mastodon

Monday, June 13, 2011

Выступление на Positive Hack Days 2011

Репост из блога Esage Lab

Этой весной в Москве прошло весьма необычное для отечественного itsec-сообщества мероприятие под названием Positive Hack Days. Делать его детальный обзор я смысла не вижу, поскольку всё уже было неоднократно высказано другими людьми, положительные отзывы которых я вполне разделаю.

На Positive Hack Days я выступил с мастер-классом "Автоматический поиск уязвимостей в программах без исходных текстов", материалы к которому я выкладываю в данной записи.

Слайды к выступлению:

Использовавшиеся на мастер-классе утилиты (многие из них я не успел показать) доступны для загрузки в виде архива. Ниже идёт описание наиболее интересного содержимого этого архива.

****


./AnalyzeDumps - Программа для анализа аварийных дампов памяти с использованием отладчика KD.EXE и расширения !exploitable.
Использование:
> python ./AnalyzeDumps/analyze_dumps.py <dumps_dir> [--noisy]

... где <dumps_dir> - директория, в которой содержатся файлы аварийных дампов. При указании ключа --noisy в консоль будут выведены все сообщения отладчика.

По завершению работы программы в текущей директории будет создан файл Analyze.log c общей информацией по всем проанализированным дампам, а в <dumps_dir> - отдельный лог для каждого аварийного дампа с полным выводом отладчика.
Примеры сгенерированных файлов для различных тестовых приложений: ./Analyze_Norman.log, ./Analyze_RPCExample.log и ./Analyze_Visio.log

Программа analyze_dumps.py требует для своей работы следующие сторонние инструменты (так же включены в архив):
  • ./MSECExtensions_1_0_6 - Расширения для отладчика MSEC Debugger Extensions, в состав которых входит !exploitable.
  • ./Microsoft KD - Консольный отладчик от Microsoft, который, обычно, входит в состав пакета Microsoft Debugging Tools for Windows.

****


./Code Coverage Tools и ./pin-2.8-37300-msvc9-ia32_intel64-windows - Основанный на PIN Toolkit набор средств для построения и анализа карты покрытия кода исследуемого приложения.

Подробная информация по использованию:

Так же в директории ./Kcachegrind находится Win32-версия одноимённого приложения, которое позволяет визуализировать карты деревьев вызовов, которые были получены с помощью Code Coverage Tools.

****


./FileFuzz - Инструменты для примитивного мутационного фаззинга на примере файлов архивов форматов 7z, ACE, ARJ, CAB, GZ, LZH, RAR, TGZ, и ZIP.

Генерация некорректных файлов производится с помощью программы ./FileFuzz/MutateGen.exe (исходные тексты доступны в ./FileFuzz/MutateGen/) на основе "хороших" файлов архивов указанных выше форматов, которые находятся в директории ./FileFuzz/TEST/.

Пример запуска MutateGen.exe для генерации файлов:
> MutateGen.exe C:\TEST.zip C:\output_dir -BLOCK_SIZE 1 -BLOCK_RANGE_START 0xa0 -BLOCK_RANGE_END 0xff -BLOCK_RANGE_N 1

Назначения параметров командной строки программы:
  • -FILE_RANGE_START - Файловое смещение для прочитанных из указанного файла начальных данных, начиная с которого MutateGen.exe осуществляет их модификацию (по умолчанию FILE_RANGE_START равен нулю).
  • -FILE_RANGE_END - Файловое смещение, определяющее конец зоны начальных данных, которую модифицирует MutateGen.exe (по умолчанию FILE_RANGE_END равен размеру начального файла).
  • -BLOCK_SIZE - Размер блока данных, которые MutateGen.exe модифицирует за один проход (допустимые значения: 1, 2 и 4).
  • -BLOCK_RANGE_START - Стартовое значение счётчика, которое используется как значение для генерируемого блока данных.
  • -BLOCK_RANGE_END - Конечное значение счётчика, которое используется как значение для генерируемого блока данных.
  • -BLOCK_RANGE_N - Величина инкремента счётчика за одну итерацию.

Таким образом, количество циклов генерации данных (количество модифицируемых байт) для одного начального файла рассчитывается по формуле:

N = (FILE_RANGE_END &~ (BLOCK_SIZE - 1)) - FILE_RANGE_START

Количество блоков данных:

BLOCKS = N / BLOCK_SIZE

Количество итераций для одного цикла генерации данных:

I_MAX = ((BLOCK_RANGE_END &~ (BLOCK_RANGE_N - 1)) - BLOCK_RANGE_START) / BLOCK_RANGE_N

Количество cгенерированных файлов с некорректными данными:

FILES = BLOCKS * I_MAX

Для генерации данных используются сценарии generate_bs_1.bat, generate_bs_2.bat, generate_bs_4.bat и generate_bs_4_hi.bat, которые запускают MutateGen.exe с различными настройками.

Фаззинг архивов на примере актуальной версии антивируса Norman Security Suite продемонстрирован на видео.


Norman Security Suite Fuzzing from eSage Lab on Vimeo.

Аварийные дампы для Norman Security Suite а так же файл вызывающий падение антивирусного сканера находятся в директории ./XcptMon/_Norman_dumps_7z, а лог анализа этих аварийных дампов с помощью analyze_dumps.py - в ./Analyze_Norman.log

****


./InMemoryFuzzer и ./RPCExample - Инструменты и тестовые приложения для демонстрации in-memory фаззинга RPC сервера.
В качестве фаззера используется программа на Python под названием InMemoryFuzzer by sinn3r, которая подробно описана в статье "In Memory Fuzzing".

Для удобства использования в архив включена её версия в виде исполняемого файла, созданного с помощью py2exe (см. директорию ./InMemoryFuzzer/dist).

В качестве уязвимого сервера используется написанная на C++ программа, исходные тексты и исполняемые файлы которой находятся в ./RPCExample/ (там же есть и клиент для этого сервера). Проведение фаззинга осуществляется в несколько шагов:
  1. Запускается сервер с помощью сценария ./RPCExample/run_server.bat
  2. Запускается фаззер с помощью сценария ./RPCExample/fuzz_server.bat
  3. Запускается клиент для RPC сервера (./RPCExample/DebugExe/ContextExampleClient.exe)
  4. В момент обработки запроса от клиента в контексте серверного процесса фаззер модифицирует переданную функции _HelloProc() ASCII-строку, в результате чего будет спровоцировано переполнение буфера в этой функции.
  5. После аварийного завершения работы серверного процесса фаззер создаст отчёт о его падении в директории ./RPCExample/crashbin

****


./MSOffice - Инструменты для более продвинутого мутационного фаззинга на примере приложения Visio из состава Microsoft Office.

Генерация данных для фаззинга осуществляется приложением ./MSOffice/fuzzgen/, которое использует принцип мутационных преобразований имеющихся .VSD-документов с частичным парсингом формата файла-контейнера Microsoft Office. Для парсинга структуры документов используются функция StgOpenStorageEx() и интерфейс IStorage.

В ./MSOffice/_faults/ находятся примеры сгенерированных фаззером .VSD-документов, обработка которых вызывает падение Visio. Лог анализа этих аварийных дампов с помощью analyze_dumps.py находится в ./Analyze_Visio.log

Для запуска процесса фаззинга используется приложение ./MSOffice/fuzzrun/, работа с ним продемонстрирована на видео.


Microsoft Office Visio Fuzzing from eSage Lab on Vimeo.

****


./XcptMon - Приложение для мониторинга исключений и создания аварийных дампов. Именно оно используется для генерации дампов в описанных выше тестах.

XcptMon работает по принципу внедрения DLL библиотеки (./XcptMon/XcptMonDll.dll) в контекст анализируемого процесса при его запуске. DLL, в свою очередь, перехватывает функцию KiUserExceptionDispatcher(). Для внедрения DLL используется параметр реестра "Debugger" в ключе HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<image_name>, где <image_name> - имя исполняемого файла анализируемого процесса.

Использование XcptMon:
> XcptMon.exe <image_name> [options]

... где в качестве опций возможно указать следующие ключи:
  • --dumpsdir <path> - Путь к директории для сохранения аварийных дампов.
  • --logpath <path> - Путь к файлу, в который будет записана информация об исключениях.
  • --remove - Отключить XcptMon для указанного имени процесса.
  • --noexit - Не завершать целевой процесс при возникновении первого #AV исключения.
Пример лога с информацией об исключениях:
[+] Target command line: "TestApp.exe"
[+] DLL injected into the target process 7404
[+] Exit on first #AV: "No"
ModuleInit(): From process 'E:\_tmp\PHD\XcptMon\TestApp.exe' (PID: 7404)
[!] EXCEPTION OCCURS:
STATUS_ACCESS_VIOLATION at 0x0119101c
  Access type: Write
      Address: 0x00000000
EAX=0x00000000 EBX=0x00000000 ECX=0x6f6e215c EDX=0x773270b4
ESI=0x6f6e20c1 EDI=0x01193380 EBP=0x0012fe54

[+] 22398742 bytes of minidump has been written to the "0xC0000005_0x0119101C_17.05_19.28.54.DMP"
[!] EXCEPTION OCCURS:
STATUS_ACCESS_VIOLATION at 0x011910af
  Access type: Write
      Address: 0x00000000
EAX=0x00000000 EBX=0x00000000 ECX=0x6f6e215c EDX=0x773270b4
ESI=0x6f6e20c1 EDI=0x00000000 EBP=0x0012fe54

[+] 22402854 bytes of minidump has been written to the "0xC0000005_0x011910AF_17.05_19.28.54.DMP"
[+] Process exit code: 0xc0000005

Так же библиотека XcptMonDll.dll экспортирует глобальные переменные m_szLastFilePath, m_szLastExceptionCode, m_szLastExceptionAddr, m_szMainModuleVersion и m_szFaultModuleVersion, в которые на момент создания аварийного дампа записывается информация о возникшем исключении и целевом приложении. Эту информацию, для включения в свой лог-файл, получает упоминавшийся выше analyze_dumps.py

****


./ioctl_fuzzer-1.2 - Исходные тексты и исполняемые файлы программы IOCTL Fuzzer, которая предназначена для автоматического выявления узявимостей в драйверах режима ядра, связанных с некорректной обработкой IOCTL-запросов.
В директории ./ioctl_fuzzer-1.2/_exploits/ находятся примеры эксплойтов к найденным спомощью IOCTL Fuzzer уязвимостям.

scsiprot_smart - "Теоретически эксплуатируемая" Local Admin to Ring0 уязвимость в стандартном драйвере Windows atapi.sys при обработке IOCTL запроса IOCTL_SCSI_MINIPORT. Приведенный демонстрационный эксплойт, к сожалению, DoS only.

TM_TmComm_9000402b_exploit - Полнофункциональный Local Admin to Ring0 эксплойт к уязвимости в драйвере актуальной версии (3.0.0.1303 на момент написания текста) антивирусного продукта под названием Trend Micro Titanium Maximum Security.

Tuesday, April 19, 2011

От покрытия кода к дереву вызовов

Репост из блога Esage Lab

В прошлом посте нами было рассказано о практических аспектах применения dynamic binary instrumentation engines (на примере PIN) для анализа покрытия кода при фаззинге. Но очевидно, что столь мощные технологии годятся для решения и более сложных задач: в этот раз речь будет идти про использование PIN для построения карты исполнения кода в виде дерева вызовов различных процедур.

Для UNIX-like операционных систем существует весьма продвинутый инструмент под названием Callgrind, представляющий собой модуль для известного профилировщика Vallgrind. Задачей Callgrind является запись информации о вызове всех процедур исследуемого приложения в процессе его исполнения, при этом, в качестве результата работы, он генерирует текстовый лог, в котором присутствует информация о том, какие процедуры, из каких мест кода и сколько раз были вызваны. Это и называется деревом вызовов. На самом деле, Callgrind способен записывать и много дополнительной информации, включая время исполнения отдельных ветвей алгоритма, однако, именно связи между процедурами исследуемого кода являются наиболее интересной информацией для реверс-инженера. Формат выходного файла Callgrind называется Calltree Profile Format, документация по нему доступна на официальном сайте.

К сожалению, версии Callgrind для Windows на данный момент не существует, поэтому нами было принято решение частично продублировать его функции в ранее разработанном пакете инструментов Code Coverage Analysis Tools.

Основную задачу по сбору информации, как несложно догадаться, выполняет разработанный нами инструментальный модуль для PIN, который, для активации режима записи всех вызовов функций, следует запускать с опцией "-c". Запуск целевого приложения удобно осуществлять с помощью сценария execute_pin_calls.bat, пример:
> execute_pin_calls.bat "C:\Program Files\Internet Explorer\iexplore.exe"
Примечание: перед этим следует не забыть поместить библиотеку Coverager.dll в корневую директорию PIN, и записать полный путь к ней в переменную среды PINPATH, путём редактирования execute_pin_calls.bat.

После завершения работы исследуемого приложения в текущей директории будет создано некоторое количество текстовых файлов с именами вида CoverageData.log.<N>, где <N> - порядковый номер потока, который исполнялся в контексте исследуемого приложения. Эти файлы содержат информацию о дереве вызовов каждого из потоков. Для работы с деревом вызовов следует преобразовать их в Calltree Profile Format с помощью программы coverage_to_callgraph.py, которая принимает следующие параметры командной строки:
> python coverage_to_callgraph.py <log_file_path> <thread_number> [options]
... где <log_file_path> - путь к файлу CoverageData.log, а <thread_number> - порядковый номер потока. Если в результирующий лог требуется включить информацию обо всех потоках, то в качестве  <thread_number> следует указать "*". В качестве опциональных параметров указываются следующие ключи:
  • --modules - Получать информацию только для указанных модулей (по именам). Для передачи списка из нескольких модулей следует разделять их имена запятой (например: --modules "iexplore.exe, ieframe.dll"). Если параметр --modules не указан - в результирующий файл будет включена информация обо всех исполняемых модулях исследуемого процесса.
  • --skip-symbols - По умолчанию coverage_to_callgraph.py автоматически загружает PDB-символы для нужных исполняемых модулей. Параметр --no-symbols позволяет отключить загрузку PDB символов, в таком случае, имена всех процедур в результирующем файле будут представлены в виде module+ofsset.
Пример:
C:\> python coverage_to_callgraph.py CoverageData.log *
SYMLIB: DLL_PROCESS_ATTACH
SYMLIB: Symbols path is "C:\Symbols;SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols"

Code Coverage Analysis Tool for PIN
by Oleksiuk Dmitry, eSage Lab (dmitry@esagelab.com)

[!] Psyco is not available
[+] Input file(s): CoverageData.log.0, CoverageData.log.1, CoverageData.log.10, CoverageData.log.11, CoverageData.log.12, CoverageData.log.13, CoverageData.log.14, CoverageData.log.15, CoverageData.log.16, CoverageData.log.17, CoverageData.log.18, CoverageData.log.19, CoverageData.log.2, CoverageData.log.20, CoverageData.log.21, CoverageData.log.22, CoverageData.log.3, CoverageData.log.4, CoverageData.log.5, CoverageData.log.6, CoverageData.log.7, CoverageData.log.8, CoverageData.log.9
[+] Output file: Callgrind.out
[+] 80 modules readed
[+] Parsing routines list, please wait...

[+] 27806 routines readed
[+] Parsing call tree, please wait...

SYMLIB: Module loaded from "C:\Windows\SYSTEM32\ntdll.dll"
SYMLIB: 4239 symbols loaded for "C:\Windows\SYSTEM32\ntdll.dll"
SYMLIB: Module loaded from "C:\Windows\system32\IEFRAME.dll"
SYMLIB: 33516 symbols loaded for "C:\Windows\system32\IEFRAME.dll"
SYMLIB: Module loaded from "C:\Windows\System32\mshtml.dll"
SYMLIB: 35150 symbols loaded for "C:\Windows\System32\mshtml.dll"
SYMLIB: Module loaded from "C:\Windows\system32\OLEAUT32.dll"
SYMLIB: 3940 symbols loaded for "C:\Windows\system32\OLEAUT32.dll"

... skipped ...

[+] DONE (15 mins., 33 secs.)

SYMLIB: DLL_PROCESS_DETACH

По завершению работы coverage_to_callgraph.py в текущей директории будет создан файл Callgrind.out, работать с которым можно с помощью программы Kcachegrind, которая является наиболее удобным и популярным просмотрщиком логов Callgrind. Windows-версия Kcachegrind так же доступна на SourceForge.

Вид главного окна программы:


Kcachegrind обладает весьма развитыми возможностями для навигации по дереву вызовов, он позволяет:
  • Строить графы вызовов для интересующих процедур.
  • Отображать список функций, которые вызывали текущую (либо непосредственно, либо по цепочке).
  • Отображать список функций, которые были вызваны текущей.
  • Фильтровать отображаемую информацию по имени класса или исполняемого модуля и многое другое.
Выбор типа группировки для списка функций:


Для отрисовки графов в графов в Kcachegrind используется инструмент dot, из состава пакета Graphviz.

Пример графа вызовов относительно текущей процедуры:


Ещё несколько примеров:





Разработанный инструмент хорошо справляется с анализом весьма тяжелых приложений (таких как веб-браузеры), что и было продемонстрировано на примерах выше. Так же его можно использовать для детектирования факта успешной эксплуатации какой-либо уязвимости: Kcachegrind покажет исполненный в результате атаки шеллкод как неизвестную страницу памяти, которая не принадлежит какому-либо исполняемому модулю.
Стоит отметить, что запуск инструментального модуля в режиме записи всех вызовов не приводит к сколь-либо заметному снижению производительности исследуемого приложения по сравнению с режимом построения простой карты покрытия кода (см. результаты тестов, которые были озвучены в предыдущей записи).
Обновленный набор Code Coverage Analysis Tools доступен для загрузки на странице проекта в GitHub.

Wednesday, March 23, 2011

Анализ покрытия кода при поиске уязвимостей

Репост из блога Esage Lab

Сутью задачи анализа покрытия кода (Code Coverage) является динамический анализ исполняемой программы с целью выяснения того, какие её части (и какое количество раз) были исполнены. Как не сложно догадаться, к основными областям, в которых решение данной задачи является востребованным, относятся тестирование программного обеспечения и фаззинг, как его более частный вариант. Для специалиста, занимающегося автоматизированным выявлением уязвимостей, полученная в ходе анализа покрытия кода информация особенно ценна из-за того, что она позволяет совершенно однозначно оценивать эффективность методов, применяемых для выявления уязвимостей: чем больше ветвей алгоритмов целевого приложения исполнялось в ходе тестирования - тем и эффективнее эти методы тестирования.

Для программ, которые написаны на компилируемых в машинный код языках, существуют следующие классы инструментов, позволяющих анализировать покрытие кода:
  1. Профилировщики, использующие промежуточное представление кода (пример: LLVM, Vallgrind). Основным их недостатком является то, что такие профилировщики пригодны только для анализа программ с доступными исходными текстами.
  2. Инструменты, использующие возможности для отладки, предоставляемые операционной системой (например: системный вызов ptrace() в *nix или Debug API в Windows). В качестве примера программ подобного рода можно привести ProcessStalker, фреймворк PaiMei а так же множество сомнительного вида скриптов, как для отладчиков, так и выполненные в виде отдельных инструментов (blocks.py, Tracer.py и другие). Данные инструменты обладают огромным количеством недостатков, что делает их совершенно непригодными для сколь-либо серьёзного использования. Среди основных: низкая производительность, сложности при анализе исполнения всех модулей в контексте процесса а так же, во многих случаях, потребность предварительного анализа интересующих модулей с помощью внешней программы (IDA Pro).
  3. Инструменты,  на прямую использующие возможности для отладки, заложенные в самой аппаратной архитектуре (пример: CFSearch, IKWYD). Подход как таковой не имеет явно выраженных недостатков, и целесообразность применения на практике той или иной программы определяется исключительно недостатками их реализации.
  4. Полные эмуляторы, представляющие собой модифицированные виртуальные машины исполняющие весь код операционной системы, в среде которой запущенно тестируемое приложение (пример: TEMU). Основным недостатком данного класса программ является крайне низкая производительность, ведь эмуляции подлежит вся операционная система, а не только тестируемое приложение.
  5. JiT-рекомпиляторы, осуществляющие анализ хода исполнения программы путём модификации её инструкций в процессе исполнения (пример: PIN Toolkit, DynamoRIO). По моему мнению, данные инструменты свободны от большей части перечисленных выше минусов, и поэтому лучше всего подходят для анализа покрытия кода при фаззинге. Из недостатков можно отметить разве что отсутствие готовых инструментов подобного рода, пригодных для анализа покрытия кода компонентов режима ядра.
В данной заметке я расскажу об относительно малоизвестном но весьма мощном инструментальном средстве под названием PIN Toolkit. PIN является наиболее функциональным и стабильным представителем программ, использующих динамическую рекомпиляцию кода для анализа его исполнения.

Основы работы с PIN


PIN представляет собой спонсируемую компанией Intel бесплатную разработку, которая поставляется с частично открытыми исходными текстами в виде SDK для Linux, Windows и Mac OS X. PIN работает на архитектурах IA-32, IA-64 (Itanium) и Intel64 (x86_64). Структурно его можно разделить на основное приложение (pin.exe), внедряемое в контекст анализируемого процесса ядро (pinvm.dll) и разрабатываемый пользователем инструментальный модуль (он так же внедряется в контекст анализируемого процесса). Инструментальный модуль представляет собой DLL-библиотеку, которая использует API, предоставляемый ядром PIN. Суть работы с API сводится к регистрации обработчиков, которые будут вызываться ядром в ходе исполнения кода исследуемого приложения и осуществлять либо логирование связанной с ходом исполнения информации, либо менять логику исполнения кода приложения любым образом, который разработчик инструментального модуля сочтет нужным.

Работать с анализируемым кодом назначаемые обработчики могут на следующих уровнях функциональности и гранулярности:
  • Исполняемые модули. Обработчик, который вызывается при загрузке какого-либо исполняемого модуля в контекст исследуемого процесса регистрируется при помощи функции IMG_AddInstrumentFunction(). Для работы с загружаемым модулем из обработчика используются другие IMG-функции, а  для работы с отдельными секциями - функции SEC-семейства.
  • Функции. Обработчик, который вызывается для каждой функции исследуемой программы регистрируется при помощи функции RTN_AddInstrumentCall(). Для работы с инструкциями исследуемой функции внутри этого обработчика следует использовать RTN_InsHead()/RTN_InsTail() а так же семейство INS-функций для исследования самих инструкций.
  • Базовые блоки (basic blocks). Под базовым блоком подразумевается линейный участок кода находящийся между двумя инструкциями, которые являются или точкой входа вектора исполнения или непосредственно меняют значение EIP (CALL, JMP/Jxx, RET, и так далее). Обработчик, который вызывается для каждого базового блока исследуемой программы, регистрируется при помощи функции TRACE_AddInstrumentFunction(). Назначать обработчики для отдельных базовых блоков можно с помощью BBL_InsertCall(). Для работы с отдельными инструкциями, составляющими базовый блок, в теле обработчика можно использовать функции BBL_InsHead()/BBL_InsNext().
  • Собственно, сами инструкции. Обработчик, который вызывается для каждой инструкции исследуемой программы регистрируется при помощи функции INS_AddInstrumentFunction(). Назначать обработчики для отдельных инструкций можно с помощью INS_InsertCall(). Для анализа инструкций PIN использует библиотеку под названием XED. Его API так же доступно для использования в инструментальных модулях.
Так как ядро PIN перехватывает исполнение целевого процесса начиная с точки входа системного загрузчика - в инструментальных модулях гарантированно безопасное использование только функций самого PIN-а и стандартной С/С++ библиотеки. Попытка использования, к примеру, Win32 API чаще всего приводит к неработоспособности инструментального модуля.

Множество примеров разработки инструментальных модулей с подробными комментариями доступны в официальном руководстве.

Как уже было сказано, PIN работает по принципу динамической рекомпиляции кода исследуемой программы: в процессе его исполнения на место нужной инструкции записывается переход (JMP) в pinvm.dll, которая вызывает зарегистрированные инструментальным модулем обработчики и модифицирует инструкцию (или базовый блок) следующую за текущей. Таким образом достигается контроль над исполнением всего кода. Высокая скорость работы исследуемого кода при таких, казалось бы, сложных манипуляциях с ним обеспечивается за счёт отсутствия накладных расходов на взаимодействие между процессами и переключение потока между режимом пользователя и ядра, которые "съедают" основную часть процессорного времени при использовании более традиционных способов трассировки. Что интересно, под PIN нормально работают и сложные многопоточные приложения и даже самомодифицирующийся код, вроде следующего:
 1 #include <Windows.h>
 2 
 3  #pragma comment(linker,"/ENTRY:f_main")
 4 #pragma comment(linker,"/SECTION:.text,EWR")
 5 
 6 VOID Func_1(VOID)
 7 {
 8     MessageBoxA(0, __FUNCTION__"() called", "Message", 0);
 9 }
10 
11 VOID Func_2(VOID)
12 {
13     MessageBoxA(0, __FUNCTION__"() called", "Message", 0);
14 }
15 
16 __declspec(naked) VOID Func(VOID)
17 {
18     __asm
19     {
20         push    0x90909090
21         ret
22     }
23 }
24 
25 DWORD WINAPI f_main(VOID)
26 {
27     DWORD Address = 0;
28     char szMessage[0x50];
29 
30     __asm
31     {
32         call    _get_addr
33 
34 _get_addr:
35 
36         pop     eax
37         mov     Address, eax
38     }
39 
40     wsprintf(szMessage, "Address is 0x%.8x", Address);
41     MessageBoxA(0, szMessage, "Message", 0);
42 
43     *(PDWORD)((PUCHAR)&Func + 1) = (DWORD)&Func_1;
44 
45     Func();
46 
47     *(PDWORD)((PUCHAR)&Func + 1) = (DWORD)&Func_2;
48 
49     Func();
50 
51     return 0;
52 }

Анализ покрытия кода с PIN


Для анализа покрытия кода с использованием PIN нами был разработан относительно несложный инструментальный модуль а так же некоторые другие утилиты, которые были объединены в набор под названием Code Coverage Analysis Tools. В него входят:
  • Coverager.dll - Собственно, инструментальный модуль для PIN.
  • coverage_test.exe - Тестовое приложение, которое позволяет строить карты покрытия кода для Internet Explorer-а используя PIN и Coverager.dll.
  • coverage_parse.py - Скрипт для работы с логами, которые генерирует Coverager.dll.
  • symlib.pyd - Используемая в coverage_parse.py небольшая Python библиотека для работы с PDB символами.
Используются эти утилиты следующим образом:
  1. Для начала необходимо загрузить актуальную версию PIN и распаковать архив в произвольную директорию.
  2. Скопировать Coverager.dll в директорию с файлами PIN.
  3. Отредактировать сценарий execute_pin.bat, что бы переменная среды PINPATH содержала актуальный путь к директории PIN.
Для построения карты покрытия кода какого-нибудь приложения следует запустить execute_pib.bat из консоли, передав ему командную строку для запуска целевого приложения в качестве аргумента. Пример:
> execute_pin.bat calc.exe
После завершения работы целевого приложения в текущей директории будут созданы следующие текстовые файлы:
  • CoverageData.log - Общая информация об исследуемом процессе (размер карты покрытия, количество потоков и исполняемых модулей, и так далее).
  • CoverageData.log.modules - Список полных путей к файлам исполняемых модулей, которые были загружены в контекст исследуемого процесса.
  • CoverageData.log.routines - Информация о функциях, которые получали управление в ходе исполнения исследуемого процесса.
  • CoverageData.log.blocks - Информация об отдельных базовых блоках инструкций, которые получали управление в ходе исполнения исследуемого процесса.
Файлы CoverageData.log.routines и CoverageData.log.blocks содержат информацию о функциях/блоках, принадлежащих всем исполняемым модулям, включая файл самого процесса а так же стандартные системные (ntdll, kernel32, user32, и так далее) и любые другие динамически загружаемые библиотеки.

Для представления информации из перечисленных выше логов в удобном для работы виде служит скрипт coverage_parse.py, который принимает следующие параметры командной строки:
> python coverage_parse.py <log_file_path> <--dump-blocks|--dump-routines> [other_options]
В качестве <log_file_path> указывается путь к файлу CoverageData.log, а один из обязательных ключей --dump-blocks или --dump-routines указывает на то, с информацией какого уровня гранулярности следует работать (базовые блоки или функции).

В качестве опциональных параметров указываются следующие ключи:
  • --outfile <file_path> - Сохранять информацию в указанный файл, вместо вывода в консоль.
  • --order-by-names - Сортировать вывод по имени (адресу) символа, который соответствует функции или базовому блоку (данный режим используется по умолчанию).
  • --order-by-calls - Сортировать вывод по количеству вызовов функции или базового блока.
  • --modules <modules> - Получать информацию только для указанных модулей (по именам).  Для передачи списка из нескольких модулей следует разделять из имена запятой (например: --modules "iexplore.exe, ieframe.dll"). Если параметр --modules не указан - в вывод скрипта будет включена информация обо всех исполняемых модулях исследуемого процесса.
  • --skip-symbols - По умолчанию скрипт автоматически загружает PDB-символы для нужных исполняемых модулей и использует их для преобразования адресов всех функций и базовых блоков к виду module!symbol+offset. Параметр --no-symbols позволяет отключить загрузку PDB символов, в таком случае, все адреса в выводе скрипта будут представлены в виде module+ofsset.
Пример использования скрипта для получения информации о наиболее часто вызываемых функциях модуля calc.exe:
> python coverage_parse.py CoverageData.log --dump-routines --modules "calc" --order-by-calls

SYMLIB: DLL_PROCESS_ATTACH
SYMLIB: Symbols path is ".\Symbols;SRV*.\Symbols*http://msdl.microsoft.com/download/symbols"
SYMLIB: Module loaded from "C:\Windows\system32\calc.exe"
SYMLIB: 2774 symbols loaded for "C:\Windows\system32\calc.exe"
Filtering by module name "calc"
[+] Ordering list by number of calls
[+] Parsing routines list, please wait...

#
#   Calls count -- Function Name
#
          11560 -- calc.exe!UIntAdd
           7863 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::operator[]
           6559 -- calc.exe!_destroynum
           5780 -- calc.exe!ULongLongToUInt
           5780 -- calc.exe!_createnum
           5102 -- calc.exe!ATL::CAtlRegExp::MatchToken
           3788 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::SetCount
           3416 -- calc.exe!ATL::CAtlArray<ATL::CAtlRegExp::INSTRUCTION,ATL::CElementTraits<ATL::CAtlRegExp::INSTRUCTION> >::CallConstructors
           3404 -- calc.exe!ATL::CAtlRegExp::AddInstruction
           3196 -- calc.exe!addnum
           2902 -- calc.exe!lessnum
           2636 -- calc.exe!memcpy
           2470 -- calc.exe!_addnum
           2453 -- calc.exe!__security_check_cookie
           1743 -- calc.exe!CArgument::getPosition
           1084 -- calc.exe!CCalculatorMode::HandleBackgroundForChildWindows
           1084 -- calc.exe!CProgrammerMode::ProgDlgProc
            918 -- calc.exe!operator delete[]
            895 -- calc.exe!CEditBoxInput::HandleDlgProcMessage
            704 -- calc.exe!ATL::CAtlArray<FORMULA_PART *,ATL::CElementTraits >::GetAt

            ...
            пропущено ещё несколько сотен функций
            ...

              1 -- calc.exe!Gdiplus::Graphics::Graphics
              1 -- calc.exe!GdipGetImageGraphicsContext
              1 -- calc.exe!Gdiplus::Graphics::SetInterpolationMode
              1 -- calc.exe!GdipSetInterpolationMode
              1 -- calc.exe!Gdiplus::Graphics::SetSmoothingMode
              1 -- calc.exe!GdipSetSmoothingMode
              1 -- calc.exe!CProgrammerMode::ShowBitStrip
              1 -- calc.exe!CProgrammerMode::NormalizeCurrentBit
              1 -- calc.exe!CIO_vUpdateDecimalSymbol

[+] Processed modules list:

#
# Routines count -- Module Name
#
            422 -- gdiplus.dll
             58 -- winmm.dll
            107 -- apphelp.dll
            221 -- gdi32.dll
            370 -- kernel32.dll
            196 -- msvcrt.dll
             57 -- oleaut32.dll
             30 -- dwmapi.dll
            692 -- ntdll.dll
             60 -- shlwapi.dll
              5 -- sechost.dll
             92 -- imm32.dll
            705 -- calc.exe
             28 -- cryptbase.dll
             37 -- advapi32.dll
            577 -- ole32.dll
             33 -- lpk.dll
            825 -- msctf.dll
             10 -- version.dll
            309 -- usp10.dll
            278 -- windowscodecs.dll
            483 -- uxtheme.dll
            138 -- oleacc.dll
             95 -- clbcatq.dll
            251 -- comctl32.dll
            258 -- kernelbase.dll
            117 -- shell32.dll
             25 -- rpcrt4.dll
            465 -- user32.dll

[+] DONE

SYMLIB: DLL_PROCESS_DETACH

PIN так же корректно анализирует исполняемый код, который не принадлежит какому-либо загруженному модулю, и находится в произвольной executable-странице памяти. Для того, что бы получить информацию о таком коде из логов, для coverage_parser.py необходимо указать "?" в качестве имени модуля:
> python coverage_parse.py CoverageData.log --dump-routines --modules "?"

SYMLIB: DLL_PROCESS_ATTACH
SYMLIB: Symbols path is ".\Symbols;SRV*.\Symbols*http://msdl.microsoft.com/download/symbols"
Filtering by module name "?"
[+] Parsing routines list, please wait...

#
#   Calls count -- Function Name
#
              5 -- ?0x541be250
              5 -- ?0x55009060

              ...

Как видно из примера - в выводе скрипта такой код будет представлен в форме ?<address>.

Тестирование


В последнее время наблюдается следующая тенденция: многие исследователи, разрабатывающие различные системы динамического анализа кода, в качестве демонстрации возможностей этих систем ссылаются на тесты, для которых в качестве объекта используются маленькие приложения вроде calc и notepad или всевозможные вариации hackme.exe/vulnerable.exe из десятка строчек. Чаще всего подобные явления говорят или о невозможности решать с помощью демонстрируемого инструмента задачи из реальной жизни, или о том, что разработчик данного инструмента слишком увлечен ним самим, и воспринимает его в отрыве от практических задач.

Так как подобные тенденции мы не разделяем - в качестве демонстрации возможностей PIN и инструментального модуля Coverager.dll будут приведены результаты их тестирования на построении карты покрытия кода процесса браузера Internet Explorer 8, работающего в среде операционной системы Windows 7 SP1.

Для проведения тестирования была написана программа coverage_test.exe, ключевая часть исходных текстов которой выглядит следующим образом:
  1 int _tmain(int argc, _TCHAR* argv[])
  2 {
  3     char *lpszCmdLine = GetCommandLine();
  4     char *lpszCmd = strstr(lpszCmdLine, "@ ");
  5     if (lpszCmd == NULL)
  6     {
  7         /*
  8             Командная строка для запуска внешнего приложения не указана,
  9             coverage_test был запущен из командной строки.
 10         */
 11         char szSelfPath[MAX_PATH], szExecPath[MAX_PATH];
 12         GetModuleFileName(GetModuleHandle(NULL), szSelfPath, MAX_PATH);
 13         DWORD dwTestIterations = 1;
 14 
 15         SetConsoleCtrlHandler(CtrlHandler, TRUE);
 16 
 17         /*
 18             Регистрируем исполняемый файл текущего процесса как отладчик для IEXPLORE.EXE,
 19             при попытке его запуска система запустит приложение а путь к исполняемому файлу
 20             IEXPLORE.EXE будет передан ему в качестве аргумента командной строки.
 21         */
 22         sprintf_s(szExecPath, "\"%s\" @", szSelfPath);
 23         SetImageExecutionDebuggerOption("IEXPLORE.EXE", szExecPath);
 24 
 25         for (int i = 1; i < argc; i++)
 26         {
 27             if (!strcmp(argv[i], "--iterations") && argc > i + 1)
 28             {
 29                 // количество итераций для теста
 30                  dwTestIterations = atoi(argv[i + 1]);
 31                 printf("Iterations count: %d\n", dwTestIterations);
 32             }
 33             else if (!strcmp(argv[i], "--instrumentation-path") && argc > i + 1)
 34             {
 35                 /*
 36                     Путь к файлу инструментальной программы (в нашем случае это pin.exe),
 37                     сохраняем его в параметре реестра и использует при запуске IEXPLORE.EXE
 38                 */
 39                 SetOptionalApp(argv[i + 1]);
 40                 printf("Instrumentation tool path: \"%s\"\n", argv[i + 1]);
 41             }
 42         }
 43 
 44         // инициализация COM
 45         HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
 46         if (FAILED(hr))
 47         {
 48             printf("CoInitializeEx() ERROR 0x%.8x\n", hr);
 49             goto end;
 50         }
 51 
 52         DWORD dwTime = GetTickCount();
 53 
 54         // редактирование настроек IE: активируем single process mode 
 55         DisableIeMultiprocessMode();
 56 
 57         /*
 58             Вызов функции, которая открывает в IE тестовый адрес указанное число раз.
 59             Для взаимодействия с процессом браузера используется интерфейс IWebBrowser2.
 60         */
 61         IeOpenUrl(TEST_URL, dwTestIterations);
 62 
 63         // замер времени исполнения
 64         dwTime = GetTickCount() - dwTime;
 65         printf("Execution time: %d ms\n", dwTime);
 66 
 67         EnableIeMultiprocessMode();
 68         SetOptionalApp(NULL);
 69     }
 70     else
 71     {
 72         /*
 73             Текущий процесс был запущен системой в результате попытки
 74             запуска IEXPLORE.EXE.
 75         */
 76         DWORD dwExitCode = 0;
 77         char szOptional[MAX_PATH], szCmdLine[MAX_PATH];
 78         strcpy_s(szCmdLine, MAX_PATH, lpszCmd);
 79 
 80         lpszCmd += 2;
 81 
 82         // удаляем опцию "Debugger" для IEXPLORE.EXE
 83         SetImageExecutionDebuggerOption("IEXPLORE.EXE", NULL);
 84 
 85         // получаем путь к инструментальной программе
 86         if (GetOptionalApp(szOptional, MAX_PATH))
 87         {
 88             sprintf_s(szCmdLine, "\"%s\" %s", szOptional, lpszCmd);
 89         }
 90 
 91         // запуск IE c использованием PIN
 92         printf("CMDLINE: %s\n", szCmdLine);
 93         ExecCmd(&dwExitCode, szCmdLine);
 94     }
 95 
 96 end:
 97     printf("Press any key to quit...\n");
 98     _getch();
 99 
100     return 0;
101 }

Программа coverage_test.exe входит в состав Code Coverage Analysis Tools, её запуск следует производить с помощью сценария coverage_test_with_pin.bat, который инициирует запуск процесса браузера Internet Explorer под контролем PIN и проводит с ним то количество тестовых итераций, которое было указанно в качестве аргумента командной строки к coverage_test_with_pin.bat. Каждая тестовая итерация представляет собой открытие адреса google.com с помощью IWebBrowser2::Navigate() и ожидание завершения полной загрузки страницы.

С данной программой было проведено пять серий тестов с количеством итераций 1, 5, 10, 20 и 30. Ниже представлена гистограмма зависимости времени исполнения процесса браузера (в миллисекундах) от количества итераций:



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

Схожие разработки


PIN является не единственным фреймворком подобного подобного плана, существует так же разработка под названием DynamoRIO, которая похожа на PIN во всех отношениях. В качестве главных преимуществ DynamoRIO можно выделить более высокую производительность а так же полностью открытый под BSD лицензией исходный код. Однако, актуальная версия DynamoRIO имеет ряд проблем со стабильностью - мне так и не удалось добиться нормальной её работы на Windows 7, из-за чего выбор и пал на PIN.

C данными по сравнительной производительности обеих фреймворков можно ознакомится в заметке на GoVirtual: Performance Comparison of DynamoRIO and Pin.

Выводы


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

Code Coverage Analysis Tools доступны для загрузки на странице проекта в GitHub.

Friday, January 21, 2011

Обход детектирования модификаций кода в ядре

Репост из блога Esage Lab

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

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

В "джентльменский набор" антируткита, обычно, входят возможности по детектированию следующих типов перехватов:
  • Модификация кода исполняемых модулей в памяти (сплайсинг - одна из возможных вариаций).
  • Модификация таблиц импорта или экспорта.
  • Модификация таблицы системных вызовов.
  • Модификация указателей на функции, которые содержатся в каких-либо динамических структурах ядра (DRIVER_OBJECT, OBJECT_TYPE_INITIALIZER и другие).

Детектирование модификаций кода в Rootkit Unhooker

Все эти типы перехватов объединяет тот факт, что их детектирование в любом случае будет представлять из себя чтение и проверку содержимого определённых регионов виртуальной памяти (ядра или пользовательского режима), в связи с чем напрашивается очевидный вывод: детектору можно подсунуть ложную страницу памяти путём манипуляций с таблицами, которые используются процессором для трансляции виртуальных адресов в физические.

Идея подобной атаки - отнюдь не нова: например, Skywing упоминал подобный метод как теоретически возможное решение для обхода PatchGuard на 64-х разрядных версиях Windows. Однако, в связи с тем, что в публичных источниках какую-либо реализацию подобного нам найти не удалось - было решено написать PoC, который бы в качестве демонстрации работоспособности идеи мог скрывать от антируткитов модификации кода, характерные для стандартных руткитов режима ядра.

Для понимания принципа работы PoC-а кратко рассмотрим реализацию механизма трансляции виртуальных адресов памяти в физические, которая применяется в современных операционных системах семейства Windows NT. Раздел "Protected Mode Memory Management" тома 3A руководства разработчика для архитектуры Intel 64 и IA-32 рассказывает нам о том, что в зависимости от значений различных управляющих флагов в контрольных регистрах процессора существует несколько режимов адресации, которые определяют как размер физической страницы, так и разрядность физического адреса (максимальное количество памяти, к которой способен обращаться процессор):


По умолчанию в Windows, начиная с XP Service Pack 2, используется режим PAE (Physical Address Extension) поскольку он является необходимым условием для поддержки NX/XD-битов, на которых, в свою очередь, базируется технология DEP. Как видно из таблицы, разрядность физического адреса в данном режиме составляет 36 бит, а размер физической страницы может составлять 2 мегабайта или 4 килобайта. Windows может использовать 2MB и 4KB физические страницы одновременно, по усмотрению диспетчера памяти ядра операционной системы. Так же стоит помнить, что размер виртуальной страницы памяти при этом всегда будет составлять 4KB (1000h байт).

Получение физического адреса из виртуального заключается в "складывании" адреса физической страницы и смещения на ней. Адрес физической страницы отыскивается процессором путём просмотра таблиц PDPTE, PDE и PTE, индексами для которых являются различные группы битов виртуального (линейного) адреса. Смещение на физической странице определяется младшими 12-ю битами виртуального адреса:

Трансляция виртуального адреса в физический на системах с PAE для 4KB страниц

Физический адрес самой первой таблицы в иерархии, PDPTE, процессор считывает из регистра CR3, в который его помещает операционная система во время перехода в защищенный режим или при переключении задач.

Таким образом, для адресации 4GB виртуального адресного пространства Windows инициализирует:
  • 4 записи в таблице PDPTE
  • 4 таблицы PDE с 512-ю записями в каждой
  • 512 таблиц PTE c 512-ю записями в каждой
В обзорных статьях, описывающих управление памятью в Windows, почему-то не упоминается один важный момент: для каждого процесса ядро аллоцирует свои собственные таблицы PDPTE и PDE а так же ту часть PTE таблиц, которые описывают пространство виртуальной памяти пользовательского режима. Общие для всех процессов только PTE-таблицы, описывающие пространство виртуальной памяти режима ядра.

Как уже было упомянуто, Windows может использовать и 2MB физические страницы, в случае с которыми схема вычисления физического адреса несколько упрощается за счёт того, что в ней не задействуются таблицы PTE:


Исходя из вышеизложенных принципов можно сделать вывод, что обращения к виртуальным адресам для какого-либо конкретного процесса в Windows можно "перенаправить" на ложную физическую страницу, путём манипуляции значениями физического адреса, которые хранятся в его PDE и/или PTE таблицах. Данная идя и легла в основу реализованного PoC-а, работа которого заключается в осуществлении следующих манипуляций:
  1. Во время инициализации драйвер режима ядра PoC-а выделяет память, в которую копирует оригинальное содержимое страницы кода ядра, на который, в последствии, будет установлен перехват методом сплайсинга.
  2. Устанавливает демонстрационный перехват функции ядра IoCallDriver() а так же нотификатор на создание/завершение процессов, который используется для отслеживания запуска процессов антируткитов.
  3. По факту запуска процесса антируткита (которые, для упрощения примера, идентифицируются по списку известных имен исполняемых файлов) PoC модифицирует таблицы трансляции виртуальных адресов для этого процесса таким образом, что бы при попытке чтения модифицированной страницы кода ядра обращение происходило к "ложной" физической странице, которая содержит оригинальную копию кода.
Модификация таблиц трансляции виртуальных адресов в физические осуществляется следующим образом:
  1. Выполняется поиск PDE записи, которая соответствует нужному виртуальному адресу.
  2. Если PDE запись указывает на 2MB страницу - PoC производит логическое разбиение этой страницы на 512 страниц по 4KB, путём аллокации для них новой таблицы PTE записей.
  3. Так как PTE записи, описывающие адреса пространства ядра, общие для всех процессов - PoC производит копирование таблицы PTE в новый регион памяти, физический адрес которого записывается в ранее найденную PDE запись.
  4. И наконец - в PTE запись, соответствующую нужному виртуальному адресу, производится установка физического адреса "ложной" страницы памяти.
В графическом представлении модифицированные таблицы выглядят так:


Данная техника сокрытия имеет и некоторые недостатки:
  • Текущая демонстрационная реализация PoC-а умеет скрывать модификации кода только от тех процессов, имена исполняемых файлов которых заранее известны. Однако, этот недостаток можно устранить в "боевой" реализации путём идентификации процесса антируткита по факту попытки считывания ним модифицированной страницы памяти. Саму попытку считывания можно перехватывать используя или отладочные регистры процессора, или продемонстрированную в рутките Shadow Walker технику принудительной инвалидации страницы с перехватом исключения #PF при обращении к ней.
  • Так как в контексте процесса антируткита все обращения будут перенаправляться на "ложную" страницу с оригинальным исполняемым кодом - установленные руткитом перехваты для данного процесса срабатывать не будут. Для рассмотренной техники сокрытия данное ограничение нельзя обойти по определению, однако, для решения стоящих перед руткитом задач оно может являться не существенным.
Видео, демонстрирующее работу PoC-а:


PTBypass PoC from eSage Lab on Vimeo.

PoC работоспособен на 32-х разрядных версиях Windows XP, Vista и 7. Установленный перехват не детектируется такими антируткитами как RootkitUnhooker, GMER, Kernel Detective и CMC Anti Rootkit. Исходные тексты и исполняемые файлы PoC-а доступны в репозитории на GitHub а так же в виде архива.

Monday, December 27, 2010

Обход детектирования скрытого исполняемого кода

Репост из блога Esage Lab

Универсальные антируткиты, как инструменты для надёжной борьбы со скрытыми объектами, за последние несколько лет стали очень сильно проигрывать в гонке технологий с писателями malware, чьи разработки постепенно уходят от классической модели реализации (драйвер + скрытый файл + скрытый системный сервис). Всем известно, что хороший современный руткит представляет собой уже не отдельный файл, а просто кусок исполняемого кода, получающий управление из зараженного драйвера или загрузочного сектора. В настоящее время присутствует довольно много вредоносных программ, которые нельзя достоверно детектировать (а уже тем более удалить) используя RootkitUnhooker, GMER, RootRepeal или любой другой антируткит подобного класса.

Однако, антируткиты по-прежнему остаются актуальными для детектирования системных аномалий в более широком понимании этого слова: модификация кода в памяти, DKOM, драйвера-фильтры или использование стандартных механизмов нотификации, и так далее. Кроме всего перечисленного важной функцией хороших антируткитов является и детектирование скрытого кода в памяти - кода, который исполняется на процессоре и при этом не принадлежит какому-либо файлу на диске. Данная техника детектирования интересна тем, что способна указать на факт заражения системы абсолютно любым сложным руткитом (из тех, что имеют распространение в настоящий момент) даже в том случае, если другие его скрытые объекты обнаружены не были (зараженные/подмененные файлы, загрузочные сектора, итд.).

Выглядит детектирование скрытого исполняемого кода так:

Rootkit Unhooker

Kernel Detective

RootRepeal

Safe'n'Sec Rootkit Detector

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

Рассмотрим возможные способы реализации поиска скрытого исполняемого кода:
  1. Проверка стартовых адресов потоков, хранящихся в полях StartAddress и Win32StartAddress структуры _ETHREAD. Наиболее примитивный способ.
  2. Перехват ключевых функций ядра (например - ExAllocatePool или IofCallDriver) с проверкой адреса возврата на предмет принадлежности образу какого-либо загруженного драйвера. Способ, который относительно легко обходится путём загрузки в память своей копии ядра. Кроме того, не способен детектировать скрытый код в том случае, если исполняющий его поток большую часть времени проводит в "спящем" состоянии.
  3. Перехват планировщика. Заключается в перехвате какой-либо ключевой функции планировщика потоков, которая вызывается при их переключении (например - SwapContext). В обработчике перехвата текущие адреса потоков, которые отдают или получают квант процессорного времени, проверяются на предмет принадлежности образу какого-либо загруженного драйвера. Данный способ так же не способен детектировать код который исполняется потоком, находящимся в состоянии ожидания большую часть времени.
  4. Анализ указателей. Заключается в поиске активных перехватов кода в ядре (системные вызовы, IRP-обработчики, сплайсинг, нотификаторы) с последующей проверкой адресов обработчиков этих перехватов на предмет принадлежности образу какого-либо загруженного драйвера. Более надёжный способ, но работать он будет только в том случае, если руткит устанавливает какие-либо перехваты заведомо известного типа.
  5. Анализ стеков вызовов. Заключается в перечислении всех активных потоков и получении стека  вызовов для каждого из них. Адрес каждой функции из стека вызовов проверяется на предмет принадлежности образу какого-либо загруженного драйвера. Самый совершенный способ, свободный от перечисленных выше недостатков.
После ознакомления с перечисленными реализациями на ум приходит идея копировать исполняемый код руткита поверх загруженного в память образа какого-либо стандартного системного драйвера (не трогая его файл на диске, разумеется). Для этого должны быть удовлетворены следующие условия:
  1. Не должно нарушаться нормальное функционирование загруженного драйвера.
  2. Участок образа загруженного драйвера (который будет использоваться для хранения кода руткита) должен быть выбран таким образом, что бы антируткит не обнаружил каких-либо модификаций при сверке кода в памяти с тем, что содержится в файле на диске.
Данные условия соблюдаются при внедрении кода руткита в так называемые Discardable-секции PE файла (те, которые имеют установленный флаг IMAGE_SCN_MEM_DISCARDABLE). В случае с драйверами режима ядра Discardable-флаг указывает PE-загрузчику на то, что секция может быть выгружена из памяти после завершения инициализации драйвера. Обычно, Discardable-флаг имеют секции содержащие ресурсы, базовые поправки или инициализационный код драйвера. Такие секции помещаются линковщиком в конец образа:
kd> !dh beep.sys

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
     14C machine (i386)
       5 number of sections
3B7D82E5 time date stamp Sat Aug 18 00:47:33 2001

   ...
     
SECTION HEADER #3
    INIT name
     284 virtual size
     880 virtual address
     300 size of raw data
     880 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
E2000020 flags
         Code
         Discardable
         (no align specified)
         Execute Read Write

SECTION HEADER #4
   .rsrc name
     3C8 virtual size
     B80 virtual address
     400 size of raw data
     B80 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only

SECTION HEADER #5
  .reloc name
      9A virtual size
     F80 virtual address
     100 size of raw data
     F80 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
42000040 flags
         Initialized Data
         Discardable
         (no align specified)
         Read Only

Как известно, в общем случае загрузка драйвера режима ядра осуществляется с помощью функции ZwLoadDriver
Рассмотрим её работу:
  1. ZwLoadDriver получает из указанного ключа реестра системной службы путь к файлу её драйвера, после чего вызывает функцию MmLoadSystemImage.
  2. MmLoadSystemImage выполняет чтение исполняемого файла с диска и его проецирование в память, после завершения которого вызывает функцию PsCallImageNotifyRoutines.
  3. PsCallImageNotifyRoutines выполняет вызов нотификаторов, которые были установлены с помощью функции PsSetLoadImageNotifyRoutine.
  4. MmLoadSystemImage вызывает точку входа загруженного образа. Если точка входа вернула статус ошибки - образ немедленно выгружается. В случае успеха вызывается функция MmFreeDriverInitialization.
  5. MmFreeDriverInitialization перечисляет все секции образа и выгружает те их них, которые имеют флаг IMAGE_SCN_MEM_DISCARDABLE.
Для демонстрации техники сокрытия исполняемого кода в Discardable-секциях был разработан PoC, который представляет собой драйвер режима ядра работающий следующим образом:
  1. Драйвер устанавливается в систему как служба, имеющая тип запуска SERVICE_BOOT_START (запускающаяся на ранних этапах загрузки операционной системы).
  2. Во время инициализации драйвер устанавливает нотификатор на загрузку исполняемых образов с помощью функции PsSetLoadImageNotifyRoutine. Функция-нотификатор ожидает загрузки системного драйвера, Discardable-секции которого будут использоваться для хранения скрытого исполняемого кода. В PoC-е, в качестве таких драйверов, были выбраны beep.sys, ndiswan.sys, i8042prt.sys или wanarp.sys.
  3. При загрузке любого из перечисленных драйверов PoC выполняет перехват его точки входа методом модификации кода и отключает нотификатор.
  4. Обработчик перехваченной точки входа вызывает оригинальную, а после того, как она вернула управление - снимает флаг IMAGE_SCN_MEM_DISCARDABLE с секций драйвера-жертвы (для того, что бы предотвратить выгрузку этих секций из памяти) и копирует поверх них свой код. 
  5. После того, как код был успешно внедрен в Discardable-секции - с помощью функции PsCreateSystemThread создается системный поток, который начинает его исполнение.
  6. Созданный поток вызывает функцию ZwUnloadDriver для выгрузки уже ненужного драйвера PoC-а и приступает к исполнению "полезной нагрузки".
В качестве полезной нагрузки PoC всего лишь циклически выводит отладочное сообщение с пятисекундным интервалом, однако, вместо этого мог быть реализован и любой стандартный руткит-функционал.

Отладочные сообщения PoC-а

PoC работоспособен на всех 32-х разрядных версиях Windows начиная с XP, скрытый исполняемый код не детектируется ни одним публично доступным антируткитом (тесты были проведены на RootkitUnhooker, GMER, RootRepeal, Kernel Detective и Safe'n'Sec Rootkit Detector). 
Исходные тексты и исполняемые файлы PoC-а доступны для загрузки в виде архива, а так же в репозитории на GitHub-е.

Tuesday, December 14, 2010

Новый блог

Приветствую всех старых и новых знакомых.

Данный блог является дальнейшей эволюцией моего LJ-аккаунта.
Некоторые старые записи доступны по соответствующему тегу.