Wiresharkでsshパケットを復号するためのssh key log作成方法 (2)

前回は、Wireshark 4.0.0以降で利用できるようになるssh復号機能を背景に、OpenSSHクライアントのデバッグシンボルがある場合にclient keyを抽出する方法を紹介した。

前回の方法では、関数名でbreakpointを設定したり、client keyをprintするのに変数名を指定したりできるようにOpenSSHをデバッグビルド(-gオプションでコンパイル)する必要があった。 sshクライアント環境ごとにOpenSSHをデバッグビルドするのは実用性にやや難があるので、今回は、より汎用的な方法としてgdbを使ってUbuntuにバンドルされているsshコマンドからclient keyを抽出する。

検証環境

$ uname -srvi
Linux 5.10.102.1-microsoft-standard-WSL2 #1 SMP Wed Mar 2 00:30:59 UTC 2022 x86_64
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.4 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.4 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Ubuntuにインストールされていたsshクライアント

$ ssh -V
OpenSSH_8.2p1 Ubuntu-4ubuntu0.5, OpenSSL 1.1.1f  31 Mar 2020
$ file /usr/bin/ssh
/usr/bin/ssh: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=48d5a82eb9975a1fe8ef84d033667aa76130726b, for GNU/Linux 3.2.0, stripped

以降の記述は、OpenSSH v8.2p1に基づいているため、他のバージョンでは実装が異なるかもしれない。

OpenSSHのデバッグシンボルがない場合の鍵抽出方針

題材として、前回同様にRaspberry Pi OSが動作しているホスト192.168.1.6でUser=pi, Password=raspberryとしてパスワード認証を試みるsshパケットを復号する。 パケットキャプチャしつつ、gdbsshクライアントを実行してclient keyを取り出す。

前回も言及したように、client keyの生成されるkex_gen_client()は、処理の終わりの方でdebug("expecting SSH2_MSG_KEX_ECDH_REPLY")を呼び出してデバッグログを出力している。 そのため、writeシステムコールでのbreakpointを設定した状態でssh-vでverbose modeにして実行し、当該文字列を出力している部分を見つければ、比較的簡単にkex_gen_client()のアドレスを特定できる。 kex_gen_client()のアドレスが特定できれば、その後の処理からkexのアドレスも分かるため、デバッグシンボルがない状況でも関数の終わりでkex構造体の中身を読めば、最終的にclient keyを抽出することは可能である。

kex_gen_client()の特定

$ man 2 writeコマンドでwriteシステムコールの定義を確認すると、以下の通り第2引数にbufを渡して書き込む文字列を指定するようになっている。

SYNOPSIS
       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

gdbを起動し、writeにbreakpointを設定する。あわせて、停止時にbufの内容を確認するために第2引数の内容を表示するようにしたい。x86_64環境なので、第2引数はrsiレジスタを見ればよい。rsiレジスタの指す文字列をdisplayで自動表示するようにする。

準備ができたら、sshコマンドを-vオプション付きで実行する。 そうすると、writeの呼び出しで停止し、sshのverbose出力を捉えられていることが分かる。この後、何度かcontinueして目的の文字列"expecting SSH2_MSG_KEX_ECDH_REPLY"での呼び出しを見つけることになる。

$ gdb -q /usr/bin/ssh
Reading symbols from /usr/bin/ssh...
(No debugging symbols found in /usr/bin/ssh)
(gdb) b write
Breakpoint 1 at 0xb8e0
(gdb) display/s $rsi
1: x/s $rsi  <error: No registers.>
(gdb) r -v pi@192.168.1.6
Starting program: /usr/bin/ssh -v pi@192.168.1.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, __GI___libc_write (fd=2, buf=0x7fffffffaec0, nbytes=62) at ../sysdeps/unix/sysv/linux/write.c:25
25      ../sysdeps/unix/sysv/linux/write.c: No such file or directory.
1: x/s $rsi  0x7fffffffaec0:    "OpenSSH_8.2p1 Ubuntu-4ubuntu0.5, OpenSSL 1.1.1f  31 Mar 2020\r\n"

