Violare un archivio rar, zip o 7z con bash

- tags: bash, programming, security - 0 comments

Ho creato uno script bash con cui poter cercare la password smarrita di un archivio 7z, rar o zip attraverso metodi di forza bruta e attacchi con dizionario.

#!/bin/bash
#=====================================================================================
#
#          Title: cerberus
#         Author: Giacomo Trudu aka `Wicker25` - wicker25 [at] gmail [dot] com
#           Site: http://hackyourmind.org/
#        License: GNU Lesser General Public License v3 (LGPL3)
#        Version: 0.6.3
#
#  = Version 0.6.3 =
#   + improved analysis resuming
#
#  = Version 0.6.2 =
#   + changed speed unit to pwds/s
#
#  = Version 0.6.1 =
#   + fixed spinner animation
#
#  = Version 0.6 =
#   + improved statistics output
#
#  = Version 0.5.4 =
#   + improved performance with large dictionary files
#
#=====================================================================================

# Versione
name='cerberus';
version='0.6.3';

# Informazioni sull'attacco
mode='incremental';
mode_opt='';
mode_format='';
mode_resume=false;
mode_length=6;
mode_jobs=( 1 1 );
decrypt='';

# Decrittatore
decrypt_7z='7z t -y -p%s %s';
decrypt_rar='unrar t -y -p%s %s';
decrypt_zip='7z t -y -p%s %s';

# Risposta affermativa del decrittatore
password_found="Everything is Ok|All Ok";

