web-dev-qa-db-ja.com

OpenSSLアプリケーションからプレマスターキーを抽出する

バグのあるOpenSSLを使用するアプリケーションを検討してください。完全なSSLセッションのパケットキャプチャ、およびアプリケーションとライブラリのコアダンプとデバッグシンボルを利用できます。 RSA秘密鍵も使用できますが、DHE暗号スイートが使用されているため、Wiresharkを使用してパケットキャプチャを復号化するためにこれを使用することはできません。

Thomasは この投稿 で、RAMからキーを抽出することが可能であることを示唆しています。 OpenSSLでこれをどのように行うことができますか? SSLデータ構造のアドレスが既知で、TLS 1.0が使用されていると仮定します。

21
Lekensteyn

注:OpenSSL 1.1.1(未リリース)以降、主要なログ行を受け取るコールバック関数を設定できるようになります。詳細は SSL_CTX_set_keylog_callback(3) マニュアルを参照してください。これは、デバッガまたは_LD_PRELOAD_フックを使用して通常どおり挿入できます。古いOpenSSLバージョンで行き詰まっている場合は、以下をお読みください。

Debian StretchでのApacheに対する_LD_PRELOAD_アプローチのウォークスルーについては、私の投稿 Extracting openssl pre-master secret from Apache2 を参照してください。技術的な詳細は以下のとおりです。


ライブプロセスまたはコアダンプへのgdbアクセスのみがある場合は、データ構造からデータを読み取ることができます。挿入ライブラリを使用することもできます。

次のテキストでは、GDBを使用したキー抽出の基本的な考え方について説明し、キャプチャを実行するための自動スクリプトを示します。

GDBの使用(基本的な考え方)

このStackoverflowの投稿 に基づいて、Wiresharkのキーログファイルに適した行を出力できる関数を構築できました。これは、コアダンプの分析中に特に役立ちます。 GDBで、次を実行します。

_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
_

その後、SSL構造を取得するまでスタックを上に移動した後、python pm()コマンドを呼び出します。例:

_(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...
_

デバッグシンボルでOpenSSLをインストールすることを忘れないでください! Debianの派生物では、_libssl1.0.0-dbg_のような名前が付けられ、Fedora/RHELは_openssl-debuginfo_と呼びます。

GDBの使用(改善された自動アプローチ)

上記の基本的な考え方は、小規模な手動テストで機能します。 (SSLサーバーなどからの)鍵の一括抽出の場合、これらの鍵の抽出を自動化したほうがよいでしょう。

これは、Python GDB用のスクリプト: https://git.lekensteyn.nl/peter/wireshark-notes/tree/src/sslkeylog.py (インストールと使用方法については、ヘッダーをご覧ください。基本的には次のように機能します。

  • 新しいプリマスターキーが発生する可能性があるいくつかの関数にブレークポイントをインストールします。
  • 関数が終了するのを待ち、これらのキー(以前に知られていない場合)をファイル(NSSのSSLKEYLOGFILE形式に従います)に書き込みます。

Wiresharkと組み合わせて、次のコマンドを実行してリモートサーバーからライブキャプチャを実行します。

_# 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
_

LD_PRELOADの使用

SSL/TLSは、SSLハンドシェイクステップでのみ鍵をネゴシエートできます。上記のアクションを実行するOpenSSL(_libssl.so_)のライブラリインターフェースを挿入することにより、プレマスターキーを読み取ることができます。

クライアントの場合は、_SSL_connect_を挿入する必要があります。サーバーの場合、_SSL_do_handshake_または_SSL_accept_を挿入する必要があります(アプリケーションによって異なります)。再ネゴシエーションをサポートするには、_SSL_read_および_SSL_write_もインターセプトする必要があります。

これらの関数が_LD_PRELOAD_ライブラリを使用してインターセプトされると、dlsym(RTLD_NEXT, "SSL_...")を使用して、SSLライブラリから「実際の」シンボルを検索できます。この関数を呼び出し、キーを抽出して戻り値を渡します。

この機能の実装は https://git.lekensteyn.nl/peter/wireshark-notes/tree/src/sslkeylog.c で入手できます。

異なるOpenSSLバージョン(1.0.2、1.1.0、1.1.1)はすべて互いに互換性がないことに注意してください。複数のOpenSSLバージョンをインストールしていて、古いバージョンをビルドする必要がある場合は、ヘッダーとライブラリパスをオーバーライドする必要がある場合があります。

_make -B CFLAGS='-I/usr/include/openssl-1.0 -DOPENSSL_SONAME=\"libssl.so.1.0.0\"'
_
20
Lekensteyn

マスターシークレットは_SSL->session->master_key_にあります。

または、次のようにセッション構造体を取得できます。

_SSL_SESSION ss = SSL_get_session(SSL);
_

上記のように、_master_key_構造体には_SSL_SESSION_フィールドがあります。

または、SSL_SESSION_print()またはSSL_SESSION_print_fp()を使用して、セッションの詳細(master_secretを含む)を印刷できます。

Master_secretが計算されると、pre_master_secretを取得できないと思います。

4
Erwan Legrand