[pipe] Crackme

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).