# Alfabeto
spinner_chars=( '\' '|' '/' '-' );
spinner=0;

# Unità del tempo
time_factors=( 31536000 2628000 604800 86400 3600 60 1 );
time_names=( 'years' 'months' 'weeks' 'days' 'hours' 'minutes' 'seconds' );

# Stato dell'analisi (none, found or interrupted)
status='none';

# Contatore delle password analizzate
count=0;

# Parametri usati per le statistiche
stats_speed='-';
stats_eta='-';
last_count=0;
last_time=`date +%s`;


# Aggiorna le statistiche dell'analisi
function update_stats {

    # Calcolo la velocità di analisi
    speed=`echo "scale = 3; ( $count - $last_count ) / ( $time - $last_time )" | bc`;

    # Converto la velocità in passwords/minuto
    stats_speed="${speed/.*} pwds/s";

    # Calcolo il tempo rimanente alla fine dell'analisi
    eta=`echo "scale = 3; ( $combinations - $count ) / $speed" | bc`;
    stats_eta='';

    # Costruisco la stringa del tempo trascorso
    elapsed="${eta/.*}";

    let 'precision = 2';

    for k in `seq 0 6`;
    do
        let 'value = elapsed / time_factors[k]';

        if [ $value -ne 0 ];
        then
            if [ $value -gt 1 ]; 
            then
                stats_eta="$stats_eta$value ${time_names[$k]} ";
            else
                stats_eta="$stats_eta$value ${time_names[$k]%?} ";
            fi;

            if [ $precision -gt 1 ];
            then
                let 'precision--';
            else
                break;
            fi;
        fi;

        let 'elapsed %= time_factors[k]';
    done;

    if [ -n "$stats_eta" ];
    then
        stats_eta="${stats_eta%?}";
    else
        stats_eta='-';
    fi;

    let 'last_count = count';
}


# Prova la password sull'obiettivo
function test_password {

    # Incremento il contatore delle password testate
    let 'count++';

    # Formatto la password
    if [ -n "$mode_format" ];
    then
        password=`echo "$password" | sed -e "$mode_format"`;
    fi;

    # Controllo che siano passati almeno 3 secondi
    time=`date +%s`;

    if [ `expr $time - $last_time` -ge 1 ];
    then
        # Aggiorna le statistiche dell'analisi
        update_stats;

        # Controllo se un altro processo ha già trovato la password
        if [ -s "$meta_password" ];
        then
            password=`cat "$meta_password"`;
            status='found';
        fi;

        # Memorizzo i progressi dell'analisi
        echo -e "$mode\n$mode_opt\n$mode_length\n$mode_format\n$i $to" > "$meta";

        # Memorizzo l'istante corrente per il prossimo controllo
        let 'last_time = time';

        # Scelgo il carattere dello spinner
        spinner_current="${spinner_chars[`expr $spinner % 4`]}";

        # Stampo i progressi dell'analisi
        printf "\r\033[K%s Checking '%s'... (%d checked, speed %s, eta %s)" "$spinner_current" "$password" $count "$stats_speed" "$stats_eta";

        # Modifico il carattere dello spinner
        let 'spinner++';
    fi;

    # Provo a decrittare l'archivio usando la password corrente
    printf -v password "%q" $password;

    if `printf "$decrypt" "$password" "$target"` 2> /dev/null | grep -Eiq "$password_found";
    then
        echo "$password" > "$meta_password";
        status='found';
    fi;
}


# Cattura il segnale di uscita
function control_attack {

    read -s -n1 -t0.001 key;

    if [ "$key" == $'\x1b' ];
    then
        status='interrupted';
    fi;
}


# Attacco forza bruta
function bruteforce_attack {

    # Costruisco l'alfabeto per l'attacco
    if [ -z $mode_opt ];
    then
        mode_opt='alnum';
    fi;

    case $mode_opt in
        num)        symbols=( {0..9} );;
        alpha)      symbols=( {a..z} );;
        alcase)     symbols=( {a..z} {A..Z} );;
        alpun)      symbols=( {a..z} ' ' '(' ')' '.' ';' ':' '!' '?' '/' '\' );;
        alcpun)     symbols=( {a..z} {A..Z} ' ' '(' ')' '.' ';' ':' '!' '?' '/' '\' );;
        alnum)      symbols=( {a..z} {0..9} );;
        alcnum)     symbols=( {a..z} {A..Z} {0..9} );;
        pun)        symbols=( ' ' '(' ')' '.' ';' ':' '!' '?' '/' '\' );;
        all)        symbols=( {a..z} {A..Z} {0..9} ' ' '(' ')' '.' ';' ':' '!' '?' '/' '\'  );;
    esac;

    symbols_l=${#symbols[*]};

    # Calcolo il numero delle combinazioni da provare
    if [ $mode == 'incremental' ];
    then
        let 'combinations = 0';

        for i in `seq 1 $mode_length`;
        do
            let 'combinations += symbols_l ** i';
        done;
    else
        let 'combinations = symbols_l ** mode_length';
    fi;

    # Carico eventuali configurazioni preesistenti
    if [ $mode_resume == false ];
    then
        total=`echo "scale = 2; $combinations / ${mode_jobs[1]} + 1" | bc`;
        total=${total/.*};

        let 'from += ( mode_jobs[0] - 1 ) * total';
        let 'to = from + total';
    fi;

    # Aggiusto i valori in modo da non uscire dalle combinazioni previste
    if [ $from -lt 0 ];
    then
        let 'from = 0';
    fi;

    if [ $to -gt $combinations ];
    then
        let 'to = combinations';
    fi;

    # Calcolo le combinazioni restanti
    let 'combinations = to - from';

    # Informazioni sull'attacco
    echo "Combinations: $combinations (from `expr $from + 1` to $to)";

    # Ciclo principale
    let 'i = from';

    while [ $i -lt $to ];
    do
        # Catturo il segnale di uscita
        control_attack;

        # Controllo se la ricerca è terminata
        if [ $status != 'none' ];
        then
            break;
        fi;

        # Se è stata richiesta, applico la ricerca incrementale
        let 'current = i';

        if [ $mode == 'incremental' ];
        then
            for j in `seq 1 $mode_length`;
            do
                let 'val = symbols_l ** j';

                if [ $current -ge $val ];
                then
                    let 'current -= val';
                else
                    break;
                fi;
            done;

            let 'password_l = j - 1';
        else
            let 'password_l = mode_length - 1';
        fi;

        # Costruisco la nuova password
        password='';

        for j in `seq 0 $password_l`;
        do
            let 'val = current % symbols_l';

            if [ $val -ge 0 ];
            then
                printf -v password "%s%s" "${symbols[$val]}" "$password";
            fi;

            let 'current /= symbols_l';

            if [ $current -lt 0 ];
            then
                break
            fi;
        done;

        # Provo la nuova password sull'obiettivo
        test_password;

        # Passo alla prossima password
        let 'i++';
    done;
}


# Attacco con dizionario
function dictionary_attack {

    # Leggo il numero delle parole presenti nel dizionario
    combinations=`wc -l "$mode_opt" | awk '{ print $1 }'`;

    # Carico eventuali configurazioni preesistenti
    if [ $mode_resume == false ];
    then
        total=`echo "scale = 2; $combinations / ${mode_jobs[1]} + 1" | bc`;
        total=${total/.*};

        let 'from = ( mode_jobs[0] - 1 ) * total';
        let 'to = from + total';
    fi;

    # Aggiusto i valori in modo da non uscire dal dizionario
    if [ $from -lt 0 ];
    then
        let 'from = 0';
    fi;

    if [ $to -gt $combinations ];
    then
        let 'to = combinations';
    fi;

    # Calcolo le combinazioni restanti
    let 'combinations = to - from';

    # Informazioni sull'attacco
    echo "Combinations: $combinations (from `expr $from + 1` to $to)";

    # Provo le parole contenute nel dizionario
    exec 3< "$mode_opt";

    let 'i = 0';

    while read -u 3 line;
    do
        # Catturo il segnale di uscita
        control_attack;

        # Controllo se la ricerca è terminata
        if [ $status != 'none' ];
        then
            break;
        fi;

        # Ignoro le password già testate
        if [ $i -ge $from ];
        then
            # Provo la nuova password sull'obiettivo
            password="`echo $line | sed 's/[\n\r]//g'`";

            test_password;
        fi;

        let 'i++';
    done;
}


# Processo i parametri
usage="Usage: `basename $0` [-b <num|...|all>|-i <num|...|all>|-d <file>] [-l <length>] [-j <current,total>] [-p <format>] [-f <type>] [-r] [-h] -t <target>";

if [ $# -eq 0 ];
then
    echo $usage;
    exit 1;
fi;

while getopts 'b:i:d:f:l:j<img class="smile" src="/wolf/plugins/wick_filter/images/face-naughty.png" alt=":p" />:t:rvh' opt;
do
    case $opt in

        b)  mode='bruteforce';

            if [[ ! "$OPTARG" =~ ^(num|alpha|alcase|alpun|alcpun|alnum|alcnum|pun|all)$ ]];
            then
                echo "Invalid brute force attack!";
                echo $usage;
                exit 1;
            fi;

            mode_opt="$OPTARG";;

        i)  mode='incremental';

            if [[ ! "$OPTARG" =~ ^(num|alpha|alcase|alpun|alcpun|alnum|alcnum|pun|all)$ ]];
            then
                echo "Invalid incremental brute force attack!";
                echo $usage;
                exit 1;
            fi;

            mode_opt="$OPTARG";;

        d)  mode='dictionary';

            if [ ! -r "$OPTARG" ];
            then
                echo "Invalid dictionary file!";
                echo $usage;
                exit 1;
            fi;

            directory=`dirname $OPTARG`;
            name=`basename $OPTARG`;
            mode_opt="`cd "$PWD"; cd "$directory"; pwd`/$name";;

        f)  case $OPTARG in
                7z)     decrypt=$decrypt_7z;;
                rar)    decrypt=$decrypt_rar;;
                zip)    decrypt=$decrypt_zip;;
            esac;;

        r)  mode_resume=true;;

        l)  if [[ ! "$OPTARG" =~ ^[0-9]*$ ]];
            then
                echo "Invalid length!";
                echo $usage;
                exit 1;
            fi;

            mode_length="$OPTARG";;

        p)  mode_format="$OPTARG";;

        j)  if [[ ! "$OPTARG" =~ ^([1-9]+),([1-9]+)$ ]] || [ ${BASH_REMATCH[1]} -gt ${BASH_REMATCH[2]} ];
            then
                echo "Invalid process splitting!";
                echo $usage;
                exit 1;
            fi;

            mode_jobs=( "${BASH_REMATCH[1]}" "${BASH_REMATCH[2]}" );;

        t)  target="$OPTARG";;

        v)  echo "$name v$version";
            exit 0;;

        h|?)    echo -e "$usage\n";
                echo '  -b <num|alpha|alcase|alpun|alcpun|alnum|alcnum|pun|all>     Brute force attack';
                echo '  -i <num|alpha|alcase|alpun|alcpun|alnum|alcnum|pun|all>     Incremental brute force attack';
                echo '  -d <file>                                                   Dictionary attack';
                echo '  -f <7z|rar|zip>                                             Target format';
                echo '  -l                                                          Length of passwords';
                echo '  -p <format>                                                 Format passwords';
                echo '  -j <current,total>                                          Split process';
                echo '  -r                                                          Resume previous attack';
                echo '  -v                                                          Print version';
                echo '  -h                                                          Print this message';
                echo;
                exit 0;;
    esac;
