• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Тренируемся анализировать бинарные файлы на примере libexif

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
Когда я только начал вливаться в мир хакерства, мне дико не хватало информации, ее приходилось собирать по крупицам. В этом материале я постарался изложить то, чего мне тогда не доставало, — всю информацию по реверсу в сжатом и систематизированном виде. А для наглядности мы с тобой разберем боевую задачу — реверс программы libexif.

Эта статья нацелена на людей, которые постепенно вырастают из тасков на CTF категории PWN и Reverse, хотят пощупать реальные программы и понять, что такое настоящий анализ бинаря. Ресерчи, которые пишут специалисты известных компаний, хороши, но большинство новичков не знают, с чего начинать делать такое исследование, как тренироваться и как заполучить стенд. Сегодня мы с тобой это исправим.

ПРИМЕРНЫЙ ПОРЯДОК ДЕЙСТВИЙ ДЛЯ БИНАРНОГО РЕСЕРЧА​

Давай разложим по пунктам все действия, которые ресерчер может производить с анализируемым бинарным файлом.
  1. Сбор информации о таргете:
    • что за бинарь, для чего он предназначен, для какой платформы, есть ли исходный код в открытом доступе, или это «черный ящик»;
    • как собирать бинарь — есть ли setup.sh или install.sh, может, нужно просто скомпилить при помощи GCC;
    • как им пользоваться: посмотреть возможности программы, что она делает и как с ней взаимодействует пользователь.
  2. Знакомство с бинарем— применение средств первичного анализа. Вот самые ходовые тулзы:
    • file — системная утилита, которая позволяет узнать тип данных внутри файла;
    • checksec — утилита, которая проверяет первичные средства безопасности в бинаре;
    • xxd — отображает файл в виде шестнадцатеричных кодов или выполняет обратное преобразование;
    • strings — утилита, применяемая для поиска печатаемых строк в двоичных файлах;
    • VirusTotal — бесплатная служба, анализирующая подозрительные файлы и ссылки на предмет наличия вирусов, червей, троянов и всевозможных вредоносных программ;
    • DIE — утилита для определения типов файлов. Кто читал книгу «Вскрытие покажет! Практический анализ вредоносного ПО», тот должен знать утилиту.
  3. Фаззинг программы(если это требуется). Это техника поиска потенциальных уязвимостей в автоматическом или полуавтоматическом режиме. Здесь нам помогут:
    • AFL++ — собирает информацию о покрытии для каждого измененного входного значения и помогает обнаружить новые пути выполнения и потенциальные баги;
    • LibFuzzer — фреймворк для обнаружения ошибок в коде, созданный в основном для тестирования программ на C/C++, но поддерживает и другие языки;
    • самописный фаззер — этот метод используется, когда программа специфичная или использовать известные фаззеры не имеет смысла или нет возможности. Пример можешь посмотреть в посте на codeby.net;
    • ручной фаззинг — по сути, просто тыкание программы, как в тасках на CTF. Ресерчер просто подает случайные данные программе вручную.
  4. Реверс. Этот шаг важен, и думаю, что пояснения тут не нужны. Информации в интернете полно, смотри, например, подборку статей «Реверс малвари». Здесь же перечислю основные утилиты:
    • IDA Pro — это самая известная программа для дизассемблирования, в представлении она не нуждается. Как ей пользоваться, можно почитать, например, в книге Рикардо Нарвахи The IDA Pro Book (доступен любительский перевод);
    • ImmunityDBG — отладчик программ для ОС Windows. Скачать можно на официальном сайте;
    • EDB Debugger — кросс‑платформенный отладчик для AArch32/x86/x86-64. В данный момент официально доступен только на Linux, но уже готовятся порты для FreeBSD, OpenBSD, macOS и Windows;
    • GDB Debugger — переносимый отладчик проекта GNU, работает на многих UNIX-подобных системах и помогает отлаживать многие языки программирования, включая C/C++, Free Pascal, FreeBASIC, Ada, фортран и Rust;
    • Ghidra — этот фреймворк для реверса создан в АНБ США и включает в себя набор полнофункциональных высококлассных инструментов анализа программ. Работает на Windows, macOS и Linux. Подробнее о нем можно прочесть в статьях «Ghidra vs IDA Pro» и «Ghidra vs crackme»;
    • radare2 — свободный кросс‑платформенный фреймворк для реверс‑инжиниринга, включает дизассемблер, шестнадцатеричный редактор, анализатор кода и прочие полезности. Используется при реверсе, отладке вредоносного ПО и прошивок. Скачать можно с GitHub;
    • Eclipse-CDT — это программа для реверса, в которой можно посмотреть место падения программы в исходном коде по файлу, который вызывает краш.
  5. Написание эксплоита. Пишем код, который будет эксплуатировать уязвимость. Здесь все зависит от твоих умений и предпочтений, а также от того, что мы эксплуатируем и на какой платформе.

