DTrace: Швейцарский армейский карманный нож для реверс-инженера

DTrace: Швейцарский армейский карманный нож для реверс-инженера

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


Tiller Beauchamp David Weston Science Applications International Corporation {Tiller.L.Beauchamp,David.G.Weston}@saic.com

Краткий обзор

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

Введение

DTrace впервые был представлен в Solaris 10, выпущенной в 2004 году компанией Sun Microsystems. Его разработку начал в 2001 году единственный программист ядра Bryan Cantrill. Команда разработки DTrace позже пополнилась двумя программистами – Adam Leventhal и Mike Shapiro.

Sun Microsystems описывает DTrace как “фреймворк динамической трассировки, использующийся для устранения системных неполадок в реальном времени”. DTrace состоит из нескольких компонентов в ядре ОС и пользовательском пространстве, связанных вместе при помощи скриптового языка D. Динамическая трассировка позволяет увидеть почти всю активность в системе по запросу при помощи встроенных программных датчиков. OS X Leopard и Solaris предоставляет тысячи возможных датчиков различных уровней, от ядра до приложений пользовательского уровня, как веб-браузеры и чат-программы. Подобная всесторонняя видимость предоставляет информацию, требующуюся системному администратору, разработчику или пользователю для понимания динамического и комплексного взаимодействия между программными компонентами.

Ответы на подобные вопросы могут быть получены запросом данных, собранных датчиками DTrace при помощи скриптов на языке D. D – Это интерпретируемый язык программирования, созданный для DTrace. Синтаксис D можно описать как C-подобный, но по структуре больше похож на синтаксис Awk. Динамический аспект DTrace заключается в том, что датчики могут быть включены по требованию и удалены, как только требуемые данные будут получены. Это достаточно ненавязчивый метод наблюдения за системой или процессом.

DTrace был первым программным компонентом компании Sun, выпущенным под их собственной Общей лицензией на разработку и распространение (CDDL). Открытость исходного кода DTrace позволила добраться фреймворку и до других операционных систем. Тем не менее, скептическое отношение к CDDL замедлило попытки портирования DTrace на FreeBSD. В RedHat решили вступить в конкуренцию вместе с их продуктом SystemTap. DTrace был портирован на Apple OS X 10.5 Leopard, выпущенную в октябре 2007 года. Двумя неделями позже было объявлено о порте DTrace на QNX. Сообщество DTrace продолжает динамично развиваться.

Синтаксис DTrace

Обработка информации, полученной с датчиков, производится в модуле ядра DTrace. Определение каждого датчика состоит из четырех элементов, разделенных двоеточиями. Общий вид:

поставщик:модуль:функция:имя

Поставщик: Модель ядра DTrace, логически группирующая вместе различные родственные датчики. В качестве примера можно привести: fbt, следящий за функциями ядра; pid, следящий за процессами пользовательского пространства и syscall; наблюдающий за системными вызовами.

Модуль: Местоположение группы датчиков. Это может быть имя модуля ядра, где находится датчик, либо пользовательская библиотека. Например: libsc.so библиотека или ufs модуль ядра.

Функция: Определяет функцию, при которой этот датчик должен сработать. Это может быть частной функцией в библиотеке, например printf() или strcpy().

Имя: Обычно это отражает назначение датчика. Например «entry» или «return» для функции или «start» для I/O датчика. Для трассировки на уровне команд указывается смещение внутри функции.

Знание синтаксиса DTrace позволяет понять назначение определенного датчика. Вы можете получить список всех датчиков в DTrace отсортированных по поставщику, введя команду “dtrace –l”. Датчики будут отображены в формате, описанном выше.

Архитектура DTrace

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

probe definition
/ optional predicate /
{
optional action statements;
}

Рисунок 1. Структура программы на языке D

Для предоставления двусторонней связи между пользовательским пространством и ядром в DTrace существует канал в виде общей библиотеки libtrace.

Рисунок 2. Общий вид архитектуры DTrace

Библиотека libtrace компилирует D скрипт в промежуточный вид. Как только программа скомпилирована, она отправляется в ядро операционной системы при помощи модулей ядра DTrace для выполнения. Именно в это время датчики, указанные в вашем скрипте активируются. После окончания выполнения скрипта, включенные датчики удаляются и система возвращается в состояние нормальной работы.

Язык D

