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