La verifica consiste nell’implementazione di un programma C che parallelizza su più core il cracking di una lista di hash SHA-256. L’archivio (link) contiene tre versioni di un cracker che implementa un attacco bruteforce basato su dizionario:
- cracker-x86: Linux, 32 bit;
- cracker-x86-64: Linux, 64 bit;
- cracker-macOS: macOS.
Il cracker legge da standard input uno o più hash (separati da spazio, newline, tab) e per ciascuno di essi stampa la parola di dizionario corrispondente all’hash (se esiste) o un messaggio di errore. Il valore di ritorno del programma è 0 se il cracking dell’ultimo hash inserito ha avuto successo, 1 altrimenti.
Gli hash da crackare sono specificati come parametri da riga di comando. Il vostro programma dovrà eseguire un’istanza del cracker per ciascun hash e inviare al suo standard input (usando una pipe) l’hash da crackare. Una volta avviati i cracker, il vostro programma dovrà attenderne la terminazione e raccoglierne lo stato, quindi stampare il numero di hash crackati rispetto al numero totale.
Ecco un esempio di esecuzione:
>>> ./crack-multicore $(cat pwd_hashes.txt) [OK] 324ddfd466bdf51d4a298d20efae406b8536e1ad4c1dbfeef340fd008649a3d0 -> MEMBERS [OK] d4cb03d5cdefac58d109264e33681f53ca194b69cf31b84716f48b0e698db1b2 -> WILKES1 [OK] f81184f6e5ba9f970753736ea733e03a0e2e1dbf35e65fd25437eb4587bd4fc9 -> b124d953i5mi5m [OK] 3997422b2703dc73a3d3d029faaff92fa193404b2ce15521101751b4bb6a3e65 -> herstelwerk [OK] e32ed0eb487b1dc55894110c0b2003e44936ea2a59c7a24acb1b48d138d6906a -> impossessate [OK] a1c61981983b7820ba5ae808ce95ce1c5b3db09614089fd3c794214bf8342cc3 -> ripetenza [OK] 91aa21a0575c66134f14a47008771fba5dca7291baf00d41533379464273b031 -> schifosamente [OK] 3582ea097a77e45fe853a947e9139a551cee899b31e4c19dc4750f9e00b5491a -> thai [FAIL] cfc40bf3c6c26543658b5ed6c2d63b23108312ce417557b9eb8ccbaa7b432fec -> boh :( [FAIL] 24bf4d2d726e7557d5dc504d72783380f2a4f2f2d6e4947328f165ea71256255 -> boh :( Crackati 8 hash su 10!
Soluzione
Segue una possibile soluzione commentata della verifica (dove l’eseguibile è stato rinominato in cracker):
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
/*
* La verifica consiste nel parallelizzare il cracking di hash SHA-256 su più
* processi e stampare una statistica sul numero di hash crackati con successo
* rispetto al numero di hash totali. Gli hash vengono forniti al programma
* tramite command-line.
*
* Abbiamo a disposizione un programma 'cracker' che si occupa di fare il
* cracking cercando se una parola presente nel dizionario ha un hash uguale a
* quello fornito via standard input. Il programma termina con valore di
* ritorno 0 se l'operazione ha successo, 1 altrimenti.
*
* Per ciascun hash da crackare:
* - creiamo una pipe senza nome e facciamo una fork;
* - il processo padre scrive nella pipe l'hash da crackare;
* - il processo figlio associa al descrittore 0 (che identifica lo standard
* input) l'estremità della pipe aperta in lettura, quindi esegue il programma
* 'cracker' che proverà a crackare l'hash fornito dal padre.
* Dopo aver creato tutti i figli, il padre ne attende la terminazione ed
* incrementa il contatore degli hash crackati quando il codice di ritorno del
* figlio è pari a 0. Notare inoltre come le pipe vengano chiuse immediatamente
* dai processi quando non sono più necessarie.
*
* NB. Non era necessaria la gestione dell'errore nella fork/exec, ma per
* completezza consideriamo anche questa possibilità. In particolare, nel caso
* della fork, il processo padre e tutti i figli creati fino al momento del
* fallimento vengono terminati (la pipe aperta prima della fork viene chiusa
* automaticamente alla terminazione del processo). Nel caso della exec, il
* figlio termina con codice di errore 1 (che identifica un fallimento).
*/
int main(int argc, char *argv[]) {
int success, total, status;
int fd[2];
pid_t pid;
success = 0;
total = argc - 1;
for (int i = 1; i < argc; i++) {
pipe(fd);
pid = fork();
if (pid < 0) {
perror("Fork fallita.");
kill(0, SIGKILL);
} else if (pid == 0) {
close(fd[1]);
dup2(fd[0], 0);
close(fd[0]);
execl("./cracker", "cracker", NULL);
perror("Exec fallita.");
exit(EXIT_FAILURE);
} else {
close(fd[0]);
write(fd[1], argv[i], strlen(argv[i])+1);
close(fd[1]);
}
}
while (wait(&status) >= 0) {
success += WIFEXITED(status) ? 1 - WEXITSTATUS(status) : 0;
}
printf("Crackati %d hash su %d!\n", success, total);
return EXIT_SUCCESS;
}