Extraer claves pre-maestras de una aplicación OpenSSL

18

Considere una aplicación que use OpenSSL que tiene un error. Está disponible una captura de paquetes de la sesión SSL completa, así como un volcado de núcleo y símbolos de depuración para la aplicación y las bibliotecas. También está disponible una clave privada RSA, pero como se está utilizando una suite de cifrado DHE, no se puede usar para descifrar la captura de paquetes usando Wireshark.

Thomas sugiere en esta publicación que es posible extraer claves de la RAM. ¿Cómo podría hacerse esto para OpenSSL? Supongamos que la dirección de la estructura de datos SSL es conocida y que TLS 1.0 está en uso.

    
pregunta Lekensteyn 27.01.2015 - 11:35
fuente

2 respuestas

15

Nota: a partir de OpenSSL 1.1.1 (no publicado), será posible establecer una función de devolución de llamada que reciba las líneas de registro clave. Consulte el manual SSL_CTX_set_keylog_callback (3) para obtener más información. Esto se puede inyectar como de costumbre usando un depurador o un gancho LD_PRELOAD . Siga leyendo si está atascado con una versión anterior de OpenSSL.

Si solo tiene acceso gdb al proceso en vivo o un volcado de memoria, puede leer los datos de las estructuras de datos. También es posible utilizar una biblioteca de interposición.

En el siguiente texto, se describe la idea básica de la extracción de claves con GDB, luego se proporciona un script automatizado para realizar la captura.

Usando GDB (idea básica)

Basado en esta publicación de Stackoverflow , pude construir una función que podría imprimir una línea adecuada para el archivo de registro de clave pre-maestro de Wiresharks. Esto es especialmente útil para volcados de núcleo. En GDB, ejecute:

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

Luego, más adelante, después de que subas en la pila hasta que obtengas una estructura SSL , invoca el comando python pm() . Ejemplo:

(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 : ¡no olvides instalar OpenSSL con símbolos de depuración ! En los derivados de Debian, se llamaría algo así como libssl1.0.0-dbg , Fedora/RHEL call it openssl-debuginfo ', etc.

Uso de GDB (enfoque mejorado y automatizado)

La idea básica que se describe anteriormente funciona para pruebas pequeñas y manuales. Para la extracción masiva de claves (desde un servidor SSL, por ejemplo), sería mejor automatizar la extracción de estas claves.

Esto se hace con este script de Python para GDB: enlace (vea Sus encabezados para las instrucciones de instalación y uso). Básicamente funciona así:

  • Instale puntos de interrupción en varias funciones en las que pueden surgir nuevas claves pre-maestras.
  • Espere a que la función termine y escriba estas claves (si no las conoce antes) en un archivo (que sigue el formato SSLKEYLOGFILE de NSS).

Junto con Wireshark, realiza una captura en vivo desde un servidor remoto ejecutando estos comandos:

%pre%

Usando LD_PRELOAD

SSL / TLS solo puede negociar claves en los pasos de negociación de SSL. Al interponer las interfaces de la biblioteca de OpenSSL ( libssl.so ) que realiza dichas acciones, podrá leer la clave pre-maestra.

Para los clientes, debe interponer SSL_connect . Para los servidores, debe interponer SSL_do_handshake o SSL_accept (según la aplicación). Para apoyar la renegociación, también deberá interceptar SSL_read y SSL_write .

Una vez que estas funciones son interceptadas usando una biblioteca LD_PRELOAD , puedes usar dlsym(RTLD_NEXT, "SSL_...") para buscar el símbolo "real" de la biblioteca SSL. Llame a esta función, extraiga las claves y pase el valor de retorno.

Una implementación de esta funcionalidad está disponible en enlace .

    
respondido por el Lekensteyn 27.01.2015 - 15:22
fuente
5

El secreto maestro está en SSL->session->master_key .

Alternativamente, puede obtener la estructura de la sesión de la siguiente manera:

SSL_SESSION ss = SSL_get_session(SSL);

Una de las indicaciones anteriores, hay un campo master_key en la estructura SSL_SESSION .

O puede imprimir los detalles de la sesión (incluido el master_secret) utilizando SSL_SESSION_print() o SSL_SESSION_print_fp() .

No creo que se pueda recuperar el pre_master_secret una vez que se haya calculado el master_secret.

    
respondido por el Erwan Legrand 27.01.2015 - 12:39
fuente

Lea otras preguntas en las etiquetas