Estrarre le chiavi pre-master da un'applicazione OpenSSL

18

Considera un'applicazione che usa OpenSSL che ha un bug. È disponibile un'acquisizione di pacchetti della sessione SSL completa, oltre a simboli di dump e debug di base per l'applicazione e le librerie. È disponibile anche una chiave privata RSA, ma poiché è in uso una suite di crittografia DHE, questa non può essere utilizzata per decodificare l'acquisizione di pacchetti tramite Wireshark.

Thomas suggerisce in questo post che è possibile estrarre le chiavi dalla RAM. Come si può fare per OpenSSL? Supponiamo che l'indirizzo della struttura dati SSL sia noto e TLS 1.0 sia in uso.

    
posta Lekensteyn 27.01.2015 - 11:35
fonte

2 risposte

15

Nota: a partire da OpenSSL 1.1.1 (non rilasciato), sarà possibile impostare una funzione di callback che riceve le linee di log delle chiavi. Consulta il manuale SSL_CTX_set_keylog_callback (3) per i dettagli. Questo può essere iniettato come al solito usando un debugger o un hook LD_PRELOAD . Continua a leggere se sei bloccato con una versione precedente di OpenSSL.

Se hai appena accesso a gdb al processo live oa un core dump, puoi leggere i dati dalle strutture di dati. È anche possibile utilizzare una libreria di interposizione.

Nel testo seguente, viene descritta l'idea di base dell'estrazione di chiavi usando GDB, quindi viene fornito uno script automatico per eseguire l'acquisizione.

Utilizzo di GDB (idea di base)

Basato su questo post StackOverflow , sono stato in grado di costruire una funzione che può stampare una riga adatta al file di log delle chiavi pre-master di Wiresharks. Questo è particolarmente utile per core dump. In GDB, esegui:

python
def read_as_hex(name, size):
    addr = gdb.parse_and_eval(name).address
    data = gdb.selected_inferior().read_memory(addr, size)
    return ''.join('%02X' % ord(x) for x in data)

def pm(ssl='s'):
    mk = read_as_hex('%s->session->master_key' % ssl, 48)
    cr = read_as_hex('%s->s3->client_random' % ssl, 32)
    print('CLIENT_RANDOM %s %s' % (cr, mk))
end

Successivamente, dopo aver eseguito il passaggio verso l'alto nello stack fino a ottenere una struttura SSL , richiamare il comando python pm() . Esempio:

(gdb) bt
#0  0x00007fba7d3623bd in read () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007fba7b40572b in read (__nbytes=5, __buf=0x7fba5006cbc3, __fd=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/unistd.h:44
#2  sock_read (b=0x7fba60191600, out=0x7fba5006cbc3 "7
# Start logging SSL keys to file premaster.txt. Be careful *not* to
# press Ctrl-C in gdb, these are passed to the application. Use
# kill -TERM $PID_OF_GDB (or -9 instead of -TERM if that did not work).
(server) SSLKEYLOGFILE=premaster.txt gdb -batch -ex skl-batch -p 'pidof nginx'
# Read SSL keys from the remote server, flushing after each written line
(local) ssh user@host stdbuf -oL tailf premaster.txt > premaster.txt
# Capture from the remote side and immediately pass the pcap to Wireshark
(local) ssh user@host 'tcpdump -w - -U "tcp port 443"' |
    wireshark -k -i - -o ssl.keylog_file:premaster.txt
3
python
def read_as_hex(name, size):
    addr = gdb.parse_and_eval(name).address
    data = gdb.selected_inferior().read_memory(addr, size)
    return ''.join('%02X' % ord(x) for x in data)

def pm(ssl='s'):
    mk = read_as_hex('%s->session->master_key' % ssl, 48)
    cr = read_as_hex('%s->s3->client_random' % ssl, 32)
    print('CLIENT_RANDOM %s %s' % (cr, mk))
end
1
(gdb) bt
#0  0x00007fba7d3623bd in read () at ../sysdeps/unix/syscall-template.S:81
#1  0x00007fba7b40572b in read (__nbytes=5, __buf=0x7fba5006cbc3, __fd=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/unistd.h:44
#2  sock_read (b=0x7fba60191600, out=0x7fba5006cbc3 "7
# Start logging SSL keys to file premaster.txt. Be careful *not* to
# press Ctrl-C in gdb, these are passed to the application. Use
# kill -TERM $PID_OF_GDB (or -9 instead of -TERM if that did not work).
(server) SSLKEYLOGFILE=premaster.txt gdb -batch -ex skl-batch -p 'pidof nginx'
# Read SSL keys from the remote server, flushing after each written line
(local) ssh user@host stdbuf -oL tailf premaster.txt > premaster.txt
# Capture from the remote side and immediately pass the pcap to Wireshark
(local) ssh user@host 'tcpdump -w - -U "tcp port 443"' |
    wireshark -k -i - -o ssl.keylog_file:premaster.txt
