CLOVER🍀

That was when it all began.

BPF Compiler Collection(BCC)のsslsniffでcurlのHTTPS(SSL/TLS)通信をスニッフィングしてみる(+Pythonではうまくいかなかった話)

これは、なにをしたくて書いたもの?

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="{&quot;hatenablog&quot;:{&quot;admin&quot;:{},&quot;analytics&quot;:{&quot;brand_property_id&quot;:&quot;&quot;,&quot;measurement_id&quot;:&quot;G-VZ4P54MK8T&quot;,&quot;non_sampling_property_id&quot;:&quot;&quot;,&quot;property_id&quot;:&quot;&quot;,&quot;separated_property_id&quot;:&quot;UA-29716941-26&quot;},&quot;blog&quot;:{&quot;blog_id&quot;:&quot;10257846132617921657&quot;,&quot;content_seems_japanese&quot;:&quot;true&quot;,&quot;disable_ads&quot;:&quot;&quot;,&quot;enable_ads&quot;:&quot;true&quot;,&quot;enable_keyword_link&quot;:&quot;true&quot;,&quot;entry_show_footer_related_entries&quot;:&quot;true&quot;,&quot;force_pc_view&quot;:&quot;false&quot;,&quot;is_public&quot;:&quot;true&quot;,&quot;is_responsive_view&quot;:&quot;false&quot;,&quot;is_sleeping&quot;:&quot;false&quot;,&quot;lang&quot;:&quot;ja&quot;,&quot;name&quot;:&quot;CLOVER\ud83c\udf40&quot;,&quot;owner_name&quot;:&quot;Kazuhira&quot;,&quot;uri&quot;:&quot;https://kazuhira-r.hatenablog.com/&quot;},&quot;brand&quot;:&quot;hatenablog&quot;,&quot;page_id&quot;:&quot;index&quot;,&quot;permalink_entry&quot;:null,&quot;pro&quot;:&quot;free&quot;,&quot;router_type&quot;:&quot;blogs&quot;}}"
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="{&quot;hatenablog&quot;:{&quot;admin&quot;:{},&quot;analytics&quot;:{&quot;brand_property_id&quot;:&quot;&quot;,&quot;measurement_id&quot;:&quot;G-VZ4P54MK8T&quot;,&quot;non_sampling_property_id&quot;:&quot;&quot;,&quot;property_id&quot;:&quot;&quot;,&quot;separated_property_id&quot;:&quot;UA-29716941-26&quot;},&quot;blog&quot;:{&quot;blog_id&quot;:&quot;10257846132617921657&quot;,&quot;content_seems_japanese&quot;:&quot;true&quot;,&quot;disable_ads&quot;:&quot;&quot;,&quot;enable_ads&quot;:&quot;true&quot;,&quot;enable_keyword_link&quot;:&quot;true&quot;,&quot;entry_show_footer_related_entries&quot;:&quot;true&quot;,&quot;force_pc_view&quot;:&quot;false&quot;,&quot;is_public&quot;:&quot;true&quot;,&quot;is_responsive_view&quot;:&quot;false&quot;,&quot;is_sleeping&quot;:&quot;false&quot;,&quot;lang&quot;:&quot;ja&quot;,&quot;name&quot;:&quot;CLOVER\ud83c\udf40&quot;,&quot;owner_name&quot;:&quot;Kazuhira&quot;,&quot;uri&quot;:&quot;https://kazuhira-r.hatenablog.com/&quot;},&quot;brand&quot;:&quot;hatenablog&quot;,&quot;page_id&quot;:&quot;index&quot;,&quot;permalink_entry&quot;:null,&quot;pro&quot;:&quot;free&quot;,&quot;router_type&quot;:&quot;blogs&quot;}}"
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を使ったデータの読み書きを対象にしているということなので、SSLTLS通信に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でした。
※ライブラリー上のリファレンスは同じページですが

試しにsslsniff-bpfccをコピーしてSSL_read_exにアタッチするように名前を変えてみると、反応するようにはなりました。
※中身はPythonスクリプトなので

$ cp $(which sslsniff-bpfcc) ./.

ただ、引数の内容が違うのでうまくは動きませんが…。

PythonがOpenSSLのどの関数を使っているのかは、こちらでも確認できましたね。

$ ltrace -e 'SSL_*' python3 server.py

というわけで、スニッフィング対象のライブラリーがOpenSSLのどの関数を使っているのかによってはうまくいかないことも
あるという話でした。

おわりに

BPF Compiler Collection(BCC)のsslsniffでHTTPSSSLTLS)通信をスニッフィングしてみました。

curlでは割とあっさりとうまくいったものの、Pythonではうまくいかなかったりとアタッチしている関数次第なんだなというのも
よくわかりました。

ただ、BCCツールはPythonスクリプトではあるので(Cもインラインで書かれていますが…)、頑張ったらなんとか
なりそうな気もするのですが…どうでしょうね。