Как сказано ранее, синтаксис языка D подобен синтаксису C. В отличие от C, язык D не использует традиционные условные операторы “if … else”. Вместо этого, D использует понятие “предиката” в качестве условного утверждения. Предикатное выражение оценивается при срабатывании датчика. Если утверждение оценивается как истинное, то выполняется любое действие, ассоциированное с этим условием. Если же утверждение оценивается как ложное, то датчик не срабатывает и наблюдение продолжается. Несколько предикатов и датчиков, соединенных вместе образуют программу на языке D. DTrace предоставляет доступ к огромному количеству данных. Эффективно написанные D скрипты должны следить только за нужными параметрами и выполнять правильные действия в зависимости от ситуации.

DTrace и реверс-инжиниринг

Реверс-инжиниринг в контексте исследования вопросов безопасности является неотъемлемой частью для понимания принципов работы кусочков программы. Реверс-инжиниринг требует много времени и тщательного анализа. При помощи DTraceвыполнить этот анализ можно гораздо проще и быстрей разными способами.

Сильная сторона DTrace заключается в масштабе и точности получаемой информации с использованием относительно простых D скриптов. Реверс-инженер может узнать много всего о кусочке программы благодаря одному или двум правильно расставленным датчикам. Это ставит DTrace в категорию окружений “быстрой разработки” для реверс-инженеров.

В оставшейся части статьи будет рассказано об использовании DTrace для выполнения различных общих задач реверс-инжиниринга. Сначала мы объясним, как можно использовать DTrace для обнаружения и точного указания места программы при переполнения буфера в стеке. Далее, мы рассмотрим обнаружение переполнения буфера в куче и другие проблемы управления динамически распределяемой памятью. Затем мы посмотрим, как использовать DTrace вместе с IDA Pro для визуализации процедуры покрытия кода. Наконец, мы обсудим возможности обнаружения вторжения при помощи DTrace и различные способы ухода от DTrace.

Мониторинг переполнения стека

Интересное занятие – написать детектор переполнения буфера в стеке при помощи DTrace. Подобная программа была написана на Python на основе PyDbg, включенный в PeiMei фреймворк. [3] PeiMei детектор устанавливает точки прерывания и производит пошаговое выполнение приложения. Мы хотим создать похожий инструмент при помощи DTrace, не требующий использования точек прерывания.

Самым простым решением является наблюдение за регистром-указателем (EIP) на предмет известной некорректной величины, например 0x41414141 или определенной величины, которую вы хотите проанализировать (0xdeadbeef например). Это потребует активации только одного датчика для каждого входа функции. Количество датчиков может быть значительным. В таблице ниже содержатся некоторые общие приложения и количество входных датчиков, доступных в OS X для этих приложений, включая функции библиотек.

Программа

Датчики

Firefox

202561

Quicktime

218404

Adium

223055

VMWare Fusion

205627

cupsd

91892

sshd

59308

ftp клиент

6370

Рисунок 3. Количество входных датчиков общих приложений в OS X 10.5

Тем не менее, мы не можем точно оценить влияние каждого датчика на скорость работы приложения, т.к. датчики влияют на скорость работы только при срабатывании. Приложение может использовать много библиотек, но вызывать только несколько функций. И наоборот, приложение может вызывать одну функцию циклически, сильно снижая производительность во время трассировки. Во избежание снижения производительности мы должны сначала убедиться в том, что наши датчики не ведут трассировку неважных модулей и функций, вызываемых слишком часто. Скрипт DTrace, показанный на рисунке 4, может быть использован для отображения наиболее часто вызываемых функций.

#!/usr/sbin/dtrace -s

pid$target:::entry {
@a[probemod,probefunc] = count();
}

