Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Обратная разработка бинарных форматов с помощью Kaitai Struct

1,314 views

Published on

Ведущий: Михаил Якшин

В докладе будут рассмотрены современные подходы к обратной разработке бинарных файлов: с чего начинают, что хотят получить на выходе, какими инструментами традиционно пользуются. Будет продемонстрирован новый проект— Kaitai Struct, представляющий собой инструментарий для декларативного описания бинарных структур данных с выводом результата в виде готовых библиотек на языках C++, Java, JavaScript, Python и Ruby. Несколько практических примеров использования обратной разработки помогут участникам лучше ознакомиться с проблематикой.

Published in: Technology
  • Be the first to comment

Обратная разработка бинарных форматов с помощью Kaitai Struct

  1. 1. PHDays 2016 Обратная разработка бинарных форматов с помощью Kaitai Struct Михаил Якшин Whitebox Labs / Kaitai Project
  2. 2. Пара слов о нас
  3. 3. Кто мы такие и чем занимаемся ● Контроллеры для – аквариумов, террариумов, гидропоники – контроля производства вина, пива, сыра – вообще любых замкнутных экосфер
  4. 4. Чем управляет контроллер? ● Датчики – уровня воды – протечки – температуры – качества воды: pH, растворенный кислород, проводимость, ORP, соленость... – потока – влажности – освещенности – камеры – уровня аккумуляторов ● Актуаторы – помпы – дозаторы – источники света, диммеры – охлаждение / нагрев – автоматические кормушки – скиммеры – реакторы – реле (произвольная нагрузка) – управление электропитанием
  5. 5. Проблема №1: vendor lock-in Контроллер Neptune Устройство GHL Устройство Neptune
  6. 6. Наше решение ● Производим собственный контроллер ● Не производим собственные датчики и актуаторы ● Reverse engineering протоколов взаимодействия существующих устройств сторонних производителей ● Реализация протокола в нашем контроллере
  7. 7. Reverse engineering бинарных структур данных
  8. 8. Объект исследования ● Коммуникационные протоколы ● Embedded firmware ● Контейнерные форматы файлов ● Исполняемые файлы (executables, objects, байт-код, ...) ● Файлы контента (базы данных, таблицы, тексты, графика, текстуры, ...)
  9. 9. Два основных подхода ● Исследовать код, работающий с данными ● Обычно не «clean room» ● Дизассемблеры, декомпиляторы, отладчики, ... ● Исследовать сами данные ● Гарантированный «clean room» ● Иногда единственно возможный вариант ● ... ?
  10. 10. Что на входе? ● просто файлы ● файловые системы / диски / массивы целиком ● содержимое ROM / Flash ● перехваченный трафик
  11. 11. Что на выходе? Описание формата ● никто на самом деле не знает, что это такое ● формального стандарта нет — ни де юре, ни де факто ● пытаются использовать – C-подобные struct – (E)BNF – таблицы / диаграммы – ASCII art
  12. 12. Описание формата в RFC 2083 RFC 2083 PNG: Portable Network Graphics March 1997 3. File Structure A PNG file consists of a PNG signature followed by a series of chunks. This chapter defines the signature and the basic properties of chunks. Individual chunk types are discussed in the next chapter. 3.1. PNG file signature The first eight bytes of a PNG file always contain the following (decimal) values: 137 80 78 71 13 10 26 10 This signature indicates that the remainder of the file contains a single PNG image, consisting of a series of chunks beginning with an IHDR chunk and ending with an IEND chunk. See Rationale: PNG file signature (Section 12.11). 3.2. Chunk layout Each chunk consists of four parts: Length A 4-byte unsigned integer giving the number of bytes in the chunk's data field. The length counts only the data field, not itself, the chunk type code, or the CRC. Zero is a valid length. Although encoders and decoders should treat the length as unsigned, its value must not exceed (2^31)-1 bytes. Chunk Type A 4-byte chunk type code. For convenience in description and in examining PNG files, type codes are restricted to consist of uppercase and lowercase ASCII letters (A-Z and a-z, or 65-90 and 97-122 decimal). However, encoders and decoders must treat the codes as fixed binary values, not character strings. For example, it would not be correct to represent the type code IDAT by the EBCDIC equivalents of those letters. Additional
  13. 13. RFC 768 J. Postel ISI 28 August 1980 User Datagram Protocol ---------------------- Introduction ------------ This User Datagram Protocol (UDP) is defined to make available a datagram mode of packet-switched computer communication in the environment of an interconnected set of computer networks. This protocol assumes that the Internet Protocol (IP) [1] is used as the underlying protocol. This protocol provides a procedure for application programs to send messages to other programs with a minimum of protocol mechanism. The protocol is transaction oriented, and delivery and duplicate protection are not guaranteed. Applications requiring ordered reliable delivery of streams of data should use the Transmission Control Protocol (TCP) [2]. Format ------ 0 7 8 15 16 23 24 31 +--------+--------+--------+--------+ | Source | Destination | | Port | Port | +--------+--------+--------+--------+ | | | | Length | Checksum | +--------+--------+--------+--------+ | | data octets ... +---------------- ... User Datagram Header Format Описание формата в RFC 768
  14. 14. Описание формата ELF ELF Header Some object f le control structures can grow, because the ELF header contains their actual sizes.  If the  object f le format changes, a program may encounter control structures that are larger or smaller than  expected.  Programs might therefore ignore ‘‘extra’’ information.  The treatment of ‘‘missing’’ informa­  tion depends on context and will be specif ed when and if extensions are def ned. Figure 1­3:  ELF Header #define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Ehdr; e_ident The initial bytes mark the f le as an object f le and provide machine­independent data  with which to decode and interpret the f le’s contents.  Complete descriptions appear  below, in ‘‘ELF Identif cation.’’ e_type This member identif es the object f le type.  Name  Value  Meaning ET_NONE 0 No f le type 
  15. 15. Описание формата как арт :)
  16. 16. Что на выходе? Парсер / декодер ● Программный код, который разбирает формат ● файл → память https://github.com/omf2097/libShadowDive/ src/bkanim.c:58
  17. 17. Что на выходе? Генератор / энкодер ● Программный код, который собирает формат ● память → файл https://github.com/omf2097/libShadowDive/ src/bkanim.c:99
  18. 18. Традиционный подход к clean room RE 1.Долго медитировать над дампом 2.Высказать гипотезу 3.Написать код парсера, выводящий расшифрованное на stdout ● обнаружить, что любимый язык имеет неидеальные средства чтения из потока и написать собственную библиотеку 4.Скомпилировать, запустить 5.Проверить, подтвердилась ли гипотеза 6.GOTO 1 или 2, если не подтвердилась
  19. 19. Hex viewers / editors: Hiew
  20. 20. Пример: Doom WAD ● Один из самых простых форматов-контейнеров ● Применялся в играх на id Tech 1 engine – Doom, Doom 2, Heretic, Hexen, Strife, ... ● Активная разработка оригинальными разработчиками в 1992-1995 ● Масса заимствованного кода из более ранних работ id software – Commander Keen, Wolfenstein 3D
  21. 21. Схема Doom WAD Смещение Содержимое 0 magic Поле всегда «IWAD» или «PWAD» 4 index_qty число элементов индекса 8 index_ofs смещение начала индекса char[4] Тип int32 int32 index_ofs + 0 file_ofs смещение начала файлаint32 + 4 file_len длина файлаint32 + 8 file_name имя файла, 0-paddedchar[8] ... повторяется index_qty раз ... file_ofs содержимое файлаchar[file_len] Все числа в little-endian — основная платформа — DOS/Intel.
  22. 22. Hex viewers / editors с шаблонами ● SweetScape 010 Editor – http://www.sweetscape.com/010editor/ ● Synalyze It! – http://www.synalysis.net/ ● Hexinator – https://hexinator.com/ ● BreakPoint Software's Hex Workshop – http://www.hexworkshop.com/ ● iBored – http://apps.tempel.org/iBored/ ● KDE Okteta – https://utils.kde.org/projects/okteta/ ● A.X.E., Advanced Hex Editor
  23. 23. SweetScape 101 Editor
  24. 24. SweetScape 101 Editor: template
  25. 25. Hexinator: просмотр дерева
  26. 26. Hexinator: редактор дерева
  27. 27. Hexinator: grammar
  28. 28. Hex editors с шаблонами Что хорошего? ● Показывают дерево объектов и их значения – В некоторых есть визуальный редактор дерева ● Показывают, какие места в бинарном потоке занимают те или иные объекты
  29. 29. Hex editors с шаблонами Что плохого? ● Императивность – 010 Editor: в основе — 100% императивный язык – Synalyze It, Hexinator, Okteta: декларативный язык, но как только его не хватает, расширяется императивными скриптами на Python / JavaScript ● Упор на отображение: в файле разметки задаются цвета, комментарии и т.п. ● Плохо автоматизируется – например, сложно прогнать один и тот же шаблон на многих однотипных файлах и построить статистику, что и как часто встречается в поле ● Зачитывают файл и размечают целиком в памяти – попытка разобрать файловую систему 5 GB DVD съедает 16 GB RAM и уходит в swap ● Закрытое, проприетарное ПО* → невозможно влиять на спецификацию языка и участвовать в разработке * кроме Okteta
  30. 30. Wireshark
  31. 31. Wireshark dissectors: факты ● Популярнейший пакет для захвата и анализа сетевого трафика ● ~646 диссекторов в комплекте ● Диссекторы реализуются: – императивно на C или Lua – декларативно для протоколов, базирующихся на ASN.1 или X11
  32. 32. Wireshark dissectors Что плохого? ● Императивность ● Значительная сложность реализации – стало чуть лучше с поддержкой Lua, но все равно hello world ~300-400 строк ● Решают только одну узкую задачу: раскладывают пакет в памяти в специально подготовленное дерево; сложно пользоваться результатом программно
  33. 33. BMS: факты ● AKA MexScript, AKA Binary Mex Script, AKA MultiEx Script ● семейство специальных скриптовых языков, предназначенный для извлечения данных из бинарных файлов ● популярен в сообществе, занимающихся reverse engineering игр ● реализации в виде – MultiEx Commander — http://multiex.xentax.com/ – QuickBMS — http://aluigi.altervista.org/quickbms.htm – ...
  34. 34. BMS: пример для WAD-файлов
  35. 35. BMS: что хорошего? ● свободный / бесплатный ● приличная база накопленных описаний форматов (разной степени проработки) ● большое множество встроенных алгоритмов – декомпрессия (~150 алгоритмов) – расшифровка (~80 алгоритмов) – хэширование и контрольные суммы – генерация псевдослучайных чисел (~20 методов) ● как ни странно, часто умеет и читать, и писать файлы- контейнеры
  36. 36. BMS: что плохого? ● императивный → описывает алгоритм парсинга, а не саму структуру данных – туча дополнительных хаков (см. ImpType и IDString), которые делают формат чуть более декларативным ● решает ровно одну задачу, описанную в скрипте (как правило — извлечение файла из файла- контейнера) ● интерпретатор ● проблемы с поддержкой 64 бит
  37. 37. BinPAC → BinPAC++ → HILTI ● система декларативного описания родом из Bro Network Security Monitor ● компилируется в C++ ● собственный C-подобный язык описания ● расширяется вставками C++
  38. 38. Более академические проекты ● DataScript: http://datascript.sourceforge.net/ – последний коммит в 2003, C only ● Tupni – публикация MS Research в 2008 ● PADS: http://padsproj.org/ – публикация AT&T Research в 2005, C only ● Discoverer – публикация MS Research / Berkeley в 2007
  39. 39. DFDL (Data Format Description Language) ● длинная история – начат в Open Grid Forum, 2003 – спецификация 1.0 опубликована в 2011 ● раздельное описание схемы данных и схемы сериализации ● решает куда более общие задачи: парсинг текстовых данных, парсинг в контексте и т.д. ● инструментарий: – визуализатор/редактор от IBM на основе Eclipse – Daffodil — генератор парсеров (на самом деле интерпретатор) для Java/Scala
  40. 40. DFDL: пример
  41. 41. Honorable mentions ● Preon — https://github.com/preon/preon – декларативное описание бинарных структур в виде аннотаций к Java-классам ● Hachoir: https://bitbucket.org/haypo/hachoir – императивное описание, только Python ● DADL: http://ops4j.github.io/dadl/0.1.0/ – попытка переосмыслить/упростить DFDL, Java-only ● jBinary: https://github.com/jDataView/jBinary – декларативный JSON-формат со вставками JS-кода, JavaScript-only ● Packet: https://github.com/bigeasy/packet – декларативный язык, интерпретатор, node.js-only, весьма ограниченные возможности ● NetZob: https://www.netzob.org/ – статистический полуавтоматический анализатор протоколов с моделью состояния – совершенно альтернативный подход и решаемая задача (фуззинг) ● + еще пара десятков проектов
  42. 42. Kaitai Struct: знакомимся?
  43. 43. Kaitai Struct: основные тезисы ● Декларативный язык описания структур бинарных данных ● Описание (в формате .ksy) компилируется в исходный код на целевом языке – C++, Java, JavaScript, Python, Ruby ● Визуализатор для быстрого прототипирования форматов и проверок гипотез при reverse engineering ● .ksy = YAML → легко писать альтернативные компиляторы/инструменты
  44. 44. Quick start: Doom WAD в формате Kaitai Struct Смещение Содержимое 0 magic Поле всегда «IWAD» или «PWAD» 4 index_qty число элементов индекса 8 index_ofs смещение начала индекса char[4] Тип int32 int32 index_ofs + 0 file_ofs смещение начала файлаint32 + 4 file_len длина файлаint32 + 8 file_name имя файла, 0-paddedchar[8] ... повторяется index_qty раз ... file_ofs содержимое файлаchar[file_len]
  45. 45. Kaitai Struct: метаинфорация ● Класс верхнего уровня будет называться DoomWad (или doom_wad, если в языке принят lower_underscore_case) ● Мы разбираем формат, пришедший с платформы Intel → по умолчанию читаем числа в little-endian, чтобы не повторять это везде ● application — комментарий, указывающий, какое ПО использует этот формат
  46. 46. Kaitai Struct: последовательность ● последовательность описаний атрибутов, идущих подряд ● все атрибуты будут прочитаны при создании объекта* в указанной последовательности ● начало каждого следующего атрибута = конец предыдущего** * если не включен --debug ** если нет указаний alignment
  47. 47. Kaitai Struct: последовательность ● magic: – строка, 4 байта, ASCII-кодировка ● index_qty – signed int, 4 байта ● index_offset – signed int, 4 байта
  48. 48. Kaitai Struct: описание атрибута ● id — идентификатор, будет записан в коде по правилам целевого языка, т.е. foo_bar станет – fooBar в Java – FooBar в C# – foo_bar в Ruby ● type — указание на тип; может – быть примитивным (=встроенным) – быть пользовательским – отсутствовать (=просто массив байт)
  49. 49. Kaitai Struct: примитивные типы ● Целочисленные ● Строки
  50. 50. Kaitai Struct: целочисленные типы u4le ● u = unsigned ● s = signed 4 байта (можно 1, 2, 4, 8) ● le = little-endian ● be = big-endian
  51. 51. Kaitai Struct: строки ● наперед известной длины (в байтах или символах*) – очень часто — прямо перед строкой идет ее длина в байтах ● до терминатора – очень часто — до «0» = C strings – редко — до «$» = DOS strings – совсем редко — все остальное ● до конца потока (файла, текущей подструктуры, ...) * за полтора года на практике ни разу не встретилось — поэтому пока так и не реализовали
  52. 52. Kaitai Struct: строки ● наперед известной длины – type: str – size: 42 ● до терминатора – type: strz – terminator: 0xa ● до конца потока – type: str – size-eos: true
  53. 53. Kaitai Struct: instances ● те же описания атрибутов, но не идущие подряд в общем потоке ● id вынесен в ключ map ● полезно, для того, чтобы – указать откуда читать – не зачитывать элемент безусловно в общем потоке, а прочитать при необходимости – объявить не существующий явно (вычислимый) атрибут
  54. 54. Kaitai Struct: повторы и условия у атрибутов ● repeat: expr repeat-expr: выражение – сформировать массив из элементов заданного типа – зачитать столько элементов, сколько указано в «выражении» ● repeat: eos – сформировать массив из элементов заданного типа – зачитать столько элементов, сколько возможно, пока не встретится конец потока – может быть зачитано от 0 до ∞ атрибутов ● if: выражение – атрибут зачитывается только если «выражение» истинно – всего может быть зачитан либо 0, либо 1 атрибут
  55. 55. Kaitai Struct: выражения ● Универсальный «язык», автоматически транслируется в целевой язык с соблюдением типов ● Синтаксис похож на среднестатический C- подобный язык с современными объектными расширениями ● Inspired by Ruby, Scala, JavaScript, Python, C# ● Чем-то похоже на haXe, но гораздо проще
  56. 56. Kaitai Struct: где могут использоваться выражения ● size — длина атрибута в байтах ● repeat-expr — число повторений атрибута в массиве ● if — условие чтения атрибута ● pos — смещение атрибута от начала потока ● io — поток, из которого читать ● value — вычислить значение атрибута по выражению
  57. 57. Kaitai Struct: выражения: примеры (1) ● repeat-expr: 5 – повторить атрибут 5 раз – во все целевые языки транслируется «5» ● repeat-expr: foo_bar – повторить атрибут столько раз, сколько указано в целочисленном атрибуте foo_bar внутри текущего типа (класса) – C++/STL: foo_bar() – Java: fooBar() – JavaScript: this.fooBar – Python: self.foo_bar – Ruby: foo_bar
  58. 58. Kaitai Struct: выражения: примеры (2) ● if: foo.bar == "T4" – взять атрибут foo (в текущем типе), обратиться к атрибуту bar в нем, сравнить полученное значение со строкой «T4»; прочитать текущий атрибут, если они равны – C++/STL: foo()->bar() == "T4" – Java: foo().bar().equals("T4") – JavaScript: this.foo.bar == "T4" – Python: self.foo.bar == "T4" – Ruby: foo.bar == "T4"
  59. 59. Kaitai Struct: выражения: примеры (3) ● size: '(foo.last == -1) ? 3 : 1' – атрибут foo (в текущем типе) — целочисленный массив; сравнить последний его элемент с -1; если равно, то зачитать атрибут размером 3 байта, если не равно, то 1 байт. – C++/STL: (foo().back() == -1) ? 3 : 1 – Java: (foo().get(foo.size() - 1) == -1) ? 3 : 1 – JavaScript: (this.foo[this.foo.length - 1] == -1) ? 3 : 1 – Python: 3 if foo[-1] == -1 else 1 – Ruby: (foo.last == -1) ? 3 : 1
  60. 60. Kaitai Struct: пользовательские типы ● Точно такое же объявление, как в корне дерева: те же seq, instances, types... ● Реализуется именованными подклассами (как правило, это будет что-то вроде DoomWad::IndexEntry) ● Указание io: _root._io используется для того, чтобы задать положение contents от начала WAD-файла (т.е. используя поток корневого элемента)
  61. 61. Doom WAD в Kaitai Struct: всё вместе
  62. 62. Компилируем ● компилируем: $ ksc -t python doom_wad.ksy ● создался doom_wad.py ● можно подключать и использовать
  63. 63. Используем
  64. 64. ... или смотрим в визуализаторе
  65. 65. Итого: простые возможности ● Парсить структуры: – фиксированного формата (как C struct) – с длинами полей, зависящими от других атрибутов – с условными полями – с повторениями полей ● Где угодно: – от начала потока – с произвольно заданным смещением – в своем потоке – в чужом потоке
  66. 66. Kaitai Struct: продвинутые возможности
  67. 67. Сгенерированный код ● Типы KS → классы / структуры – вложенные типы → вложенные классы ● Атрибуты KS – из seq → ● выражения парсинга в конструкторе класса ● тривиальные геттеры ● объявления членов класса – из instances → ● выражения парсинга в отдельных методах/функциях, кэширующие результаты парсинга ● они же геттеры ● объявления членов класса ● Умеренное количество syntactic sugar – SomeClass.fromFile(...) – properties, если нужно
  68. 68. Сгенерированный код: общий вид
  69. 69. Сгенерированный код: один instance
  70. 70. Instances без парсинга: вычислимые instances ● Полезно, если нужно результат одного и того же (особенно сложного) значения использовать много раз в нескольких местах
  71. 71. Enums ● Поставить в соответствие целочисленным константам некие строки ● Реализуется, насколько возможно в целевом языке: – C++: enum – Java: Enum + Map – JavaScript: frozen object справочник констант – Python: Enum – Ruby: symbols + Hash ● Можно обращаться из выражений самого KS
  72. 72. Process ● Что делать, если файл / секция зашифрована / обфусцирована / сжата? ● Для преобразований над произвольным байтовым буфером используется операция process ● Может быть использована – без типа – с пользовательским типом ● Требуется явное указание размера
  73. 73. Process ● Сейчас реализованы: – xor – ror, rol – zlib – lzma ● Не густо :( ● Надо брать пример с BMS ● Многие преобразования можно сделать с помощью языка выражений – например, преобразовать байты 31 32 42 43, составляющие строку «12BC» в число 0x12bc.
  74. 74. Работа на этапе неполной информации о структуре ● Непонятно (пока) назначение структуры, но известна длина — пропустить, не назначая тип
  75. 75. Работа на этапе неполной информации о структуре ● По мере накопления информации о структуре можно начать ее детализировать, как отдельный тип ● Внутри baz будет создан отдельный IO поток: не обязательно суммарный размер полей в seq должен соответствовать объявленному size – если полей будет меньше — это нормально; остаток будет пропущен – если полей будет больше, чем на 0x83 байта, это приведет к ошибке end-of- stream (EOF), а не к съехавшей структуре ● Вложенность сразу будет правильно видна в визуализаторе
  76. 76. Типичный разбор опкодов виртуальной машины ● Массив ops верхнего уровня зачитывает все побайтово ● Справочник опкодов и их мнемоник — в enum ● Тип op считывает опкод и дополнительные аргументы через типы op_* ● Позволяет делать гипотезы дизассемблирования при небольшом количестве известных опкодов
  77. 77. Спасибо за внимание! Web: http://kaitai.io/ Twitter: @kaitai_io GitHub: http://github.com/kaitai_io/
  78. 78. Планы на обозримое будущее ● Сделать поддержку атрибутов на уровне битов ● Доделать поддержку популярных языков – C++ с различными библиотеками (STL, Qt, ...?) – C – C# – Swift – PHP ● Доделать на сайте онлайн-компилятор (средствами JavaScript)
  79. 79. Планы на далекое будущее ● Накопить библиотеку описаний широко распространенных бинарных форматов – файловые системы – архивы – базы данных – файлы с изображениями, звуком и видео – исполнимые файлы

×