web-dev-qa-db-fra.com

Extraire les clés pré-maîtres d'une application OpenSSL

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.

21
Lekensteyn

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.

Utiliser GDB (idée de base)

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.

Utilisation de GDB (approche automatisée améliorée)

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:

  • Installez des points d'arrêt sur plusieurs fonctions où de nouvelles clés pré-maîtresses peuvent apparaître.
  • Attendez que la fonction se termine et écrivez ces clés (si elles ne sont pas connues auparavant) dans un fichier (qui suit le format 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

Utilisation de LD_PRELOAD

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\"'
20
Lekensteyn

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é.

4
Erwan Legrand