END { trunc(@a,10);

Рисунок 4. Скрипт для подсчета вызовов функций

Когда данный скрипт запущен вместе с Firefox и QuickTime Player, становятся очевидными функции и библиотеки, которые могут быть исключены из трассировки. В QuickTime Player мы увидели большое количество вызовов функции __i686.get_pc_thunk.cx. Оба приложения большинство своих вызовов обращают к функциям в модуле libSystem.B.dylib. Исключая эти часто вызываемые функции и библиотеки, мы заметим значительный прирост производительности во время трассировки данных приложений. Наш опыт в работе с DTrace показал наибольшую эффективность при написании уникальных скриптов, использующих ограниченное количество датчиков, чем при написании универсального DTrace скрипта, применимого к любой ситуации.

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

Датчик сработает, когда величина EIP будет равна 0x41414141. В обычной ситуации, это приведет к сбою и закрытию приложения. Но с DTrace мы можем остановить выполнение приложения до того момента, как оно попытается выполнить инструкцию по адресу 0x41414141. Это позволит нам извлечь такие данные как значения регистров процессора и параметров функций, либо задействовать традиционный отладчик для изучения стека.

#/usr/sbin/dtrace -s
pid$target:a.out::return
/ uregs[R_EIP] == 0x41414141 / {
printf("Don’t tase me bro!!!");
printf(“Module: %s Function %s”,
probemod, probefunc);
...
}

Рисунок 5. Проверка EIP на предмет некорректного значения

В этом примере мы предположили, что переполнение стека произойдет при значении EIP 0x41414141. Этого будет достаточно для простого анализа, но эффективный детектор должен обнаруживать переполнение в более общем виде. Достигнуть этого можно, записывая адрес возврата в стековом фрейме, созданном во время входа в функцию. Записанный адрес возврата позже можно сравнить с возвращаемой величиной. Мы не сравниваем значение EIP с сохраненной возвращаемой величиной из-за того, что DTrace использует оптимизацию хвостовой рекурсии. [2] DTrace будет возвращать результат к месту вызова функции. Тем не менее, значение EIP при возврате значения функции будет являться первой инструкцией, вызываемой функции, а не возвращаемым значением, сохраненным в стековом фрейме. Монитор целостности произведет сравнение сохраненных возвращенных значений со значением EIP. Наш детектор предупредит, если сохраненный адрес возврата отличается от текущего адреса возврата и EIP равняется текущему адресу возврата.

Вышеописанная логика подходит для большинства приложений. Однако нужно пояснить некоторые особенности работы DTrace. В частности, DTrace не может отслеживать функции, использующие таблицы переходов. Когда DTrace не может определить, что происходит в функции, он прекращает наблюдение. По этой причине, вы можете закончить на функции, для которой существует входной датчик, но отсутствует выходной. Это как раз тот случай, когда DTrace не может полностью отследить поведение функции по причине использования ею таблиц перехода. Если происходит вызов подобной функции и об этом сообщает наш монитор стека, но не возвращает значения, наш список сохраненных адресов возврата перестает соответствовать стеку. Эти функции должны быть проигнорированы во время трассировки для правильного мониторинга стека. Команда “-l” в DTrace может быть использована для вывода списка датчиков, соответствующих их определению. Список входных датчиков можно сравнить со списком выходных датчиков для выявления функций, мониторинг которых не следует выполнять.

При выполнении этих соображений, наш монитор переполнения стека смог обнаружить RTSP переполнение в QuickTime Player. Краткий вывод показан ниже. Полный вывод программы включает в себя трассировку вызовов.

# ./eiptrace.d -q -p 4450

STACK OVERFLOW DETECTED
STACK OVERFLOW DETECTED
STACK OVERFLOW DETECTED

Module: QuickTimeStreaming
Function: _EngineNotificationProc
Expected return value: 0x1727bac4
Actual return value: 0xdeadbeef
Stack depth: 14
Registers:
EIP: 0xdeadbeef
EAX: 0xffffeae6
EBX: 0x11223344
ECX: 0x00000005
EDX: 0x00000000
EDI: 0x31337666
ESI: 0x41424142
EBP: 0xdefacedd
ESP: 0x183f6000
...

Рисунок 6. Обнаружено переполнение стека

Монитор будет обнаруживать переполнения стека, основываясь на замену адреса возврата. В большинстве случаев переполнения будут изменять не только адрес возврата, но и остальные данные в стеке. В результате это может привести к попыткам неправильного доступа к памяти во время попытки функции получить значение по указателю перед возвратом. Дополнительный скрипт DTrace может быть использован для точного указания инструкции, вызывающей переполнение. Для этого необходимо произвести трассировку каждой команды в уязвимой функции, и после каждого ее выполнения проверить возвращаемое значение в стеке. Как только будет обнаружено переполнение, мы будем знать, что последнее значение EIP и является инструкцией, вызвавшей переполнение.

Стоит изучить и остальные способы использования DTrace для обнаружения переполнения стека. Подобным образом, как и при мониторинге переполнения кучи, описанном ниже, могут записываться размеры параметров функции и адресы. Затем их можно сравнить во время операции копировании функциями bcopy, memcpy, strcpy. Другой подход заключается в записи границ стекового фрейма и проверки правильности параметра копирования во время выполнения функций bcopy, memcpy или strcpy. Это поле для дальнейшей деятельности.

Мониторинг переполнения кучи

Одной из самых сильных сторон DTrace является возможность «захвата» функции. Как показано выше, эта особенность в сочетании с Ruby или другим объектно-ориентированным языком программирования может создать очень мощную платформу реверс-инжиниринга. В последние годы многие команды разработчиков освоили методы написания безопасного кода. Возросшая осведомленность среди компаний-разработчиков ПО, наряду с продвижением в области защиты в операционных системах (неисполняемые стеки, например), сделали такой традиционно излюбленный метод как переполнения стека все более редкими на используемых платформах. Это привело к повышенному интересу к такому направлению атаки как «переполнение кучи».

pid$target::malloc:entry{
self->trace = 1;
self->size = arg0;
}
pid$target::malloc:return
/self->trace == 1/
{
ustack(1);
printf("mallocreturn:ptr=0x%p|size=%
d", arg1, self->size);
self->trace = 0;
self->size = 0;

Рисунок 7. Probe instrumenting malloc entry size argument and return pointer

Nemo с FelineMenace.org опубликовал фундаментальное исследование "Exploiting Mac OS X heap overflows (Использование уязвимости переполнение кучи в Mac OS X)" в журнале Phrak 63 [11]. Его метод основывается на изменении объема и частоты размещений в куче (в OS X называемой "зонами") в сочетании с переполнением буфера кучи для замены указателей функции, содержащихся в начальной структуре кучи (“malloc_zone_t"). Структура, содержащая указатели к различным функциям, таким как malloc(), calloc(), realloc() и т.д., загружается в пространство процессов. Когда адреса этих функций заменены, следующий вызов может разрешить выполнение произвольного кода. Это только один из многих методов использования уязвимости кучи.

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

В настоящий момент существует множество недавно выпущенных инструментов, сосредоточенных на понимании механизма работы кучи с точки зрения реверс-инжиниринга. Immunity Debugger на платформе Windows имеет мощный API, предоставляющий множество инструментов для понимания механизма работы кучи. На платформе Linux и Solaris существует инструмент Core Security's HeapTracer, написанный Gerado Richarte. Он использует ltruss или ltrace для мониторинга системных вызовов, размещающих динамическую память. Разработанный на основе этой же идеи монитор переполнения кучи, включенный RE:Trace, следит за её состоянием, перехватывая функции динамического размещения. Heap Smash Detector входящий в состав RE:Trace не просто отслеживает размещения в куче. Он идет на шаг впереди, перехватывая функции, пытающиеся разместить данные в куче.

RE:Trace Heap Smash Detector создает хеш Ruby, отслеживающий запрашиваемый функцией malloc() объем, используемый в качестве величины. Указатель возврата используется в качестве ключа. Heap Smash Detector также хранит запись всех вызовов calloc(), realloc(), free() соответственно. Второй хеш следит за стековым фреймом, в котором расположен оригинальный блок памяти. Например, RE:Trace захватывает вызов стандартной функции C strncpy(), адрес и параметры объема сравниваются с хешем malloc(). Если размер strncpy() больше выделенного блока, то мы знаем, что произошло переполнение кучи. Heap Smasher определил с высокой точностью место переполнения и стековый фрейм, выполнивший вызов malloc(). Неплохо для относительно небольшого скрипта!

Рисунок 8. Перехвачена функция strncpy()

Схожая техника определения уязвимости была создана с использованием пакета Microsoft Detours [14]. Инструмент "VulnTrace" использует DLL, внедренную в пространство процессов, перехватывающую функции, которые импортируются в таблицу IAT таким образом, что инструмент может изучать аргументы на предмет ошибок безопасности. Этот метод намного более трудоемкий, чем метод, используемый в RE:Trace. Метод требует индивидуальной доработки для каждого приложения. Из-за использования дополнительной DLL возможно снижения производительности. DTrace реализован в качестве компонента операционной системы и не вмешивается в работу тестируемого ПО.

Есть несколько предостережений касательно алгоритма работы распределения зон в OS X и их нужно принять во внимание при реализации Heap Smash Detector. Как упомянул Nemo в его статье"Exploiting Mac OS X heap overflows (Использование уязвимости переполнение кучи в Mac OS X)", OS X хранит раздельные «зоны» или кучи для размещений разных объемов. Приведенная ниже таблица из книги “Mac OS X Internals (Внутренности Mac OS X)”, написанной A. Singh, показывает разделение «зон» в зависимости от объема размещений.

Тип зоны

Размер зоны

Размер распределения (байты)

Величина распределения

Маленькая

2 Мб

< 1993

32 байта

Небольшая

8 Мб

1993 - 15,359

1024 байта

Большая

-

15,360—16,773,120

1 страница (4096 байтов)

Огромная

-

> 16,773,120

1 страница (4096 байтов)

Рисунок 9. Типы зон в OS X Leopard

Мы можем использовать раздельные хеши для каждой зоны. Один интересный аспект маленькой и небольшой зоны заключается в том, что их размер ограничен 2 и 8 Мб. Это позволяет с легкостью рассчитать количество памяти, выделенной каждой зоне.

Еще один интересный факт работы зон в OS X, описанный Nemo, заключается в том, что алгоритм распределения в большинстве случаев не будет выполнять функцию free() дважды, если зона расположена по тому же адресу [11]. При данных обстоятельствах (т.е. free() того же размера и указатель free() все еще существует) эта уязвимость может быть использована, и это состояние должно быть обнаружено. Мы можем наблюдать за этим состоянием при помощи RE:Trace Heap Smash Detector. Будущие дополнения в фреймворке могут включать интеграцию с IDA для автоматизированного создания датчиков.

Покрытие кода

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

Трассировка на уровне блоков может предоставить нам намного больше информации о покрытии кода. Блок кода – это набор инструкций, работающий таким образом, что при выполнении первой инструкции выполняются все инструкции в блоке. Операции условного перехода разделяют блоки инструкций. Это представлено на схеме дизассемблера IDA Pro, показанной ниже.

Рисунок 10. Схема дизассемблера IDA Pro

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

С точки зрения производительности, наблюдение за датчиками в каждой инструкции в большом приложении может быть накладным. Это помогает сосредоточиться на отдельной библиотеке или на самом коде приложения. Дальнейшие усовершенствования можно выполнить при помощи статического анализа. Только одна инструкция должна снабжаться датчиком в каждом блоке. С трассировкой на уровне инструкций в DTrace датчики определенных инструкций заданы в качестве смещения внутри функции, так же как и адрес памяти относительно начала библиотеки или глобального адреса инструкций в виртуальной памяти. Например, приведенный ниже датчик сработает на инструкции со смещением 3f в функции getloginname программы /usr/bin/login:

pid4573:login:getloginname:3f {}

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

Мы используем несколько технологий для соединения DTrace и IDA Pro для визуализации процедуры покрытия кода в реальном времени. Ruby-dtrace используется для сворачивания libtrace, позволяя использовать Ruby для программирования действий, выполняемых при срабатывании определенных датчиков [4]. IDARub используется в качестве удаленного интерфейса для доступа к API IDA Pro [5]. IDA Pro запускается на системе Windows и окружение Ruby отправляет команды в IDA по сети. Когда датчик срабатывает, сигнализируя о выполнении инструкции в приложении, эта инструкция высвечивается в IDA Pro. В поле комментария этой инструкции может быть показано, сколько раз эта команда была выполнена. Рисунок 11 показывает представление операции покрытия кода. Красные блоки содержат выполненный код, белые блоки - еще невыполненный код.

Рисунок 11. Представление покрытия кода в IDA

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

RE:Trace: DTrace/Rubyфреймворк для реверс-инжиниринга

Как было сказано ранее, Ruby-DTrace добавляет большую гибкость в без того мощный фреймворк DTrace, позволяя реверс-инженеру писать скрипты на языке D. Он имеет достаточную стандартную функциональность, требуемую для реверс-инжиниринга, как например контекст процессора, дамп/поиск памяти, и т.д. Мы совместили эту функциональность вместе с дополнительными особенностями в фреймворк, который мы называем RE:Trace. Совмещая мощь Ruby и DTrace, RE:Trace является фреймворком, сочетающим в себе вышеупомянутые Stack Pin Point, Heap Smash Detector и скрипты покрытия кода. Объединение функциональности, необходимой реверс-инженерам, в объектно-ориентированный фреймворк с множеством вспомогательных функций позволяет стать RE:Trace основой множества мощных инструментов. В настоящее время ведется активная разработка RE:Trace и скоро он будет выпущен с такими функциями, как взаимодействие с командной строкой через Ruby IRB, и возможностью создавать датчики без использования D синтаксиса.

Использование DTrace для защиты

DTrace чрезвычайно расширяем. Он может применяться для выполнения множества задач. В то время как мы смотрели на DTrace с позиции реверс-инжиниринга, существуют способы использования DTrace для защиты системы. Коммерческие системы HIDS (хостовые системы обнаружения атак) справедливо получили распространение на платформе win32. Производители выпустили такие продукты как McAfee Entercept и Cisco Security Agent. Согласно официальному описанию McAfee “System Call Interception (Перехват системных вызовов)”, технология “Entercept “ изменяет указатели функций в таблице системных вызовов внутри ядра. Таким образом, драйвер ядра "Entercept" может захватывать любой системный вызов, и, применяя различные методы, определять, является ли вызов, совершенный процессом пользовательского пространства правомерным или нет. Cisco Security Agent имеет такой же принцип.

DTrace позволяет пользователю производить такой же перехват системных вызовов как и коммерческие продукты McAfee и Cisco в почти такой же ненавязчивой форме. Подобную систему обнаружения атак, основанную на анализе системных вызовов будет несложно реализовать на языке D [10].

Используя публично доступный эксплойт Subreption для QuickTime 7.3, основанный на RTSP переполнении стека, в качестве образца, мы можем увидеть? как можно быстро создать HIDS систему при помощи D скрипта.

Эксплойт Subreption для QuickTime 7.3 на Leopard OS X 10.5.1 использует классическую атаку “возврата в библиотеку” для переполнения стека. Атака “возврата в библиотеку” использует переполнение буфера для установки произвольных аргументов в стеке перед возвратом в функцию System() для выполнения системного вызова. Это, вероятно, наиболее популярная техника использования уязвимости на платформах с неисполняемым стеком. Полезность большинства этих атак зависит от последовательности системных вызовов, обычно включающих в себя вызов "/bin/sh" или "/bin/bash". Если мы хотим защитить уязвимый QuickTime 7.3 от эксплойта "возврата в библиотеку", нам нужно сначала изучить нормальное поведение QuickTime с помощью системных вызовов. Для этого можно использовать DTrace вместе со скриптом, показанным на рисунке 12.

#!/usr/sbin/dtrace -q –s
proc:::exec
/execname == "QuickTime
Player"/
{
printf("%s execs %s\n",

Рисунок 12. Изучение системных вызовов QuickTime

Как только мы изучили системные вызовы, мы можем начать создавать отличительные признаки атаки. Простое “занесение атак в черный список”, основанных на одном или двум эксплойте, не может являться достаточным для “промышленных” систем HIDS, но послужит примером создания подобных систем, основанных на DTrace. Для получения подробной информации ознакомьтесь с патентом # 20070107058 на HIDS систему, разработанную компанией Sun на основе DTrace.

Сравнивая вывод системных вызовов простого D скрипта во время нормальной работы и поведение во время использования уязвимости, мы можем определить, какие системные вызовы могут служить в качестве отличительных признаков для нашего D скрипта системы HIDS. После анализа системных вызовов атаки "возвращения в библиотеку" становится очевидным, что QuickTime Player обычно не будет выполнять системный вызов для запуска “/bin/sh” во время нормальной работы (разумеется, это банальный пример). Использование следующего утверждения: "“execname == “QuickTime Player” & args[0] == “/bin/sh”/” будет достаточным для создания общего D скрипта, способного обнаружить эксплойт Subreption и его варианты. После обнаружения эксплойта при помощи датчика syscall, событие необходимо занести в журнал, вывести на экран или остановить атакуемый процесс. Весь скрипт, показанный на рисунке 13, занимает всего несколько строк.

#!/usr/sbin/dtrace -q -s
proc:::exec
/execname == "QuickTime Player"
&& args[0] == "/bin/sh"/
{
printf("\n%s Has been p0wned!
it spawned %s\n", execname,
args[0]);
}

Рисунок 13. D скрипт HIDS системы QuickTime

Несмотря на то, что приведенный выше пример является простым, он может быть улучшен путем добавления отличительных признаков других атак. Существует несколько преимуществ создания "собственной" HIDS системы. Первое заключается в невозможности протестировать эффективность специальной HIDS системы, не выполнив атаку. Коммерческие готовые системы могут быть изучены в управляемом окружении для того, чтобы убедиться в возможности эксплойта уклониться от обнаружения. Второе преимущество состоит в том, что HIDS систему можно подогнать под другое приложение во избежание ошибочного результата. Использование Ruby-DTrace для реализации HIDS системы позволит разработчику хранить отличительные признаки в реляционной базе данных и использовать интерфейс Ruby-On-Rails.

Прячем приложение от DTrace

В своем блоге 18 января 2008-го года разработчик DTrace Adam Leventhal рассказал о том, как заметил неожиданное странное поведение DTrace: “Apple явно запрещает DTrace вести трассировку некоторых процессов”. Это прямо противоположно целям DTrace и духу открытости исходных кодов” [11].

Для достижения этого Apple использует тот же метод, что и в GDB для предотвращения отладки некоторых приложений. Как объясняет Landon Fuller: “PT_DENY_ATTACH – нестандартный запрос ptrace(), предотвращающий отладку некоторых процессов” [10].

Обе версии GDB и DTrace, написанные Apple, проверяют наличие этого флага перед отладкой и трассировкой процесса. Landon Fuller является также автором расширения ядра (kext) для XNU, позволяющего наблюдение за любым процессом при помощи DTrace. Заменяя указатель функции ptrace в системной структуре внутри ядра XNU на указатель к специальной обертке PTrace, Fuller сделал возможным использовать DTrace для любых процессов.

В его презентации на Chaos Computer Congress, названной “B.D.S.M The Solaris 10 way” Archim рассказал об огромной проведенной работе, позволяющей его руткиту “SInAR” прятаться от DTrace на платформе Solaris. Проблема для автора руткита заключается в поставщике DTrace fbt, хранящем список всех модулей, загруженных в ядре. Поэтому даже если вы нашли способ спрятать приложение от mbd, ps, и т.д., опытный администратор до сих пор сможет обнаружить руткит на базе ядра. Даже модули, имеющие значения mod_loaded и mod_installed равные 0, все равно видны для DTrace:

“При комбинировании вызовов dtrace_sync() и, затем dtrace_condense(&fbt_provider), вы будете удалены из списка модулей-поставщиков в DTrace.”

Это заставит DTrace удалить руткит из внутреннего списка поставщиков и деактивирует все его датчики. В настоящее время версия 0.3 SInAR на vulndev.org работает только на SPARC. На сегодняшний день нет известного руткита, имеющего возможность спрятаться от DTrace на OS X Leopard или Solaris 10 x86.

Заключение

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

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

Ссылки

  1. Bryan Cantrill, Mike Shapiro and Adam Leventhal, Advanced DTrace – Tips, Tricks and Gotchas, slide 43.
  2. Sun Microssystems, Inc., Solaris Dynamic Tracing Guide, pp.
  3. Pedram Amini, Pin Pointing Stack Smashes, http://dvlabs.tippingpoint.com/blog/2007/05/02/pinpointing- stack-smashes
  4. Chris Andrews, Ruby-DTrace, http://rubyforge.org/projects/ruby-dtrace/
  5. spoonm, IdaRub, REcon 2006
  6. Amit Singh, Mac OS X Internals A Systems Approach, Addison-Wesley, 2006
  7. Landon Fuller, Fixing ptrace(pt_deny_attach, ...) on Mac OS X 10.5 Leopard, http://landonf.bikemonkey.org/code/macosx/Leopard_PT _DENY_ATTACH.20080122.html, 2008
  8. Adam Leventhal, Mac OS X and the missing probes, http://blogs.sun.com/ahl/entry/mac_os_x_and_the, 2008
  9. Archim, “SUN – Bloody Daft Solaris Mechanisms.”, Chaos Computer Congress, 2004
  10. Subreption, LLC., QuickTime RTSP Redux, http://static.subreption.com/public/exploits/qtimertsp_red ux.rb
  11. Nemo, “Exploiting OS X Heap Overflows”, Phrack Magazine, Issue 63
  12. Richard McDougall, Jim Mauro, Brendan Greg, “Solaris™ Performance and Tools: DTrace and MDB Techniques for Solaris 10 and OpenSolaris” Prentice Hall, 2006
  13. Stefan Parvu, “DTrace & DTraceToolkit-0.96”, http://www.nbl.fi/~nbl97/solaris/dtrace/dtt_present.pdf
  14. Various, “The Shellcoder's Handbook: Discovering and Exploiting Security Holes”, Wiley and Sons, 2007

Большой брат следит за вами, но мы знаем, как остановить его

Подпишитесь на наш канал!