Perché è così facile da decompilare il codice .NET IL?
-
21-08-2019 - |
Domanda
Perché è così facile da decompilare .NET IL-codice nel codice sorgente, rispetto al decompilazione file binari x86 nativa? (Riflettore produce abbastanza buona codice sorgente maggior parte del tempo, mentre decompilazione l'output di un compilatore C ++ è quasi impossibile.)
E 'perché IL contiene un sacco di meta dati? O è perché IL è un'astrazione maggiore di istruzioni x86? Ho fatto qualche ricerca e ho trovato i seguenti due articoli Utili, ma nessuno dei due risponde alla mia domanda.
Soluzione
Credo che hai già i pezzi più importanti.
- Come dici tu, non c'è più metadati disponibili. Non conosco i dettagli di ciò che viene emesso da un compilatore C o C ++, ma ho il sospetto molto più nomi e informazioni simili sono inclusi in IL. Basta guardare a ciò che il decompilatore sa di cosa c'è in un particolare stack frame, per esempio - per quanto riguarda il 86 è interessato, si conosce solo come la pila è utilizzato ; in IL si sa che cosa il contenuto della pila rappresentano (o almeno, il tipo - non il significato semantico)
- Anche in questo caso, come si è già accennato, IL è un'astrazione di livello superiore rispetto x86. x86 non ha idea di quello che una chiamata di metodo o una funzione è, o di un evento, o una proprietà, ecc IL dispone di tutte le informazioni ancora al suo interno.
- In genere C e C ++ compilatori ottimizzare molto più pesantemente di (diciamo) il compilatore C #. Questo perché il compilatore C # presuppone che la maggior parte della ottimizzazione può ancora essere eseguita dopo - dal JIT. In un certo senso ha senso per il compilatore C # non per cercare di fare molto di ottimizzazione, in quanto vi sono diversi bit di informazioni che sono disponibili per il JIT ma non il compilatore C #. codice ottimizzato è più difficile da decompilare, perché è lontano dall'essere una rappresentazione naturale del codice sorgente originale.
- IL stato progettato per essere JIT-compilato; 86 è stato progettato per essere eseguito in modo nativo (dichiaratamente tramite micro-code). Le informazioni il compilatore JIT ha bisogno è simile a quella che un decompilatore vorrebbe, quindi un decompilatore ha un tempo più facile con IL. In un certo senso questo è davvero solo una riaffermazione del secondo punto.
Altri suggerimenti
Ci sono una serie di cose che rendono il rovescio di ingegneria abbastanza facile.
-
digitare le informazioni. Questo è massiccia. In assembler x86, si deve dedurre i tipi di variabili in base a come vengono utilizzati.
-
Struttura. Informazioni sulla struttura dell'applicazione è più disponibile in smontaggi IL. Questo, combinato con informazioni sul tipo, ti dà una quantità impressionante di dati. Stai lavorando ad un livello piuttosto alto, a questo punto (rispetto al assembler x86). In assembler nativo, si deve dedurre i layout di struttura (e anche il fatto che si tratta di strutture) in base a come vengono utilizzati i dati. Non impossibile, ma molto di più in termini di tempo.
-
nomi. Conoscere i nomi delle cose può essere utile.
Queste cose, in combinazione, significa che hai un sacco di dati circa l'eseguibile. Il è fondamentalmente lavorando ad un livello molto più vicino alla sorgente di un compilatore di codice nativo sarebbe. Il livello più alto è il bytecode lavora, il reverse engineering più semplice è, in generale.
C # e IL quasi mappa uno-a-uno. (Questo è meno così con alcune più recenti C # 3.0 caratteristiche.) La vicinanza della mappatura (e la mancanza di un ottimizzatore del compilatore C #) rende le cose cosi 'reversibile'.
L'estensione risposta corretta di Brian
Se pensate che tutto IL è facilmente riconvertibile, vi suggerisco di scrivere un programma # F non banale e il tentativo di decompilare il codice. F # fa un sacco di trasformazioni di codice e quindi ha una pessima mappatura dal reale emessa IL e la base di codice originale. IMHO, è molto più difficile da guardare decompilato codice F # e tornare al programma originale quanto lo sia per C # o VB.Net.