done;

# Controllo la validità dell'obiettivo
if [ -r "$target" ];
then
    # Costruisco i percorsi ai file d'appoggio
    meta="$target.meta${mode_jobs[0]}";
    meta_password="$target.password";

    # Controllo se la password è già stata trovata
    if [ -s "$meta_password" ];
    then
        password=`cat "$meta_password"`;
        echo "Password already found: $password";
        exit 1;
    fi;

    # Se non è stato scelto il decrittatore imposto quello più adatto al file
    if [ -z "$decrypt" ];
    then
        format=`file -ib "$target"`;

        if [[ "$format" =~ ^application/.*7z ]];
        then
            decrypt=$decrypt_7z;

        elif [[ "$format" =~ ^application/.*rar ]]
        then
            decrypt=$decrypt_rar;

        elif [[ "$format" =~ ^application/.*zip ]]
        then
            decrypt=$decrypt_zip;
        else
            echo "Unknown target format!";
            exit 1;
        fi;
    fi;

    # Continuo l'attacco iniziato in precedenza
    if [ $mode_resume == true ];
    then
        echo "Resuming...";

        if [ -r "$meta" ];
        then
            mode=`head -1 "$meta"`;
            mode_opt=`sed -n "2p" "$meta"`;
            mode_length=`sed -n "3p" "$meta"`;
            mode_format=`sed -n "4p" "$meta"`;
            from=`tail -1 "$meta" | awk '{ print $1 }'`;
            to=`tail -1 "$meta" | awk '{ print $2 }'`;
        else
            echo "Invalid meta file!";
            exit 1;
        fi;
    fi;

    # Inizio l'attacco sull'obiettivo
    case $mode in
        bruteforce|incremental) bruteforce_attack;;
        dictionary)             dictionary_attack;;
    esac;

    # Memorizzo i progressi dell'analisi
    echo -e "$mode\n$mode_opt\n$mode_length\n$mode_format\n$i $to" > "$meta";

    # Mostro all'utente i risultati dell'elaborazione
    case $status in
        none)           echo -e "\nPassword not found! ($count checked)";;
        found)          echo -e "\nPassword found: '$password' ($count checked)";;
        interrupted)    echo -e "\nInterrupted. ($count checked)";;
    esac;
