これは、なにをしたくて書いたもの?
Ubuntu Linux 24.04 LTSでは、BPF Compiler Collection(BCC)のツールが使えるようになっていたという話を前に書きました。
Ubuntu Linux 24.04 LTSでBPF Compiler Collection(BCC)のツールが入れられるようになっていたという話 - CLOVER🍀
BCCツールに含まれているものの中に、sslsniffというものがあり、これでOpenSSLを使った通信をスニッフィングできるようなので
試してみました。
あと、うまくいかなかった話もあります。
環境
今回の環境はこちら。
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04.2 LTS Release: 24.04 Codename: noble $ uname -srvmpio Linux 6.8.0-53-generic #55-Ubuntu SMP PREEMPT_DYNAMIC Fri Jan 17 15:37:52 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux $ apt-cache show bpfcc-tools | grep Version Version: 0.29.1+ds-1ubuntu7
sslsniffを見てみる
まずはsslsniffの説明を見てみましょう。
$ sslsniff-bpfcc --help usage: sslsniff-bpfcc [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] [--hexdump] [--max-buffer-size MAX_BUFFER_SIZE] [-l] [--handshake] [--extra-lib EXTRA_LIB] Sniff SSL data options: -h, --help show this help message and exit -p PID, --pid PID sniff this PID only. -u UID, --uid UID sniff this UID only. -x, --extra show extra fields (UID, TID) -c COMM, --comm COMM sniff only commands matching string. -o, --no-openssl do not show OpenSSL calls. -g, --no-gnutls do not show GnuTLS calls. -n, --no-nss do not show NSS calls. -d, --debug debug mode. --hexdump show data as hexdump instead of trying to decode it as UTF-8 --max-buffer-size MAX_BUFFER_SIZE Size of captured buffer -l, --latency show function latency --handshake show SSL handshake latency, enabled only if latency option is on. --extra-lib EXTRA_LIB Intercept calls from extra library (format: lib_type:lib_path) examples: ./sslsniff # sniff OpenSSL and GnuTLS functions ./sslsniff -p 181 # sniff PID 181 only ./sslsniff -u 1000 # sniff only UID 1000 ./sslsniff -c curl # sniff curl command only ./sslsniff --no-openssl # don't show OpenSSL calls ./sslsniff --no-gnutls # don't show GnuTLS calls ./sslsniff --no-nss # don't show NSS calls ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 ./sslsniff -x # show process UID and TID ./sslsniff -l # show function latency ./sslsniff -l --handshake # show SSL handshake latency ./sslsniff --extra-lib openssl:/path/libssl.so.1.1 # sniff extra library
SSLデータのスニッフィングツールだとされています。
Sniff SSL data
見ていると、指定したPIDのものやUIDのものだけを対象にしたりもできるようです。
また、README.md
を見るとOpenSSLの読み書きを対象にしているようですが、ヘルプの内容からするとGnuTLSとNSSも
対象にしているみたいですね。
tools/sslsniff: Sniff OpenSSL written and readed data. Examples.
https://github.com/iovisor/bcc/tree/v0.29.1
curlに対して使ってみる
では、curlに対して使ってみましょう。
sslsniffをroot権限で起動します。
$ sudo sslsniff-bpfcc
少し待っていると、こんなヘッダーが現れます。
FUNC TIME(s) COMM PID LEN
ここで、curlでこのブログにアクセスしてみます。
$ curl https://kazuhira-r.hatenablog.com
すると、sslsniffを実行しているコンソールにこんなログが現れます。
WRITE/SEND 0.000000000 curl 2362 64 ----- DATA ----- PRI * HTTP/2.0 SM d> ----- END DATA ----- WRITE/SEND 0.000117645 curl 2362 47 ----- DATA ----- &��A��?v�k���4���A�\���z�%�P�˶�?S*/* ----- END DATA ----- READ/RECV 0.004003457 curl 2362 49 ----- DATA ----- ���� ----- END DATA ----- WRITE/SEND 0.004127526 curl 2362 9 ----- DATA ----- ----- END DATA ----- READ/RECV 0.008120684 curl 2362 16384 ----- DATA ----- ��p"S��_�I|���Mjq� S*�{���-i[D<��ovary��[f"����*��V_�ŰVb-��̷�aԔ}BB����CU7T!b��z�ǰ��*�!�IjJ�)-����g�������%c�G������â��7�ȱRKR~��ĭm�Rt��\a��<��4���QW�[���ak=�p3p����V��^�M\6�w��7o����Iʱ����eFD�1��RKRVO�ʱ�I�R?�������EcI'��4��ܹ����E��˴d\��� ���T������z�cԏDENY���d���vG]!D�\y��Ґb�2���!j�:JD��1��ԓR_�� �d��q�Q�?�q��x-6���i�C�Ńe��via� �Pլ,����kT{9F���zk�%\�$�T:���ʌ��uv����I�HIT� �9V!�M�����wK��vϥ�/s ǫ��vG]!D�\y��Ґb�2�#o7$#Q��!�IjJ�)-����g��������X� ��a{ZT% <!DOCTYPE html> <html lang="ja" data-admin-domain="//blog.hatena.ne.jp" data-admin-origin="https://blog.hatena.ne.jp" data-author="Kazuhira" data-avail-langs="ja en" data-blog="kazuhira-r.hatenablog.com" data-blog-host="kazuhira-r.hatenablog.com" data-blog-is-public="1" data-blog-name="CLOVER🍀" data-blog-owner="Kazuhira" data-blog-show-ads="1" data-blog-show-sleeping-ads="" data-blog-uri="https://kazuhira-r.hatenablog.com/" data-blog-uuid="10257846132617921657" data-blogs-uri-base="https://kazuhira-r.hatenablog.com" data-brand="hatenablog" data-data-layer="{"hatenablog":{"admin":{},"analytics":{"brand_property_id":"","measurement_id":"G-VZ4P54MK8T","non_sampling_property_id":"","property_id":"","separated_property_id":"UA-29716941-26"},"blog":{"blog_id":"10257846132617921657","content_seems_japanese":"true","disable_ads":"","enable_ads":"true","enable_keyword_link":"true","entry_show_footer_related_entries":"true","force_pc_view":"false","is_public":"true","is_responsive_view":"false","is_sleeping":"false","lang":"ja","name":"CLOVER\ud83c\udf40","owner_name":"Kazuhira","uri":"https://kazuhira-r.hatenablog.com/"},"brand":"hatenablog","page_id":"index","permalink_entry":null,"pro":"free","router_type":"blogs"}}" data-device="pc" data-dont-recommend-pro="false" data-global-domain="https://hatena.blog" data-globalheader-color="b" data-globalheader-type="pc" data-has-touch-view="1" data-help-url="https://help.hatenablog.com" data-page="index" data-parts-domain="https://hatenablog-parts.com" data-plus-available="" data-pro="false" data-router-type="blogs" data-sentry-dsn="https://03a33e4781a24cf2885099fed222b56d@sentry.io/1195218" data-sentry-environment="production" data-sentry-sample-rate="0.1" data-static-domain="https://cdn.blog.st-hatena.com" data-version="2707d77ce205d01688a448fca2fbe6" data-initial-state="{}" > <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#"> <meta name="robots" content="max-image-preview:large" /> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=7; IE=9; IE=10; IE=11" /> <title>CLOVER🍀</title> 〜省略〜 ----- END DATA (TRUNCATED, 8192 bytes lost) -----
すごいですね、テキストで内容が見えています。
ただ、HTTP/2だからかリクエストの内容がまったくわかりませんね…。
HTTP/1.1にして見てみましょう。
$ curl --http1.1 https://kazuhira-r.hatenablog.com
今度はリクエストの内容も読めるようになりました。
Possibly lost 2 samples WRITE/SEND 169.381227270 curl 2366 88 ----- DATA ----- GET / HTTP/1.1 Host: kazuhira-r.hatenablog.com User-Agent: curl/8.5.0 Accept: */* ----- END DATA ----- READ/RECV 169.390197768 curl 2366 16384 ----- DATA ----- HTTP/1.1 200 OK Server: nginx Date: Tue, 11 Feb 2025 11:20:02 GMT Content-Type: text/html; charset=utf-8 Transfer-Encoding: chunked Connection: keep-alive Vary: Accept-Encoding Vary: X-Epic-Device-Type,X-Epic-Flag-Variants,Accept-Encoding Access-Control-Allow-Origin: * Content-Security-Policy-Report-Only: block-all-mixed-content; report-uri https://blog.hatena.ne.jp/api/csp_report P3P: CP="OTI CUR OUR BUS STA" X-Cache-Only-Varnish: 1 X-Content-Type-Options: nosniff X-Dispatch: Hatena::Epic::Web::Blogs::Index#index X-Frame-Options: DENY X-Revision: 2707d77ce205d01688a448fca2fbe6 X-XSS-Protection: 1 X-Runtime: 0.117030 X-Varnish: 212361497 211874631 Age: 4021 Via: 1.1 ip-10-1-19-102.ap-northeast-1.compute.internal (Varnish/7.5) X-Cache: HIT Cache-Control: private X-Proxy-Revision: 2707d77ce205d01688a448fca2fbe6d581b6dc4b Content-Security-Policy: upgrade-insecure-requests 5f71 <!DOCTYPE html> <html lang="ja" data-admin-domain="//blog.hatena.ne.jp" data-admin-origin="https://blog.hatena.ne.jp" data-author="Kazuhira" data-avail-langs="ja en" data-blog="kazuhira-r.hatenablog.com" data-blog-host="kazuhira-r.hatenablog.com" data-blog-is-public="1" data-blog-name="CLOVER🍀" data-blog-owner="Kazuhira" data-blog-show-ads="1" data-blog-show-sleeping-ads="" data-blog-uri="https://kazuhira-r.hatenablog.com/" data-blog-uuid="10257846132617921657" data-blogs-uri-base="https://kazuhira-r.hatenablog.com" data-brand="hatenablog" data-data-layer="{"hatenablog":{"admin":{},"analytics":{"brand_property_id":"","measurement_id":"G-VZ4P54MK8T","non_sampling_property_id":"","property_id":"","separated_property_id":"UA-29716941-26"},"blog":{"blog_id":"10257846132617921657","content_seems_japanese":"true","disable_ads":"","enable_ads":"true","enable_keyword_link":"true","entry_show_footer_related_entries":"true","force_pc_view":"false","is_public":"true","is_responsive_view":"false","is_sleeping":"false","lang":"ja","name":"CLOVER\ud83c\udf40","owner_name":"Kazuhira","uri":"https://kazuhira-r.hatenablog.com/"},"brand":"hatenablog","page_id":"index","permalink_entry":null,"pro":"free","router_type":"blogs"}}" data-device="pc" data-dont-recommend-pro="false" data-global-domain="https://hatena.blog" data-globalheader-color="b" data-globalheader-type="pc" data-has-touch-view="1" data-help-url="https://help.hatenablog.com" data-page="index" data-parts-domain="https://hatenablog-parts.com" data-plus-available="" data-pro="false" data-router-type="blogs" data-sentry-dsn="https://03a33e4781a24cf2885099fed222b56d@sentry.io/1195218" data-sentry-environment="production" data-sentry-sample-rate="0.1" data-static-domain="https://cdn.blog.st-hatena.com" data-version="2707d77ce205d01688a448fca2fbe6" data-initial-state="{}" > <head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#"> <meta name="robots" content="max-image-preview:large" /> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=7; IE=9; IE=10; IE=11" /> <title>CLOVER🍀</title> 〜省略〜 </body> </html> 0 ----- END DATA -----
これで使い方のイメージはわかった気がします。
Pythonでやってうまくいかなかった話
sslsniffはOpenSSLを使ったデータの読み書きを対象にしているということなので、SSL/TLS通信にOpenSSLを使っている
ものであればスニッフィングできる(自前で実装しているJavaなどは不可)だろうと思い、Pythonで試してみることに
しました。
標準ライブラリーを使います。
urllib --- URL を扱うモジュール群 — Python 3.12.9 ドキュメント
環境はこちら。
$ python3 --version Python 3.12.3
curlで行っていたように、このブログにアクセスするプログラムを作成します。またHTTPサーバーとして実装します。
server.py
from http.server import BaseHTTPRequestHandler, HTTPServer from urllib.request import urlopen address = ("0.0.0.0", 8080) class MyHttpRequestHandler(BaseHTTPRequestHandler): def do_GET(self): with urlopen("https://kazuhira-r.hatenablog.com") as f: body = f.read().decode("utf8") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.end_headers() self.wfile.write(body.encode("utf8")) if __name__ == "__main__": with HTTPServer(address, MyHttpRequestHandler) as server: server.serve_forever()
起動。
$ python3 server.py
今回はsslsniffにPythonアプリケーションのPIDを指定して実行することにします。
$ sudo sslsniff-bpfcc -p 2384 FUNC TIME(s) COMM PID LEN
別のターミナルから、作成したHTTPサーバーにアクセス。
$ curl localhost:8080
結果は返ってくるのですが、スニッフィングがうまくいきません。
FUNC TIME(s) COMM PID LEN
これはどうしてだろう?と思って調べてみたのですが、どうやらsslsniffがトレース対象にしている関数をPythonでは
使っていないようです。
sslsniffのOpenSSLにアタッチしている部分を見ると、対象にしている関数は以下の3つのようです。
https://github.com/iovisor/bcc/blob/v0.29.1/tools/sslsniff.py#L276-L293
これに対して、Pythonが使っているのはSSL_write_exとSSL_read_exでした。
※ライブラリー上のリファレンスは同じページですが
- https://github.com/python/cpython/blob/v3.12.3/Modules/_ssl.c#L2370
- https://github.com/python/cpython/blob/v3.12.3/Modules/_ssl.c#L2523
試しにsslsniff-bpfccをコピーしてSSL_read_exにアタッチするように名前を変えてみると、反応するようにはなりました。
※中身はPythonスクリプトなので
$ cp $(which sslsniff-bpfcc) ./.
ただ、引数の内容が違うのでうまくは動きませんが…。
PythonがOpenSSLのどの関数を使っているのかは、こちらでも確認できましたね。
$ ltrace -e 'SSL_*' python3 server.py
というわけで、スニッフィング対象のライブラリーがOpenSSLのどの関数を使っているのかによってはうまくいかないことも
あるという話でした。
おわりに
BPF Compiler Collection(BCC)のsslsniffでHTTPS(SSL/TLS)通信をスニッフィングしてみました。
curlでは割とあっさりとうまくいったものの、Pythonではうまくいかなかったりとアタッチしている関数次第なんだなというのも
よくわかりました。
ただ、BCCツールはPythonスクリプトではあるので(Cもインラインで書かれていますが…)、頑張ったらなんとか
なりそうな気もするのですが…どうでしょうね。