Технологии анализа бинарного кода приложений:...
-
Upload
positive-development-user-group -
Category
Software
-
view
356 -
download
0
Transcript of Технологии анализа бинарного кода приложений:...
Заголовок
ptsecurity.com
Технологии анализа бинарного кода приложений: методы, проблемы, инструментыКонстантин ПанаринPositive Technologies
Заголовок
• Константин Панарин, Positive Technologies, [email protected]
• Разработчик группы анализа низкоуровневых приложений • Интересы: x86-64 reverse-engineering, C++ Template
Metaprogramming
#whoami
Заголовок
• Цели анализа бинарного кода• Некоторые методики анализа• Обзор проблем• Обзор современных средств бинарного анализа
Содержание
Заголовок
• Поиск ошибок• Поиск уязвимостей• Поиск недекларированных возможностей (НДВ)• Восстановление логики работы программы (RE)• Построение тестов
Задачи анализа бинарного кода
ЗаголовокОсобенности бинарного анализа
• Почти полное отсутствие информации о типах*• Гораздо сложнее локализовать различную «метаинформацию»
(например, обработчики исключений)• В исполняемых файлах возможно применение обфускации
и антиотладочных приёмов, затрудняющих анализ• Высокая семантическая нагрузка отдельных ассемблерных инструкций
(особенно на CISC архитектурах)
*Классы легко распознаются благодаря наличию виртуальных таблиц, но что делать с элементарными типами?
Заголовок
Типы анализа:• Статический анализ
• Исполнения программы не происходит
• Динамический анализ• Анализ по одной трассе исполнения
• Комбинированный анализТехнологии, применяемые в анализе:
• Символьное исполнение (symbolic execution)• Как правило, используется в статическом анализе
• Анализ помеченных данных (taint analysis)• Как правило, применяется при динамическом анализе
• Fuzzing• Ожидаемые входные данные подменяются случайными
или специально сформированными• И многие другие
Методики анализа бинарного кода
Заголовок
Типы анализа:• Статический анализ
• Исполнения программы не происходит
• Динамический анализ• Анализ по одной трассе исполнения
• Комбинированный анализТехнологии, применяемые в анализе:
• Символьное исполнение (symbolic execution)• Как правило, используется в статическом анализе
• Анализ помеченных данных (taint analysis)• Как правило, применяется при динамическом анализе
• Fuzzing• Ожидаемые входные данные подменяются случайными или специально
сформированными
Методики анализа бинарного кода
На практике инструменты анализа комбинируют в себе различные типы и технологии из-за ограничений, существующих в них. Согласованное применение различных подходов позволяет преодолевать эти ограничения полностью или частично
ЗаголовокСтатический анализ vs динамический анализ
Динамический анализ• Наличие run-time информации:
карты памяти процесса, адресов неявных вызовов и др.
• Явное исполнение программы может требовать специфического окружения
• Не всегда возможно воспроизвести результаты анализа
Статический анализ• Как правило, работает быстрее• Один анализ покрывает
потенциально бесконечное число путей исполнения
• Работоспособен при отсутствии части исходников / библиотек
• Пасует перед обфускацией и шифрованием
• Отсутствие информации о неявных вызовах
Заголовок
• Основная идея – замена конкретных входных данных программы (аргументов функции) на символьные
• Символ представляет множество всех возможных значений переменной
• Вместо конкретных значений программа будет обрабатывать символьные выражения
• Символьное исполнение способно покрывать все возможные пути в программе
• Каждый путь – это «состояние» программы, в котором хранятся условия прохождения по этому пути (path constraints) и набор ограничений на значения символьных данных (value constraints)
• SMT решатель (solver) – инструмент, определяющий совместность (разрешимость) условий для прохождения по заданному пути
Символьное исполнение
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
При помощи символьного исполнения найдем значения x и y, при которых исполнение попадет в ERROR
Пример взят из http://www.srl.inf.ethz.ch/pa2015/Lecture8.pdf
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0
Path constraints:True
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:True
Символьно исполняем вызов функции
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2y0
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 != 2y0
Два различных состояния после условного перехода if (x==z)
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 =2y0 ^ x0 > y0+10
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 =2y0 ^ x0 <= y0+10
Исследуем условие x==z
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2y0 ^ x0 > y0+10
Условие достижимости ERROR:
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Символьное исполнение: пример
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2y0 ^ x0 > y0+10
Условие достижимости ERROR:
SMT Solver выдает решение:x0 = 40, y0 = 20
ЗаголовокСимвольное исполнение: общая схема
Транслятор в IR
Ассемблерная инструкция
Набор инструкций IR
Пул состояний (по одному для каждого пути исполнения)
State №1
State №2
State №…
State №500Каждое состояние хранит следующие данные:• Текущий IP (instruction pointer)• Символьный контекст (регистры,
ячейки памяти, символьные ресурсы)• Constraints
Executor (director) – занимается обработкой конкретного состояния
X86: mov eax, ecx ___________________
IR: STR R_ECX:32, , V_00:32 STR V_00:32, , R_EAX:32
Интерпретатор – содержит
обработчики для каждой
инструкции IR
Трансляция
Инструкция перехода по условию X (branch)
Если достигнута контрольная точка программы, проверяем её достижимость: извлекаем path constraints, решаем SMT-задачу
SMT-Solvers: Z3, STP, Boolector
New state a:Constraints += X
New state b:Constraints += ~X
Добавляем новые состояния в пул
Searcher – выбирает состояние из пула
ЗаголовокSymbolic execution: проблемы
• Path explosion (как генерировать меньшее число состояний?)• Cycle-unrolling (что делать с циклами, условие остановки
которых зависит от символьной переменной?)• Symbolic pointers (что делать с операциями load и store, адрес
которых тоже символический?)• Constraint difficulty (не все SMT-solver’ы справятся
с нахождением решения)• External resources (что делать с файлами, хэндлерами
и другими внешними объектами?)
ЗаголовокSymbolic execution: возможные пути решения проблем
• Path explosion – мёрджить (объединять) несколько состояний в одно (но как и когда это делать?)
• Path explosion – распараллелить обработку различных состояний
• Cycle unrolling, symbolic pointers – применять специальные логики, созданные для верифицирования программ (но насколько это эффективно?)
• External resources – создать DSL для описания внешних вызовов в терминах executor’а или SMT-solver’а (насколько это быстро и реализуемо?)
ЗаголовокSymbolic execution: ссылки
• KLEE: Unassisted and Automatic Generation of High-Coverage Tests for Complex Systems Programs (C. Cadar, D. Dunbar, D. Engler)
• Unleashing MAYHEM on Binary Code (S. Cha, T. Avgerinos, A. Rebert and D. Brumley)
• S2E: A Platform for In-Vivo Multi-Path Analysis of Software Systems (V. Chipounov, V. Kuznetsov, G. Candea)
Заголовок
• Исключительно динамический метод анализа• Связывает трассу исполнения программы с данными, которые
обрабатывались в ней в процессе этого исполнения• Помогает дать ответ на вопрос о том, как именно программа
обрабатывала те или иные входные данные
Анализ помеченных данных (taint analysis)
ЗаголовокTaint analysis: базовая идея
• Основные концепции: shadow memory и taint propagation
Shadow memory
Taint propagation
Заголовок
mov eax, tainted_inputxor eax, eax ; eax is UNTAINTED
-----------------------------------------push tainted_inputpop eax ; eax is TAINTED, dword[esp + 4] is TAINTED -----------------------------------------------------------------xor eax, eax cmp eax, tainted_input ; AF, CF, OF, PF, SF, ZF is TAINTED
Taint propagation: примеры
mov eax, tainted _inputmov ecx, untainted_inputadd ecx, eax ; ecx is TAINTED -----------------------------------------mov eax, tainted_inputmov ecx, untainted_inputmov ax, cx ; ax is UNTAINTED, eax is TAINTED -----------------------------------------------------------------
Пример взят из http://defcon.org.ua/data/1/4_Oleksyk_Code_Analysis.pdf
ЗаголовокTaint analysis: общая схема
Program code:________________push ebpmov ebp, esplea eax, [esp+8]…ret
Анализ исполняемых инструкций во время
исполнения
add eax, [esp+8]
Instruction handler:Синтаксический парсинг инструкции на операнды,разрешение адресов у memory операндов
Taint context
EBX: not tainted
Taint propagation
ECX: tainted
…
EDI: tainted
EAX: not tainted
SHADOW MEMORY
Операнды:dest - eax,src: eax, 0x7f2300
Чтение контекста:eax – not tainted0x7f2300 - tainted
Запись контекста:eax – tainted
ЗаголовокTaint analysis
Чем полезен taint-analysis:• Tainted EIP говорит о возможности перехвата управления
(например, в результате stack\heap overflow)• Tainted arguments в некоторых функциях (например, форматная строка
в printf или строка команды в system) говорят о возможной уязвимости• Tainted resources (например, хэндлеры, мьютексы и пр., которые не зависят
напрямую от пользовательского ввода) говорят о возможной ошибке в программе
Недостатки:• По своей природе требует детального анализа каждой исполняемой
инструкции, что может быть очень тяжело для набора x86• Идеальный taint analysis должен отслеживать и инструментировать весь код,
исполняемый операционной системой (как в режиме пользователя, так и в режиме ядра) Чревато низкой производительностью анализа
ЗаголовокTaint analysis: ссылки
• All You Ever Wanted to Know About Dynamic Taint Analysis and Forward Symbolic Execution (but might have been afraid to ask) E. Schwartz, T. Avgerinos, D. Brumley
• Dynamic taint analysis: Automatic detection, analysis, and signature generation of exploit attacks on commodity software (J. Newsome , D. Song , J. Newsome, D. Song)
• Program slicing (M. Weiser)
ЗаголовокКомбинированный анализ – concolic execution
Concrete + symbolic = concolic:• Для некоторых символьных переменных используются их «конкретные» значения
при символьном исполнении
Применение:• На контрольных точках создаём снимок всего процесса• Инструментируем конкретную трассу: делаем taint-analysis и одновременно набираем
очередь символьных условий (constraints) для каждой инструкции перехода на пути• После завершения анализа текущей трассы откатываем процесс к контрольной точке,
выбираем символьное условие из очереди, решаем для него SMT-задачу, полученное решение (регистры и участки памяти) подставляем в память и контекст процесса
• Инструментируем новую трассу
Concolic execution – метод, применяемый для покрытия максимального количества кода
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
Теперь воспользуемся техникой concolic execution и найдем значения x и y, при которых исполнение попадет в ERROR
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
Предположим, что функция read вернула «конкретные» значения X=22Y=7
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=22Y=7Делаем снимок процесса в точке входа в функцию test
Value constraints:X->x0Y->y0
Path constraints:True
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=22Y=7Z=14
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:True
Исполняем вызов функции
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=22Y=7Z=14
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:X0 != 2*y0Заталкиваем X0 == 2*y0 в пул собранных условий
«Конкретное» исполнение пойдет по ветке else
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=2Y=1Z=2Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2*y0
Символьное исполнение вычислит новые x и y, чтобы пойти по ветке true, и «конкретное» исполнение будет перезапущенос точки входа в test
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=2Y=1Z=2Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2*y0 ^ x0 <= y0 + 10
Однако «конкретное» исполнение опять не дойдёт до error
Заголовок
int twice(int v) { return 2 * v; } void test(int x, int y) { z = twice(y); if (x == z) { if (x > y + 10) ERROR; } } int main() { x = read(); y = read(); test(x,y); }
Комбинированный анализ: пример
«Конкретные» значения: X=30Y=15Z=30
Value constraints:X->x0Y->y0Z->2*y0
Path constraints:x0 = 2*y0 ^ x0 > y0 + 10
Символьное исполнение вычислит новые значения x и y для нужных path constraints,откатимся к снимку и подменим x и y, исходяиз новых условий.
ЗаголовокСуществующие инструменты (Open Source)
KLEE• Базируется на llvm IR• Использует символьное исполнение• Автоматическая генерация тестов
(максимальное покрытие исходного кода)• Имеет несколько стратегий выбора состояний
в процессе symbolic executionKLEE используется в S2E – платформе для анализа исполнения приложений в «реальной» среде
ЗаголовокСуществующие инструменты (Open Source)
Triton • Реализует схему concolic execution• Переводит инструкции непосредственно в выражения solver’а
(Z3), минуя внутреннее представлениеДругие: FuzzBall, BitBlaze, Avalanche и прочие• Как правило, нет инструментов надлежащего продуктового
качества• Каждый инструмент «заточен» под решение некоторой своей
специфичной задачи
ЗаголовокСуществующие инструменты (Closed Source)
MAYHEM • Создан для поиска и автоматической генерации exploit’ов • Есть продвижение в работе с символьными адресами• Победитель конкурса DARPA в 2016
CodeSurfer, VeraCode• Платные инструменты бинарного анализа• Очень мало информации о деталях их работы
ЗаголовокОбщий вывод
• Методики бинарного анализа все еще нуждаются в глубоких исследованиях
• В данный момент не существует универсального инструмента бинарного анализа
• Каждый инструмент решает какую-либо конкретную задачу, обходя известные ограничения за счет качества анализа
• Positive Technologies работает над своим инструментом – STAY TUNED!
ЗаголовокBackup
ЗаголовокСимвольное исполнение: общая схема
Транслятор в IR
Ассемблерная инструкция
Набор инструкций IR
Пул состояний (по одному для каждого пути исполнения)
State №1
State №2
State №…
State №500
Searcher – выбирает состояние из пула.Возможные стратегии выбора:• DPS
BPS• Random choice• Best coverage state
Каждое состояние хранит следующие данные:• Текущий IP (instruction pointer)• Символьный контекст (регистры, ячейки
памяти, символьные ресурсы)• Path constraints
Executor (director) – занимается обработкой конкретного состояния
mov eax, ecx ___________________
STR R_ECX:32, , V_00:32 STR V_00:32, , R_EAX:32
Интерпретатор – содержит
обработчики для каждой
инструкции IR
Трансляция
Логическая или арифметическая микроинструкция:xor, and, or, bvadd, bvsub и пр. – изменить символьный контекст обрабатываемого состояния
Обработка внешнего вызова: изменить символьный контекст в соответствии с семантикой, приписанной (в DSL) конкретной сторонней функции
База с семантикой внешних функций
Микроинструкции аллокации памяти / работы с памятью: создание новой или изменение существующей символьной ячейки памяти для обрабатываемого состояния
Микроинструкции передачи управления по условию X
Если достигнута контрольная точка программы, проверяем её достижимость: извлекаем path constraints, решаем SMT-задачу
SMT-Solvers: Z3, STP, Boolector
New state a:Constraints += X
New state b:Constraints += ~X
Добавляем новые состояния в пул
Заголовок
ptsecurity.com
Спасибо!Спасибо!