Байт-код Java и Python относительно легко декомпилировать, чем скомпилированный машинный код, сгенерированный компилятором C / C ++.
Я не могу найти убедительный ответ, почему информация из опции -g недостаточна для декомпиляции, но достаточна для отладки?
Что такое дополнительный материал, содержащийся в байт-коде Python / Java, который облегчает декомпиляцию?
Я не могу найти убедительный ответ, почему информация из опции -g недостаточна для декомпиляции, но достаточна для отладки?
Отладочная информация в основном содержит только отображение между адресами в сгенерированном коде и номерами строк исходных файлов. Отладчику не нужно декомпилировать код — он просто показывает исходные коды. Если исходные файлы отсутствуют, отладчик не будет магически показывать их.
Тем не менее, наличие отладочной информации облегчает декомпиляцию. Если информация отладки включает в себя макет используемых типов и прототипов функций, декомпилятор может использовать ее и обеспечить гораздо более точную декомпиляцию. Однако во многих случаях он все равно будет отличаться от исходного источника.
Например, вот функция, декомпилированная с помощью декомпилятора Hex-Rays без использования информации отладки:
int __stdcall sub_4050A0(int a1)
{
int result; // eax@1
result = a1;
if ( *(_BYTE *)(a1 + 12) )
{
result = sub_404600(*(_DWORD *)a1);
*(_BYTE *)(a1 + 12) = 0;
}
return result;
}
Поскольку он не знает тип a1
доступ к его полям представлен в виде дополнений и приведений.
И вот та же функция после загрузки файла символов:
void __thiscall mytree::write_page(mytree *this, PAGE *src)
{
if ( src->isChanged )
{
cache::set_changed(this->cache, src->baseAddr);
src->isChanged = 0;
}
}
Вы можете видеть, что это было улучшено довольно много.
Что касается того, почему декомпилирование байт-кода обычно проще, в дополнение к проверке ответа NPE также этот.
Вот несколько причин для этого:
Некоторые процессоры, такие как x86, имеют инструкции переменной длины. Если управление передается в середину (= где-нибудь после первого байта) инструкции, это также может быть допустимой командой (или несколькими инструкциями). Это затрудняет однозначную разборку машинного кода. Код C / C ++ может использовать эту функцию.
На некоторых процессорах и ОС можно выполнять данные, как если бы они были кодом, и использовать код, как если бы они были данными. Это затрудняет однозначное разделение двух. И, опять же, это то, что программы на C / C ++ часто могут делать легко.
На некоторых процессорах и операционных системах легко генерировать код на лету и выполнять его, а также можно изменять существующий код во время выполнения. Это также способствует неоднозначности в декомпиляции кода. И программы на C / C ++ часто могут это делать.
РЕДАКТИРОВАТЬКроме того, некоторые процессоры имеют несколько разных кодировок для одной и той же инструкции. Например, процессоры x86 имеют 2 инструкции mov reg, reg/mem
а также mov reg/mem, reg
, Они позволяют перемещать данные между регистром и ячейкой памяти (в любом направлении) и между двумя регистрами. Обе эти инструкции могут использоваться для передачи данных между двумя регистрами, но они имеют разные кодировки. Если программа каким-то образом полагается на определенную кодировку (например, с целью проверки ее целостности с помощью контрольных сумм), то при разборке, например, mov eax, ebx
Вы не сможете сказать, какой из двух mov
инструкции, как это было изначально, и поэтому, если вы попытаетесь собрать разборку, вы можете сломать программу.
Вы можете использовать отладчик для отладки программы с информацией об отладке / символах или без нее. Эта информация только облегчает человеку навигацию по коду и данным, поскольку многие (но не обязательно все) подпрограммы и переменные могут быть идентифицированы и показаны с использованием их имен и типов, а не только необработанных адресов и необработанных данных без типов.
Я предполагаю, что различные байт-коды менее двусмысленны и более ограничены в том, что они могут делать, и это облегчает их декомпиляцию.