else
    echo "Invalid target!";
fi;

[Scarica il sorgente]

Per scaricare ed installare cerberus sul vostro sistema operativo sarà sufficiente dare come amministratore i seguenti comandi:

# cd /usr/bin
# wget http://www.hackyourmind.org/public/sources/cerberus
# chmod +x cerberus

Quindi recuperiamo alcune dipendenze con un ultimo comando:

$ sudo apt-get install unrar p7zip-full

Attacco con forza bruta

Se avviamo cerberus usando l’opzione -t seguita dal nome dell’archivio bersaglio, verrà avviato un attacco con forza bruta che utilizza caratteri alfanumerici minuscoli.

$ cerberus -t archivio.7z

Tuttavia possiamo scegliere quali simboli utilizzare nell’analisi dell’archivio attraverso l’opzione -b, in questo modo:

$ cerberus -b <simboli> -t archivio.7z

Dove il parametro simboli può assumere uno dei seguenti valori:

  • num, per soli caratteri numerici;
  • alpha, per caratteri alfabetici minuscoli;
  • alcase, per caratteri alfabetici minuscoli e maiuscoli;
  • alpun, per caratteri alfabetici minuscoli e simboli di punteggiatura;
  • alnum, per caratteri alfanumerici minuscoli;
  • alcnum, per caratteri alfanumerici minuscoli e maiuscoli;
  • pun, per simboli di punteggiatura;
  • all, per tutti i caratteri possibili, minuscoli e maiuscoli, compresi i simboli di punteggiatura.

Con l’opzione -l, invece, possiamo impostare la lunghezza delle password generate:

$ cerberus -b <simboli> -l 6 -t archivio.7z

Attacco con forza bruta incrementale

Nel caso in cui volessimo utilizzare come password tutte le combinazioni di caratteri di lunghezza minore o uguale a quella specificata è possibile utilizzare l’opzione -i, che segue la stessa sintassi di quella vista in precedenza:

$ cerberus -i <simboli> -t archivio.7z

Ecco ad esempio come potremmo fare per provare tutte le password numeriche inferiori ai 4 caratteri di lunghezza, ossia quelle della sequenza 0, 1, 2, …, 9, 00, 01, 02, …, 09, 10, 11, …, 000, 001, 002, …, 998, 999:

$ cerberus -i num -l 3 -t archivio.7z

Attacco con dizionario

Per cercare di forzare un archivio usando un elenco di parole contenute in un file di testo utilizziamo il parametro -d seguito dal percorso al file del dizionario:

$ cerberus -d <dizionario> -t archivio.7z

Le parole presenti nel dizionario dovranno essere disposte una su ogni riga.

Attacco distribuito

Supponiamo di avere a disposizione un certo numero di calcolatori e di voler ripartire su di essi il carico di lavoro generato dall’analisi del file, in modo tale da incrementarne la velocità complessiva. A questo scopo possiamo utilizzare il parametro -j che permette di suddividere le operazioni svolte per la ricerca in parti uguali rispetto al numero dei calcolatori:

