2011
02.13

Per seguire questo articolo, bisogna avere un minimo di esperienza di debugging con gdb, con il codice assembly e C, e con un editor esadecimale .
Comunque cercherò di spiegare le cose in modo molto chiaro .

Il Reverse Engineering, o Ingegneria Inversa, viene eseguito su dispositivi elettronici, software, o altro, per studiare in dettaglio questi, e ricostruire la loro logica .
In questo tutorial vi illustrerò come modificare il flusso di un file eseguibile .

Prendiamo subito il nostro codice in C:

#include <stdio.h>
#include <string.h>

int main(){
 
        char password[]="obscured";
        char input[30];
 
        printf("Password: ");
        scanf("%s",input);
 
        if(strcmp(password,input) == 0){
                printf("Logged inn");
        }
        else{
                printf("Error, wrong passwordn");
        }
 
        return 0;
 
}

Questo codice C ha una password, “obscured”, che è conservata nella variabile array “password” .
Dopodichè viene chiesto all’utente di inserire la password tramite scanf, e ciò che verrà scritto viene memorizzato nella variabile array input .
Poi viene controllata se le due variabili coincidono con la funzione strcmp (è usato == 0 perchè questa funzione ritorna 0 se le due password sono uguali) .
Se sono ugali allora printa “Logged in”, altrimenti printa “Error, wrong password” .

Compiliamolo con gcc ed eseguiamolo :

root@bt:~/programming/c# ./pwd
Password: obscured
Logged in
root@bt:~/programming/c# ./pwd
Password: asd
Error, wrong password
root@bt:~/programming/c#

Si, tutto normale .
Ora mettiamo che noi non avessimo il codice sorgente del programma, e neanche la password, però vogliamo usare il programma senza sapere la password .

Come facciamo ?

Per prima cosa, disassembliamo il programma che abbiamo compilato con gdb

gdb -q nome_programma_compilato

Diamo il comando

disas main

Per disassemblare il main

(no debugging symbols found)
(gdb) disas main
Dump of assembler code for function main:
0x080484d4 <main+0>:    lea    0x4(%esp),%ecx
0x080484d8 <main+4>:    and    $0xfffffff0,%esp
0x080484db <main+7>:    pushl  -0x4(%ecx)
0x080484de <main+10>:   push   %ebp
0x080484df <main+11>:   mov    %esp,%ebp
0x080484e1 <main+13>:   push   %ecx
0x080484e2 <main+14>:   sub    $0x44,%esp
0x080484e5 <main+17>:   mov    %gs:0x14,%eax
0x080484eb <main+23>:   mov    %eax,-0x8(%ebp)
0x080484ee <main+26>:   xor    %eax,%eax
0x080484f0 <main+28>:   movl   $0x6373626f,-0x11(%ebp)
0x080484f7 <main+35>:   movl   $0x64657275,-0xd(%ebp)
0x080484fe <main+42>:   movb   $0x0,-0x9(%ebp)
0x08048502 <main+46>:   movl   $0x8048630,(%esp)
0x08048509 <main+53>:   call   0x80483d0 <printf@plt>
0x0804850e <main+58>:   lea    -0x2f(%ebp),%eax
0x08048511 <main+61>:   mov    %eax,0x4(%esp)
0x08048515 <main+65>:   movl   $0x804863b,(%esp)
0x0804851c <main+72>:   call   0x80483f0 <__isoc99_scanf@plt>
0x08048521 <main+77>:   lea    -0x2f(%ebp),%eax
0x08048524 <main+80>:   mov    %eax,0x4(%esp)
0x08048528 <main+84>:   lea    -0x11(%ebp),%eax
0x0804852b <main+87>:   mov    %eax,(%esp)
<strong>0x0804852e <main+90>:   call   0x8048410 <strcmp@plt></strong>
0x08048533 <main+95>:   test   %eax,%eax
<strong>0x08048535 <main+97>:   jne    0x8048545 <main+113></strong>
0x08048537 <main+99>:   movl   $0x804863e,(%esp)
0x0804853e <main+106>:  call   0x8048400 <puts@plt>
0x08048543 <main+111>:  jmp    0x8048551 <main+125>
0x08048545 <main+113>:  movl   $0x8048648,(%esp)
0x0804854c <main+120>:  call   0x8048400 <puts@plt>
0x08048551 <main+125>:  mov    $0x0,%eax
0x08048556 <main+130>:  mov    -0x8(%ebp),%edx
0x08048559 <main+133>:  xor    %gs:0x14,%edx
0x08048560 <main+140>:  je     0x8048567 <main+147>
0x08048562 <main+142>:  call   0x80483e0 <__stack_chk_fail@plt>
0x08048567 <main+147>:  add    $0x44,%esp
0x0804856a <main+150>:  pop    %ecx
0x0804856b <main+151>:  pop    %ebp
0x0804856c <main+152>:  lea    -0x4(%ecx),%esp
0x0804856f <main+155>:  ret
End of assembler dump.

