Перейти к содержимому
New

DeepSeek V4.1 Flash free for 7 days: 10M tokens a day after a top-up of $10 or more. Until Nov 30.

How it works

Что такое реверс-инжиниринг: применение, ограничения и правовые основы

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

Начальный8 мин чтенияАвтор: NotHoly

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

Причин заниматься этим много, и большинство из них вполне обычные. Инженер по безопасности проверяет бинарный файл вендора, прежде чем ему доверять. Разработчик пытается заставить закрытый формат файлов работать с открытым инструментом. Студент хочет увидеть, что на самом деле выдаёт компилятор. В статье разобрано, что теряется при компиляции, чем статический анализ отличается от динамического, дизассемблеры от декомпиляторов, для каких законных целей выполняется эта работа и какие правовые границы нужно знать перед началом. В следующей статье серии проводится первый настоящий анализ.

Что выбрасывает компиляция

Компилятор превращает читаемый человеком исходный код в инструкции для процессора. Результат верен для машины и мало говорит о замысле. По пути исчезает многое:

  • Комментарии и форматирование. В вывод они не попадают.
  • Имена. Имена локальных переменных и многих функций отбрасываются, если сборка не сохраняет отладочные символы или файл не поставляется с таблицей символов.
  • Типы. 32-битное целое, флаг и адрес памяти становятся просто битами в регистре или на стеке.
  • Управляющие конструкции. Цикл for превращается в сравнение и переход. Условие if становится условным переходом в другое место файла.
  • Исходное расположение кода. Оптимизаторы переставляют, объединяют и встраивают код, поэтому написанной вами функции может не соответствовать один блок в бинарном файле.

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

В миниатюре это видно на Python 3.12. Пример ниже — функция из трёх строк с комментарием. Байт-код Python отличается от машинного кода, так что считайте это аналогией и не переносите на вывод компилятора C. Суть он всё же показывает.

python
# 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

Скомпилируйте и дизассемблируйте его такими командами:

bash
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)"

На машине, где проверялась статья, вывод был таким:

text
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 на бинарном файле, который вы компилируете сами, разобран первый проект на собранной вами программе.

Частые ошибки

  • Принимать декомпилированный вывод за исходный код. Это интерпретация. Сравнивайте её с дизассемблированным кодом.
  • Считать, что в stripped-файле ничего не осталось. Строки, импорты, константы и поведение обычно сохраняются. Strip удаляет имена, а остальное остаётся.
  • Начинать с неизвестного образца на повседневном компьютере. Используйте изолированную виртуальную машину со снимками.
  • Применять закон одной страны везде. Значение имеют и юрисдикция, и договоры.
  • Путать анализ с доступом. Изучать имеющийся у вас файл и пользоваться живым сервисом, который вы не контролируете, — разные вещи.
  • Публиковать находки до раскрытия. Передайте подробности через процесс вендора, прежде чем писать о них публично.

Что дальше

Изучите процессор, с которым работаете (на современных настольных системах это обычно x86-64 или ARM64), и то, как операционная система передаёт аргументы функциям. Скомпилируйте один и тот же небольшой файл на C дважды компилятором C, которому доверяете: один раз с отладочной информацией и один раз со strip, и сравните, что осталось. Прочитайте документацию Ghidra и README проекта. Затем переходите к практической статье, которая идёт следующей.

Источники

Источники

  1. Ghidra project site
  2. Directive 2009/24/EC on the legal protection of computer programs (EUR-Lex)
  3. U.S. Copyright Office: section 1201 rulemaking
  4. NSA Ghidra repository on GitHub

Ещё по теме: Реверс-инжиниринг

Реверс-инжиниринг

Знакомство с Ghidra на безобидном бинарнике, который вы компилируете сами

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

Средний11 мин чтения