Почему так легко декомпилировать код .NET IL? - PullRequest
16 голосов
/ 22 марта 2009

Почему так легко декомпилировать IL-код .NET в исходный код по сравнению с декомпиляцией собственных двоичных файлов x86? (Reflector создает довольно хороший исходный код большую часть времени, в то время как декомпиляция вывода компилятора C ++ практически невозможна.)

Это потому, что IL содержит много метаданных? Или потому, что IL - более высокая абстракция, чем инструкции x86? Я провел небольшое исследование и нашел следующие две полезные статьи, но ни одна из них не отвечает на мой вопрос.

Ответы [ 4 ]

24 голосов
/ 22 марта 2009

Я думаю, у вас уже есть самые важные биты.

  • Как вы говорите, доступно больше метаданных. Я не знаю деталей того, что испускается компилятором C или C ++, но я подозреваю, что far больше имен и подобной информации включены в IL. Посмотрите, например, что знает декомпилятор о том, что находится в конкретном фрейме стека - что касается x86, вы знаете только, как используется стек ; в IL вы знаете, что содержимое стека представляет (или, по крайней мере, тип - не семантическое значение!)
  • Опять же, как вы уже упоминали, IL - это абстракция более высокого уровня, чем x86. x86 понятия не имеет, что такое вызов метода или функции, или событие, или свойство и т. д. IL хранит всю эту информацию внутри себя.
  • Обычно компиляторы C и C ++ оптимизируют гораздо сильнее, чем, скажем, компилятор C #. Это связано с тем, что компилятор C # предполагает, что большая часть оптимизации может быть выполнена позже - с помощью JIT. В некотором смысле имеет смысл для компилятора C # , а не пытаться выполнить большую оптимизацию, поскольку есть различные фрагменты информации, которые доступны JIT, но не компилятору C #. Оптимизированный код сложнее декомпилировать, поскольку он далеко не является естественным представлением исходного исходного кода.
  • IL был разработан для JIT-компиляции; x86 был спроектирован так, чтобы он выполнялся изначально (правда, через микрокод). Информация, в которой нуждается JIT-компилятор, аналогична той, которая нужна декомпилятору, поэтому декомпилятору легче работать с IL. В некотором смысле это просто повторение второго пункта.
9 голосов
/ 22 марта 2009

Есть ряд вещей, которые делают обратный инжиниринг довольно простым.

  • Тип информации. Это массивно. В ассемблере x86 вы должны определить типы переменных в зависимости от того, как они используются.

  • структура. Информация о структуре приложения более доступна в ил разборки. Это, в сочетании с информацией о типе, дает вам огромное количество данных. В данный момент вы работаете на довольно высоком уровне (относительно ассемблера x86). В нативном ассемблере вы должны определить структуру структур (и даже тот факт, что они являются структурами) на основе того, как используются данные. Не невозможно, но гораздо больше времени.

  • имена. Знание названий вещей может быть полезным.

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

4 голосов
/ 22 марта 2009

C # и IL почти отображаются один на один. (Это не так с некоторыми новыми функциями C # 3.0.) Близость отображения (и отсутствие оптимизатора в компиляторе C #) делает вещи такими «обратимыми».

3 голосов
/ 22 марта 2009

Расширение правильного ответа Брайана

Если вы считаете, что все IL легко декомпилируются, я предлагаю написать нетривиальную F # -программу и попытаться декомпилировать этот код. F # выполняет много преобразований кода и, следовательно, имеет очень плохое отображение из фактического выданного IL и исходной базы кода. ИМХО, гораздо сложнее взглянуть на декомпилированный код F # и вернуть исходную программу, чем для C # или VB.Net.

...