Vediamo un esercizio sulle pipe (è una delle verifiche degli scorsi anni).
Crackme
Il programma crackme chiede un Personal Identification Number (PIN) di 5 cifre e ne verifica la correttezza. Si limita a stampare un messaggio ma si può pensare che solo nel caso il PIN sia corretto il programma dia accesso a risorse protette.
Una volta lanciato, crackme utilizza due pipe per interagire con altri processi. Su una pipe viene letto il PIN e sull’altra viene inviato il risultato della verifica in forma di stringa.
Il sorgente del programma (senza il PIN segreto) è il seguente:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #define PNAME1 "tmpPipeInput" #define PNAME2 "tmpPipeOutput" #define SUCCESSO "PIN corretto. Sei autenticato\n" #define FALLIMENTO "PIN errato\n" // il PIN è stato oscurato! void chiuditutto() { unlink(PNAME1); // rimuove la pipe unlink(PNAME2); // rimuove la pipe exit(1); } // stampa l'errore e termina void die(char *s) { perror(s); exit(EXIT_FAILURE); } int main() { int fdI,fdO; int leggi; signal(SIGINT,chiuditutto); mkfifo(PNAME1,0666); // crea la pipe, se esiste gia' non fa nulla mkfifo(PNAME2,0666); // crea la pipe, se esiste gia' non fa nulla if ( (fdI = open (PNAME1,O_RDWR)) < 0 ) // apre la pipe per la lettura die("errore apertura pipe\n"); if ( (fdO = open (PNAME2,O_RDWR)) < 0 ) // apre la pipe per la scrittura die("errore apertura pipe\n"); while ( read(fdI, &leggi, sizeof(int) ) ) { // Controlla la correttezza del PIN if (leggi == PINsegreto) write(fdO, SUCCESSO, strlen(SUCCESSO)+1); // qui si ha accesso alle risorse ... else write(fdO, FALLIMENTO, strlen(FALLIMENTO)+1); } }
Scaricare crackme qui.
Scrivere un programma crack.c che scopra il PIN segreto e lo stampi a video. Il programma deve interagire con crackme solo utilizzando le pipe (scoprire il PIN tramite debugging, anche se utile e divertente, non è considerata una soluzione, visto che l’esercizio è sull’uso delle pipe).
Notare che, per semplicità, gli interi vengono ricevuti direttamente nella loro rappresentazione binaria utilizzando la seguente istruzione:
read(fdI, &leggi, sizeof(int) )
Nel caso di interi a 32 bit, ad esempio, verranno letti direttamente i 4 byte che rappresentano il numero intero.
NOTA: È possibile interagire con le pipe di crackme da terminale ma si deve tener presente che gli interi sono rappresentati in little-endian, con il byte meno significativo al primo posto. Ad esempio il PIN 21664 che in esadecimale è 0x54a0
quando è rappresentato in 4 byte little-endian diventa 0xa0
0x54
0x00
0x00
. Per mandarlo sulla pipe si può usare il comando echo
con le opzioni -ne
che tolgono l’a-capo finale (-n) e interpretano le sequenze \xa0
come byte (-a).
$ echo -ne "\xa0\x54\x00\x00" > tmpPipeInput $ cat tmpPipeOutput PIN errato
Possiamo anche inviare due PIN consecutivi
$ echo -ne "\xa0\x54\x00\x00\xa1\x54\x00\x00" > tmpPipeInput $ cat tmpPipeOutput PIN errato PIN errato
Come si può notare crackme si aspetta i PIN codificati in 4 byte consecutivi senza separatore. Per inviarli da C non serve fare alcuna conversione: se il PIN è in una variabile intera sarà già rappresentato in little-endian.
Crackme con stringhe
La soluzione di inviare direttamente un intero nella sua rappresentazione interna può creare problemi di portabilità. Di solito si preferisce usare una rappresentazione che non dipenda dall’architettura o dal linguaggio utilizzato.
Questa variante di crackme legge i numeri interi come stringhe terminate da 0x00 e li converte, successivamente, in numeri interi per il confronto con il PIN.
Ecco il sorgente:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <stdio.h> #define PNAME1 "tmpPipeInputChars" #define PNAME2 "tmpPipeOutputChars" #define SUCCESSO "PIN corretto. Sei autenticato\n" #define FALLIMENTO "PIN errato\n" #define PINsegreto 13495 void chiuditutto() { unlink(PNAME1); // rimuove la pipe unlink(PNAME2); // rimuove la pipe exit(1); } // stampa l'errore e termina void die(char *s) { perror(s); exit(EXIT_FAILURE); } int main() { int fdI,fdO,r,pin; char leggi[6]; signal(SIGINT,chiuditutto); mkfifo(PNAME1,0666); // crea la pipe, se esiste gia' non fa nulla mkfifo(PNAME2,0666); // crea la pipe, se esiste gia' non fa nulla if ( (fdI = open (PNAME1,O_RDWR)) < 0 ) // apre la pipe per la lettura die("errore apertura pipe\n"); if ( (fdO = open (PNAME2,O_RDWR)) < 0 ) // apre la pipe per la scrittura die("errore apertura pipe\n"); while (1) { r=0; // legge il pin un carattere alla volta while ( r<6 && read(fdI, &leggi[r], 1 ) && leggi[r] != 0 ) r++; pin = atoi(leggi); // converte la stringa in intero // Controlla la correttezza del PIN if (pin == PINsegreto) write(fdO, SUCCESSO, strlen(SUCCESSO)+1); // qui si ha accesso alle risorse ... else write(fdO, FALLIMENTO, strlen(FALLIMENTO)+1); } }
Scaricare la variante di crackme.
Provare a scrivere il un programma crack-chars.c che interagisca con crackme-chars e scopra il PIN segreto.
NOTA: anche in questo caso si può interagire con il programma da linea di comando ma i PIN saranno stringhe terminate dal byte 0x00
:
$ echo -ne "21664\x00" > tmpPipeInputChars $ cat tmpPipeOutputChars PIN errato ^C $ echo -ne "21664\x0021666\x00" > tmpPipeInputChars $ cat tmpPipeOutputChars PIN errato PIN errato
In questo caso da C è necessario effettuare una conversione da interi a stringhe utilizzando, ad esempio, sprintf
(vedere il manuale).