31
Инструменты динамического анализа бинарного кода Докладчик: Олексюк Дмитрий, Esage Lab

4 Oleksyk Code Analysis

Embed Size (px)

Citation preview

Инструменты динамического анализа

бинарного кода Докладчик: Олексюк Дмитрий, Esage Lab

Задачи

• Code Coverage – Построение карты покрытия кода: какие инструкции и

сколько раз исполнялись в процессе работы программы

• Taint Analysis

– Сопоставление данных с конкретными участками кода, которые были исполнены при их обработке

• Symbolic Execution – Контроль над исполнением кода для генерации входных

данных, удовлетворяющих конкретным условиям

Code coverage

• Позволяет установить, какие инструкции исполнялись в процессе запуска программы

• Может служить в качестве объективной оценки качества тестирования (фаззинга) программы

• Помогает изучать execution flow в процессе реверс-инжиниринга

• Используется в системах honeypot для детектирования факта эксплуатации каких-либо уязвимостей (в том числе, не известных ранее)

Taint Analysis

• Позволяет связать трассу исполнения программы с данными, которые обрабатывались ней в процессе этого исполнения

• Помогает дать ответ на вопрос о том, как именно программа обрабатывала те или иные входные данные

• Используется для анализа тестовых кейсов, приводящих к аварийному завершению работы исследуемой программы

• Является необходимым фундаментом для реализации Symbolic Execution

Taint Analysis: тезисы

• Данные получают дополнительную характеристику, относительно которой они могут пребывать в двух состояниях: TAINTED и UNTAINTED

• В качестве источника или приёмника дынных в процессе исполнения программы выступают память и регистры процессора

• Тейнтинг передаётся от источника к приёмнику данных при их обработке программой (Taint Propagation)

Taint Propagation

mov eax, in_tainted mov ecx, in_untainted add ecx, eax ; ecx is TAINTED ---------------------------------------------------------- mov eax, in_tainted mov ecx, in_untainted mov ax, cx ; ax is UNTAINTED, eax is TAINTED ---------------------------------------------------------- mov eax, in_tainted xor eax, eax ; eax is UNTAINTED ---------------------------------------------------------- push in_tainted pop eax ; eax is TAINTED, dword[esp + 4] is TAINTED ---------------------------------------------------------- xor eax, eax cmp eax, in_tainted ; AF, CF, OF, PF, SF, ZF is TAINTED

Taint Analysis и уязвимости

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

• Если в ходе анализа обнаружилось, что eip – TAINTED, то это является показателем факта успешной эксплуатации уязвимости с передачей управления на контролируемый атакующим код (шеллкод)

Taint Analysis: проблемы и решения

• Taint Analysis по своей природе требует детального анализа каждой исполняемой инструкции

• x86 и x86_64 – CISC архитектуры с огромной системой команд и большим количеством расширений, написать код, который бы корректно анализировал все существующие инструкции – сложно

• Так же существуют и другие популярные архитектуры (ARM, MIPS, PPC), писать реализацию Taint Analysis для каждой из них отдельно – совершенно нерационально

Taint Analysis: проблемы и решения

• Разумно использовать промежуточное представление кода (IR, Intermediate Representation) для работы с ним

• IR представляет собой псевдо-ассемблер с упрощенной системой адресации, небольшим набором базовых инструкций и возможностью преобразования кода для любой из архитектур в обе стороны (native → IR, IR → native)

• Существующие реализации IR:

– VEX: используется в Valgrind – LLVM bytecode: используется в системе LLVM – REIL: используется в продуктах от Zynamics (RIP ) – RTL: используется в закрытом исследовательском проекте Phoenix от

Microsoft Research

Taint Analysis: проблемы и решения

• Идеальный анализатор кода для задач Taint Analysis должен отслеживать и инструментирвать весь код, исполняемый операционной системой (как режиме пользователя, так и в режиме ядра)

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

• В ряде случаев, уход вектора исполнения в режим ядра может нарушать Taint Propagation при анализе только кода пользовательского режима (пример – системные вызовы, работающие с memory mappings). Анализатор кода должен уметь корретно обрабатывать такие случаи

• Активное использование IPC при обработке данных – страшнейший кошмар для любого анализатора кода, работающего только в контексте конкретного процесса

Symbolic Execution

• Концепция динамического анализа кода, позволяющая решить основной вопрос реверс-инжинринга: «Может ли переменная X получить значение Y после исполнения определённого набора инструкций? Какие данные должна получить программа на вход, что бы это произошло?»

• Любая программа имеет конечное количество промежуточных и финальных состояний, переход между которыми и является её исполнением

• Входные данные определяют в каком именно состоянии окажется программа на том или ином шаге исполнения

• Symbolic Execution позволяет получить такой набор входных данных для исследуемой программы, обработка которых привёдет вектор исполнения в нужное исследователю промежуточное или финальное состояние программы

Symbolic Execution: применение

