Не знаю, как вам, а мне часто приходится сталкиваться с реверсом и отладкой на MIPS. И к сожалению, но достойных инструментов для этого совсем немного, особенно если сравнивать с другими архитектурами. Да чего уж там говорить, если даже в HexRays до сих пор нет декомпиляции MIPS. После того как я не нашел правильного решения, я решил создать свой собственный инструмент, который поможет мне в этом.
MIPS — микропроцессорная архитектура, которая была разработана в середине восьмидесятых сотрудниками компании MIPS Computer Systems в соответствии с концепцией проектирования процессоров RISC (то есть для процессоров с упрощенным набором команд).
В сегодняшней мы поговорим именно об отладке с помощью популярного GDB. Сначала я хотел найти какое-нибудь решение, чтобы каждый раз не вводить тонну магических команд типа x/* $reg, которые помогают понимать то, что происходит с регистрами, стеком, кучей и прочим.
Есть ли готовые решения?
Я, как и все нормальные пользователи, полез в Google в поисках готовых инструментов, которые помогут облегчить мою жизнь. Вот что мне удалось найти.
GDB по умолчанию имеет поддержку исполнения скриптов, через команду source или аргумент командной строки -x. В случае если GDB был скомпилирован с поддержкой Python, появляется также возможность использовать скрипты на этом языке.
PEDA
A точнее — PEDA-MIPS. Скачиваем и запускаем.
Понятно: у меня везде стоит Python 3, который не позволяет одновременного использования табуляции и пробелов в качестве разделителей, а авторы, видимо, не слышали о прекращении поддержки Python 2 в 2020 году. Ну да ладно, исправим это и запустим еще разок.
И вроде бы все хорошо, но хочется большего: например, поддержку IDA Pro или удобный вывод состояния кучи. Разбираться почти в 7000 строк, чтобы добавить новую фичу, не очень-то заманчиво.
РЕКОМЕНДУЕМ:
Взлом приложений для Андроид с помощью отладчика
Плюсы:
+ умеет работать с MIPS;
+ простота реализации.
Минусы:
- 7000 строк кода в одном файле;
- отсутствует расширяемость.
GEF
Скачиваем и начинаем тестировать.
В этот раз хотя бы запустился без ошибок. Но строки здесь не разыменовываются.
Разыменование — термин, применяемый к указателям, позволяет получить значение, записанное в области памяти, на которую ссылается указатель. В случае со строками очень удобно, когда в результате такого чтения отладчик вместо последовательности 0x41414141 показывает строку AAAA.
Лезем в код, чтобы выяснить причину, и, перековыряв около 10 000 строк кода, видим, что GEF настолько хитрый, что не понимает бинари, в которых не определяются секции, а в случае с MIPS это практически каждый второй.
Зато GEF может работать с IDA и даже импортировать структуры! К сожалению, и тут не без проблем…
В начале сообщается, что имя метода состоит из строчных букв, а после мы проверяем его, но уже на наличие заглавных букв: естественно, проверка всегда будет возвращать ложь.
Плюсы:
+ умеет работать с MIPS;
+ красиво выполнена система команд.
Минусы:
- 10 000 строк кода в одном файле;
- много логических ошибок.
Pwndbg
Приступаем и… сразу разочарование!
Позже выяснилось, что грамотная реализация отображения для MIPS тут не написана, но наличие модульной структуры меня порадовало. Определенный набор команд, схожий по назначению, выведен в отдельный файл или даже каталог. Это дает возможность с легкостью ориентироваться в проекте и быстро вносить изменения.
Плюсы:
+ модульная структура;
+ развитая система событий — почти к каждому действию привязано несколько событий.
Минусы:
- инструмент очень перегружена событиями;
- нет главной цели — удобства дебага исполняемых файлов для MIPS.
Красивая и удобная отладка MIPS
Можно ли сделать отладку для MIPS красивой и удобной, взяв лучшее от всех этих решений? Задавшись этим вопросом, я захотел попробовать самостоятельно объединить их во что-то, что будет отвечать моим требованиям.
GDB предоставляет хорошие API для Python. Подробнее о них вы можете узнать на странице проекта.
Структуру проекта решил взять из pwndbg — вынести код в отдельные файлы либо группировать по каталогам, это улучшает понимание и облегчает поддержку в будущем.
В GEF неплохо реализована система команд — это отдельный класс, наследуемый от gdb.Command, со своими плюшками в виде красивой печати справки.
В PEDA я подсмотрел, как просто реализованы некоторые методы. Например, достаточно взглянуть, как в GEF реализовано определение того, находится ли по указанному адресу строка или нет, и сравнить c PEDA.
GEF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def read_ascii_string(address): """Read an ASCII string from memory""" cstr = read_cstring_from_memory(address) if isinstance(cstr, unicode) and cstr and all([x in string.printable for x in cstr]): return cstr return None def is_ascii_string(address): """Helper function to determine if the buffer pointed by `address` is an ASCII string (in GDB)""" try: return read_ascii_string(address) is not None except Exception: return False ... if is_ascii_string(start): ustr = read_ascii_string(start) |
PEDA
1 2 3 4 5 6 7 |
def examine_data(value, bits=32): out = self.execute_redirect("x/%sx 0x%x" % ("g" if bits == 64 else "w", value)) if out: v = out.split(":\t")[-1].strip() if is_printable(int2hexstr(to_int(v), bits // 8)): out = self.execute_redirect("x/s 0x%x" % value) return out |
Зачем на такую простую операцию навешивать обработчики исключений и несколько раз считывать память, мне понять не удалось. Мой вариант реализации можно найти здесь.
А весь проект я окрестил pwngef, вы можете скачать его с моего GitHub и использовать в свое удовольствие.
Во время работы была замечена очень плохая особенность GDB API для Python, связанная с чтением значения регистра. В некоторых ситуациях API возвращал отрицательное значение из-за банального переполнения типа int. Приходилось закодить это временным костылем: 0xFFFFFFFF + reg_val + 1, что позволяет восстановить переполненное значение.
Проект состоит из нескольких файлов и директорий:
- gdbinit.py — начальный скрипт, который необходимо подгружать в GDB через -x или source;
- pwngef — директория с «сердцем» плагина;
- pwngef/color — каталог со скриптами, отвечающими за раскраску плагина и его составляющих;
- pwngef/commands — каталог с обработчиками дополнительных команд, каждая команда — это отдельный класс, наследуемый от GenericCommand — базового класса, который содержит функции обработки параметров для каждой команды и реализует приятный формат вывода справочной информации;
- pwngef/functions — каталог с обработчиками функций в GDB. Пока до конца не реализовано;
- pwngef/*.py — скрипты, реализующие базовые функции;
- scripts — плагин к IDA Pro для взаимодействия с GDB.
Профит
На момент написания этой статьи плагин поддерживает следующие архитектуры:
- i386;
- x86-64;
- MIPS;
- SPARC;
- SPARC64;
- ARM;
- AArch64;
- PowerPC;
- RISC-V.
Да, в нем еще не так много реализовано, как у конкурентов, поскольку он только проходит стадию первичного развития. Но уже сейчас можно потестить основные функции.
РЕКОМЕНДУЕМ:
Способы обхода защиты и взлома программ
Начать работу с ним очень просто, достаточно запустить GDB со следующими параметрами:
1 |
$ gdb -x ./pwngef/gdbinit.py |
Или один раз прописать его в автозагрузку и забыть:
1 |
$ echo "source ~/pwngef/gdbinit.py" >> ~/.gdbinit |
После запуска через команду self help мы можем узнать актуальный список дополнительных команд, которые нам доступны.
Поменять параметры можно, выполнив команду self config param value; чтобы посмотреть список текущих параметров, достаточно выполнить self config.
Сразу после подключения к gdbserver, например командой target remote host:port, перед нами появляется текущий контекст исполнения.
Взаимодействие с IDA Pro
Загружаем в IDA плагин ida_script.py.
Устанавливаем адрес хоста, где работает IDA:
1 |
pwngef> self config ida.host '192.168.1.2' |
Есть возможность синхронизировать точки останова с IDA. Ни для кого не секрет, что IDA — это в основном хороший дизассемблер, а вот как отладчик файлов ELF подходит не очень. Поэтому удобно в процессе реверса разобрать весь код в IDA, установить там точки останова и разом экспортировать их в GDB — для проверки той или иной теории. Все, что для этого нужно, — выполнить команду ida bp в pwngef.
Вкратце пройдемся по другим полезным функциям, которые уже есть в pwngef.
- Трассировка функций с закрашиванием в IDA базовых блоков. Это помогает в динамике увидеть, какие ветви кода активизируются в тех или иных ситуациях. Для активации нужно выполнить команду ida trace 0xADDRESS.
- Можно включить отображение дизассемблированного вывода из IDA. Опять же, удобно, когда вы разреверсили в IDA весь код (восстановили структуры названия функций и так далее), перенести этот результат для наглядности в GDB. Отладка становится приятнее и информативнее. Выполняем команду self config context.use_ida True.
- Разыменовывание строк, как для стека, так и для регистров. Происходит автоматически при печати стека или регистров.
- Функция hexdump. Ну, это уже мейнстрим. Hexdump есть везде — эта функция позволяет отобразить раскрашенный вывод памяти по указанному адресу. Нужно выполнить команду hexdump [address] [size].
- Отображение аргументов для вызываемой функции. Еще одна полезная опция, которой по умолчанию нет в GDB. Чтобы каждый раз перед вызовом функции не вбивать вручную отображение соответствующих регистров, можно набрать команду ctx args, и это будет делаться автоматически.
- Команда next или n в MIPS теперь не заходит внутрь каждой функции. Чаще всего GDB не видит стековый фрейм и не может понять, где начинается или заканчивается функция. Мне удалось это исправить.
- Добавлена возможность чтения кучи, с разбиением на чанки. Команда — heap chunks [base_heap_address].
- Есть возможность настроить цвета по вкусу.
В планах:
- расширить возможности;
- добавить импорт структур из IDA;
- добавить возможность заполнения сегментов, символов;
- добавить разные фичи для мониторинга функций, malloc, free, strcpy и так далее.
РЕКОМЕНДУЕМ:
Лучший редактор бинарных файлов для Windows
И я всегда рад предложениям по улучшению, комментариям, пул-реквестам и конструктивной критике!
GH0st3rs