5. Problemi dell'esempio precedente

Consideriamo i problemi che vengono introdotti dal semplice decompilatore descritto prima, e proviamo a trovare una possibile soluzione. Iniziamo con l’istruzione iniziale che abbiamo decompilato:

0x004011E5:  or byte [ebp-0x3], 0x8  ->  L4011E5:    ebp[-0x3] |= 0x8;
  • EBP è un registro del processore, quindi non è noto a un compilatore generico, ma nell’architettura x86 si riferisce al puntatore alla base dello stack fornito al nostro programma; quindi, per compilare il nostro programma, dobbiamo dichiarare EBP. In questo caso, possiamo dichiarare che EBP è un puntatore ad uno spazio di memoria, cioè:
unsigned char *ebp;
ebp[-0x3] |= 0x8;
  • EBP viene utilizzato principalmente per puntare al frame della procedura locale (vedremo in seguito come questo può esserci utile). L’istruzione precedente sta probabilmente tentando di accedere a una variabile locale che è stata dichiarata di dimensioni in byte. Tuttavia, potrebbero esserci altre variabili locali di dimensioni diverse e saranno tutte accessibili tramite EBP (a diversi offset da EBP), quindi dichiarare EBP come puntatore a unsigned char non funzionerà per quelle altre variabili. Possiamo risolvere questo problema forzando la dimensione di ogni accesso attraverso il registro, in base alla dimensione dell’istruzione originale:
char *bp;
*(char *)(ebp-0x3) |= 0x8;

L’accesso a una variabile diversa utilizzerà un cast diverso:

*(short *)(ebp-0x8) += 10;
  • Anche se di solito EBP contiene un indirizzo, ci sono casi in cui il compilatore potrebbe usarlo per mantenere una costante numerica (nelle funzioni senza frame EBP è usato proprio come qualsiasi altro registro). Se EBP viene utilizzato in un’operazione aritmetica diversa da + o -, il compilatore si lamenterà se lo dichiariamo char *. Quindi dobbiamo dichiararlo come int. Questo non è un problema quando si utilizza l’approccio cast sopra, perché l’aggiunta e la sottrazione da un numero intero è essenzialmente la stessa dell’aggiunta e della sottrazione da un char *.

  • Un tipo di istruzione che non è facilmente tradotto utilizzando il metodo sopra è per quelle istruzioni che impostano un registro con un indirizzo per un uso successivo. Queste istruzioni possono essere indistinguibili dalle istruzioni che caricano una costante numerica per qualche calcolo successivo:

8D 3C 85 DC 68 40 00       lea edi, [eax*4+0x4068DC] // A
8D 3C 85 04 00 00 00       lea edi, [eax*4+4]        // B

La traduzione di queste due istruzioni è piuttosto diversa:

edi = &var_4068DC[eax*4];    // A
edi = 4 + eax * 4;           // B

Perché c’è questa differenza? Poiché in un caso si accederà a un’area di memoria, quindi dobbiamo prendere l’indirizzo di quell’area di memoria e indicizzarla (in effetti, dovremmo probabilmente rimuovere anche la moltiplicazione, poiché è così che si accede tipicamente all’array di numeri interi, quindi il l’istruzione dovrebbe diventare: edi = &var_4068DC[eax]; ). Nel secondo caso, viene eseguito un calcolo numerico e il compilatore ha scelto di utilizzare un’istruzione lea (Load Effective Address) invece della sequenza più lunga mul e add . Risolvere questo problema non è facile.

Questi sono solo alcuni esempi. Come hai visto, le istruzioni del flusso di controllo (salti, chiamate) non si traducono molto bene e l’utilizzo del metodo di traduzione meccanica descritto sopra crea rapidamente codice C generato illeggibile che può gonfiarsi se ricompilato in codice macchina dopo la traduzione.

Abbiamo chiaramente bisogno di una soluzione migliore. Le euristiche fin’ora viste possono aiutare l’analista a rappresentare il programma ad alto livello, tuttavia per evitare di incorrere in alcuni problemi è necessario procedere per modularità. Nei prossimi capitoli vedremo come affrontare in maniera più rigorosa la traduzione inversa e cercheremo di implementare una prima versione solida del compilatore.

Biografia

Sono uno specialista in materia di sicurezza informatica, scrittore, contributore per il progetto Monero, una criptovaluta che è focalizzata nel proteggere le informazioni sulle transazioni. Il libro che ho pubblicato Mastering Monero è diventato una delle migliori risorse per padroneggiare Monero. Più informazioni su di me

Seguimi su Twitter o scrivimi una e-mail. Le donazioni sono molto apprezzate, mi permettono di continuare a lavorare e a scrivere.

Mastering Monero Book