• В ходе тестирования (фаззинга) программы может использоваться для генерации такого набора тестовых данных, при обработке которого произойдёт исполнение всех возможных ветвей алгоритма

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

• Может использоваться для взлома некоторых типов защит от несанкционированного копирования программы: «Invalid serial number, try again» и «Welcome, thank you for purchasing our program» - это всего лишь состояния программы, переход между которыми осуществляется путём прохода вектора исполнения через промежуточные состояния, соответствующие опеределённым входным данным

Symbolic Execution: как это работает

• Для отслеживания того, как определённый набор вхоных данных влияет на состояние программы на каждом шаге её исполнения используется Taint Analysis

• Для описания конкретного состояния программы относительно входных данных используются символьные выражения, описывающие шаги обработки данных

• Для вычисления набора входных данных, которые позволяли бы получить желаемое состояние относительно известного, используется бескванторная бит-векторная арифметика с конечной точностью (quantifier-free finite-precision bit-vector arithmetic, QF-BV), позволяющая находить точные или приближенные данные, удволтворяющие условиям символьных выражений

Symbolic Execution: примеры

Пример программы:

val = read_from_user(); val = val + 10; if (val == 42) { // state #1 sweet_delicious(); } else { // state #2 gtfo_bitch(); }

Символьное выражение для state #1:

Символьное выражение для state #2:

Symbolic Execution: примеры

• Для решения символьных выражений существуют готовые библиотеки, например SMT-LIB (Satisfiability Modulo Theories Library) – http://www.smtlib.org/

• Запись рассмотренного ранее примера символьного

выражения в формате SMT-LIB: (benchmark test :status unknown :logic QF_BV :extrafuns ((a BitVec[8])(b BitVec[8])(c BitVec[8])(d BitVec[8])(e BitVec[8])) :assumption (= a b) :assumption (= c bv10[8]) :assumption (= d (bvadd a c)) :assumption (= e d) :formula (= e bv42[8]) )

Symbolic Execution: практика

• После поверхностного ознакомления с Symbolic Execution может показаться, что при помощи данной технологии очень легко искать уязвимости нулевого дня, не прибегая к большому количеству ручной работы

• Но это не соответствует действительности: – Это достаточно молодое направление исследований, интерес которому больше

проявляют люди из академической среды, чем «крутые эксплойтеры» – Существующие в настоящий момент инструмены очень далеки от идеала и не годятся

для серьёзного промышленного применения – Само по себе Symbolic Execution упирается во можество ограничений, основными из

которых являются высокие требования к вычислительным ресурсам и сложность в реализации

• Однако, существуют интересные проекты, в рамках которых удалось

реализовать работающие (хотя бы в тесных рамках) инструменты: – Fuzzgrind (http://esec-lab.sogeti.com/pages/Fuzzgrind) – KLEE (http://klee.llvm.org/)

Symbolic Execution: практика

• Если вы хотите получать в процессе поиска уязвимостей максимально качественный и предсказуемый результат за приемлемое время прямо сейчас – Symbolic Execution вряд-ли в этом поможет

Технологии решения задач

• Возможности по отладке кода, предоставляемые процессором – Отладочные регистры и Last Branch Recording в IA-32 и Intel 64

• Отладчики и отладочный API

– ptrace(), Debugging Tools for Windows

• Dynamic Binary Instrumentation (DBI, динамическое инструментирование бинарного кода) – PIN, DynamoRIO, Valgrind

• Intermediate Representation (IR, промежуточное представление кода) – Valgrind, LLVM и инструменты на их базе (Fuzzgrind, KLEE, S²E)

• Эмуляция – BitBlaze, Ether, S²E

Инструменты: отладчики и отладочный API

• (+) В том или ином виде реализованы в любой операционной системе

• (+) Easy to code – easy to use

– Простой API – Предсказуемая эксплуатация – Почти во всех современных отладчиках есть возможность

подключения пользовательских плагинов и скриптов

• (–) Производительность недостаточная даже для задач

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

Инструменты: DBI

• Используют динамическую рекомпиляцию инструментируемого кода

• Обеспечивают высокую производительность из-за низких накладных расходов на инструментирование одной инструкции (так как нет необходимости в механизмах IPC)

• Наиболее известные представители: – PIN (http://www.pintool.org/) – DynamoRIO (http://dynamorio.org/)

• PIN используется для динамического анализа кода в инструментах BAP

(http://bap.ece.cmu.edu/) и Vera (http://www.offensivecomputing.net/vera/), хотя, как и DynamoRIO, является вполне самодостаточным

Инструменты: DBI

• (+) Из всех существующих технологий – наиболее пригодна для практического применения уже сейчас, недостатки есть, но весьма несущественные

• (+) Существующие инструменты могут работать с IA-32, IA-64 и Intel 64 под Windows, Linux и Mac OS X

• (–) Инструментальный модуль может использовать только тот API, который предоставляется самим движком (в PIN и DynamoRIO)

• (–) Инструментальный модуль вынужден работать только с машинным кодом, который, в случае IA-32, IA-64 и Intel 64, несколько сложен для анализа (а вы ожидали другого от CISC-процессоров?)

Инструменты: Valgrind

• Одина из самых мощных открытых сред для профайлинга и инструментирования кода

• Инструменты Valgrind, в отличии от инструментальных модулей PIN и DynamoRIO, работают с промежуточным представлением кода (VEX IR)

• Машинный код транслируется в VEX IR динамически, в процессе исполнения программы

• Ядро Valgrind не требует наличия исходных текстов, но они (или хотя бы отладочная информация) весьма желательны при использовании инструментов для поиска ошибок, входящих в состав Valgrind

Инструменты: Valgrind

• (+) Является серьёзным и стабильно работающим инструментом, применяется в промышленной разработке ПО

– Джулиан Сюард, первоначальный автор Valgrind, выиграл в 2006-м году

Google-O’Reilly Open Source Award за свою работу – Для поиска нашумевшей «фичи», в реализации memcpy() из glibc в Fedora

14, Линус Торвальдс запускал под Valgrind WEB-браузер с Adobe Flash плагином

• (–) Работает только в Linux и Mac OS X (зато кроме IA-32 и Intel 64 поддерживает PowerPC)

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

Инструменты: LLVM

Инструменты: LLVM

• Задумывался как универсальный компилятор/транслятор чего угодно во что угодно, с возможностью реализации оптимизации кода на промежуточном уровне

• Состоит из:

– LLVM Front-end – предназначен для преобразования какого-либо кода (исходного или машинного – не важно) в байт-код LLVM

– LLVM Back-end – предназначен для трансляции байт-кода LLVM в

машинный код для заданной архитектуры (как статически, так и в JiT)

– Виртуальная машина LLVM – предназначена для исполнения, преобразования (в том числе оптимизации) и инструментирования байт-кода LLVM

Инструменты: LLVM

• Для языков C, С++ и Objective-C в качестве front-end используется компилятор Clang

• Существует версия GCC, которая использует LLVM в качестве back-end

• Реализация front-end для машинного кода IA-32 ведётся в рамках таких проектов как libcpu (http://www.libcpu.org/) и S²E

• В реальной жизни трансляция машинного кода в байт-код LLVM должным образом реализована только в статике, что делает LLVM «as is» не пригодным для работы с программами без исходных текстов

• Наиболее известные инструменты для Symbolic Execution, основанные на LLVM: – KLEE (http://klee.llvm.org/): работает только при наличии исходных текстов – S²E (https://s2e.epfl.ch/): использует модифицированный QEMU для генерации байт-кода

LLVM в процессе исполнения машинного кода

Инструменты: LLVM

• (+) Является серьёзным и стабильно работающим инструментом, применяется в промышленной разработке ПО

– Используется в таких компаниях как Apple, Adobe и Google – на LLVM основана подсистема OpenGL в Mac OS X 10.5 – iPhone SDK использует GCC с бэкэндом на LLVM

• (–) Сам по себе LLVM существует под большинство архитектур и операционных систем, однако, многие фронт-енды и инструменты, основанные на LLVM, имеют проблемы с переносимостью

– Windows-версия Clang поддерживает только Plain C – KLEE и S²E «из коробки» не работают в Windows

Инструменты: KLEE

• Пожалуй, наиболее удачная реализация идей Symbolic Execution, разрабатывается под крылом проекта LLVM

• Для инструментирования кода использует виртуальную машину LLVM, может работать с программами на любых языках, для которых есть соответствующий LLVM front-end

• Основная задача KLEE – генерация таких наборов выходных данных, которые приводили бы к исполнению всех ветвей алгоритма исследуемой программы

• Авторы KLEE успешно применяли свой инструмент для поиска ошибок в GNU Coreutils

Инструменты: KLEE

• (+) Хоть и применим для анализа далеко не всех программ – работает весьма качественно, там где может

• (+) Результат работы предоставляет в удобном виде:

– Возможность «воспроизведения» произвольного тестового кейса на native версии исследуемой программы

– Kcachegrind-совместимые графы и трассы исполнения исследуемой программы

• (–) Linux и Mac OS X only, портирование под Windows возможно только в теории

• (–) Для более-менее нормальной работы требуется компилировать в LLVM-байткод не только исследуемую программу, но и все библиотеки, которые она использует – В состав KLEE входит только LLVM-версия ucLibc, все остальные нужные билиотеки

придётся пересобирать в ручную, и это довольно рутинная работа

Инструменты: Ether

• Представляет собой достаточно простой трассировщик на базе модифицированного XEN

• Возможность сохранения полной трассы

исполнения для указанного исполняемого файла

• Возможность логирования системных вызовов отдельно от трассировки

Инструменты: Ether

• (+) Производительность на уровне DBI • (+) Хорошая, законченная реализация

• (–) Не развивается, работает только с XEN 3.1.0 (4-х

летней давности) • (–) Отсутствуют удобные средства для интеграции

со сторонними системами • (–) Поддерживает только Windows Guests