Ведущий: Михаил Якшин
В докладе будут рассмотрены современные подходы к обратной разработке бинарных файлов: с чего начинают, что хотят получить на выходе, какими инструментами традиционно пользуются. Будет продемонстрирован новый проект— Kaitai Struct, представляющий собой инструментарий для декларативного описания бинарных структур данных с выводом результата в виде готовых библиотек на языках C++, Java, JavaScript, Python и Ruby. Несколько практических примеров использования обратной разработки помогут участникам лучше ознакомиться с проблематикой.
3. Кто мы такие и чем занимаемся
● Контроллеры для
– аквариумов, террариумов, гидропоники
– контроля производства вина, пива, сыра
– вообще любых замкнутных экосфер
4. Чем управляет контроллер?
● Датчики
– уровня воды
– протечки
– температуры
– качества воды: pH,
растворенный кислород,
проводимость, ORP, соленость...
– потока
– влажности
– освещенности
– камеры
– уровня аккумуляторов
● Актуаторы
– помпы
– дозаторы
– источники света, диммеры
– охлаждение / нагрев
– автоматические кормушки
– скиммеры
– реакторы
– реле (произвольная нагрузка)
– управление электропитанием
5. Проблема №1: vendor lock-in
Контроллер Neptune
Устройство GHL
Устройство Neptune
6. Наше решение
● Производим собственный контроллер
● Не производим собственные датчики и
актуаторы
● Reverse engineering протоколов
взаимодействия существующих устройств
сторонних производителей
● Реализация протокола в нашем контроллере
8. Объект исследования
● Коммуникационные протоколы
● Embedded firmware
● Контейнерные форматы файлов
● Исполняемые файлы (executables, objects,
байт-код, ...)
● Файлы контента (базы данных, таблицы,
тексты, графика, текстуры, ...)
9. Два основных подхода
● Исследовать код,
работающий с
данными
● Обычно не «clean
room»
● Дизассемблеры,
декомпиляторы,
отладчики, ...
● Исследовать сами
данные
● Гарантированный
«clean room»
● Иногда единственно
возможный вариант
● ... ?
10. Что на входе?
● просто файлы
● файловые системы / диски / массивы
целиком
● содержимое ROM / Flash
● перехваченный трафик
11. Что на выходе?
Описание формата
● никто на самом деле не знает, что это такое
● формального стандарта нет — ни де юре, ни
де факто
● пытаются использовать
– C-подобные struct
– (E)BNF
– таблицы / диаграммы
– ASCII art
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. 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. Описание формата 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 13: 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 machineindependent 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
16. Что на выходе?
Парсер / декодер
● Программный код,
который разбирает
формат
● файл → память
https://github.com/omf2097/libShadowDive/
src/bkanim.c:58
17. Что на выходе?
Генератор / энкодер
● Программный код,
который собирает
формат
● память → файл
https://github.com/omf2097/libShadowDive/
src/bkanim.c:99
18. Традиционный подход
к clean room RE
1.Долго медитировать над дампом
2.Высказать гипотезу
3.Написать код парсера, выводящий расшифрованное
на stdout
● обнаружить, что любимый язык имеет неидеальные средства
чтения из потока и написать собственную библиотеку
4.Скомпилировать, запустить
5.Проверить, подтвердилась ли гипотеза
6.GOTO 1 или 2, если не подтвердилась
20. Пример: Doom WAD
● Один из самых простых
форматов-контейнеров
● Применялся в играх на id Tech 1
engine
– Doom, Doom 2, Heretic, Hexen,
Strife, ...
● Активная разработка
оригинальными
разработчиками в 1992-1995
● Масса заимствованного кода из
более ранних работ id software
– Commander Keen, Wolfenstein 3D
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.
28. Hex editors с шаблонами
Что хорошего?
● Показывают дерево объектов и их значения
– В некоторых есть визуальный редактор дерева
● Показывают, какие места в бинарном потоке
занимают те или иные объекты
29. Hex editors с шаблонами
Что плохого?
● Императивность
– 010 Editor: в основе — 100% императивный язык
– Synalyze It, Hexinator, Okteta: декларативный язык, но как только его не хватает,
расширяется императивными скриптами на Python / JavaScript
● Упор на отображение: в файле разметки задаются цвета, комментарии и т.п.
● Плохо автоматизируется
– например, сложно прогнать один и тот же шаблон на многих однотипных файлах и
построить статистику, что и как часто встречается в поле
● Зачитывают файл и размечают целиком в памяти
– попытка разобрать файловую систему 5 GB DVD съедает 16 GB RAM и уходит в
swap
● Закрытое, проприетарное ПО* → невозможно влиять на спецификацию
языка и участвовать в разработке
* кроме Okteta
31. Wireshark dissectors: факты
● Популярнейший пакет для захвата и анализа
сетевого трафика
● ~646 диссекторов в комплекте
● Диссекторы реализуются:
– императивно на C или Lua
– декларативно для протоколов, базирующихся на
ASN.1 или X11
32. Wireshark dissectors
Что плохого?
● Императивность
● Значительная сложность реализации
– стало чуть лучше с поддержкой Lua, но все
равно hello world ~300-400 строк
● Решают только одну узкую задачу:
раскладывают пакет в памяти в специально
подготовленное дерево; сложно
пользоваться результатом программно
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
– ...
35. BMS: что хорошего?
● свободный / бесплатный
● приличная база накопленных описаний форматов
(разной степени проработки)
● большое множество встроенных алгоритмов
– декомпрессия (~150 алгоритмов)
– расшифровка (~80 алгоритмов)
– хэширование и контрольные суммы
– генерация псевдослучайных чисел (~20 методов)
● как ни странно, часто умеет и читать, и писать файлы-
контейнеры
36. BMS: что плохого?
● императивный → описывает алгоритм
парсинга, а не саму структуру данных
– туча дополнительных хаков (см. ImpType и IDString),
которые делают формат чуть более декларативным
● решает ровно одну задачу, описанную в скрипте
(как правило — извлечение файла из файла-
контейнера)
● интерпретатор
● проблемы с поддержкой 64 бит
37. BinPAC → BinPAC++ → HILTI
● система декларативного описания родом из
Bro Network Security Monitor
● компилируется в C++
● собственный C-подобный язык описания
● расширяется вставками C++
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. DFDL (Data Format Description
Language)
● длинная история
– начат в Open Grid Forum, 2003
– спецификация 1.0 опубликована в 2011
● раздельное описание схемы данных и схемы сериализации
● решает куда более общие задачи: парсинг текстовых
данных, парсинг в контексте и т.д.
● инструментарий:
– визуализатор/редактор от IBM на основе Eclipse
– Daffodil — генератор парсеров (на самом деле интерпретатор)
для Java/Scala
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/
– статистический полуавтоматический анализатор протоколов с моделью состояния
– совершенно альтернативный подход и решаемая задача (фуззинг)
● + еще пара десятков проектов
43. Kaitai Struct: основные тезисы
● Декларативный язык описания структур бинарных
данных
● Описание (в формате .ksy) компилируется в
исходный код на целевом языке
– C++, Java, JavaScript, Python, Ruby
● Визуализатор для быстрого прототипирования
форматов и проверок гипотез при reverse engineering
● .ksy = YAML → легко писать альтернативные
компиляторы/инструменты
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. Kaitai Struct: метаинфорация
● Класс верхнего уровня будет
называться DoomWad (или
doom_wad, если в языке
принят lower_underscore_case)
● Мы разбираем формат,
пришедший с платформы Intel
→ по умолчанию читаем числа
в little-endian, чтобы не
повторять это везде
● application — комментарий,
указывающий, какое ПО
использует этот формат
46. Kaitai Struct: последовательность
● последовательность
описаний атрибутов,
идущих подряд
● все атрибуты будут
прочитаны при создании
объекта* в указанной
последовательности
● начало каждого
следующего атрибута =
конец предыдущего**
* если не включен --debug
** если нет указаний alignment
48. Kaitai Struct: описание атрибута
● id — идентификатор, будет записан в коде по
правилам целевого языка, т.е. foo_bar станет
– fooBar в Java
– FooBar в C#
– foo_bar в Ruby
● type — указание на тип; может
– быть примитивным (=встроенным)
– быть пользовательским
– отсутствовать (=просто массив байт)
50. Kaitai Struct: целочисленные типы
u4le
● u = unsigned
● s = signed
4 байта
(можно 1, 2, 4, 8)
● le = little-endian
● be = big-endian
51. Kaitai Struct: строки
● наперед известной длины (в байтах или символах*)
– очень часто — прямо перед строкой идет ее длина в
байтах
● до терминатора
– очень часто — до «0» = C strings
– редко — до «$» = DOS strings
– совсем редко — все остальное
● до конца потока (файла, текущей подструктуры, ...)
* за полтора года на практике ни разу не встретилось — поэтому пока так и не
реализовали
52. Kaitai Struct: строки
● наперед известной длины
– type: str
– size: 42
● до терминатора
– type: strz
– terminator: 0xa
● до конца потока
– type: str
– size-eos: true
53. Kaitai Struct: instances
● те же описания атрибутов,
но не идущие подряд в
общем потоке
● id вынесен в ключ map
● полезно, для того, чтобы
– указать откуда читать
– не зачитывать элемент
безусловно в общем потоке, а
прочитать при необходимости
– объявить не существующий
явно (вычислимый) атрибут
54. Kaitai Struct: повторы и условия у
атрибутов
● repeat: expr
repeat-expr: выражение
– сформировать массив из элементов заданного типа
– зачитать столько элементов, сколько указано в «выражении»
● repeat: eos
– сформировать массив из элементов заданного типа
– зачитать столько элементов, сколько возможно, пока не встретится конец
потока
– может быть зачитано от 0 до ∞ атрибутов
● if: выражение
– атрибут зачитывается только если «выражение» истинно
– всего может быть зачитан либо 0, либо 1 атрибут
55. Kaitai Struct: выражения
● Универсальный «язык», автоматически
транслируется в целевой язык с соблюдением
типов
● Синтаксис похож на среднестатический C-
подобный язык с современными объектными
расширениями
● Inspired by Ruby, Scala, JavaScript, Python, C#
● Чем-то похоже на haXe, но гораздо проще
56. Kaitai Struct: где могут
использоваться выражения
● size — длина атрибута в байтах
● repeat-expr — число повторений атрибута в
массиве
● if — условие чтения атрибута
● pos — смещение атрибута от начала потока
● io — поток, из которого читать
● value — вычислить значение атрибута по
выражению
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. 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. 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. Kaitai Struct:
пользовательские типы
● Точно такое же объявление, как
в корне дерева: те же seq,
instances, types...
● Реализуется именованными
подклассами (как правило, это
будет что-то вроде
DoomWad::IndexEntry)
● Указание io: _root._io
используется для того, чтобы
задать положение contents от
начала WAD-файла (т.е.
используя поток корневого
элемента)
65. Итого: простые возможности
● Парсить структуры:
– фиксированного формата (как C struct)
– с длинами полей, зависящими от других атрибутов
– с условными полями
– с повторениями полей
● Где угодно:
– от начала потока
– с произвольно заданным смещением
– в своем потоке
– в чужом потоке
67. Сгенерированный код
● Типы KS → классы / структуры
– вложенные типы → вложенные классы
● Атрибуты KS
– из seq →
● выражения парсинга в конструкторе класса
● тривиальные геттеры
● объявления членов класса
– из instances →
● выражения парсинга в отдельных методах/функциях, кэширующие результаты парсинга
● они же геттеры
● объявления членов класса
● Умеренное количество syntactic sugar
– SomeClass.fromFile(...)
– properties, если нужно
70. Instances без парсинга:
вычислимые instances
● Полезно, если нужно результат одного и того
же (особенно сложного) значения
использовать много раз в нескольких местах
71. Enums
● Поставить в соответствие
целочисленным константам
некие строки
● Реализуется, насколько
возможно в целевом языке:
– C++: enum
– Java: Enum + Map
– JavaScript: frozen object справочник
констант
– Python: Enum
– Ruby: symbols + Hash
● Можно обращаться из
выражений самого KS
72. Process
●
Что делать, если файл /
секция зашифрована /
обфусцирована / сжата?
●
Для преобразований над
произвольным байтовым
буфером используется
операция process
● Может быть использована
– без типа
– с пользовательским типом
●
Требуется явное указание
размера
73. Process
● Сейчас реализованы:
– xor
– ror, rol
– zlib
– lzma
● Не густо :(
● Надо брать пример с BMS
● Многие преобразования можно сделать с помощью языка
выражений
– например, преобразовать байты 31 32 42 43, составляющие строку
«12BC» в число 0x12bc.
74. Работа на этапе неполной
информации о структуре
● Непонятно (пока) назначение структуры, но
известна длина — пропустить, не назначая
тип
75. Работа на этапе неполной
информации о структуре
● По мере накопления информации о
структуре можно начать ее
детализировать, как отдельный тип
●
Внутри baz будет создан отдельный
IO поток: не обязательно суммарный
размер полей в seq должен
соответствовать объявленному size
– если полей будет меньше — это
нормально; остаток будет пропущен
– если полей будет больше, чем на 0x83
байта, это приведет к ошибке end-of-
stream (EOF), а не к съехавшей
структуре
●
Вложенность сразу будет правильно
видна в визуализаторе
76. Типичный разбор опкодов
виртуальной машины
● Массив ops верхнего уровня
зачитывает все побайтово
● Справочник опкодов и их
мнемоник — в enum
● Тип op считывает опкод и
дополнительные аргументы
через типы op_*
● Позволяет делать гипотезы
дизассемблирования при
небольшом количестве
известных опкодов
77. Спасибо за внимание!
Web: http://kaitai.io/
Twitter: @kaitai_io
GitHub: http://github.com/kaitai_io/
78. Планы на обозримое будущее
● Сделать поддержку атрибутов на уровне битов
● Доделать поддержку популярных языков
– C++ с различными библиотеками (STL, Qt, ...?)
– C
– C#
– Swift
– PHP
● Доделать на сайте онлайн-компилятор
(средствами JavaScript)
79. Планы на далекое будущее
● Накопить библиотеку описаний широко
распространенных бинарных форматов
– файловые системы
– архивы
– базы данных
– файлы с изображениями, звуком и видео
– исполнимые файлы