Почему процесс дизассемблирования собственного образа Win32 (встроенного в C/С++, например,) намного сложнее, чем дизассемблирование .NET-приложения?
В чем главная причина? Из-за чего?
Почему процесс дизассемблирования собственного образа Win32 (встроенного в C/С++, например,) намного сложнее, чем дизассемблирование .NET-приложения?
В чем главная причина? Из-за чего?
A.net-сборка встроена в Common Intermediate Language. Он не компилируется до тех пор, пока он не будет выполнен, когда CLR скомпилирует его для запуска в соответствующей системе. CIL имеет множество метаданных, поэтому их можно скомпилировать на разных процессорных архитектурах и разных операционных системах (в Linux, используя Mono). Классы и методы остаются в основном неповрежденными.
.net также позволяет отражать, что требует хранения метаданных в двоичных файлах.
Код C и С++ скомпилирован в выбранную архитектуру процессора и систему при компиляции. Исполняемый файл, скомпилированный для Windows, не будет работать в Linux и наоборот. Результатом компилятора C или С++ является инструкция по сборке. Функции в исходном коде могут не существовать как функции в двоичном формате, а каким-то образом оптимизироваться. У компиляторов также могут быть довольно агрессивные оптимизаторы, которые возьмут логически структурированный код и сделают его похожим. Код будет более эффективным (во времени или в пространстве), но может сделать его более трудным для изменения.
Благодаря внедрению .NET, позволяющему взаимодействовать между языками, такими как С#, VB и даже C/С++ через CLI и CLR, это означает, что дополнительные метаданные должны быть помещены в объектные файлы для правильной передачи свойств класса и объекта, Это упрощает дизассемблирование, поскольку двоичные объекты все еще содержат эту информацию, тогда как C/С++ может отбросить эту информацию, поскольку она не является необходимой (по крайней мере для выполнения кода, информация по-прежнему требуется во время компиляции).
Эта информация обычно ограничивается полями и объектами, связанными с классом. Переменные, выделенные в стеке, вероятно, не будут содержать аннотации в сборке релизов, поскольку их информация не требуется для взаимодействия.
Еще одна причина - оптимизация, выполняемая большинством компиляторов С++ при создании окончательных двоичных файлов, не выполняется на уровне IL для управляемого кода.
В результате что-то вроде итерации над контейнером будет выглядеть как пара inc
/jnc
инструкции по сборке для собственного кода по сравнению с вызовами функций со значимыми именами в IL. Результат исполняемого кода может быть одним и тем же (или, по крайней мере, близко), поскольку компилятор JIT будет вызывать некоторые вызовы, похожие на собственный компилятор, но код, который можно посмотреть, гораздо читабельнее на земле CLR.
Люди упомянули некоторые из причин; Я упомянул еще один, предполагая, что мы говорим о разборке, а не декомпиляции.
Проблема с кодом x86 заключается в том, что различение кода и данных очень сложно и подвержено ошибкам. Дисассемблеры должны полагаться на угадывание, чтобы понять это, и они почти всегда чего-то пропускают; напротив, промежуточные языки предназначены для "дизассемблирования" (так что компилятор JIT может превратить "разборку" в машинный код), поэтому они не содержат двусмысленностей, подобных тому, который вы найдете в машинных кодах. Конечным результатом является то, что разбор кода IL довольно тривиален.
Если вы говорите об декомпиляции, это другое дело; это связано с (главным образом) отсутствием оптимизаций для приложений .NET. Большинство оптимизаций выполняется компилятором JIT, а не С#/VB.NET/etc. компилятор, поэтому код сборки почти соответствует 1:1 исходному коду, так что выяснение оригинала вполне возможно. Но для собственного кода существует миллион различных способов перевода нескольких исходных строк (черт, даже не-ops имеют gazillion различные способы написания с различными характеристиками производительности!), Поэтому довольно сложно понять, что такое оригинал.
В общем случае нет никакой разницы между дизассемблированием кода С++ и .NET. Из-за С++ сложнее разобрать, потому что он делает больше оптимизаций и тому подобное, но это не основная проблема.
Основная проблема заключается в именах. В разобранном С++-коде есть все, что называется A, B, C, D,... A1 и т.д. Если вы не смогли распознать алгоритм в таком формате, вы не сможете извлечь из дизассемблированного бинарного файла С++ информацию.
Библиотека .NET с другой стороны содержит в себе имена методов, параметров метода, имена классов и имена полей класса. Это значительно упрощает понимание дизассемблированного кода. Все остальные вещи являются второстепенными.
Кроме того, что-то о метаданных, отладочная информация и все технические причины указывают на другие ответы; о чем я думал:
Основная причина, по которой вам кажется, что дизассемблирование win32
более сложна, чем программы .Net
, связано с перспективой человека.
С точки зрения машины, собственный код намного более прозрачен, даже при обработке обратной инженерии.
Напротив, я хотел бы сказать, что для более сложного дизассемблирования .Net
приложений/библиотек CAN будет сложнее , если код был запутан.
Вам может показаться трудным разобрать собственные программы win32
, потому что его природа состоит из машинного кода. Но на самом деле, по аналогии с физическим миром и психикой, я думаю, что машинный код больше похож на физический - он действует на то, что он на самом деле делает. Хотя обратная инженерия программ win32
может быть очень сложной, код находится в области набора команд для процессоров. Самое сложное может быть:
Есть количество обфускаторов и де-обфускаторов для .Net
, реализованных в разных техниках. Вполне возможно, что приложения .Net
гораздо сложнее разобрать, чем win32
программы. По этой причине большинство программ на базе виртуальной машины легче разобрать, я думаю, что есть следующие соображения, чтобы они не были слишком запутанными:
Если вы прочитали код OpCodes
структуры .Net
, и вы понимаете, что существуют более сложные концепции уровня языка и ООП. Например, с помощью Reflection.Emit
вы можете испустить код операции вызова конструктора, метода или виртуального метода. Да, он основан на MSIL(CIL)
и работает под CLR
; но это не значит, что его легче разобрать; это может быть сделано запутанным образом и становится намного сложнее изменить исходный код; как и в психическом мире, всегда более непроницаем, чем физический мир.