СБОР ИНФОРМАЦИИ О ТАРГЕТЕ И ЗНАКОМСТВО С БИНАРЕМ​

Из названия статьи ты уже знаешь, что будем пывнить libexif версии 0.6.14. Давай познакомимся с нашим таргетом поближе.

Libexif — это библиотека для разбора, редактирования и сохранения данных EXIF, то есть зашитых в некоторые изображения метаданных.

Давай теперь найдем какую‑нибудь программу с этой библиотекой на роль подопытного. Благо на просторах GitHub этого добра сколько угодно. Один из самых простых вариантов — программа exif. Это небольшая утилита командной строки для отображения информации EXIF. Написана она как раз для того, чтобы демонстрировать возможности libexif.

Для начала скачаем и соберем таргет:
Код:
mkdir libexif && cd libexif/
wget https://github.com/libexif/libexif/archive/refs/tags/libexif-0_6_14-release.tar.gz
tar -xzvf libexif-0_6_14-release.tar.gz
Собираем и устанавливаем libexif:
Код:
cd libexif-libexif-0_6_14-release/
sudo apt-get install autopoint libtool gettext libpopt-dev
autoreconf -fvi
./configure --enable-shared=no --prefix="$PWD/fuzzing_libexif/install/"
make
make install
Библиотека готова, теперь скачаем и соберем использующую ее программу:
Код:
cd $PWD/fuzzing_libexif
wget https://github.com/libexif/exif/archive/refs/tags/exif-0_6_15-release.tar.gz
tar -xzvf exif-0_6_15-release.tar.gz
Собираем exif:
Код:
cd exif-exif-0_6_15-release/
autoreconf -fvi
./configure --enable-shared=no --prefix="$PWD/fuzzing_libexif/install/" PKG_CONFIG_PATH=$PWD/fuzzing_libexif/install/lib/pkgconfig
make
make install
После сборки можно запускать программу.
exif.png

Давай попробуем и посмотрим, как программа работает. Из описания понятно, что на вход нужно подать картинку, поэтому скачаем из того же репозитория примеры картинок.
Код:
mkdir in; mkdir out; cd in
wget https://github.com/ianare/exif-samples/archive/refs/heads/master.zip
unzip master.zip
Команда mkdir in; mkdir out; cd in создаст две директории: /in и /out, а затем перейдет в директорию /in. Эти папки потом понадобятся нам при фаззинге.

Запускаем программу:
Код:
./exif Canon_400.jpg
И видим результат ее работы.
exif-pow.png

Отлично! С работой программы разобрались.

ФАЗЗИНГ​

Теперь настало время фаззинга. Он поможет нам напасть на след потенциальных багов. Для фаззинга у нас есть тесткейсы и наш скромный опыт общения с программой. В качестве средства фаззинга я буду использовать AFL++. Давай установим его.

Установка AFL++​

Ставим зависимости:
Код:
sudo apt-get update
sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools
sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang
sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev
Качаем AFL++ из официального репозитория и устанавливаем:
Код:
cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
export LLVM_CONFIG="llvm-config-11"
make distrib
sudo make install
После сборки можно запустить бинарь afl-fuzz и посмотреть краткую справку.
afl-fuzz.png

Где почитать об AFL++​

Об использовании AFL++ советую эти статьи:
У AFL++ есть форк WinAFL. По нему рекомендую эти статьи:
Запускаем фаззер:
Код:
afl-fuzz -i /path/to/testcases -o /path/to/crashes /path/to/binary @@
Здесь
  • -i /path/to/testcases указывает на путь к тесткейсам;
  • -o /path/to/crashes — место, куда будут попадать краши;
  • @@ — этот параметр означает, что на ввод будут поступать данные при запуске программы. Грубо говоря, через argv[], а не через scanf().
После запуска появится окно фаззера.
20240225145321.png

