significado msil language intermediate common .net bytecode cil decompiling intermediate-language

msil - ¿Por qué es tan fácil descompilar el código.NET IL?



common intermediate language (4)

¿Por qué es tan fácil descompilar código .NET IL en código fuente, en comparación con descompilar binarios nativos x86? (Reflector produce un código fuente bastante bueno la mayor parte del tiempo, mientras que descompilar la salida de un compilador de C ++ es casi imposible).

¿Es porque IL contiene muchos metadatos? ¿O es porque IL es una abstracción más alta que las instrucciones x86? Hice una investigación y encontré los siguientes dos artículos útiles, pero ninguno de ellos responde mi pregunta.


C # e IL casi se asignan uno a uno. (Esto es menos con algunas características más nuevas de C # 3.0). La proximidad de la asignación (y la falta de un optimizador en el compilador de C #) hace que las cosas sean tan "reversibles".


Extendiendo la respuesta correcta de Brian

Si crees que todo IL es fácilmente descompilable, sugiero escribir un programa F # no trivial e intentar descompilar ese código. F # hace muchas transformaciones de código y, por lo tanto, tiene un mapeo muy pobre de la IL real emitida y la base de código original. En mi humilde opinión, es mucho más difícil mirar el código F # descompilado y recuperar el programa original que para C # o VB.Net.


Hay una serie de cosas que hacen que la ingeniería inversa sea bastante fácil.

  • Clasificar información. Esto es masivo En el ensamblador x86, debes inferir los tipos de variables en función de cómo se utilizan.

  • estructura. La información sobre la estructura de la aplicación está más disponible en los desensamblajes. Esto, combinado con la información de tipo, le brinda una increíble cantidad de datos. Está trabajando a un nivel bastante alto en este punto (en relación con el ensamblador x86). En el ensamblador nativo, debe inferir los diseños de estructura (e incluso el hecho de que son estructuras) en función de cómo se utilizan los datos. No es imposible, pero consume mucho más tiempo.

  • nombres Conocer los nombres de las cosas puede ser útil.

Estas cosas, combinadas, significa que tienes bastante información sobre el ejecutable. Básicamente, Il está trabajando a un nivel mucho más cercano al origen que un compilador de código nativo. Cuanto más alto es el nivel en el que trabaja el código de bytes, más fácil es la ingeniería inversa, en términos generales.


Creo que ya tienes los bits más importantes.

  • Como dices, hay más metadatos disponibles. No conozco los detalles de lo que emite un compilador de C o C ++, pero sospecho que se incluyen muchos más nombres e información similar en IL. Solo mire lo que el descompilador sabe acerca de lo que hay en un marco de pila en particular, por ejemplo, en lo que respecta al x86, solo sabe cómo se usa la pila; en IL sabes lo que representan los contenidos de la pila (o al menos, el tipo, ¡no el significado semántico!)
  • Nuevamente, como ya mencionó, IL es una abstracción de nivel más alto que x86. x86 no tiene idea de lo que es una llamada de método o función, un evento, una propiedad, etc. IL tiene toda esa información aún dentro de ella.
  • Normalmente, los compiladores C y C ++ optimizan mucho más que (por ejemplo) el compilador C #. Esto se debe a que el compilador de C # asume que la mayor parte de la optimización aún puede realizarse más tarde, mediante el JIT. De alguna manera, tiene sentido que el compilador de C # no intente hacer mucha optimización, ya que hay varios bits de información que están disponibles para el JIT pero no el compilador de C #. El código optimizado es más difícil de descompilar, porque está más lejos de ser una representación natural del código fuente original.
  • IL fue diseñado para ser compilado por JIT; x86 fue diseñado para ejecutarse de forma nativa (es decir, a través de microcódigo). La información que necesita el compilador JIT es similar a la que desearía un descompilador, por lo que un descompilador tiene un tiempo más fácil con IL. En cierto modo, esto es solo una reafirmación del segundo punto.