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_compilatoDiamo 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
0×08048533
0×08048535
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


Questo se la prima scritta è “logged in”..
Altrimenti il nop non va bene… Specificalo.. XD
Vabè come spiegato questo è per dare l’idea del reversing, le cose in realtà sono più complicate xD
bella sys..anche se non mi piace molto il reversing (non ci capisco nulla xD)
È divertente farlo, un passatempo utile u.u
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