Modern Reverse Engineering: TTD

от автора

Будущих учащихся на курсе «Reverse-Engineering. Basic» и всех желающих приглашаем посмотреть открытый урок на тему «Анализ шелкода».

Также делимся авторской статьей.


Обратная разработка сегодня это достаточно трудоемкий процесс в первую очередь потому, что в период с 2000х по 2020 годы вышло в свет много языков программирования, которые ставили перед собой задачу стать интуитивными и простыми для изучения, безопасными и в то же время эффективными для решения поставленных задач. Что значит интуитивными и простыми? Это когда не нужно тратить время на синтаксически сложные для запоминания конструкции языка и писать чистый код, который не нужно разбирать часами, чтобы понять что он делает.

Статья расскажет о способах анализа современных языков программирования на примере Crackmes. Среди исследуемых языков: 

•   Go

•   Python

•   Rust

Опишем некоторые проблемы обратной разработки связанных с каждым из перечисленных языков программирования.

Проблемы RE Go

Язык программирования, который стал популярен по ряду причин. Разработан компанией Google, по синтаксису очень похож на Python и Swift. Используется для написания приложений для Cloud приложений. Используют его в первую очередь из-за того что он заявлен как типобезопасный язык программирования и он частично лишен проблем, которые присущи С++ и С. Вот так выглядит традиционный “Hello World”:

А вот так он выглядит собранный исполняемый файл, открытый в дизассемблере:

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

1762 функции, ориентироваться в этом количестве функций без обозначений достаточно трудно. Для работы с такими файлами существует множество скриптов и приложений, которые либо в рантайме, либо статически находят названия функций и ускоряют процесс обратной разработки.

Python

Язык программирования, который до сегодняшнего дня так и не определился какая версия популярнее — ветка 2 или 3. Спор идет в первую очередь потому что большое количество программного обеспечения и инструментов написаны с использованием Python 2. Язык программирования в отличии от всех, которые будут описаны в статье, использует собственный подход для выполнения команд. Он работает на собственной виртуальной машине. Продемонстрируем это на примере:

Дизасемблирование теперь не поможет, так как исполняемый файл состоит сразу из нескольких упакованных файлов, список файлов представим на снимке экрана:

Основной файл с кодом, который писал программист это одноименный файл с расширением pyc. pyc это скомпилированная версия Python скрипта, который был показан выше. Дезассемблированного листинга с нативными командами не представить, поэтому файл main.pyc в шестнадцатеричном редакторе:

Rust

Язык программирования, который призван заменить C/C++. Позиционируется как типобезопасный. Исходник “Hello World”:

Количество функций меньше, чем в Go:

Всё создается сразу в нативных командах:

Кстати, несмотря на то, что это main функция программист её не пишет. Эту функцию генерирует компилятор, а вот функция, которая в исходном виде была представлена выше, находится по выделенному на снимке адресу:

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

TTD

Time Travel Debugging — маховик времени для реверс инженера, все действия приложения записываются и можно просто отматывать на тот момент, который хочется проанализировать и увидеть что конкретно происходило в каждую команду приложения. Самый доступный из всех представленных на сегодняшний день отладчиков, которые могут продемонстрировать эту функцию — WinDBG Preview. Для использования этой чудной функции будем использовать следующие основные команды:

•    !tt- команда Time Travel позволяет двигаться по всей записи

•    g/g- go или выполнить приложение дальше

•    t/t- trace или выполнить по шагам, входя в функции и выполняя каждую команду

Начнем анализ с Crackme на языке программирования Go. Внешний вид приложения:

Ничего необычного, консольная версия Crackme. Файл не содержит отладочных данных, а значит исследовать придется в дефиците данных о функциях, которые выполняются для работы приложения. Запустим файл и приаттачимся из отладчика:

Самое главное — не забыть поставить флажок на использование TTD. Далее необходимо ввести пару тестовых значений и нажать кнопку “Stop Record”. Отладчик автоматически загрузит запись всех событий, которые произошли с приложением от момента отдача до вывода сообщения о том, что пароль не подходит. Теперь мы можем проанализировать алгоритм.

Crackme Go

Записанную последовательность команд помещаем в отладчик и нас приветствует вот такой интерфейс:

Начнем путешествие во времени и перейдём к точке !tt 40. При изучении алгоритмов с использованием TTD стоит сначала просмотреть середину трассировки и ее окончание, обычно именно там есть что-то интересное. Начало и конец пропускается на первых этапах так как там очень много системных вызовов работы операционной системы. Чтобы определить где находится настоящая main функция, которая включает код программиста нужно:

          1. Выяснить базовый адрес запущенного файла

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

Пункт 1 — можно ввести команду lm — покажет список модулей и их базовые адреса:

Теперь воспользовавшись функцией Step Out 3 раза можно найти вот такую функцию:

Для решения можно использовать тот факт, что ABI Windows x64 предполагает, что данные, которые передаются в качестве аргументов функции, должны находиться в регистрах общего назначения. Если просматривать их до вызова функции, то можно предположить какие данные используются для проверки ключа и, если повезет, то можно найти сам ключ. Чтобы не искать по функциям вручную, поставим точки останова на каждую инструкцию call внутри функции main. При использовании команд g/g- точки останова будут срабатывать как в одну так и в другую сторону, поэтому если случайно потеряетесь в функциях, всегда можно набрать команду g- чтобы остановиться на последней точке останова.

Результат наших исследований в итоге обнаруживается по смещению 0x1c303:

Попробуем ввести это значение:

Crackme Python

Для анализа используем вот это CrackMe. Как было уже сказано выше, анализ приложения, которое написано на Python усложнено тем, что все команды выполняет виртуальная машина. Попробуем записать и ее действия через TTD:

Не всё можно собрать через TTD, в этом случае придется писать трассировщик, который будет показывать в каждый момент времени все команды, которые выполняет виртуальная машина. Это не выгодно с точки зрения производительности, да и проще анализировать такой код в декомпилированном виде. Для разбора файлов pyc используют вот этот проект. Оставим чтение получившегося исходника читателю, в качестве самостоятельной работы.

Crackme Rust

Для анализа будем использовать вот это Crackme. В этом случае повезло больше, так как Rust все же много берет от шаблонов, которые использует C и C++ при создании исполняемого файла.  В частности, найти его main функцию не так уж и сложно. Основная точка входа находится здесь: !tt 3C:12

Команды которые используются на точке входа типичны для компилятора С++ от Microsoft. Поэтому дальше приложение на Rust можно анализировать как и любое другое приложение написанное на C++: !tt 124:133. Теперь можно повторить те же операции, которые мы выполняли для Crackme на Go — проставить точки останова на интересные функции и узнать ключ:

К сожалению, ключ не проходит какую-то их проверок и придется перезаписывать выполнение Crackme. Несмотря на то, что этот вид отладки имеет множество преимуществ, но он действительно статичен, как фильм, который можно перематывать в любую сторону, но изменить ничего в этом фильме нельзя. Вы не можете просто взять изменить кусок памяти или изменить значение регистра, чтобы приложение выполняло другие ветки функций. Каждый раз если хочется изучить что-то новое, приложение придется перезапустить и записать всё с другими параметрами. В итоге, после нескольких перезапуском с разной длиной ключа были получены данные по значениям, которые нужно получить, чтобы решить Crackme:

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

Вывод

TTD весьма упрощает процесс анализа даже самого запутанного алгоритма. Не вообразить сколько часов он может помочь сэкономить при анализе какой-нибудь сложной функции. Но ничего не бывает бесплатно и просто, что стоит помнить, что:

A.   Запись возможна только для User Mode

B.   Изменять ничего в процессе исследования нельзя

C.   Для анализа трансляторов и виртуальных машин всё равно нужно писать код, чтобы собирать все события среди действий операционной системы.


Узнать подробнее о курсе «Reverse-Engineering. Basic»

Посмотреть открытый урок на тему «Анализ шелкода».


ЗАБРАТЬ СКИДКУ

ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/532994/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *