Considérons une application utilisant OpenSSL qui a un bogue. Une capture de paquets de la session SSL complète est disponible, ainsi qu'un vidage de mémoire et des symboles de débogage pour l'application et les bibliothèques. Une clé privée RSA est également disponible, mais comme une suite de chiffrement DHE est en cours d'utilisation, elle ne peut pas être utilisée pour déchiffrer la capture de paquets à l'aide de Wireshark.
Thomas suggère dans cet article qu'il est possible d'extraire des clés de la RAM. Comment cela pourrait-il être fait pour OpenSSL? Supposons que l'adresse de la structure de données SSL
est connue et que TLS 1.0 est en cours d'utilisation.
Remarque: à partir d'OpenSSL 1.1.1 (non publié), il sera possible de définir une fonction de rappel qui reçoit les lignes du journal des clés. Voir le manuel SSL_CTX_set_keylog_callback (3) pour plus de détails. Cela peut être injecté comme d'habitude à l'aide d'un débogueur ou d'un crochet LD_PRELOAD
. Lisez la suite si vous êtes bloqué avec une ancienne version d'OpenSSL.
Pour une procédure pas à pas de l'approche LD_PRELOAD
Pour Apache sur Debian Stretch, consultez mon article à Extraire le secret de pré-master openssl d'Apache2 . Les détails techniques suivent ci-dessous.
Si vous n'avez qu'un accès gdb au processus en direct ou à un vidage de mémoire, vous pouvez lire les données des structures de données. Il est également possible d'utiliser une bibliothèque d'interposition.
Dans le texte suivant, l'idée de base de l'extraction de clés à l'aide de GDB est décrite, puis un script automatisé est donné pour effectuer la capture.
Basé sur ce post Stackoverflow , j'ai pu construire une fonction qui pourrait imprimer une ligne adaptée au fichier journal des clés de Wireshark. Ceci est particulièrement utile lors de l'analyse de vidages mémoire. Dans GDB, exécutez:
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
Ensuite, après avoir remonté la pile jusqu'à obtenir une structure SSL
, appelez la commande python pm()
. Exemple:
(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 "\027\003\001\001\220T", 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...
Remarque : n'oubliez pas d'installer OpenSSL avec symboles de débogage ! Sur les dérivés Debian, il serait nommé quelque chose comme libssl1.0.0-dbg
, Fedora/RHEL l'appelle openssl-debuginfo
, Etc.
L'idée de base décrite ci-dessus fonctionne pour les petits tests manuels. Pour l'extraction en bloc de clés (à partir d'un serveur SSL par exemple), il serait plus agréable d'automatiser l'extraction de ces clés.
Cela se fait par ce Python pour GDB: https://git.lekensteyn.nl/peter/wireshark-notes/tree/src/sslkeylog.py ( voir ses en-têtes pour les instructions d'installation et d'utilisation. Cela fonctionne comme suit:
SSLKEYLOGFILE
de NSS).Associé à Wireshark, vous effectuez une capture en direct à partir d'un serveur distant en exécutant ces commandes:
# 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
SSL/TLS ne peut négocier les clés qu'aux étapes de la négociation SSL. En interposant les interfaces de bibliothèque d'OpenSSL (libssl.so
) Qui effectue lesdites actions, vous pourrez lire la clé pré-maître.
Pour les clients, vous devez interposer SSL_connect
. Pour les serveurs, vous devez interposer SSL_do_handshake
Ou SSL_accept
(Selon l'application). Pour prendre en charge la renégociation, vous devrez également intercepter SSL_read
Et SSL_write
.
Une fois ces fonctions interceptées à l'aide d'une bibliothèque LD_PRELOAD
, Vous pouvez utiliser dlsym(RTLD_NEXT, "SSL_...")
pour rechercher le symbole "réel" dans la bibliothèque SSL. Appelez cette fonction, extrayez les clés et passez la valeur de retour.
Une implémentation de cette fonctionnalité est disponible sur https://git.lekensteyn.nl/peter/wireshark-notes/tree/src/sslkeylog.c .
Notez que différentes versions d'OpenSSL (1.0.2, 1.1.0, 1.1.1) sont toutes incompatibles entre elles. Si plusieurs versions d'OpenSSL sont installées et que vous devez créer une version plus ancienne, vous devrez peut-être remplacer les chemins d'en-tête et de bibliothèque:
make -B CFLAGS='-I/usr/include/openssl-1.0 -DOPENSSL_SONAME=\"libssl.so.1.0.0\"'
Le secret principal est dans SSL->session->master_key
.
Vous pouvez également obtenir la structure de la session comme suit:
SSL_SESSION ss = SSL_get_session(SSL);
Comme indiqué ci-dessus, il y a un champ master_key
Dans la structure SSL_SESSION
.
Ou vous pouvez imprimer les détails de la session (y compris le master_secret) en utilisant SSL_SESSION_print()
ou SSL_SESSION_print_fp()
.
Je ne pense pas que le pre_master_secret puisse être récupéré une fois le master_secret calculé.