Реверс-инжиниринг
Зачем использовать NotHoly для реверс-инжиниринга
Оставьте Ghidra, radare2 или IDA. Вставьте вывод декомпилятора: модель объяснит и напишет скрипт, не отказываясь. Статический triage для начала.
Что теряется при компиляции, чем дизассемблеры отличаются от декомпиляторов и какие правовые границы нужно знать перед анализом бинарного файла.
Реверс-инжиниринг — это работа, в которой устройство готовой вещи выясняют, изучая её саму. В программном обеспечении это обычно скомпилированная программа: исполняемый файл, разделяемая библиотека или образ прошивки. Исходного кода нет или его никогда не публиковали, поэтому вы начинаете с машинного кода и движетесь к пониманию того, что делает программа и зачем.
Причин заниматься этим много, и большинство из них вполне обычные. Инженер по безопасности проверяет бинарный файл вендора, прежде чем ему доверять. Разработчик пытается заставить закрытый формат файлов работать с открытым инструментом. Студент хочет увидеть, что на самом деле выдаёт компилятор. В статье разобрано, что теряется при компиляции, чем статический анализ отличается от динамического, дизассемблеры от декомпиляторов, для каких законных целей выполняется эта работа и какие правовые границы нужно знать перед началом. В следующей статье серии проводится первый настоящий анализ.
Компилятор превращает читаемый человеком исходный код в инструкции для процессора. Результат верен для машины и мало говорит о замысле. По пути исчезает многое:
for превращается в сравнение и переход. Условие if становится условным переходом в другое место файла.Каждая потеря делает следующий шаг менее определённым. Часть этих сведений реверсер восстанавливает по косвенным признакам: по строкам программы, по функциям, импортируемым из операционной системы, и по движению данных через регистры.
В миниатюре это видно на Python 3.12. Пример ниже — функция из трёх строк с комментарием. Байт-код Python отличается от машинного кода, так что считайте это аналогией и не переносите на вывод компилятора C. Суть он всё же показывает.
# Teaching constant, not real access control.
def check_pin(user_pin):
training_pin = 2468 # the comment never reaches the bytecode
return user_pin == training_pinСкомпилируйте и дизассемблируйте его такими командами:
python -c "import py_compile; py_compile.compile('check_pin.py', cfile='check_pin.pyc', doraise=True)"
python -c "import marshal, dis; data = open('check_pin.pyc','rb').read()[16:]; code = marshal.loads(data); fn = [c for c in code.co_consts if hasattr(c,'co_code')][0]; print('name:', fn.co_name, '| constants:', fn.co_consts, '| locals:', fn.co_varnames); dis.dis(fn)"На машине, где проверялась статья, вывод был таким:
name: check_pin | constants: (None, 2468) | locals: ('user_pin', 'training_pin')
2 0 RESUME 0
3 2 LOAD_CONST 1 (2468)
4 STORE_FAST 1 (training_pin)
4 6 LOAD_FAST 0 (user_pin)
8 LOAD_FAST 1 (training_pin)
10 COMPARE_OP 40 (==)
14 RETURN_VALUEЗаметны две вещи. Комментарий исчез: поиск его текста в скомпилированном файле ничего не находит. Имена сохранились, потому что Python хранит их в объекте кода. Скомпилированная программа на C часто не сохраняет ни того, ни другого, поэтому то же упражнение на нативном бинарном файле сложнее. Используйте этот пример как мысленную модель, для всех языков он ничего не гарантирует.
Методы реверс-инжиниринга делятся на две большие группы.
Статический анализ изучает файл без запуска. Вы читаете инструкции, перечисляете импорты и строки, составляете карту функций и перекрёстных ссылок и строите модель программы. Файл не исполняется, но ваши инструменты его разбирают, поэтому обновляйте их и работайте на изолированной машине, если образец может быть враждебным. Статическая работа идёт медленнее, зато её можно повторить, и большинство начинает именно с неё.
Динамический анализ запускает программу под наблюдением. Отладчик останавливает её на точках останова, чтобы вы могли прочитать регистры и память. Трассировщик записывает системные вызовы, обращения к файлам и сетевые соединения. Песочница фиксирует изменения на диске. Динамическая работа показывает реальное поведение, включая значения, существующие только во время выполнения, но приходится исполнять код, которому вы можете не доверять. Делайте это в одноразовой виртуальной машине без настоящих учётных записей, общих папок и конфиденциальных данных и, в идеале, без доступа к сети.
В реальной работе эти подходы обычно сочетают. Статический проход находит функцию, проверяющую пароль. Запуск под отладчиком показывает, что она на самом деле получает на вход и что возвращает.
Дизассемблер преобразует байты в ассемблерные инструкции конкретной процессорной архитектуры. Перевод механический: шаблон байтов превращается в мнемонику вроде mov, cmp или jne с операндами. Результат в точности соответствует машине, но остаётся низкоуровневым. Чтобы разобраться в одной возможности программы, приходится долго читать тысячи инструкций.
Декомпилятор идёт дальше. Из тех же байтов он пытается получить высокоуровневый псевдокод, обычно похожий на C. Он восстанавливает границы функций, управляющие конструкции вроде if и while и приблизительные типы данных. Его вывод — это интерпретация, а не копия исходников. Декомпилятор может ошибиться в типах, объединить раздельные вещи или придумать структуру, которой никогда не было. Хорошая привычка: читать декомпилированный код рядом с дизассемблированным и при расхождении верить ассемблеру.
Ghidra — один из самых распространённых бесплатных инструментов, которые умеют и то и другое. Этот фреймворк для реверс-инжиниринга создан и поддерживается Research Directorate в U.S. National Security Agency, распространяется по лицензии Apache-2.0 и работает на Windows, macOS и Linux. Ghidra поддерживает множество процессорных архитектур и форматов файлов, а расширять её можно на Java или Python. Для запуска официального релиза её README требует JDK 25 (64-bit). Есть и другие инструменты, коммерческие и открытые, и идеи этой статьи применимы ко всем.
У реверс-инжиниринга есть несколько законных применений.
Совместимость. Вашей программе нужно обмениваться данными с закрытой программой или форматом, а вендор не публикует подробностей. Европейский союз рассматривает этот случай отдельно, как объясняется в правовом разделе.
Поиск уязвимостей. Вы изучаете программное обеспечение, которое вам разрешено тестировать, находите ошибки и сообщаете о них, чтобы вендор мог их исправить. Обычно это идёт по схеме coordinated disclosure: закрытый отчёт, срок на исправление, затем публикация подробностей.
Анализ вредоносного ПО. Защитники разбирают подозрительный файл, чтобы узнать, что он делает, какие системы затрагивает и как его обнаружить. Многое из того, что публично известно об угрозах, получено благодаря этой работе.
Обучение. Чтение скомпилированной программы — один из лучших способов понять, как компьютеры выполняют код: раскладку памяти, соглашения о вызовах и разрыв между тем, что вы пишете, и тем, что исполняется.
Проверка заявлений. Возможно, вы захотите проверить, что программа отправляет по сети или хранит локально, особенно когда речь о приватности. Здесь возникают те же правовые вопросы, что и при любом другом анализе.
Это обзор, юридической консультацией он не считается. Законы различаются по странам, а договоры могут добавлять условия, которые действуют везде. Главных уровней три.
1. Условия лицензии. Большинство коммерческих лицензий запрещают реверс-инжиниринг, кроме случаев, когда его допускает закон. Лицензии open source устроены иначе и часто разрешают читать и изменять код, но условия есть и у них. Чтение полученного вами исходного кода и реверс бинарного файла — разные вещи, и ни то, ни другое не равно взлому сервиса. Прежде чем анализировать чужой продукт, прочитайте его лицензию.
2. Европейский союз. Directive 2009/24/EC on the legal protection of computer programs содержит в Article 6 исключение для совместимости. Если коротко, законный пользователь или тот, кто действует от его имени, может декомпилировать программу, когда это необходимо для получения сведений, нужных, чтобы независимо созданная программа работала с другими. Есть условия. Эти сведения не должны быть уже легко доступны, работа должна ограничиваться частями программы, нужными для совместимости, а сведения нельзя использовать для других целей или передавать сверх того, что требует совместимость. Их нельзя использовать для разработки существенно похожей программы, и исключение не может необоснованно вредить законным интересам правообладателя. Article 6 касается совместимости, общее любопытство оно не покрывает.
3. Соединённые Штаты. Section 1201 закона Digital Millennium Copyright Act запрещает обход технических мер, контролирующих доступ к произведениям, защищённым авторским правом. Этот запрет отделён от нарушения авторских прав, так что нарушить правило можно, ничего не копируя. В законе есть исключения, среди них section 1201(j), которая при определённых условиях охватывает некоторое тестирование безопасности. Copyright Office также раз в три года проводит процедуру, которая может добавить временные исключения. Его страница о section 1201, ссылка на которую есть в источниках, описывает этот процесс. На момент проверки началась десятая такая процедура, и продлённые в ней исключения будут действовать три года, с октября 2027 по октябрь 2030. Прочитайте закон и действующие нормы, прежде чем полагаться на какое-либо исключение. Силу имеет сам текст, а не пересказ.
В других странах свои правила. Считайте три уровня выше примерами того, как ставятся эти вопросы. Вашу юрисдикцию они не описывают.
Отсюда три практических вывода. Анализируйте программное обеспечение, которым владеете, свои сборки или материалы, на тестирование которых есть разрешение. Ведите тестирование только в системах, которые контролируете или на которые есть письменное разрешение. Если проект несёт правовые риски, обратитесь к юристу до начала работы, а не после публикации.
Безопаснее всего учиться на своём коде. Напишите небольшую программу, скомпилируйте её и проанализируйте результат. Исходник вам известен, поэтому каждую догадку можно сверить с истиной. Заодно вы избегаете правовых вопросов, связанных с чужим ПО, и риска запустить неизвестный образец.
Чтобы практика оставалась чистой, держитесь двух правил. Никогда не запускайте неизвестные бинарные файлы на своей повседневной машине. Никогда не направляйте инструменты анализа или сканирования на системы, которыми не владеете или на тестирование которых нет письменного разрешения, даже если сайт выглядит безобидно. В следующей статье, первый анализ в Ghidra на бинарном файле, который вы компилируете сами, разобран первый проект на собранной вами программе.
Изучите процессор, с которым работаете (на современных настольных системах это обычно x86-64 или ARM64), и то, как операционная система передаёт аргументы функциям. Скомпилируйте один и тот же небольшой файл на C дважды компилятором C, которому доверяете: один раз с отладочной информацией и один раз со strip, и сравните, что осталось. Прочитайте документацию Ghidra и README проекта. Затем переходите к практической статье, которая идёт следующей.
Реверс-инжиниринг
Оставьте Ghidra, radare2 или IDA. Вставьте вывод декомпилятора: модель объяснит и напишет скрипт, не отказываясь. Статический triage для начала.
Реверс-инжиниринг
Скомпилируйте игрушечную проверку лицензии на C двумя способами, затем найдите её строку в Ghidra, пройдите по ссылкам и сравните вывод декомпилятора со своим исходником.