[pipe] Hash cracking

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;
}