目的の文字列を通り過ぎないように注意しながら、十数回continueを繰り返すのは大変なので、文字列一致でconditional breakpointを設定する(今回は、writeのシンボルが読み込まれていたので、bufを利用)。

(gdb) condition 1 strcmp(buf, "debug1: expecting SSH2_MSG_KEX_ECDH_REPLY\r\n")==0

continueすると、一気にwrite(2, "expecting SSH2_MSG_KEX_ECDH_REPLY", 43)で停止するので、btでバックトレースを表示する。

(gdb) c
Continuing.
(snip)
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none

Breakpoint 1, __GI___libc_write (fd=2, buf=0x7fffffffac40, nbytes=43) at ../sysdeps/unix/sysv/linux/write.c:25
25      in ../sysdeps/unix/sysv/linux/write.c
1: x/s $rsi  0x7fffffffac40:    "debug1: expecting SSH2_MSG_KEX_ECDH_REPLY\r\n"
(gdb) bt
#0  __GI___libc_write (fd=2, buf=0x7fffffffac40, nbytes=43) at ../sysdeps/unix/sysv/linux/write.c:25
#1  0x000055555559a344 in ?? ()
#2  0x000055555559a728 in ?? ()
#3  0x00005555555b7327 in ?? ()
#4  0x00005555555b5035 in ?? ()
#5  0x00005555555a3cba in ?? ()
#6  0x00005555555a3d6d in ?? ()
#7  0x0000555555576f06 in ?? ()
#8  0x00005555555732ef in ?? ()
#9  0x0000555555563652 in ?? ()
#10 0x00007ffff7a61083 in __libc_start_main (main=0x555555561200, argc=3, argv=0x7fffffffde98, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffde88) at ../csu/libc-start.c:308
#11 0x0000555555564dce in ?? ()

今、OpenSSHのdebug()経由で呼び出されたwriteで止まっているので、いくつか上のフレームがdebug()で、それを呼び出している部分がkex_gen_client()ということになる。 finで関数の終わりまで実行しながら、適当に手前の逆アセンブルを見る。

(gdb) d display 1
(gdb) fin
Run till exit from #0  __GI___libc_write (fd=2, buf=0x7fffffffac40, nbytes=43) at ../sysdeps/unix/sysv/linux/write.c:25
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
0x000055555559a344 in ?? ()
Value returned is $2 = 43
(gdb) x/16i $rip-0x20
   0x55555559a324:      test   $0x8080,%eax
   0x55555559a329:      cmove  %ecx,%eax
   0x55555559a32c:      lea    0x2(%rdx),%rcx
   0x55555559a330:      cmove  %rcx,%rdx
   0x55555559a334:      mov    %eax,%ebx
   0x55555559a336:      add    %al,%bl
   0x55555559a338:      sbb    $0x3,%rdx
   0x55555559a33c:      sub    %r15,%rdx
   0x55555559a33f:      callq  0x55555555f8e0 <write@plt>
=> 0x55555559a344:      pop    %rax
   0x55555559a345:      pop    %rdx
   0x55555559a346:      jmpq   0x55555559a118
   0x55555559a34b:      callq  0x555555560720 <__stack_chk_fail@plt>
   0x55555559a350 <error>:      endbr64
   0x55555559a354 <error+4>:    sub    $0xd8,%rsp
   0x55555559a35b <error+11>:   mov    %rdi,%r10
(gdb) fin
Run till exit from #0  0x000055555559a344 in ?? ()
0x000055555559a728 in ?? ()
(gdb) x/16i $rip-0x20
   0x55555559a708:      add    %al,(%rax)
   0x55555559a70a:      lea    0x20(%rsp),%rax
   0x55555559a70f:      mov    %rax,0x10(%rsp)
   0x55555559a714:      movl   $0x8,(%rsp)
   0x55555559a71b:      movl   $0x30,0x4(%rsp)
   0x55555559a723:      callq  0x55555559a020
=> 0x55555559a728:      mov    0x18(%rsp),%rax
   0x55555559a72d:      xor    %fs:0x28,%rax
   0x55555559a736:      jne    0x55555559a740
   0x55555559a738:      add    $0xd8,%rsp
   0x55555559a73f:      retq
   0x55555559a740:      callq  0x555555560720 <__stack_chk_fail@plt>
   0x55555559a745:      data16 nopw %cs:0x0(%rax,%rax,1)
   0x55555559a750:      endbr64
   0x55555559a754:      sub    $0xd8,%rsp
   0x55555559a75b:      mov    %rdi,%r10
(gdb) fin
Run till exit from #0  0x000055555559a728 in ?? ()
0x00005555555b7327 in ?? ()
(gdb) x/16i $rip-0x20
   0x5555555b7307:      shlb   $0x48,-0x5f(%rbp)
   0x5555555b730b:      mov    %ebp,%edi
   0x5555555b730d:      callq  0x55555559f270
   0x5555555b7312:      mov    %eax,%r12d
   0x5555555b7315:      test   %eax,%eax
   0x5555555b7317:      jne    0x5555555b72ab
   0x5555555b7319:      lea    0x2a4a0(%rip),%rdi        # 0x5555555e17c0
   0x5555555b7320:      xor    %eax,%eax
   0x5555555b7322:      callq  0x55555559a680
=> 0x5555555b7327:      lea    -0x75e(%rip),%rdx        # 0x5555555b6bd0
   0x5555555b732e:      mov    $0x1f,%esi
   0x5555555b7333:      mov    %rbp,%rdi
   0x5555555b7336:      callq  0x5555555a3c20
   0x5555555b733b:      jmpq   0x5555555b72ab
   0x5555555b7340:      mov    %rbx,%rdi
   0x5555555b7343:      callq  0x5555555c1f60

#3のフレーム(0x5555555b7327)まで上ったところで、lea 0x2a4a0(%rip),%rdiでrdiレジスタに値をセットしてからcallq 0x55555559a680している部分が引数を1つだけ取るdebug()に見える。 rdiレジスタにセットされる0x5555555e17c0の内容を確認する。

(gdb) x/s 0x5555555e17c0
0x5555555e17c0: "expecting SSH2_MSG_KEX_ECDH_REPLY"

0x5555555b7322のcallqは、見事にkex_gen_clientでのdebugの呼び出し方と一致しているので、#4のフレームの手前の逆アセンブルを調べる。

(gdb) x/8i 0x5555555b5035-0x10
   0x5555555b5025:      add    %al,(%rax)
   0x5555555b5027:      test   %rax,%rax
   0x5555555b502a:      je     0x5555555b526c
   0x5555555b5030:      mov    %rbp,%rdi
   0x5555555b5033:      callq  *%rax
   0x5555555b5035:      mov    %eax,%r14d
   0x5555555b5038:      jmpq   0x5555555b4af0
   0x5555555b503d:      nopl   (%rax)

callq *%raxkex_gen_clientが呼び出されていると考えられるので、0x5555555b5033にbreakpointを設定して実行しなおす。

(gdb) d
Delete all breakpoints? (y or n) y
(gdb) b *0x5555555b5033
Breakpoint 2 at 0x5555555b5033
(gdb) k
Kill the program being debugged? (y or n) y
[Inferior 1 (process 235) killed]
(gdb) r -v pi@192.168.1.6
Starting program: /usr/bin/ssh -v pi@192.168.1.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
OpenSSH_8.2p1 Ubuntu-4ubuntu0.5, OpenSSL 1.1.1f  31 Mar 2020
(snip)

Breakpoint 2, 0x00005555555b5033 in ?? ()
(gdb) x/i $rip
=> 0x5555555b5033:      callq  *%rax
(gdb) si
0x00005555555b7270 in ?? ()
(gdb) fin
Run till exit from #0  0x00005555555b7270 in ?? ()
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
0x00005555555b5035 in ?? ()

callq *%raxで0x5555555b7270が呼び出されており、finで終わりまで実行すると、debug1:~文字列が出力されることが分かる。 このことから、0x5555555b7270がkex_gen_client()と特定できた。

client keyの取り出し

特定したkex_gen_client()の0x5555555b7270でbreakpointを設定し直して、sshを実行する。直接breakpointを設定するので、この段階でsshの実行時に-vオプションは不要(付けても良い)。 kex_gen_client()は第1引数でstruct ssh *sshを受け取るので、停止時点でのrdiレジスタの値がsshのアドレスを指している。

$ gdb -q /usr/bin/ssh
Reading symbols from /usr/bin/ssh...
(No debugging symbols found in /usr/bin/ssh)
(gdb) b *0x5555555b7270
Breakpoint 1 at 0x5555555b7270
(gdb) r pi@192.168.1.6
Starting program: /usr/bin/ssh pi@192.168.1.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, 0x00005555555b7270 in ?? ()
(gdb) i r rdi
rdi            0x55555563d2b0      93824993186480
(gdb) x/16i $rip
=> 0x5555555b7270:      endbr64
   0x5555555b7274:      push   %r12
   0x5555555b7276:      push   %rbp
   0x5555555b7277:      mov    %rdi,%rbp
   0x5555555b727a:      push   %rbx
   0x5555555b727b:      mov    0x8(%rdi),%rbx
   0x5555555b727f:      mov    0x48(%rbx),%eax
   0x5555555b7282:      cmp    $0x8,%eax
   0x5555555b7285:      je     0x5555555b7340
   0x5555555b728b:      ja     0x5555555b72b8
   0x5555555b728d:      cmp    $0x4,%eax
   0x5555555b7290:      jbe    0x5555555b7360
   0x5555555b7296:      cmp    $0x7,%eax
   0x5555555b7299:      jne    0x5555555b72d0
   0x5555555b729b:      mov    %rbx,%rdi
   0x5555555b729e:      callq  0x5555555c1c00

前回同様に、まず使用されているKEXアルゴリズムを示すkex->kex_typeの値を調べる。 kex_gen_client関数の処理では、まずstruct kex *kex = ssh->kex;int r;でローカル変数が初期化された後、switch (kex->kex_type)に入る。 あわせて、ssh構造体の定義を確認すると、1番目のメンバ変数はstateへのポインタで、2番目のメンバ変数にkex構造体へのポインタが入っている。 これらを踏まえて、上記逆アセンブルを解釈すると、mov 0x8(%rdi),%rbxでrbxレジスタssh->kexの値が入り、mov 0x48(%rbx),%eaxでeaxレジスタkex->kex_typeの値が入ることが分かる。 7回ステップ実行して、ここまで実行する。

(gdb) si 7
0x00005555555b7282 in ?? ()
(gdb) i r rbx eax
rbx            0x55555563db30      93824993188656
eax            0x8                 8
(gdb) x/2ga $rdi
0x55555563d2b0: 0x55555563ad90  0x55555563db30

この時点で、rbxレジスタの値がssh->kexの値を指している。rdiレジスタの指すアドレスから2番目のポインタが指すアドレスと一致していることからも、ssh構造体の定義通りになっている。 また、eaxレジスタの値はkex->kex_typeの値で8なので、enum kex_exchangeの定義からKEX_C25519_SHA256と分かる。

ここで、kex構造体のシンボルがないため、c25519_client_keyまでのオフセットが分からない問題に直面する。

参考に、前回デバッグシンボル付きでコンパイルしたOpenSSH 8.2p1のkex構造体の大きさを調べると1984となっている。 アーキテクチャとOpenSSHのバージョンが同じなので、Ubuntu向けビルドとkex構造体の配置も同じと考えてしまいそうだが、コンパイラのアラインメント等でUbuntu向けビルドとはkex構造体の各メンバ変数のオフセットは異なる可能性がある*1

$ gdb -q ./ssh
Reading symbols from ./ssh...
(gdb) p sizeof(struct kex)
$1 = 1984
(gdb) p (int)&((struct kex*)0)->my
$2 = 96
(gdb) p (int)&((struct kex*)0)->c25519_client_key
$3 = 312

そこで少し強引な方法ではあるが、kexの先頭から1984バイトより少し余裕を持たせた2048バイト分にwatchpointを設定して、メモリの値に変化があった部分がclient keyの書き込みとみなすこととする。 kex_gen_client()の戻り先である、0x5555555b5035にtemporary breakpointを設定したうえで、続行する。

(gdb) watch (char[2048])*0x55555563db30
Watchpoint 2: (char[2048])*0x55555563db30
(gdb) bt
#0  0x00005555555b7282 in ?? ()
#1  0x00005555555b5035 in ?? ()
#2  0x00005555555a3cba in ?? ()
#3  0x00005555555a3d6d in ?? ()
#4  0x0000555555576f06 in ?? ()
#5  0x00005555555732ef in ?? ()
#6  0x0000555555563652 in ?? ()
#7  0x00007ffff7a61083 in __libc_start_main (main=0x555555561200, argc=2, argv=0x7fffffffde98, init=<optimized out>,
    fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffde88) at ../csu/libc-start.c:308
#8  0x0000555555564dce in ?? ()
(gdb) tb *0x00005555555b5035
Temporary breakpoint 3 at 0x5555555b5035
(gdb) c
Continuing.

Watchpoint 2: (char[2048])*0x55555563db30

Old value = '\000' <repeats 16 times>, "\340\305cUUU\000\000 \263cUUU\000\000@\000\000\000@", '\000' <repeats 11 times>, "\340'dUUU\000\000\220\004dUUU\000\000\002\000\000\000\237\001\000\000\b", '\000' <repeats 23 times>, " \231cUUU\000\000\220\310cUUU\000\000p\231cUUU\000\000p\224cUUU\000\000\000\000\000\000\003\000\000\000\002", '\000' <repeats 39 times>, "P6WUUU", '\000' <repeats 34 times>...
New value = '\000' <repeats 16 times>, "\340\305cUUU\000\000 \263cUUU\000\000@\000\000\000@", '\000' <repeats 11 times>, "\340'dUUU\000\000\220\004dUUU\000\000\002\000\000\000\237\001\000\000\b", '\000' <repeats 23 times>, " \231cUUU\000\000\220\310cUUU\000\000p\231cUUU\000\000p\224cUUU\000\000\000\000\000\000\003\000\000\000\002", '\000' <repeats 39 times>, "P6WUUU", '\000' <repeats 34 times>...
__memmove_sse2_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:241
241     ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: No such file or directory.
(gdb) x/8i $rip-0x10
   0x7ffff7af8bbe <__memmove_sse2_unaligned_erms+14>:   cmp    $0x20,%edx
   0x7ffff7af8bc1 <__memmove_sse2_unaligned_erms+17>:   ja     0x7ffff7af8c41 <__memmove_sse2_unaligned_erms+145>
   0x7ffff7af8bc3 <__memmove_sse2_unaligned_erms+19>:   movups (%rsi),%xmm0
   0x7ffff7af8bc6 <__memmove_sse2_unaligned_erms+22>:   movups -0x10(%rsi,%rdx,1),%xmm1
   0x7ffff7af8bcb <__memmove_sse2_unaligned_erms+27>:   movups %xmm0,(%rdi)
=> 0x7ffff7af8bce <__memmove_sse2_unaligned_erms+30>:   movups %xmm1,-0x10(%rdi,%rdx,1)
   0x7ffff7af8bd3 <__memmove_sse2_unaligned_erms+35>:   retq
   0x7ffff7af8bd4 <__memmove_sse2_unaligned_erms+36>:
    cmp    0x1358dd(%rip),%rdx        # 0x7ffff7c2e4b8 <__x86_shared_non_temporal_threshold>
(gdb) i r rdi
rdi            0x55555563dcb8      93824993189048

movups %xmm0,(%rdi)kex->c25519_client_keyへの実際の書き込みが行われたと思われるので、rdiレジスタの指す0x55555563dcb8が、kex->c25519_client_keyのアドレスとなる。 kex先頭からのオフセットは、0x55555563dcb8 - 0x55555563db30 = 392で、上記自前OpenSSHビルドの312と異なっている。

ともあれ、乖離した値でもないので、watchpointを削除した後にkex_gen_clientの終わりまで続行して、client keyの値を取り出す。 CURVE25519_SIZE32なので、kex->c25519_client_keyのアドレスから32バイトを表示する。

(gdb) d 2
(gdb) c
Continuing.

Temporary breakpoint 3, 0x00005555555b5035 in ?? ()
(gdb) x/32bx 0x55555563dcb8
0x55555563dcb8: 0x07    0x09    0x30    0x71    0x14    0x42    0x37    0x04
0x55555563dcc0: 0xc3    0xf1    0xa7    0xe4    0xb4    0x9c    0xae    0x14
0x55555563dcc8: 0xde    0xb1    0x36    0x8f    0x2c    0x7d    0x07    0x5e
0x55555563dcd0: 0xa5    0xc9    0x67    0x09    0x8d    0x32    0xc2    0x1f

cookieの取り出し

kex構造体の定義を確認すると、cookieの格納されているkex->myは、kex->kex_typeの3つ先のメンバ変数となっている。 前節でのkex_gen_client()の逆アセンブル結果から、kex->kex_typeへのオフセットは0x48と判明している(mov 0x48(%rbx),%eax)。 kex構造体の定義通り、kex_typeの3つ先にmyのポインタ0x0000555555639920が入っているので、sshbuf構造体の定義より、my->dの0x0000555555643a80に入っているcookieの値を表示する。

(gdb) x/8gx 0x55555563db30+0x48
0x55555563db78: 0x0000000000000008      0x0000000000000000
0x55555563db88: 0x0000000000000000      0x0000555555639920
0x55555563db98: 0x000055555563c890      0x0000555555639970
0x55555563dba8: 0x0000555555639470      0x0000000300000000
(gdb) x/8gx 0x0000555555639920
0x555555639920: 0x0000555555643a80      0x0000555555643a80
0x555555639930: 0x0000000000000000      0x00000000000005d8
0x555555639940: 0x0000000008000000      0x0000000000000600
0x555555639950: 0x0000000000000000      0x0000000000000001
(gdb) x/16bx 0x0000555555643a80
0x555555643a80: 0x09    0xb7    0x04    0x00    0xa4    0xb4    0xa5    0x67
0x555555643a88: 0x39    0xaa    0x2c    0xc4    0x2c    0x45    0xfd    0xf1

ssh key logファイルの作成とWiresharkへの設定

cookieとclient keyの対が揃ったので、以下のようなssh key logfile形式(<cookie> <key>)のssh key logファイルを作成する。

09b70400a4b4a56739aa2cc42c45fdf1 0709307114423704c3f1a7e4b49cae14deb1368f2c7d075ea5c967098d32c21f

Wiresharkの設定で作成したssh key logfileを指定する。 正しく設定されていれば、画面のようにUser=pi, Password=raspberryでパスワード認証を試みる様子が復号される。

Wiresharkでのsshパケット復号結果

*1:実際、前回の自前ビルドとUbuntu向けビルドでは異なった。