Questa è la parte importante:

0x0804852e : call 0×8048410
0×08048533 : test %eax,%eax
0×08048535 : jne 0×8048545

Viene richiamata la call strcmp per confrontare le due stringhe (la password del programma con quello che noi gli diamo in input), per vedere se sono uguali .
Se sono uguali, si passa lal funzione jne .
Questa funzione controlla il risultato della call strcmp .
Se il risultato è 0, allora il programma ci riporta alla printf “Logged in”, se è diverso da 0 allora ci riporta alla printf “Error, wrong password” .

Quello che dobbiamo fare noi, è eliminare questo salto condizionato, in modo che qualsiasi password mettiamo, il programma và comunque avanti .

Ci serve un editor esadecimale .
Io ho usato Okteta .

Allora, per prima cosa, con gdb, dobbiamo trovare il codice operativo esadecimale della funzione jne .
Rivediamo l’struzione:

<strong>0x08048535 <main+97>:   jne    0x8048545 <main+113></strong>

Come vediamo, l’istruzione jne si trova al byte 97 (main+97) .
Possiamo dedurre che questa istruzione è lunga 2 byte, visto che la prossima si trova all’etichetta main+99 .
Proviamo con gdb a vedere quindi i prossimi due byte di questa istruzione

(gdb) x/2xb 0x08048535
0x8048535 <main+97>:    0x75    0x0e
(gdb)

0×08048535 È l’indirizzo della istruzione .
0×75 0x0e, viene tradotto in 750e, che è il codice esadecimale che cercavamo .

Ora che abbiamo il codice esadecimale, lo possiamo andare a modificare con il nostro editor .
Apriamo Okteta o l’editor che avete scelto .
Su Okteta con ctrl+f appare il search .
Scriviamo 750e e diamo invio .
Dovrebbe evidenziarci due blocchi (visto che l’istruzione vale 2 byte), uno che contiene 75, e l’altro che contiene 0e .
Ora, noi dobbiamo sostituire questi codici per evitare il salto jne .
Possiamo usare l’istruzione NOP (No Operation) .
Infatti questa istruzione, non fà praticamente nulla .
Il codice esadecimale di questa funzione è 0×90 .
Quindi noi dobbiamo scrivere al posto di 75 e di 0e, 90 e 90 .

Dopo averlo fatto, salviamo il programma e rieseguiamolo

 
root@bt:~/programming/c# ./pwd
Password: asd
Logged in
root@bt:~/programming/c# ./pwd
Password: lol
Logged in
root@bt:~/programming/c# ./pwd
Password: xd
Logged in
root@bt:~/programming/c#

Qualsiasi password mettiamo, avremo la scritta “Logged in” .
Questo perchè, il programma confronta le due password con strcmp .
E dopo il confronto, l”operazione jne è stata sostiuita con operazioni nop, ovvero operazioni che non eseguono nulla e che fanno eseguire le operazioni del programma, anche se la password inserita non è quella giusta .

Ovviamente questo era un semplice esempio, modificare il flusso di un eseguibile non è sempre cosi facile, però i passi sono quelli .

SYSTEM_OVERIDE 

  1. Questo se la prima scritta è “logged in”..
    Altrimenti il nop non va bene… Specificalo.. XD ;-)

  2. Vabè come spiegato questo è per dare l’idea del reversing, le cose in realtà sono più complicate xD

  3. bella sys..anche se non mi piace molto il reversing (non ci capisco nulla xD)

  4. È divertente farlo, un passatempo utile u.u

  5. ciao, molto molto interessante.. soprattutto per uno che si è avvicinato da poco a questo “mondo”. Una cosa non mi è chiara però: come fai a sapere l’indirizzo di memoria della funzione scanf? c’è qualche tabella?

    luca.

    • Beh come vedi te lo printa gdb l’indirizzo in memoria della funzione scanf:

      0x0804851c : call 0x80483f0 <__isoc99_scanf@plt

*