$ cerberus -j <corrente>,<totale> -b num -l 4 -t archivio.7z

Dove “corrente” va sostituito con il numero del pc sul quale stiamo avviando cerberus e “totale” con il numero totale dei calcolatori su cui è stato distribuito l’attacco.
Se per esempio volessimo dar inizio ad un attacco distribuito su 3 diversi calcolatori, potremmo usare la seguente configurazione:

Computer #1

$ cerberus -j 1,3 -b num -l 4 -t archivio.7z

Computer #2

$ cerberus -j 2,3 -b num -l 4 -t archivio.7z

Computer #3

$ cerberus -j 3,3 -b num -l 4 -t archivio.7z

Formattare una password

Attraverso l’opzione -p è possibile post-formattare le password utilizzate per l’analisi:

$ cerberus -b num -p <regexp> -t archivio.rar

L’opzione deve essere seguita da un’espressione regolare secondo la sintassi dell’utility sed.

Se ad esempio conoscessimo uno o più caratteri della password che stiamo cercando, potremmo includerli nella nostra ricerca in questo modo:

$ cerberus -b num -l 2 -p 's/.*/carla&/' -t archivio.rar

Così facendo verrebbero provate tutte le combinazioni di caratteri composte dalla parola “carla” e seguite da due cifre decimali (carla00, carla01, carla02, …, carla99 ).

Utilizzando la medesima tecnica potremmo rendere maiuscole tutte le password prelevate da un dizionario:

$ cerberus -d dict.txt -p 's/.*/\U&/' -t archivio.rar

Schermata di cerberus

Schermata di cerberus

Continuazione e conclusione di un attacco

Quando diamo inizio ad un attacco su un archivio viene generato automaticamente un metafile dal nome “archivio.ext.meta”, in cui verranno memorizzati i progressi compiuti durante l’analisi dell’archivio.
In questo modo è possibile riprendere un attacco precedentemente interrotto semplicemente utilizzando come parametri di cerberus il nome del bersaglio seguito dall’opzione -r, così come segue:

$ cerberus -t archivio.rar -r

Per riprendere un attacco distribuito, invece, è necessario aggiungere anche l’opzione -j con i parametri usati nell’attacco.

$ cerberus -j 1,2 -t archivio.rar -r

Una volta trovata la password dell’archivio, l’attacco verrà interrotto e la password trovata verrà memorizzata in un secondo metafile dal nome “archivio.ext.password”.

Attacco multi-core

Un interessante stratagemma applicabile alle macchine multi-core è quello di assegnare ad ogni core della CPU un distinto processo di ricerca, ad esempio utilizzando uno script come il seguente:

#!/bin/bash
target='archivio.7z';
cores=`grep -c ^processor /proc/cpuinfo`;
console='urxvt -e';

for i in `seq 1 $cores`;
do
   taskset -c `expr $i - 1` $console cerberus -j $i,$cores -b num -l 2 -t "$target" &
done;

for job in `jobs -p`;
do
   wait $job;
done;

if [ -r "$target.password" ];
then
   echo -e "\nPassword: `cat "$target.password"`";
fi;

Se lo desiderate potete confrontare le prestazioni di quest’ultimo metodo attraverso quest’altro script:

#!/bin/bash
target='archivio.7z';

echo "- Single -";

start_time=`date +%s`;
cerberus -b num -l 2 -t "$target";
finish_time=`date +%s`;

echo -e "Time duration: `expr $finish_time - $start_time` secs.\n";

echo "- Multi-core -";

cores=`grep -c ^processor /proc/cpuinfo`;
console='urxvt -e';

start_time=`date +%s`;

for i in `seq 1 $cores`;
do
   taskset -c `expr $i - 1` $console cerberus -j $i,$cores -b num -l 2 -t "$target" &
done;

for job in `jobs -p`;
do
   wait $job;
done;

finish_time=`date +%s`;

echo -e "Time duration: `expr $finish_time - $start_time` secs.";

Analizzare più archivi alla volta

Se vogliamo analizzare più di un archivio in un colpo solo possiamo dare da terminale un rapido:

$ ls *.rar | xargs -n 1 cerberus -b num -l 2 -t

Oppure usare un classico ciclo for in uno script bash:

#!/bin/bash
for target in `ls -1 *.rar`;
do
    echo "Analizzo '$target'...";
    cerberus -b num -l 2 -t "$target";
    echo;
done;

Questo è tutto.. ;)

Did you find this article helpful?

0 comments