3%pre%1%pre%10T", outl=5) at bss_sock.c:142 #3 0x00007fba7b40374b in BIO_read (b=0x7fba60191600, out=0x7fba5006cbc3, outl=5) at bio_lib.c:212 #4 0x00007fba7b721a34 in ssl3_read_n (s=0x7fba60010a60, n=5, max=5, extend=<optimized out>) at s3_pkt.c:240 #5 0x00007fba7b722bf5 in ssl3_get_record (s=0x7fba60010a60) at s3_pkt.c:507 #6 ssl3_read_bytes (s=0x7fba60010a60, type=23, buf=0x7fba5c024e00 "Z", len=16384, peek=0) at s3_pkt.c:1011 #7 0x00007fba7b720054 in ssl3_read_internal (s=0x7fba60010a60, buf=0x7fba5c024e00, len=16384, peek=0) at s3_lib.c:4247 ... (gdb) frame #4 0x00007fba7b721a34 in ssl3_read_n (s=0x7fba60010a60, n=5, max=5, extend=<optimized out>) at s3_pkt.c:240 240 in s3_pkt.c (gdb) python pm() CLIENT_RANDOM 9E7EFAC51DBFFF84FCB9...81796EBEA5B15E75FF71EBE 6ED2EA80181...
10T", outl=5) at bss_sock.c:142 #3 0x00007fba7b40374b in BIO_read (b=0x7fba60191600, out=0x7fba5006cbc3, outl=5) at bio_lib.c:212 #4 0x00007fba7b721a34 in ssl3_read_n (s=0x7fba60010a60, n=5, max=5, extend=<optimized out>) at s3_pkt.c:240 #5 0x00007fba7b722bf5 in ssl3_get_record (s=0x7fba60010a60) at s3_pkt.c:507 #6 ssl3_read_bytes (s=0x7fba60010a60, type=23, buf=0x7fba5c024e00 "Z", len=16384, peek=0) at s3_pkt.c:1011 #7 0x00007fba7b720054 in ssl3_read_internal (s=0x7fba60010a60, buf=0x7fba5c024e00, len=16384, peek=0) at s3_lib.c:4247 ... (gdb) frame #4 0x00007fba7b721a34 in ssl3_read_n (s=0x7fba60010a60, n=5, max=5, extend=<optimized out>) at s3_pkt.c:240 240 in s3_pkt.c (gdb) python pm() CLIENT_RANDOM 9E7EFAC51DBFFF84FCB9...81796EBEA5B15E75FF71EBE 6ED2EA80181...

Nota : non dimenticare di installare OpenSSL con i simboli di debug ! Sui derivati Debian sarebbe chiamato qualcosa come libssl1.0.0-dbg , Fedora/RHEL call it openssl-debuginfo ', ecc.

Utilizzo di GDB (approccio automatizzato migliorato)

L'idea di base descritta sopra funziona per piccoli test manuali. Per l'estrazione di massa delle chiavi (ad esempio da un server SSL), sarebbe più semplice automatizzare l'estrazione di queste chiavi.

Questo è fatto da questo script Python per GDB: link (vedi le sue intestazioni per le istruzioni di installazione e utilizzo). Funziona fondamentalmente in questo modo:

  • Installa i punti di interruzione su diverse funzioni in cui possono sorgere nuove chiavi pre-master.
  • Attendi che la funzione finisca e scrivi questi tasti (se non è noto prima) in un file (che segue il formato SSLKEYLOGFILE di NSS).

Accoppiato con Wireshark, esegui un'acquisizione dal vivo da un server remoto eseguendo questi comandi:

%pre%

Utilizzo di LD_PRELOAD

SSL / TLS può negoziare solo le chiavi durante le fasi di handshake SSL. Interponendo le interfacce della libreria di OpenSSL ( libssl.so ) che esegue dette azioni, sarai in grado di leggere la chiave pre-master.

Per i clienti, devi interporre SSL_connect . Per i server è necessario interporre SSL_do_handshake o SSL_accept (a seconda dell'applicazione). Per supportare la rinegoziazione, devi anche intercettare SSL_read e SSL_write .

Una volta che queste funzioni sono state intercettate utilizzando una libreria LD_PRELOAD , puoi utilizzare dlsym(RTLD_NEXT, "SSL_...") per cercare il simbolo "reale" dalla libreria SSL. Chiama questa funzione, estrai le chiavi e passa il valore di ritorno.

Un'implementazione di questa funzionalità è disponibile all'indirizzo link .

    
risposta data 27.01.2015 - 15:22
fonte
5

Il segreto principale è in SSL->session->master_key .

In alternativa, puoi ottenere la struttura della sessione come segue:

SSL_SESSION ss = SSL_get_session(SSL);

A indicato sopra, c'è un campo master_key nella struttura SSL_SESSION .

Oppure puoi stampare i dettagli della sessione (incluso master_secret) utilizzando SSL_SESSION_print() o SSL_SESSION_print_fp() .

Non penso che il pre_master_secret possa essere recuperato una volta che il master_secret è stato calcolato.

    
risposta data 27.01.2015 - 12:39
fonte

Leggi altre domande sui tag