Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Технологии анализа бинарного кода

Технологии анализа бинарного кода

Доклад Константина Панарина (Positive Technologies), посвященный анализу бинарного кода, на PDUG Meetup: J'adore Hardcore.

Positive Development User Group

September 25, 2017
Tweet

More Decks by Positive Development User Group

Other Decks in Programming

Transcript

  1. Заголовок • Константин Панарин, Positive Technologies, [email protected] • Разработчик группы

    анализа низкоуровневых приложений • Интересы: x86-64 reverse-engineering, C++ Template Metaprogramming #whoami
  2. Заголовок • Цели анализа бинарного кода • Некоторые методики анализа

    • Обзор проблем • Обзор современных средств бинарного анализа Содержание
  3. Заголовок • Поиск ошибок • Поиск уязвимостей • Поиск недекларированных

    возможностей (НДВ) • Восстановление логики работы программы (RE) • Построение тестов Задачи анализа бинарного кода
  4. Заголовок Особенности бинарного анализа • Почти полное отсутствие информации о

    типах* • Гораздо сложнее локализовать различную «метаинформацию» (например, обработчики исключений) • В исполняемых файлах возможно применение обфускации и антиотладочных приёмов, затрудняющих анализ • Высокая семантическая нагрузка отдельных ассемблерных инструкций (особенно на CISC архитектурах) *Классы легко распознаются благодаря наличию виртуальных таблиц, но что делать с элементарными типами?
  5. Заголовок Типы анализа: • Статический анализ • Исполнения программы не

    происходит • Динамический анализ • Анализ по одной трассе исполнения • Комбинированный анализ Технологии, применяемые в анализе: • Символьное исполнение (symbolic execution) • Как правило, используется в статическом анализе • Анализ помеченных данных (taint analysis) • Как правило, применяется при динамическом анализе • Fuzzing • Ожидаемые входные данные подменяются случайными или специально сформированными • И многие другие Методики анализа бинарного кода
  6. Заголовок Типы анализа: • Статический анализ • Исполнения программы не

    происходит • Динамический анализ • Анализ по одной трассе исполнения • Комбинированный анализ Технологии, применяемые в анализе: • Символьное исполнение (symbolic execution) • Как правило, используется в статическом анализе • Анализ помеченных данных (taint analysis) • Как правило, применяется при динамическом анализе • Fuzzing • Ожидаемые входные данные подменяются случайными или специально сформированными Методики анализа бинарного кода На практике инструменты анализа комбинируют в себе различные типы и технологии из-за ограничений, существующих в них. Согласованное применение различных подходов позволяет преодолевать эти ограничения полностью или частично
  7. Заголовок Статический анализ vs динамический анализ Динамический анализ • Наличие

    run-time информации: карты памяти процесса, адресов неявных вызовов и др. • Явное исполнение программы может требовать специфического окружения • Не всегда возможно воспроизвести результаты анализа Статический анализ • Как правило, работает быстрее • Один анализ покрывает потенциально бесконечное число путей исполнения • Работоспособен при отсутствии части исходников / библиотек • Пасует перед обфускацией и шифрованием • Отсутствие информации о неявных вызовах
  8. Заголовок • Основная идея – замена конкретных входных данных программы

    (аргументов функции) на символьные • Символ представляет множество всех возможных значений переменной • Вместо конкретных значений программа будет обрабатывать символьные выражения • Символьное исполнение способно покрывать все возможные пути в программе • Каждый путь – это «состояние» программы, в котором хранятся условия прохождения по этому пути (path constraints) и набор ограничений на значения символьных данных (value constraints) • SMT решатель (solver) – инструмент, определяющий совместность (разрешимость) условий для прохождения по заданному пути Символьное исполнение
  9. Заголовок 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
  10. Заголовок 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->x0 Y->y0 Path constraints: True
  11. Заголовок 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->x0 Y->y0 Z->2*y0 Path constraints: True Символьно исполняем вызов функции
  12. Заголовок 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->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2y0 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: x0 != 2y0 Два различных состояния после условного перехода if (x==z)
  13. Заголовок 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->x0 Y->y0 Z->2*y0 Path constraints: x0 =2y0 ^ x0 > y0+10 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: x0 =2y0 ^ x0 <= y0+10 Исследуем условие x==z
  14. Заголовок 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->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2y0 ^ x0 > y0+10 Условие достижимости ERROR:
  15. Заголовок 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->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2y0 ^ x0 > y0+10 Условие достижимости ERROR: SMT Solver выдает решение: x0 = 40, y0 = 20
  16. Заголовок Символьное исполнение: общая схема Транслятор в 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 – выбирает состояние из пула
  17. Заголовок Symbolic execution: проблемы • Path explosion (как генерировать меньшее

    число состояний?) • Cycle-unrolling (что делать с циклами, условие остановки которых зависит от символьной переменной?) • Symbolic pointers (что делать с операциями load и store, адрес которых тоже символический?) • Constraint difficulty (не все SMT-solver’ы справятся с нахождением решения) • External resources (что делать с файлами, хэндлерами и другими внешними объектами?)
  18. Заголовок Symbolic execution: возможные пути решения проблем • Path explosion

    – мёрджить (объединять) несколько состояний в одно (но как и когда это делать?) • Path explosion – распараллелить обработку различных состояний • Cycle unrolling, symbolic pointers – применять специальные логики, созданные для верифицирования программ (но насколько это эффективно?) • External resources – создать DSL для описания внешних вызовов в терминах executor’а или SMT-solver’а (насколько это быстро и реализуемо?)
  19. Заголовок 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)
  20. Заголовок • Исключительно динамический метод анализа • Связывает трассу исполнения

    программы с данными, которые обрабатывались в ней в процессе этого исполнения • Помогает дать ответ на вопрос о том, как именно программа обрабатывала те или иные входные данные Анализ помеченных данных (taint analysis)
  21. Заголовок mov eax, tainted_input xor eax, eax ; eax is

    UNTAINTED ----------------------------------------- push tainted_input pop 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 _input mov ecx, untainted_input add ecx, eax ; ecx is TAINTED ----------------------------------------- mov eax, tainted_input mov ecx, untainted_input mov ax, cx ; ax is UNTAINTED, eax is TAINTED ----------------------------------------------------------------- Пример взят из http://defcon.org.ua/data/1/4_Oleksyk_Code_Analysis.pdf
  22. Заголовок Taint analysis: общая схема Program code: ________________ push ebp

    mov ebp, esp lea 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 tainted 0x7f2300 - tainted Запись контекста: eax – tainted
  23. Заголовок Taint analysis Чем полезен taint-analysis: • Tainted EIP говорит

    о возможности перехвата управления (например, в результате stack\heap overflow) • Tainted arguments в некоторых функциях (например, форматная строка в printf или строка команды в system) говорят о возможной уязвимости • Tainted resources (например, хэндлеры, мьютексы и пр., которые не зависят напрямую от пользовательского ввода) говорят о возможной ошибке в программе Недостатки: • По своей природе требует детального анализа каждой исполняемой инструкции, что может быть очень тяжело для набора x86 • Идеальный taint analysis должен отслеживать и инструментировать весь код, исполняемый операционной системой (как в режиме пользователя, так и в режиме ядра) Чревато низкой производительностью анализа
  24. Заголовок 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)
  25. Заголовок Комбинированный анализ – concolic execution Concrete + symbolic =

    concolic: • Для некоторых символьных переменных используются их «конкретные» значения при символьном исполнении Применение: • На контрольных точках создаём снимок всего процесса • Инструментируем конкретную трассу: делаем taint-analysis и одновременно набираем очередь символьных условий (constraints) для каждой инструкции перехода на пути • После завершения анализа текущей трассы откатываем процесс к контрольной точке, выбираем символьное условие из очереди, решаем для него SMT-задачу, полученное решение (регистры и участки памяти) подставляем в память и контекст процесса • Инструментируем новую трассу Concolic execution – метод, применяемый для покрытия максимального количества кода
  26. Заголовок 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
  27. Заголовок 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=22 Y=7
  28. Заголовок 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=22 Y=7 Делаем снимок процесса в точке входа в функцию test Value constraints: X->x0 Y->y0 Path constraints: True
  29. Заголовок 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=22 Y=7 Z=14 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: True Исполняем вызов функции
  30. Заголовок 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=22 Y=7 Z=14 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: X0 != 2*y0 Заталкиваем X0 == 2*y0 в пул собранных условий «Конкретное» исполнение пойдет по ветке else
  31. Заголовок 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=2 Y=1 Z=2 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2*y0 Символьное исполнение вычислит новые x и y, чтобы пойти по ветке true, и «конкретное» исполнение будет перезапущено с точки входа в test
  32. Заголовок 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=2 Y=1 Z=2 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2*y0 ^ x0 <= y0 + 10 Однако «конкретное» исполнение опять не дойдёт до error
  33. Заголовок 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=30 Y=15 Z=30 Value constraints: X->x0 Y->y0 Z->2*y0 Path constraints: x0 = 2*y0 ^ x0 > y0 + 10 Символьное исполнение вычислит новые значения x и y для нужных path constraints, откатимся к снимку и подменим x и y, исходя из новых условий.
  34. Заголовок Существующие инструменты (Open Source) KLEE • Базируется на llvm

    IR • Использует символьное исполнение • Автоматическая генерация тестов (максимальное покрытие исходного кода) • Имеет несколько стратегий выбора состояний в процессе symbolic execution KLEE используется в S2E – платформе для анализа исполнения приложений в «реальной» среде
  35. Заголовок Существующие инструменты (Open Source) Triton • Реализует схему concolic

    execution • Переводит инструкции непосредственно в выражения solver’а (Z3), минуя внутреннее представление Другие: FuzzBall, BitBlaze, Avalanche и прочие • Как правило, нет инструментов надлежащего продуктового качества • Каждый инструмент «заточен» под решение некоторой своей специфичной задачи
  36. Заголовок Существующие инструменты (Closed Source) MAYHEM • Создан для поиска

    и автоматической генерации exploit’ов • Есть продвижение в работе с символьными адресами • Победитель конкурса DARPA в 2016 CodeSurfer, VeraCode • Платные инструменты бинарного анализа • Очень мало информации о деталях их работы
  37. Заголовок Общий вывод • Методики бинарного анализа все еще нуждаются

    в глубоких исследованиях • В данный момент не существует универсального инструмента бинарного анализа • Каждый инструмент решает какую-либо конкретную задачу, обходя известные ограничения за счет качества анализа • Positive Technologies работает над своим инструментом – STAY TUNED!
  38. Заголовок Символьное исполнение: общая схема Транслятор в 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 Добавляем новые состояния в пул