Декомпилятор — это компьютерная программа , которая транслирует исполняемый файл в исходный код высокого уровня . Таким образом, он делает противоположность типичному компилятору , который транслирует язык высокого уровня в язык низкого уровня. В то время как дизассемблеры транслируют исполняемый файл в язык ассемблера , декомпиляторы идут на шаг дальше и транслируют код в язык более высокого уровня, такой как C или Java , требуя более сложных методов. Декомпиляторы обычно не могут идеально восстановить исходный код, поэтому часто создают запутанный код . Тем не менее, они остаются важным инструментом в обратном проектировании компьютерного программного обеспечения .
Термин декомпилятор чаще всего применяется к программе, которая транслирует исполняемые программы (выход компилятора ) в исходный код на (относительно) высокоуровневом языке , который при компиляции создаст исполняемый файл, поведение которого будет таким же, как у исходной исполняемой программы. Для сравнения, дизассемблер транслирует исполняемую программу на язык ассемблера (и ассемблер может быть использован для ее обратной сборки в исполняемую программу).
Декомпиляция — это действие по использованию декомпилятора, хотя этот термин может также относиться к выходным данным декомпилятора. Он может использоваться для восстановления утерянного исходного кода, а также полезен в некоторых случаях для компьютерной безопасности , взаимодействия и исправления ошибок . [1] Успех декомпиляции зависит от количества информации, присутствующей в декомпилируемом коде, и сложности выполняемого над ним анализа. Форматы байт-кода, используемые многими виртуальными машинами (такими как Java Virtual Machine или .NET Framework Common Language Runtime ), часто включают в себя обширные метаданные и высокоуровневые функции, которые делают декомпиляцию вполне осуществимой. Применение отладочных данных , т. е. отладочных символов, может позволить воспроизвести исходные имена переменных и структур и даже номера строк. Машинный язык без таких метаданных или отладочных данных гораздо сложнее декомпилировать. [2]
Некоторые компиляторы и инструменты посткомпиляции создают запутанный код (то есть пытаются создать вывод, который очень трудно декомпилировать, или который декомпилируется в запутанный вывод). Это делается для того, чтобы затруднить обратную разработку исполняемого файла.
Хотя декомпиляторы обычно используются для (вос)создания исходного кода из двоичных исполняемых файлов, существуют также декомпиляторы для преобразования определенных двоичных файлов данных в доступные для чтения и редактирования исходные тексты. [3] [4]
На уровень успеха, достигнутый декомпиляторами, могут влиять различные факторы. К ним относится уровень абстракции исходного языка, если объектный код содержит явную информацию о структуре класса, это помогает процессу декомпиляции. Описательная информация, особенно с подробностями именования, также ускоряет работу компилятора. Более того, менее оптимизированный код быстрее декомпилируется, поскольку оптимизация может вызвать большее отклонение от исходного кода. [5]
Декомпиляторы можно рассматривать как состоящий из ряда фаз, каждая из которых вносит свой вклад в определенные аспекты общего процесса декомпиляции.
Первая фаза декомпиляции загружает и анализирует входной машинный код или двоичный формат файла программы на промежуточном языке . Он должен быть в состоянии обнаружить основные факты о входной программе, такие как архитектура (Pentium, PowerPC и т. д.) и точка входа. Во многих случаях он должен быть в состоянии найти эквивалент функции программы на языке C , которая является началом написанного пользователем кода. Это исключает код инициализации времени выполнения, который не должен декомпилироваться, если это возможно. Если доступны, также загружаются таблицы символов и отладочные данные. Фронтенд может быть в состоянии идентифицировать используемые библиотеки, даже если они связаны с кодом, это предоставит библиотечные интерфейсы. Если он может определить используемый компилятор или компиляторы, он может предоставить полезную информацию для идентификации идиом кода. [6]main
Следующий логический этап — это разборка инструкций машинного кода в машинно-независимое промежуточное представление (IR). Например, машинная инструкция Pentium
mov eax , [ ebx + 0x04 ]
может быть переведено в IR
eax : = m [ ebx + 4 ] ;
Идиоматические последовательности машинного кода — это последовательности кода, объединенная семантика которых не сразу очевидна из индивидуальной семантики инструкций. Либо как часть фазы разборки, либо как часть более поздних анализов, эти идиоматические последовательности должны быть переведены в известный эквивалентный IR. Например, код ассемблера x86 :
cdq eax ; edx устанавливается в знаковое расширение≠edi,edi +(tex)push xor eax , edx sub eax , edx
можно перевести на
еакс := абс(еакс);
Некоторые идиоматические последовательности не зависят от машины; некоторые включают только одну инструкцию. Например, очищает регистр (устанавливает его в ноль). Это можно реализовать с помощью правила упрощения, не зависящего от машины, например .xor eax, eax
eax
a = 0
В целом, лучше всего отложить обнаружение идиоматических последовательностей, если это возможно, на более поздние этапы, которые в меньшей степени зависят от порядка инструкций. Например, фаза планирования инструкций компилятора может вставлять другие инструкции в идиоматическую последовательность или изменять порядок инструкций в последовательности. Процесс сопоставления с образцом на этапе дизассемблирования, вероятно, не распознает измененный образец. Более поздние этапы группируют выражения инструкций в более сложные выражения и преобразуют их в каноническую (стандартизированную) форму, что повышает вероятность того, что даже измененная идиома будет соответствовать образцу более высокого уровня позже в декомпиляции.
Особенно важно распознавать идиомы компилятора для вызовов подпрограмм , обработки исключений и операторов switch . Некоторые языки также имеют обширную поддержку строк или длинных целых чисел .
Различные программные анализы могут быть применены к IR. В частности, распространение выражений объединяет семантику нескольких инструкций в более сложные выражения. Например,
mov eax ,[ ebx + 0x04 ] add eax ,[ ebx + 0x08 ] sub [ ebx + 0x0C ], eax
может привести к следующему IR после распространения выражения:
m[ebx+12] := m[ebx+12] - (m[ebx+4] + m[ebx+8]);
Полученное выражение больше похоже на язык высокого уровня, а также исключило использование машинного регистра eax
. Более поздние анализы могут исключить ebx
регистр.
Места, где определяется и используется содержимое регистра, должны отслеживаться с помощью анализа потока данных . Тот же анализ может быть применен к местоположениям, которые используются для временных и локальных данных. Затем для каждого такого связанного набора определений и использования значений может быть сформировано другое имя. Возможно, что одно и то же местоположение локальной переменной использовалось для более чем одной переменной в разных частях исходной программы. Хуже того, анализ потока данных может определить путь, по которому значение может перетекать между двумя такими использованиями, даже если это никогда не произойдет или не будет иметь значения в реальности. В плохих случаях это может привести к необходимости определять местоположение как объединение типов. Декомпилятор может позволить пользователю явно разрывать такие неестественные зависимости, что приведет к более ясному коду. Это, конечно, означает, что переменная потенциально используется без инициализации, и поэтому указывает на проблему в исходной программе. [ необходима цитата ]
Хороший декомпилятор машинного кода выполнит анализ типов. Здесь способ использования регистров или ячеек памяти приводит к ограничениям на возможный тип ячейки. Например, and
инструкция подразумевает, что операнд является целым числом; программы не используют такую операцию для значений с плавающей точкой (за исключением специального библиотечного кода) или для указателей . add
Инструкция приводит к трем ограничениям, поскольку операнды могут быть как целыми числами, так и одним целым числом и одним указателем (с целым числом и указателем соответственно; третье ограничение возникает из-за упорядочивания двух операндов, когда типы различны). [7]
Различные выражения высокого уровня могут быть распознаны, что запускает распознавание структур или массивов. Однако трудно различить многие возможности из-за свободы, которую машинный код или даже некоторые языки высокого уровня, такие как C, допускают с приведениями и арифметикой указателей.
Пример из предыдущего раздела может привести к следующему высокоуровневому коду:
структура T1 * ebx ; структура T1 { int v0004 ; int v0008 ; int v000C ; }; ebx -> v000C -= ebx -> v0004 + ebx -> v0008 ;
Предпоследняя фаза декомпиляции включает в себя структурирование IR в конструкции более высокого уровня, такие как while
циклы и if/then/else
условные операторы. Например, машинный код
xor eax , eax l0002: или ebx , ebx jge l0003 add eax ,[ ebx ] mov ebx ,[ ebx + 0x4 ] jmp l0002 l0003: mov [ 0x10040000 ], eax
можно перевести как:
eax = 0 ; пока ( ebx < 0 ) { eax += ebx -> v0000 ; ebx = ebx -> v0004 ; } v10040000 = eax ;
Неструктурированный код сложнее перевести в структурированный, чем уже структурированный код. Решения включают репликацию некоторого кода или добавление булевых переменных. [8]
Заключительная фаза — это генерация высокоуровневого кода в бэкенде декомпилятора. Так же, как компилятор может иметь несколько бэкендов для генерации машинного кода для разных архитектур, декомпилятор может иметь несколько бэкендов для генерации высокоуровневого кода на разных высокоуровневых языках.
Непосредственно перед генерацией кода может быть желательно разрешить интерактивное редактирование IR, возможно, с использованием какой-либо формы графического пользовательского интерфейса . Это позволит пользователю вводить комментарии и неуниверсальные имена переменных и функций. Однако их почти так же легко вводить в редактировании после декомпиляции. Пользователь может захотеть изменить структурные аспекты, например, преобразовать цикл while
в for
цикл. Их сложнее изменить с помощью простого текстового редактора, хотя инструменты рефакторинга исходного кода могут помочь в этом процессе. Пользователю может потребоваться ввести информацию, которая не была идентифицирована на этапе анализа типа, например, изменить выражение памяти на выражение массива или структуры. Наконец, может потребоваться исправить неправильный IR или внести изменения, чтобы сделать выходной код более читаемым.
Разработаны декомпиляторы с использованием нейронных сетей . Такой декомпилятор может быть обучен машинным обучением для улучшения своей точности с течением времени. [9]
Большинство компьютерных программ подпадают под действие законов об авторском праве . Хотя точный объем того, что подпадает под действие авторского права, различается от региона к региону, закон об авторском праве обычно предоставляет автору (программисту(ам) или работодателю) набор исключительных прав на программу. [10] Эти права включают право делать копии, включая копии, сделанные в оперативной памяти компьютера (если только создание такой копии не является необходимым для использования программы). [11] Поскольку процесс декомпиляции включает в себя создание нескольких таких копий, он, как правило, запрещен без разрешения владельца авторских прав. Однако, поскольку декомпиляция часто является необходимым шагом для достижения совместимости программного обеспечения , законы об авторском праве как в Соединенных Штатах, так и в Европе разрешают декомпиляцию в ограниченной степени.
В Соединенных Штатах защита добросовестного использования авторских прав успешно применялась в делах о декомпиляции. Например, в деле Sega против Accolade суд постановил, что Accolade может законно заниматься декомпиляцией, чтобы обойти механизм блокировки программного обеспечения, используемый игровыми консолями Sega. [12] Кроме того, Закон об авторском праве в цифровую эпоху (PUBLIC LAW 105–304 [13] ) имеет надлежащие исключения как для тестирования и оценки безопасности в §1201(i), так и для обратного проектирования в §1201(f). [14]
В Европе Директива о программном обеспечении 1991 года прямо предусматривает право на декомпиляцию для достижения совместимости. Результатом жарких дебатов между, с одной стороны, сторонниками защиты программного обеспечения, и, с другой стороны, учеными, а также независимыми разработчиками программного обеспечения, Статья 6 разрешает декомпиляцию только при соблюдении ряда условий:
Кроме того, статья 6 предписывает, что информация, полученная путем декомпиляции, не может быть использована в других целях и не может быть передана другим лицам.
В целом, право на декомпиляцию, предусмотренное статьей 6, кодифицирует то, что, как утверждается, является общепринятой практикой в индустрии программного обеспечения. Известно, что лишь немногие европейские иски возникли из права на декомпиляцию. Это можно интерпретировать как одно из трех:
В отчете 2000 года о реализации Директивы о программном обеспечении государствами-членами ЕС Европейская комиссия , по-видимому, поддержала вторую интерпретацию. [16]
[…] Анализатор, валидатор и декомпилятор файлов кодовых страниц .CPI и .CP […] Обзор параметров /Style: […] Исходные файлы ASM include […] Отдельные исходные файлы ASM […] Модульные исходные файлы ASM […]