Реверс-инжиниринг для начинающих: основные концепции программирования
Авторизуйтесь
Реверс-инжиниринг для начинающих: основные концепции программирования
В этой статье мы заглянем под капот программного обеспечения. Новички в реверс-инжиниринге получат общее представление о самом процессе исследования ПО, общих принципах построения программного кода и о том, как читать ассемблерный код.
Примечание Программный код для этой статьи компилируется с помощью Microsoft Visual Studio 2015, так что некоторые функции в новых версиях могут использоваться по-другому. В качестве дизассемблера используется IDA Pro.
Инициализация переменных
Переменные — одна из основных составляющих программирования. Они делятся на несколько видов, вот некоторые из них:
Примечание в С++ строка — не примитивная переменная, но важно понять, как она будет выглядеть в машинном коде.
Давайте посмотрим на ассемблерный код:
Здесь можно увидеть как IDA показывает распределение пространства для переменных. Сначала под каждую переменную выделяется пространство, а потом уже она инициализируется.
Как только пространство выделено, в него помещается значение, которое мы хотим присвоить переменной. Инициализация большинства переменных представлена на картинке выше, но как инициализируется строка, показано ниже.
Инициализация строковой переменной в C++
Для инициализации строки требуется вызов встроенной функции.
Стандартная функция вывода
Примечание Здесь речь пойдёт о том, что переменные помещаются в стек и затем используются в качестве параметров для функции вывода. Концепт функции с параметрами будет рассмотрен позднее.
13–15 декабря, Онлайн, Беcплатно
Теперь посмотрим на машинный код. Сначала строковый литерал:
Вывод строкового литерала
Теперь посмотрим на вывод одной из переменных:
Математические операции
Сейчас мы поговорим о следующих математических операциях:
Переведём каждую операцию в ассемблерный код:
Для сложения мы используем инструкцию add :
При вычитании используется инструкция sub :
При умножении — imul :
При поразрядной конъюнкции используется инструкция and :
При поразрядной дизъюнкции — or :
При поразрядном исключающем ИЛИ — xor :
Поразрядное исключающее ИЛИ
При поразрядном отрицании — not :
При битовом сдвиге вправо — sar :
Битовый сдвиг вправо
При битовом сдвиге влево — shl :
Битовый сдвиг влево
Вызов функций
Мы рассмотрим три вида функций:
Вызов функций без параметров
Функция newfunc() просто выводит сообщение «Hello! I’m a new function!»:
Вызов такой функции выглядит следующим образом:
Вызов функции с параметрами
Посмотрим на код функции:
Циклы
Теперь, когда мы изучили вызов функции, вывод, переменные и математику, перейдём к контролю порядка выполнения кода (flow control). Сначала мы изучим цикл for:
Графический обзор цикла for
Прежде чем разбить ассемблерный код на более мелкие части, посмотрим на общий вариант. Как вы можете видеть, когда цикл for запускается, у него есть 2 варианта:
Теперь давайте взглянем на цикл while :
В этом цикле генерируется случайное число от 0 до 20. Если число больше 10, то произойдёт выход из цикла со словами «I’m out!», в противном случае продолжится работа в цикле.
Условный оператор
Теперь поговорим об условных операторах. Для начала посмотрим код:
Посмотрим на ассемблерный граф:
Ассемблерный граф для условного оператора
Оператор выбора
Оператор выбора очень похож на оператор условия, только в операторе выбора одна переменная или выражение сравнивается с несколькими «случаями» (возможными эквивалентностями). Посмотрим код:
Оператор выбора не следует правилу «Если X, то Y, иначе Z» в отличии от условного оператора. Вместо этого программа сравнивает входное значение с существующими случаями и выполняет только тот случай, который соответствует входному значению. Рассмотрим два первых блока подробней.
Два первых блока оператора выбора
Если var_D0 (A) равно 5, то код перейдёт в секцию, которая показана выше, выведет «5» и затем перейдёт в секцию возврата.
Пользовательский ввод
В этом разделе мы рассмотрим ввод пользователя с помощью потока сin из C++. Во-первых, посмотрим на код:
Разберём это в машинном коде. Во-первых, функция cin :
Функция C++ cin детальнее
Мы рассмотрели лишь основные принципы работы программного обеспечения на низком уровне. Без этих основ невозможно понимать работу ПО и, соответственно, заниматься его исследованием.
Национальная библиотека им. Н. Э. Баумана
Bauman National Library
Персональные инструменты
Reverse engineering
Reverse engineering – исследование некоторого готового устройства или программы, а также документации на него с целью понять принцип его работы. Фактически Reverse engineering заключается в процессе разбора приложения с целью понять, как оно работает, чтобы в будущем воссоздать этот процесс с внесением необходимых изменений. Обычно для этих целей используются отладчик и ассемблер. В зависимости от качества используемого программного обеспечения будут отличаться результат и количество времени, которое необходимо потратить на его приведение в нормальный вид.
Нельзя сказать, что реверс-инжиниринг можно представить как что-то однородно плохое или хорошее. Конечно, с точки зрения разработчиков, которые создали приложение, это совсем не радостное событие. Но с другой стороны, во многих случаях написание опытными программистами необходимых файлов может быть по времени менее затратным, нежели использование подобного инструментария. Хотя для начинающих разработчиков реверс-инжиниринг может сослужить неплохую службу, если нет представления о том, как реализовать что-то, даже примерные и не совсем понятные наброски могут помочь с достижением поставленной цели. [Источник 1]
Содержание
Существуют следующие виды реверс-инжиниринга:
Безопасность
Если нужна защита от реверс-инжиниринга, то в таком случае есть два варианта: использование специальных программ или создание кодовой конструкции, которая будет мешать совершать разбор написанного. Последний вариант подходит исключительно опытным специалистам. Для Android- приложений в качестве специализированного программного обеспечения используется ProGuard. Это приложение, которое используется для сокращения, обфускации и оптимизации кода. Если программу «прогнать» через него, то получим файл с расширением *.apk меньшего размера, чем он был. В таком случае его будет значительно сложней разобрать. Причем преимуществом данной программы является то, что она даже была внедрена в систему сборки Android-приложений с обновления r9. Поэтому воспользоваться ею может любой разработчик, который имеет стандартные инструменты создания и разработки.
Также есть способ узнать, подключен ли отладчик к вашему коду. Но есть одна проблема. Эта проверка определяет, подключен ли отладчик в данное конкретное время. Хакер может подключиться к приложению после этой проверки, когда приложение уже не подозревает об опасности. Эту проблему можно решить двумя способами:
Первый вариант обычно нежелателен. Его цена — трата драгоценного процессорного времени. [Источник 3]
Сферы применения
Машиностроение, автомобилестроение, авиация
Копирование различных механизмов и машин без фактической разработки. Позволяет минимальными затратами воспроизвести удачную конструкцию, но есть случаи копирования и неудачных машин.
Электроника
Обратная разработка электронных устройств появилась ещё на заре радиотехники. В 1920—1930 годах различные фирмы копировали друг у друга радиолампы и схемотехнические решения их применения. Именно с обратной разработкой, а не с лицензионным производством, связано то, что радиолампы одного назначения почти всех производителей оказались унифицированными и взаимозаменяемыми. Например европейская лампа EL95 клон более ранней американской лампы 6KA5. Есть и обратные примеры: американские 6CA5 и KT88 — клоны европейской лампы EL34. То же самое относится и к советским радиолампам, многие из которых являются клонами американских ламп, получаемых по ленд-лизу (например 6Ж4, 6П9) или немецких из трофейной аппаратуры (6Г2, ГУ50). Вместе с лампами копировались и типовые схемы их включения, то есть фактически схемы блоков радиоаппаратуры. Поэтому именно благодаря обратной разработке ламповая эпоха охарактеризовалась по сути переходом к типовому проектированию в электронике. В частности, все выпускаемые в мире бытовые приемники-супергетеродины строились фактически по трем схемам, которая определялась лампой смесительно-гетеродинного узла: двойной триод, триод-гептод или пентагрид. То же самое относилось и к усилителям низкой частоты. Существовало не более десятка типовых схем их построения в зависимости от выходной мощности: маломощные однотактные на комбинированной лампе типа 6Ф3П, маломощные однотактные с темброблоком на двойном триоде (типа 6Н1П) и выходном пентоде (6П14П, EL84) или лучевом тетроде (6П3С, EL34), двухтактные с триодным фазоинвертором (6Н1П + 2х6П14П), двухтактные многокаскадные (6Н8П + 2х6П6С + ГУ50). Черно-белые телевизоры и вовсе строились по одной-двум схемам. Примеры:
Программное обеспечение
Исследование и обратная разработка программ обычно осуществляются с целью дальнейшей модификации, копирования, или, например, написания генераторов ключей, алгоритм работы которых получен на основе анализа алгоритма их проверки. Также исследование программ применяется с целью получения некоторых закрытых сведений о внутреннем устройстве программы — о протоколе сетевого обмена с сервером, аппаратным средством, ключом защиты или о взаимодействии с другой программой. Ещё одна область применения — получение информации о способах экспортирования данных из многочисленных проприетарных форматов файлов.
С развитием Интернета популярные операционные системы и программы всё интенсивнее исследуются на предмет обнаружения в них уязвимостей или т. н. «дыр». В дальнейшем найденные дыры могут использоваться для получения несанкционированного доступа к удалённому компьютеру или компьютерной сети. C другой стороны, обратная разработка применяется при исследовании антивирусными компаниями вредоносного ПО c целью добавления его сигнатур в базы своих продуктов.
Одним из широко известных примеров обратной разработки является исследование BIOS персонального компьютера IBM, ставшее серьёзным шагом на пути развития производства IBM-совместимых компьютеров сторонними производителями. Создание сервера Samba (входящего в состав ОС GNU/Linux и работающего с серверами на базе ОС Microsoft) также потребовало обратной разработки используемого Microsoft протокола SMB. Создание многих ICQ-клиентов также потребовало обратной разработки протокола ICQ.
Обратная разработка программного обеспечения производится с помощью следующих методик:
В настоящее время под словами «reverse engineering» чаще всего понимается т. н. clean room reverse engineering, то есть процесс, при котором одна группа разработчиков анализирует машинный код программы, составляет алгоритм данной программы на псевдокоде либо, если программа является драйвером какого-либо устройства, составляет исчерпывающие спецификации интересующего устройства. После получения спецификаций другая группа разработчиков пишет собственный драйвер на основе полученных спецификаций или алгоритмов. Такой подход позволяет избежать обвинений в нарушении авторских прав на исходную программу, так как по законам, к примеру в США, подпадает под понятие «fair use», то есть добросовестного использования оригинальной программы. Результат обратной разработки редко идентичен оригиналу, что и позволяет избежать ответственности перед законом, особенно при условии контроля отсутствия этой идентичности первой группой разработчиков и отсутствия нарушений торговых марок и патентов.
Базы данных
Reverse engineering на примере Android
Декомпиляция
Анализ данных
Чтобы получить информацию о приложении, необходимо открыть его манифест. По нему определяется, что выступает в роли главной активности. Она-то и представляет наибольшую важность. Необходимо проверить наличие информации о менеджере лицензий, если она есть, то это существенно усложнит проведение реверсного инжиниринга. Если переключиться на jd-gui и развернуть дерево, то появится несколько пространств имён. Необходимо зайти в пространство имен, в котором находятся необходимые данные. Здесь необходимо найти и удалить ключ, а потом и остальные строки, которые проверяют, является ли лицензионной работающая версия. Потом в Apk Manager ищем место, куда указано размещать байт-код. После этого осталось только скомпилировать программу.
Сборка приложения
В этом поможет всё тот же Apk Manager. В окне консоли выбираем пункт №14. Далее дело техники. Если приложение довольно сложное, то при запуске оно может частично или полностью потерять свою работоспособность. Сказать, что нужно делать в конкретном случае, общими словами невозможно, поэтому искать проблемное место придётся самостоятельно. Так, если окно приложения блокируется всплывающим окном, то необходимо просмотреть код и удалить ту его часть, что отвечает за данный диалог. Помочь с этим сможет jd-gui. Реверсный инжиниринг не является лёгким делом, и он требует наличия значительного багажа знаний. Даже если всё без проблем запустилось, необходимо будет протестировать работоспособность приложения. То есть реверс-инжиниринг является ещё затратным по времени действием. Необходимо продолжать работать, пока не будут выявлены все проблемы.
Reverse engineering: обратная разработка приложений для самых маленьких
Выделяют 4 методики проведения обратной разработки:
анализ обмена данными приложения, с помощью различных анализаторов трафика;
использование режима отладки для поиска нужных участков кода и просмотра данных с которыми работает приложение;
дизассемблирование машинного кода программы (изучение требует довольно много времени);
декомпиляция кода программы для создания исходного кода программы на языке программирования высокого уровня.
Установка
Рекомендуемым разработчиками способом установки и обновления Radare2 является установка из официального git-репозитория. Предварительно в системе должны присутствовать установленные пакеты git, build-essential и make.
Запуск установки рекомендуется производить не из под пользователя root, иначе скрипт сам произведёт понижение привилегий.
Далее устанавливаем графическую оболочку для Radare2. Мы будет устанавливать официальный GUI под названием Iaito. Установим пакеты, необходимые для установки Iaito:
Для дистрибутивов Linux на базе Debian, есть готовые пакеты, ссылки на которые можно взять тут. Скачаем и установим нужную версию пакета:
Теперь установим плагин r2ghidra, который является интеграцией декомпилятора Ghidra для Radare2. Плагин не требует отдельной установки Ghidra, так как содержит в себе всё необходимое. Для установки плагин доступен в качестве r2pm пакета:
Установленный плагин автоматически интегрируется в GUI Iaito. После установки запускаем графическую оболочку и если все сделали правильно, то видим стартовый экран:
Теперь мы можем заняться нашим примером. Суть программы-примера заключается в следующем: при запуске на экране выводится некий токен, необходимый для того, чтобы зафиксировать выполнение задания и приватный SSH ключ. Но что-то пошло не так и в результате ключ выводится в некорректном виде, а токен не принимается в качестве правильного.
Первый запуск программы-примера
Открываем файл в Iaito, оставляем настройки анализа по умолчанию:
После того, как Radare2 проанализирует файл, смотрим результат, открывшийся во вкладке Dashboard:
Программа скомпилирована под 64-битную версию Linux, написана на языке C. Слева мы видим список функций, которые Radare2 смог обнаружить. Среди них импортируемые из библиотеки libc функции printf, puts и putchar, выводящие на экран строку по формату и символ.
Функция main – это главная функция программы. Выполнение начинается с неё. Кликнув два раза по её названию, открывается вкладка Disassembly с результатом её дизассемблирования:
Немного про Ассемблер
Команды ассемблера
Каждая команда Ассемблера — это команда для процессора. Синтаксис команды состоит из нескольких частей:
Или рассмотрим другой пример как выглядит возведение числа в степень в Ассемблере:
Это же действие будет выглядеть на языке высокого уровня, например, Си как:
Вернемся к нашему заданию
Для большего понимания логики выполнения программы можно переключиться на вкладку Graph внизу окна. Там мы увидим блоки команд функции, в которой мы находимся, и переходы между ними, построенные Radare2 на основе команд условных и безусловных переходов.
Масштабирование на этой вкладке выполняется сочетаниями клавиш Ctrl+»-» и Ctrl+»+». Можно было бы начать разбираться в работе программы уже с этого места, но есть возможность посмотреть на программу в ещё более “читаемом” виде. Переключаемся на вкладку Decompiler, внизу окна и видим псевдокод, полученный в результате декомпиляции (восстановление до кода на языке, на котором программа была написана, в нашем случае – язык C) средствами встроенного декомпилятора Radare2.
В полученном тексте всё ещё много упоминаний регистров и безусловных переходов. Переключимся на декомпилятор Ghidra, который мы ранее установили. Для этого в правом нижнем углу окна в выпадающем списке выберем “pdg” вместо “pdc”.
Теперь код программы стал практически полностью читаем, за исключением имён переменных.
В коде мы видим, что сначала выводится строка “Token:”, после чего происходит вызов некой функции с двумя параметрами, после которого идёт цикл с переменной var_8h, которая проходит значения от 0 до 14 включительно и выводит что-то посимвольно, основываясь на адресе памяти 0x5020 и счётчике с множителем 8. Из этого можно сделать вывод, что в памяти, начиная с адреса 0x5020, расположен массив структур из 15 значений размером 8 байт. Также стоит обратить внимание, что адрес 0x5020 передавался в качестве первого параметра в функцию, вызываемую перед этим циклом. Будем для простоты далее называть его “токен”. Далее по коду выводятся строки начала закрытого ключа и в цикле выводится посимвольно закрытый ключ. Внутри цикла вывода ключа идёт повторяющийся цикл по обнаруженному нами ранее массиву структур, используя переменную var_ch. Перед выводом на экран над каждым символом закрытого ключа производится операция исключающего ИЛИ (XOR) с текущим символом токена. После цикла выводится строка, завершающая закрытый SSH ключ. Исходя из того, что выводимый программой токен не является правильным, можно сделать вывод, что что-то происходит не так в ранее обнаруженной нами функции с двумя параметрами fcn.00001189, вызываемой перед выводом токена на экран. Перейдём на неё, дважды кликнув по названию функции в списке слева.
В полученном после декомпиляции коде функции мы видим, что она представляет из себя двойной цикл с параметром, в котором после сравнения двух значений элементов структуры происходит их обмен местами, если одно значение меньше другого. Больше всего это похоже на алгоритм сортировки. В частности, на одну из реализаций сортировки “пузырьком”. Основываясь на информации об алгоритме сортировки “пузырёк” и полученном нами коде, можно сделать вывод, что условие выхода из вложенного цикла написано с ошибкой. Проход осуществляется не до конца массива структур.
Получается, нужно это исправить. Переключимся на вкладку дизассемблера:
В полученном коде мы видим только одну команду вычитания 8:
Переключимся в режим графа, чтобы соотнести ассемблерный код с результатом декомпиляции:
Представление в виде графов
Проанализировав логику переходов и соотнеся её с ассемблерным кодом подтверждаем, что нас интересует именно эта область функции.
Для этого, находясь на вкладке дизассемблера, поставим курсор на эту команду и переключимся на вкладку Hexdump:
По относительному адресу команды 0x00001211 убеждаемся, что курсор стоит там, где необходимо. Выделяем 4 байта, начиная с адреса 0x00001211 и справа выберем вкладку “Parsing”. Увидим результат дизассемблирования выделенных байт.
Теперь нужно заменить выделенные байты на 4 байта со значением 90 (шестнадцатиричное значение машинного кода команды nop), но тут мы сталкиваемся с тем, что в Iaito нельзя просто так отредактировать шестнадцатиричное значение по адресу. Список доступных действий мы можем увидеть, нажав на выделенных байтах правую кнопку мыши.
Да, можно воспользоваться сторонним hex-редактором, но это было бы “неспортивно”. Так как мы пробуем выполнить все действия только в рамках функционала Radare2, то будем использовать что есть.
Сначала выберем “Write zeros”. Iaito напомнит нам, что файл открыт в режиме “только для чтения” и предложит переоткрыть его либо в режиме для записи, либо включить режим кэширования. В режиме кэширования все изменения к исходному файлу будут применяться только после выбора пункта меню “File → Commit changes”.
Выберем режим кэширования, после чего снова попытаемся записать нули. И теперь это у нас получается. На каждом из четырёх байт выберем из контекстного меню пункт “Edit → Increment/Decrement” и добавим значение 144 (десятичную запись шестнадцатиричного числа 90).
Смотрим на получившийся результат:
После внесения изменений не забываем нажать “File → Commit Changes”. Запускаем ещё раз программу dechip, чтобы посмотреть результат наших действий:
Стоит отметить, что часть наших действий основывалась на предположениях. И не всегда они подтверждаются так быстро и успешно. Для гарантированного успеха нужно более глубоко изучать язык Ассемблера той архитектуры процессоров, реверсом программ для которой Вы хотите заниматься, а также наиболее распространённые алгоритмы.
Заключение
В целом, бесплатный аналог IDA Pro в лице Radare2 является довольно неплохим решением. Однако, официальный GUI Radare2 хоть и позволяет удобно перемещаться между инструментами Radare2 и в части отображения информации удобнее консольной версии, но в то же время он ещё недостаточно доработан и не предоставляет всех возможностей, которые можно реализовать через консоль. Со всеми возможностями консольной версии можно ознакомиться в официальной книге по Radare2.
Что касается обратной разработки, то он оказался совсем не страшным и даже при начальном уровне знания языка Ассемблер можно разбираться в устройстве какого-нибудь простенького приложения. А в Корпоративных лабораторияx Pentestit можно попробовать свои силы не только в реверс-инжинеринге бинарных файлов, но и в деассемблировании Android/IOS приложений.
