В нашей лаборатории есть продукт, где процесс запуска фаззера автоматизирован вплоть до вывода отчета. Он называется S0NGB1RDZ.

20240225183721.png

Действия все те же самые, как и выше, только в одной утилите. Таким образом, фазер можно запустить в четыре шага. На скриншоте ниже — опции для фаззинга.

20240225184308.png


И вот результат запуска фаззинга в S0NGB1RDZ.

20240225184420.png


Спустя некоторое время в правом верхнем углу можно увидеть найденные краши.
20240225174719.png

Все найденные краши хранятся по пути /your_directory/out/default/crashes.

20240225185558.png

С фаззингом закончили, настало время для реверса!

РЕВЕРС​

Тут есть два пути. Первый — трушный и хардовый: использовать GDB и там отслеживать цепочку вызовов, потом реверсить в IDA Pro. Однако, когда есть исходный код, можно облегчить себе задачу и прибегнуть к Eclipse-CDT. Спойлер: мы дальше будем использовать все эти инструменты.

Eclipse-CDT​

Eclipse-CDT — это среда разработки на C и C++, основанная на платформе Eclipse. Среди ее возможностей — настройка управляемой сборки для разных тулчейнов, стандартная сборка make, навигация по исходному коду, разнообразные инструменты исследования кода, инструменты рефакторинга и генерации кода, а также инструменты для визуальной отладки, включая просмотр памяти, регистров и дизассемблера.

Так как программа написана на Java, необходимо скачать Java SDK:
Код:
sudo apt install default-jdk
Скачаем программу и распакуем ее:
Код:
wget https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2021-03/R/eclipse-cpp-2021-03-R-linux-gtk-x86_64.tar.gz](https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2021-03/R/eclipse-cpp-2021-03-R-linux-gtk-x86_64.tar.gz
tar -xzvf eclipse-cpp-2021-03-R-linux-gtk-x86_64.tar.gz
В итоге распаковки получаем набор файлов, среди которых будет исполняемый файл eclipse. Запускаем его.

20240226201253.png

Нас встречает вот такое, вполне информативное окошко.

20240226201442.png

В меню выбираем File → Import, после этого задаем язык программирования, в нашем случае это C/C++. Выбираем пункт Existing Code as Makefile Project, поскольку у нас есть makefile.

Makefile — это файл, который используется для автоматизации сборки и компилирования программ. Он содержит все необходимые проверки, настройки и команды.
20240226201906.png


20240226201942.png


Далее называем проект, выбираем директорию с программой, в которой присутствует makefile, и указываем компилятор — Linux GCC.

20240226202237.png


Следующий шаг — подготовка параметров отладки. Переходим на вкладку Run → Debug Configurations.

20240226202911.png


Далее выбираем C/C++ Application, а на вкладке Main — нужный проект. На вкладке Arguments вставим путь до нашего краш‑файла.

20240226203415.png


20240226203450.png


Теперь жмем Debug и попадаем в главное окно.

20240226204201.png

В левом списке можно увидеть ошибку Segmentation fault, что означает потенциальное переполнение буфера. Также мы видим места в коде, где была обнаружена эта ошибка.

20240226204330.png


20240226204527.png

Теперь нужно понять, почему возникает ошибка и как это можно проэксплуатировать.

Идем копаться в интернете и узнаём, что эти ошибки потенциально могут быть связаны или с переполнением кучи (CVE-2009-3895), или с обычным отказом в обслуживании, которое толком ни к чему не приводит (CVE-2012-2836). Пока что нельзя с уверенностью сказать, к какой именно CVE относятся эти краши, поэтому продолжим изучение.

Просмотрев потенциально уязвимые места, которые выявила Eclipse-CDT, я обнаружил функцию exif_data_load_data_thumbnail().

20240228214554.png

Однако определить, почему упала программа и что в это время происходило с памятью, в Eclipse-CDT у меня не получилось. Поэтому переходим к GDB.

GDB​

GDB пригодится нам, чтобы определить цепочку вызовов функций.

Загружаем программу в GDB и запускаем с указанием краш‑файла:
Код:
gdb -q exif
run crash.jpeg
gdb-exif.png

Отловить место падения нам поможет команда bt.

20240229151823.png

INFO​

bt — это команда в GDB, которая отображает стек вызовов для текущего потока. Стек вызовов содержит информацию о функциях, вызываемых в текущей точке программы. GDB выведет список, начиная с самой высокоуровневой функции и заканчивая функцией, в которой произошла ошибка.
Программа упала в этой функции, причем внутри этой функции, видимо, происходит копирование чего‑то в кучу. Этот факт подтверждает участок кода, на котором остановился отладчик, а также регистры.

20240229152346.png
20240229152403.png

На самом деле понятно, почему произошел краш. Регистр rsi указывает на пустую ячейку динамической памяти. Так как такого чанка не существует, а программа пытается перенести его в xmm0, возникает ошибка. Вот этот участок кода доказывает нашу догадку:
Код:
0x7ffff7cc498d <__memmove_sse2_unaligned_erms+13> movups xmm0, xmmword ptr [rsi]

IDA Pro​

Подключаем к делу IDA Pro, чтобы понять, что происходило до момента вызова уязвимой функции. Мы можем полностью восстановить цепочку вызовов и определить место падения программы. Давай пройдемся по всем сработавшим функциям:
  • main() загружает файл, а затем вызывает exif_loader_get_data() для генерации ExifData;
  • exif_loader_get_data() запрашивает место для ExifData и затем вызывает exif_data_load_data() для чтения потока байтов из адреса loader->buf длиной loader->bytes_read. Проще говоря, выделяется место для чтения файла;
  • exif_data_load_data() проверяет заголовок, после чего вызывает exif_data_load_data_content();
  • exif_data_load_data_content() считывает количество записей, и для каждой выполняется маршрутизация по тегу. Если тег — EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, то есть 0x0202, то считывается длина миниатюры. Если значение thumbnail_offset уже установлено, то вызывается exif_data_load_data_thumbnail, чтобы разобрать миниатюры;
  • exif_data_load_data_thumbnail() копирует данные в динамическую память и дальше выводит на экран.
Разберем подробнее последнюю функцию.

В ее начале идет проверка размера и смещения, если их сумма больше числа ds, то программа завершается.
20240229155155.png

После этого проверяется память: если она выделена, то очищается.

20240229155422.png

Дальше выделяется новая память и проверяется, выделена ли она.

20240229155550.png

Последний блок, который у меня выделен красным, и есть место, в котором программа падает.

У функции void memcpy(void *dest, const void *src, size_t n) три аргумента:
  • *dst — указатель на область памяти, в которую будут скопированы данные;
  • src — указатель на область памяти, из которой будут скопированы данные;
  • n — количество байтов, которые нужно скопировать.
В контексте исследуемой программы для memcpy() первый аргумент — данные, второй аргумент — смещение, третий аргумент — размер.

Благодаря GDB я выяснил, что регистр rdx хранит в себе размер, регистр rsi — указатель на область памяти, из которой надо копировать, а смещение хранится в регистре rbp. Причем адрес rsi получается путем сложения смещения и начала адреса кучи, в которую был загружен файл. Это подтверждает инструкция lea rsi, [r13+rbp+0] и регистры.

20240229160911.png

В регистре rbp хранится значение 0x00000000FFFFFEC3, которое является переменной offset. В регистре r13 — указатель на кучу 0x000055F902C7C9A6. После сложения получаем значение 0x55fa02c7c869, которое хранится в rsi и указывает в никуда. Собственно, что и требовалось доказать.

Продебажив программу еще несколько раз, я понял, что все эти значения берутся из самого файла, а не вычисляются. Я открыл краш‑файл в HEX-редакторе и по сигнатуре нашел значение для переменной offset 0x00000000FFFFFEC3 и размер 0x8cb.

signature.png

То есть если изменить эти значения и загрузить в программу, то можно попасть в какой‑то другой чанк и перетереть его. Однако, исследовав память, я выяснил, что никуда это не приводит и проэксплуатировать этот баг не получится. Поэтому, к сожалению, до полного роадмэпа взлома мы в этот раз не дотянули.

ВЫВОДЫ​

В этой статье я постарался показать шаги реверсера на реальном кейсе. К великому сожалению, на практике бывает такое, что уязвимость не получается проэксплуатировать и баг приводит только к отказу в обслуживании. Однако в процессе мы научились искать и собирать таргет, фаззить исполняемый файл, пользоваться статическим анализатором кода и исследовать уязвимое место в бинаре. Думаю, время потрачено не зря!

Автор AFANX
Источник xakep.ru
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх