Reverse engineering: обратная разработка приложений для самых маленьких
Обратная разработка (англ. Reverse Engineering) — метод исследования устройств или программного обеспечения с целью понять принцип его работы или обнаружить недокументированные возможности. В информационной безопасности она занимает значительную роль, благодаря ей специалисты в области ИБ могут исследовать вредоносные приложения, разбираться как они работают для последующего, например, составления сигнатур в базы антивирусов и защиты других пользователей от предстоящей цифровой угрозы.
Выделяют 4 методики проведения обратной разработки:
- анализ обмена данными приложения, с помощью различных анализаторов трафика;
- использование режима отладки для поиска нужных участков кода и просмотра данных с которыми работает приложение;
- дизассемблирование машинного кода программы (изучение требует довольно много времени);
- декомпиляция кода программы для создания исходного кода программы на языке программирования высокого уровня.
Тема дизассмеблирования машинного кода, в целом неразрывно связана с обратной разработкой, но у неспециалиста может попросту отбить какое-либо желание связываться с ним. Связано это с необходимым уровнем подготовки и временем, которое затрачивается на «разборку». Тем не менее, каждый случай может оказаться довольно увлекательным «путешествием» в недра приложения. Поэтому сегодня попробуем наглядно разобрать одно из приложений, чтобы показать, что дизассемблирование — это совсем не страшно и при должном старании и терпении можно прокачать новый скилл.
В качестве подопытного возьмем задание, которое использовалось в бесплатной лаборатории Test lab, где зарегистрировано свыше 30 тысяч участников. Но для большей наглядности программа была скомпилирована без отладочной информации и без использования оптимизаций (gcc -O0). После чего к ней была применена утилита strip с параметром -s, который удаляет всю лишнюю символьную информацию из исполняемого файла, вроде имён переменных и функций.
Рабочим инструментом выступит Radare2 — кроссплатформенный набор утилит для обратной разработки и отладки. Включает в себя инструменты для анализа, дизассемблирования, декомпилирования и патчинга. В отличие от популярного IDA Pro распространяется бесплатно.
Установка
Рекомендуемым разработчиками способом установки и обновления Radare2 является установка из официального git-репозитория. Предварительно в системе должны присутствовать установленные пакеты git, build-essential и make.
$ sudo apt install git build-essential make $ git clone https://github.com/radareorg/radare2 $ cd radare2 $ sys/install.sh
Запуск установки рекомендуется производить не из под пользователя root, иначе скрипт сам произведёт понижение привилегий.
Далее устанавливаем графическую оболочку для Radare2. Мы будет устанавливать официальный GUI под названием Iaito. Установим пакеты, необходимые для установки Iaito:
$ sudo apt install qttools5-dev-tools qt5-default libqt5svg5-dev
Для дистрибутивов Linux на базе Debian, есть готовые пакеты, ссылки на которые можно взять тут. Скачаем и установим нужную версию пакета:
$ wget https://github.com/radareorg/iaito/releases/download/5.2.2/iaito_5.2.2_amd64.deb $ sudo dpkg -i iaito_5.2.2_amd64.deb
Теперь установим плагин r2ghidra, который является интеграцией декомпилятора Ghidra для Radare2. Плагин не требует отдельной установки Ghidra, так как содержит в себе всё необходимое. Для установки плагин доступен в качестве r2pm пакета:
$ r2pm update $ r2pm -ci r2ghidra
Установленный плагин автоматически интегрируется в GUI Iaito. После установки запускаем графическую оболочку и если все сделали правильно, то видим стартовый экран:
Теперь мы можем заняться нашим примером. Суть программы-примера заключается в следующем: при запуске на экране выводится некий токен, необходимый для того, чтобы зафиксировать выполнение задания и приватный SSH ключ. Но что-то пошло не так и в результате ключ выводится в некорректном виде, а токен не принимается в качестве правильного.
Первый запуск программы-примера
Открываем файл в Iaito, оставляем настройки анализа по умолчанию:
После того, как Radare2 проанализирует файл, смотрим результат, открывшийся во вкладке Dashboard:
Программа скомпилирована под 64-битную версию Linux, написана на языке C. Слева мы видим список функций, которые Radare2 смог обнаружить. Среди них импортируемые из библиотеки libc функции printf, puts и putchar, выводящие на экран строку по формату и символ.
Функция main – это главная функция программы. Выполнение начинается с неё. Кликнув два раза по её названию, открывается вкладка Disassembly с результатом её дизассемблирования:
Немного про Ассемблер
Ассемблер — машинно-ориентированный язык программирования низкого уровня. Представляет собой систему обозначений, используемую для представления в удобно читаемой форме программ, записанных в машинном коде. Назван по одноименной утилите, которая транслирует программу в машинный код процессора.
Команды ассемблера
Каждая команда Ассемблера — это команда для процессора. Синтаксис команды состоит из нескольких частей:
Команда — означает какую операцию необходимо выполнить. Например:
- mov — команда пересылки данных. Копирует содержимое одного операнда в другой;
- lea — вычисляет эффективный адрес операнда-источника и сохраняет его в регистре;
- cmp — сравнение двух операндов;
- условные и безусловные переходы (jmp, jne, je, jle, …) — безусловные и условные (требуется выполнение условия) переходы к меткам. Например, jump @exit выполняет безусловный переход к метке exit;
- nop — однобайтовая команда, которая ничего не выполняет, а только занимает место и время. В основном используется для создание задержки в программе или как заполнитель удаленных инструкций. Например, команду проверки лицензионного ключа во взломанных программах заменяют на «ничего не делать»;
- и тд
Операнды — то, над чем будут выполняться команды. Операндами могут быть названия регистров, ячейки памяти или служебные части команд.
Комментарий — понятно из названия, что это комментарий для удобства чтения кода. Пишется после точки с запятой.
Метки — обозначение участка кода. Также улучшает читаемость кода, но нужны еще и для перехода к помеченному им участку.
mov ax, 0 ; Помещаем значение 0 в регистр ax
- mov — команда (перемещение значения из одного операнда в другой);
- ax, 0 — операнды (регистр и значение);
- ; — комментарий
Или рассмотрим другой пример как выглядит возведение числа в степень в Ассемблере:
mov ax,2 ; Помещаем значение 2 в регистр AX mov bx,ax ; Помещаем значение регистра AX в BX mul bx ; Выполняем дважды командой mul возведение в степень числа 2 mul bx
Это же действие будет выглядеть на языке высокого уровня, например, Си как:
pow (2, 3);
Вернемся к нашему заданию
Для большего понимания логики выполнения программы можно переключиться на вкладку Graph внизу окна. Там мы увидим блоки команд функции, в которой мы находимся, и переходы между ними, построенные Radare2 на основе команд условных и безусловных переходов.
Масштабирование на этой вкладке выполняется сочетаниями клавиш Ctrl+»-» и Ctrl+»+». Можно было бы начать разбираться в работе программы уже с этого места, но есть возможность посмотреть на программу в ещё более “читаемом” виде. Переключаемся на вкладку Decompiler, внизу окна и видим псевдокод, полученный в результате декомпиляции (восстановление до кода на языке, на котором программа была написана, в нашем случае – язык C) средствами встроенного декомпилятора Radare2.
В полученном тексте всё ещё много упоминаний регистров и безусловных переходов. Переключимся на декомпилятор Ghidra, который мы ранее установили. Для этого в правом нижнем углу окна в выпадающем списке выберем “pdg” вместо “pdc”.
Теперь код программы стал практически полностью читаем, за исключением имён переменных.
undefined8 main(void) < uint32_t uVar1; uint32_t uVar2; uint32_t var_ch; undefined8 var_8h; puts("Token:"); fcn.00001189(0x5020, 0x5090); for (var_8h._0_4_ = 0; (int32_t)var_8h < 0xf; var_8h._0_4_ = (int32_t)var_8h + 1) < putchar(*(undefined *)((int64_t)(int32_t)var_8h * 8 + 0x5020)); >puts(0x2c78); puts("Key:"); printf("\n-----BEGIN RSA PRIVATE KEY-----"); var_ch = 0; for (var_8h._4_4_ = 0; (int32_t)var_8h._4_4_ < 0xc68; var_8h._4_4_ = var_8h._4_4_ + 2) < if ((var_8h._4_4_ & 0x7f) == 0) < putchar(10); >uVar1 = (*(uint8_t *)((int32_t)var_8h._4_4_ + *(int64_t *)0x5098) & 0x1f) + 9; uVar2 = (*(uint8_t *)(*(int64_t *)0x5098 + (int64_t)(int32_t)var_8h._4_4_ + 1) & 0x1f) + 9; putchar((char)uVar2 + (char)(uVar2 / 0x19) * -0x19 + ((char)uVar1 + (char)(uVar1 / 0x19) * -0x19) * '\x10' ^ *(uint8_t *)((int64_t)(int32_t)var_ch * 8 + 0x5020)); var_ch = var_ch + 1; if (var_ch == 0xf) < var_ch = 0; >> putchar(10); puts("-----END RSA PRIVATE KEY-----"); return 0; >
В коде мы видим, что сначала выводится строка “Token:”, после чего происходит вызов некой функции с двумя параметрами, после которого идёт цикл с переменной var_8h, которая проходит значения от 0 до 14 включительно и выводит что-то посимвольно, основываясь на адресе памяти 0x5020 и счётчике с множителем 8. Из этого можно сделать вывод, что в памяти, начиная с адреса 0x5020, расположен массив структур из 15 значений размером 8 байт. Также стоит обратить внимание, что адрес 0x5020 передавался в качестве первого параметра в функцию, вызываемую перед этим циклом. Будем для простоты далее называть его “токен”. Далее по коду выводятся строки начала закрытого ключа и в цикле выводится посимвольно закрытый ключ. Внутри цикла вывода ключа идёт повторяющийся цикл по обнаруженному нами ранее массиву структур, используя переменную var_ch. Перед выводом на экран над каждым символом закрытого ключа производится операция исключающего ИЛИ (XOR) с текущим символом токена. После цикла выводится строка, завершающая закрытый SSH ключ. Исходя из того, что выводимый программой токен не является правильным, можно сделать вывод, что что-то происходит не так в ранее обнаруженной нами функции с двумя параметрами fcn.00001189, вызываемой перед выводом токена на экран. Перейдём на неё, дважды кликнув по названию функции в списке слева.
В полученном после декомпиляции коде функции мы видим, что она представляет из себя двойной цикл с параметром, в котором после сравнения двух значений элементов структуры происходит их обмен местами, если одно значение меньше другого. Больше всего это похоже на алгоритм сортировки. В частности, на одну из реализаций сортировки “пузырьком”. Основываясь на информации об алгоритме сортировки “пузырёк” и полученном нами коде, можно сделать вывод, что условие выхода из вложенного цикла написано с ошибкой. Проход осуществляется не до конца массива структур.
_var_18h < (undefined8 *)(arg2 + -8)
Получается, нужно это исправить. Переключимся на вкладку дизассемблера:
В полученном коде мы видим только одну команду вычитания 8:
0x00001211 sub rax, 8
Переключимся в режим графа, чтобы соотнести ассемблерный код с результатом декомпиляции:
Представление в виде графов
Проанализировав логику переходов и соотнеся её с ассемблерным кодом подтверждаем, что нас интересует именно эта область функции.
Теперь нам нужно убрать команду sub rax, 8. Наиболее просто это можно сделать переписав поверх нее команду nop (No Operation) - то есть, заменив команду вычитания командой "ничего не делать".
Для этого, находясь на вкладке дизассемблера, поставим курсор на эту команду и переключимся на вкладку 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 приложений.
- radare2
- reverse-engineering
- Блог компании Pentestit
- Информационная безопасность
Реверс-инжиниринг: правовое регулирование
При разработке нового программного обеспечения (далее – «ПО») и, в частности, мобильных приложений иногда требуется прибегать к таким техническим приемам, как декомпиляция и дизассемблирование существующих программ. Эти приемы представляют собой преобразование объектного кода уже существующих программ в понятный для разработчика язык (исходный текст) с целью последующего его изучения. Но насколько правомерны такие действия? Какие условия необходимо соблюсти, чтобы изучить программу для ЭВМ, не нарушая прав правообладателя?
Основные термины статьи:
Реверс-инжиниринг (реинжиниринг; англ. reverse engineering) — исследование некоторого устройства или программы, а также документации на них с целью понять принцип его (ее) работы и, чаще всего, воспроизвести устройство, программу или иной объект с аналогичными функциями, но без копирования как такового. Процесс реверс-инжиниринга включает в себя декомпиляцию, дизассемблирование и др. [1]
Декомпиляция – технический прием, посредством которого происходит преобразование объектного кода программы в исходный текст.
Законодательное регулирование реверс-инжиниринга
Критерии правомерности осуществления декомпиляции содержатся в ст. 1280 Гражданского Кодекса РФ. Согласно данной норме проведение реинжиниринга возможно при соблюдении следующих условий:
1) Лицо, осуществляющее декомпиляцию, должно владеть ПО на законных основаниях.
Приобрести ПО, получить разрешение на его использование – это первый шаг к правомерному реверс-инжинирингу. Приобретение «пиратской» версии ПО и последующее его декомпилирование является правонарушением согласно ст. 7.12 Кодекса об административных правонарушениях РФ (КоАП РФ). Также правонарушением является использование для декомпиляции взломанных или иных испорченных версий ПО. Это подтверждает Постановление Пленума Верховного Суда РФ N 5, Пленума ВАС РФ N 29 от 26.03.2009: «Судам следует учитывать, что право совершения в отношении программы для ЭВМ или базы данных действий, предусмотренных статьей 1280 ГК РФ, принадлежит только лицу, правомерно владеющему экземпляром такой программы для ЭВМ или базы данных (пользователю)».
Правомерность владения экземпляром ПО определяется наличием у лица оснований владения, установленных договором (например, приобретение экземпляра ПО по договору купли-продажи).
2) Единственно возможная легальная цель декомпиляции – достижение способности к взаимодействию независимо разработанной лицом программы для ЭВМ с другими программами, которые, в том числе, могут взаимодействовать с декомпилируемой программой.
Изучение программы конкурента с целью создания собственной аналогичной программы, по общему правилу, будет считаться незаконным. Также попытка устранить найденные программные ошибки самостоятельно, то есть без привлечения правообладателя, может привести к появлению ответственности
Постановление Федерального Арбитражного Суда Северо-Западного округа от 07 июня 2013 года по делу N А13-6254/2012): «Исследовав и оценив представленные лицами, участвующими в деле, доказательства по правилам статей 65 и 71 АПК РФ, в том числе заключенные Обществом в 2010-2011 годах договоры на оказание услуг по сопровождению программного обеспечения , судебные инстанции установили, что внесение изменений в программное обеспечение, исправление выявленных ошибок в программном обеспечении без согласия правообладателя приведет к нарушению исключительного права на результат интеллектуальной деятельности».
3) Информация, необходимая для достижения способности программы для ЭВМ к взаимодействию, ранее не была доступна лицу, осуществляющему декомпиляцию, из других источников.
Данные об исходном тексте программы могут находиться в открытом доступе и в этом случае нет необходимости в проведении декомпиляции. Под «другими источниками» понимаются официальный интернет-сайт компании-разработчика или руководство к пользованию программой и прочее.
4) Декомпиляция не должна противоречить обычному использованию программы для ЭВМ или базы данных и не должна ущемлять необоснованным образом законные интересы автора или иного правообладателя.
На практике факт ущемления законных интересов автора или иного правообладателя устанавливается с учетом таких факторов как цель декомпиляции, конкурирование лица, осуществляющего декомпиляцию, с правообладателем, доля дохода, которая может быть утрачена правообладателем из-за действий пользователя и т.п.
Считается, что вред законным интересам правообладателя достигает необоснованного уровня, если декомпиляция его программы причиняет или имеет возможность причинить неразумные потери доходам правообладателя.
Разрешенная декомпиляция на практике
В пользовательских соглашениях / договорах разработчики ПО часто устанавливают запрет на реверс-инжиниринг. Многие правообладатели хотят ограничить действия пользователя в части исследования полученной по лицензии программы, чтобы избежать копирования кода. Приведем типичный пример подобного запрета, взятый из реального лицензионного соглашения: «4.1. За исключением использования в объёмах и способами, прямо предусмотренными настоящей Лицензией или законодательством РФ, Пользователь не имеет права изменять, декомпилировать, дизассемблировать, дешифровать и производить иные действия с объектным кодом и исходным текстом Программы, имеющие целью получение информации о реализации алгоритмов, используемых в Программе, создавать производные произведения с использованием Программы, а также осуществлять (разрешать осуществлять) иное использование Программы, любых компонентов Программы, хранимых Программой на мобильном устройстве Пользователя картографических материалов, иных изображений и прочих данных, без письменного согласия Правообладателя».
Такие формулировки о запрете реверс-инжиниринга – это юридическая защита правообладателя от действий пользователя по исследованию программного продукта с закрытым исходным кодом с целью его копирования, то есть с целью нарушения прав правообладателя. Гарантией соблюдения пользователем такого запрета является перспектива наступления для пользователя различных негативных последствий в случае его несоблюдения (начиная от досрочного прекращения лицензии и заканчивая взысканием с пользователя понесенных правообладателем убытков, включая упущенную выгоду, или денежной компенсации за нарушение авторских прав).
Проведем анализ судебной практики с целью получения ответа на вопрос: возможна ли декомпиляция в рамках, установленных статьей 1280 ГК РФ, в случаях, когда лицензионное соглашение запрещает реверс-инжиниринг вообще и декомпиляцию, в частности?
Постановление Одиннадцатого арбитражного апелляционного суда от 25 октября 2012 года по делу № А55-13189/2012: «Заключение лицензионного договора означает, что пользователь программы вправе совершить в отношении нее действия, предусмотренные ст. 1280 ГК РФ, а также иные действия, обусловленные договором и связанные с эксплуатацией программы. На этот договор в отличие от иных лицензионных соглашений не распространяются правила, установленные пунктами 2-6 ст. 1235 ГК РФ».
Лицензионным договором (пользовательским соглашением) нельзя ограничить лицо в праве осуществлять те действия в отношении ПО, которые предусмотрены статьей 1280 ГК РФ (в том числе и декомпиляцию). Следовательно, положения лицензионных договоров, ограничивающие права лиц на реверс-инжиниринг, не должны признаваться законными.
Нормы законодательства не дают точный ответ на вопрос, должно ли лицо, имеющее интерес в декомпиляции ПО, осуществлять такую декомпиляцию только лично или для этого можно привлечь третьих лиц?
Прямого запрета на осуществление реинжиниринга одним лицом в интересах другого не содержится в нормах Гражданского Кодекса. По смыслу ст. 1274 ГК РФ и п. 2 ст. 1280 ГК РФ исследование программы для ЭВМ, как и любого другого объекта исключительных прав, может быть проведено как пользователем самостоятельно, так и любым иным лицом, обладающим специальными знаниями, но в интересах пользователя, с его ведома и по его согласию.[2] Это подтверждает Решение Арбитражного суда города Москвы от 29 мая 2013 г. по делу № А40-10750/2013: «По смыслу ст. 1274 ГК РФ и п. 2 ст. 1280 ГК РФ исследование программы для ЭВМ, как и любого другого объекта исключительных прав, может быть проведено как пользователем самостоятельно, так и любым иным лицом, обладающим специальными знаниями, но в интересах пользователя, с его ведома и по его согласию. Это объясняется тем, что само по себе исследование в силу ст. 1270 ГК РФ не указано в качестве способа использования объекта исключительных прав и не предполагает его возмездное отчуждение или иное введение в оборот».
На настоящий момент судебная практика по вопросу реверс-инжиниринга в России не так обширна, как в странах Запада, где есть громкие и известные судебные прецеденты (например, дело Blizzard v. BnetD [3] и др.). Суды США в большинстве судебных решений отмечали, что реверс-инжиниринг был добросовестным и не нарушающим права правообладателя ПО. Например, в деле Sega Enterprises v. Accolade [4] компания Sega подала иск против издателя игр (Accolade) после того, как издатель произвел реверс-инжиниринг консоли Sega в целях создания совместимых с нею игр. Благодаря реверс-инжинирингу Accolade получила информацию об объектном коде, которая позже была собрана в письменное руководство, которое использовали программисты для создания своих игр. Код самой компании Sega при этом не был скопирован в игры от Accolade — код Accolade был полностью оригинальным. Такое «промежуточное» копирование было признано судом законным.
- Реверс-инжиниринг будет неправомерен в случае, если владение программой/мобильным приложением, которые подвергаются реверс-инжинирингу, незаконно. Данный вывод подтверждает практика Верховного Суда РФ. За нарушение прав правообладателя ПО возможно привлечение к административной ответственности.
- Декомпиляция ПО, проводимая в соответствии с правилами, предусмотренными ст. 1280 ГК РФ, правомерна, даже если это нарушает установленный лицензионным соглашением с правообладателем такого ПО запрет на осуществление декомпиляции. Свободный реинжиниринг должен соответствовать одной из целей его проведения, разрешенных статьей 1280 ГК РФ, например, цели достижения способности взаимодействия ПО, разработанного одним лицом, с ПО других лиц.
- Неправомерно копирование исходного кода декомпилируемой программы в создаваемую (новую) программу, поскольку такое копирование нарушает авторские права правообладателя декомпилируемой программы. Отметим, что копирование может включать в себя одновременно как имитацию нефункциональных элементов, так и буквальное копирование.
- Декомпиляция в законных целях может проводиться как самим лицом, которому необходима такая декомпиляция, так и привлеченными им специалистами, если они действуют исключительно в интересах привлекшего их лица.
- Российское законодательство и российская судебная практика по вопросу реверс-инжиниринга не дают ответы на все вопросы, которые возникают у лиц, имеющих интерес в его проведении.
Александр Нечаев, Анна Андрусова
Дизассемблеры, декомпиляторы и отладчики
Подготовка к анализу загрузочного файла – тема отдельного разговора. Отладчики – это программные средства, предназначенные для контроля выполнения программ. Отладчики позволяют приостановить выполнение программы в некоторой точке, изменить значение переменных и даже, в некоторых случаях, внести изменения в машинный код программы на лету в процессе ее выполнения. К сожалению, возможность выполнения отладчиком подобных действий зависит от включения в выполнимый код отладочной информации, прежде всего таблицы соответствия символов (для большинства загрузочных программ это не выполняется). Если отладочной информации в выполнимом коде нет, то отладчик может выполнить некоторые функции, хотя большую часть работы по отладке программ приходится выполнять вручную, например при указании точек прерывания вместо имен приходится задавать адреса памяти.
Декомпилятор (или дизассемблер) – программа, которая преобразует двоичный код программ в исходный текст, написанный на одном из языков программирования, чаще всего – ассемблере. Некоторые дизассемблеры могут представить исходный текст на простом языке C. В процессе трансляции большая часть информации об исходном тексте программы теряется, например имена переменных, поэтому декомпилятор пытается восстановить исходный текст программы настолько, насколько это возможно. Если при декомпиляции таблица соответствия имен была не найдена, то зачастую декомпилятор присваивает переменным имена, составленные из плохо воспринимаемой последовательности цифр и букв.
Проблема несколько упрощается, если исследователь в состоянии разобраться с ассемблерным кодом, генерируемым декомпилятором. В этом случае декомпилятор особенно полезен. Рассмотрим пример результатов работы декомпилятора.
Среди коммерческих декомпиляторов для Windows хорошая репутация у IDA Pro компании DataRescue (пример работы декомпилятора показан на рис. 4.1). IDA Pro может декомпилировать программный код многих процессоров, включая виртуальную машину Java.
Рис. 4.1. Пример работы IDA Pro
На рисунке показан пример применения декомпилятора IDA Pro для дизассемблирования программы pbrush.exe (Paintbrush). IDA Pro нашел секцию внешних функций, используемых программой pbrush.exe. Если программа выполняется под управлением операционной системы, которая поддерживает разделяемые библиотеки (например, под управлением операционных систем Windows или UNIX), то она содержит список необходимых ей библиотек. Обычно этот список представлен в удобочитаемом виде, который легко обнаружить при экспертизе выполняемого кода. Для выполнения программ операционной системе также требуется этот список, поэтому она загружает его в память. В большинстве случаев это позволяет декомпилятору вставить список в двоичный код программы, сделав его более понятным.
Чаще всего таблица соответствия имен pbrush.exe недоступна, поэтому в большей части сгенерированного декомпилятором ассемблерного кода отсутствуют имена.
Оценочную версию IDA Pro, пригодную для первоначального знакомства с программой, можно загрузить с www.datarescue.com/idabase/ida.htm. SoftICE компании Numega – другой популярный отладчик. Дополнительные сведения о нем можно найти по адресу www.compuware.com/products/numega/drivercentral/.
Для сравнения была написана небольшая программа на языке C (классическая небольшая программа, выводящая строку «Hello World»). Для отладки использовался отладчик GNU (GDB). Код программы представлен ниже:
printf (“Hello World ”);
Программа была скомпилирована с включением отладочной информации (был включен переключатель —g):
[elliptic@]$ gcc -g hello.c -o hello
Пример протокола отладки под управлением GDB показан ниже:
[elliptic@ellipse]$ gdb hello
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public
License, and you are welcome to change it and/or
distribute copies of it under certain conditions.
Type “show copying” to see the conditions.
There is absolutely no warranty for GDB. Type “show
warranty” for details.
This GDB was configured as “i386-redhat-linux”.
Была установлена точка прерывания при входе в функцию main. При ее достижении выполнение программы было приостановлено для выполнения программистом действий по отладке программы. Контрольная точка была установлена до выдачи команды run.
Breakpoint 1 at 0x80483d3: file hello.c, line 5.
Команда run начинает выполнение программы под управлением отладчика.
Starting program: /home/ryan/hello
Breakpoint 1, main () at hello.c:5
5 printf (“Hello World ”);
После того достижения программой точки прерывания и начала сессии отладки была выдана команда disassemble, позволяющая вывести дополнительную отладочную информацию о программе.
Dump of assembler code for function main:
0x80483d0 : push %ebp
0x80483d1 : mov %esp,%ebp
0x80483d3 : push $0x8048440
0x80483d8 : call 0x8048308
0x80483dd : add $0x4,%esp
0x80483e0 : xor %eax,%eax
0x80483e2 : jmp 0x80483e4
End of assembler dump.
Протокол отображает ассемблерный код программы «hello world», соответствующий ассемблеру x86 Linux. Исследование собственных программ в отладчике – хороший способ изучения листингов ассемблерного кода.
printf (format=0x8048440 “Hello World ”) at printf.c:30
printf.c: No such file or directory.
После задания командой s («step») режима пошагового выполнения программы в отладчике выполняется переход к вызову функции printf. GDB сообщает о невозможности дальнейшей детализации функции printf из-за отсутствия в распоряжении отладчика исходного кода функции printf.
Далее выполняется несколько шагов реализации программы внутри функции printf и программа завершается. По команде c («continue») программа будет выполнена до следующей точки прерывания или будет завершена.
Program exited normally.
В число аналогичных программ входят программы nm и objdump пакета GNU. Программа objdump позволяет управлять объектными файлами. Она применяется для отображения символов объектного файла, его заголовка и даже дизассемблирования. Nm выполняет аналогичные действия, что и objdump, позволяя просматривать символьные ссылки на объектные файлы.
Инструментарий и ловушки
Инструментарий никогда не заменит знаний
Некоторые из средств дизассемблирования и отладки предлагают фантастические возможности. Но они, как и любой другой инструментарий, несовершенны. Особенно это проявляется при анализе злонамеренного кода (вирусов, червей, Троянских коней) или специально разработанных программ. Обычно авторы подобных поделок делают все возможное для затруднения их анализа и снижения эффекта от применения отладчиков, дизассемблеров и других подобных инструментальных средств. Например, вирус RST для Linux в случае обнаружения, что он работает под управлением отладчика, завершает свою работу. Этот же вирус при инфицировании исполняемых и компонуемых файлов ELF (Executable and Linkable Format – формат исполняемых и компонуемых модулей) модифицирует их заголовки таким образом, что некоторые дизассемблеры не могут дизассемблировать двоичный код вируса (обычно это достигается путем размещения кода вируса в необъявленном программном сегменте). Часто злоумышленный код шифруется или сжимается, что защищает его от экспертизы. Черви Code Red распространялись половинками своих программ, маскируясь под строки переполнения, и, таким образом, формат их двоичных данных не подходил ни под один из стандартных заголовков файла.
Поэтому при анализе двоичных данных нужно знать не только о том, какие инструментальные средства и как применять, но и как обойтись без них. Анализируя заголовок файла, возможно, потребуется определить, какие данные в файле модифицированы, как и с какой целью. Вероятно, потребуется проанализировать зашифрованные данные и процедуру их расшифровки, восстановить алгоритм работы программы и выяснить результаты ее работы.
Необходимо знать ассемблер не только в объеме, достаточном для чтения ассемблерных программ, но и для написания программ расшифровки или восстановления данных. Писать на ассемблере намного сложнее, чем читать программы, написанные на этом языке.
Из этого не следует, что инструментальные средства бесполезны. Далеко не так. Нужно только выявить часть программы, оказавшуюся не по зубам инструментальным средствам, и проанализировать ее самостоятельно, а остальную часть программы – при помощи инструментальных средств. Кроме того, иногда для лучшего понимания логики работы программы следует воспользоваться инструментарием.
Данный текст является ознакомительным фрагментом.
Чем отличается декомпиляция от дизассемблирования
Дизассемблеры и декомпиляторы исполняемых файлов
В комментариях к статьям меня часто спрашивают где взять тот или иной инструмент, используемый в исследовании. По возможности я всегда указываю ссылки, но теперь настало время самых мощных инструментов, а именно дизассемблеров и декомпиляторов исполняемых файлов. Сразу уточню терминологию. Дизассемблирование - преобразование программы из двоичного кода к ее ассемблерному представлению. Декомпиляция - процесс воссоздания исходного кода программы.
Скриншот программы dnSpy
Начнем с популярного нынче дотнета. Не будет преувеличением сказать, что самый мощный на сегодняшний день инструмент для потрошения приложений на .NET - это бесплатный проект dnSpy. Он включает в себя декомпилятор C# и Visual Basic .NET, отладчик, редактор сборки с подсветкой синтаксиса, HEX-редактор и еще множество инструментов. Русский язык в наличии. Самую свежую версию можно всегда скачать с офсайта.
Скриншот программы IDA Pro Advanced
IDA Pro (сокращение от Interactive DisAssembler) - один из моих основных инструментов для реверс-инжиниринга и разбора файлов. Это интерактивный дизассемблер и отладчик с поддержкой множества форматов исполняемых файлов для большого числа процессоров и операционных систем. Чтобы перечислить все его возможности потребуется целая книга. Но даже тут возможности IDA не заканчиваются. Плагин Hex-Rays для IDA Pro позволяет декомплировать ассемблерный листинг в более-менее человекопонятный псевдокод, по синтаксису похожий на C. В некоторых случаях это значительно облегчает работу. Просто так приобрести IDA Pro частным лицам практически невозможно, и дело не только в непомерной цене, а в том, что автор придерживается абсолютно неадекватной политики в плане продаж. К счастью, несколько последних версий этого замечательного дизассемблера, несмотря на все трудности, были успешно слиты в свободный доступ. Это IDA Pro Advanced 6.8, последняя доступная версия, которая работает с 32-битными системами, а также IDA Pro Advanced 7.0 и IDA Pro Advanced 7.2 для 64-битных систем. Если по каким-то причинам вы не можете использовать варез, то на офсайте есть бесплатные демо-версии с урезанным функционалом.
Скриншот программы Relyze
Relyze - бесплатный интерактивный дизассемблер для исполняемых файлов в формате x86, x64, ARM32 и ARM64. Поддерживает многие фичи, присущие IDA, такие как декомпиляция двоичного кода в C-подобный псевдокод или построение графов. Кроме этого имеет и уникальные для дизассемблера функции, такие как сравнение двух файлов, поддержка плагинов на Ruby. Лично для меня особой ценностью является Data Type Manager, который показывает структуры и интерфейсы, использованные в исследуемой программе, и не просто как факт их наличия, а с описанием их полей, параметров вызова, смещениями в структуре и размером каждого поля. Скачать дистрибутив нужной разрядности можно с офсайта.
Скриншот программы Interactive Delphi Reconstructor
IDR (Interactive Delphi Reconstructor) - бесплатный декомпилятор исполняемых файлов и динамических библиотек. В отличие от IDA Pro, этот декомпилятор создан специально для разбора файлов, написанных на языке Delphi. Сейчас проект прекратил развитие, если какие изменения и вносятся, то исключительно косметические. Исходники для доработки открыты. Лично я пользуюсь стабильным комплектом Interactive Delphi Reconstructor 2.6.0.1.
Скриншот программы VB Decompiler Pro
Еще один специализированный декомпилятор - VB Decompiler Pro. Он работает с программами (EXE, DLL, OCX), написанными на Visual Basic. В случае, если приложение собрано в p-code, декомпилятор может разобрать его практически до исходного кода. Но даже если приложение скомпилировано в native code, в этом случае VB Decompiler анализирует и восстанавливает довольно много инструкций, чтобы насколько это возможно приблизить ассемблерный код к исходному. Это сильно упростит задачу анализа алгоритмов исследуемой программы. Честные граждане могут воспользоваться бесплатной Lite-версией с офсайта, для любителей полных версий софта есть релиз VB Decompiler Pro 10.0. Антивирусы могут ругаться на активатор, но тут вы уже сами решайте что делать.
Конечно, это далеко не полный список инструментов для дизассемблирования и декомпиляции, который есть в свободном доступе. Например, та же набирающая популярность Ghidra от АНБ может составить конкуренцию IDA Pro с Hex-Rays. Но я в этой статье перечислил лишь те программы, которыми пользуюсь сам и которые упоминаются в статьях на этом сайте.