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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

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

Avatar for Positive Development User Group

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 Добавляем новые состояния в пул