Japan
サイト内の現在位置
Bluetoothによるファイル送信の解析
NECセキュリティブログ2021年05月14日
NECサイバーセキュリティ戦略本部の川北です。
依然としてCOVID-19による脅威の続くなか、日々の業務をテレワークで行なっている方が多いのではないかと思います。テレワークでは、インターネットへの接続やリモート会議用のヘッドセットを利用するために、Bluetoothを介してスマートフォンとパソコンを接続するケースがあります。Bluetoothの利用には様々なセキュリティリスクがあることは知られていますが、一律に禁止すると利便性が下がるため、仕方なく利用を許可している環境もあるのではないでしょうか。
今回はBluetoothの持つ機能の一つである「ファイル送信」の解析方法について紹介します。
1.Bluetooth経由のファイル送信
Windows 10の導入されたパソコンからBluetoothを介してスマートフォンにファイルを送信することは簡単に行えます。デスクトップ右下の通知領域にあるBluetoothアイコンを右クリックするだけで、コンテキストメニューに「ファイルの送信」コマンドが現れます。

この「ファイルの送信」から実行したBluetoothによる通信はネットワークトラフィックとして観測されないため、例えば機密度の高いファイルを私物のスマートフォンにこっそり移した場合でも十分な痕跡がログに残らない場合があります。
内部不正、乗っ取り、マルウェアの感染等により、Bluetoothを介したファイルの送信が行われている疑いのあるパソコンに対して、その様子をネットワークトラフィックのようにキャプチャできるのか、また、その結果から送信したファイルを復元できるのかを本稿にて検証します。
2.WiresharkによるBluetoothパケットキャプチャ
検証用にBluetooth対応のWindows 10パソコンとAndroid 11スマートフォンを用意しました。そして、Wireshark [1]をパソコンにインストールします。Wiresharkはネットワークパケットキャプチャツールとして広く知られていますが、USB等のデバイスを流れるデータもキャプチャできます。パソコンには国内の通販サイトで安く売られているUSBタイプのBluetooth 4.0アダプターを装着したため、Wiresharkのインストール時にオプションのUSBPcapも併せて導入します。
Wiresharkを起動し、USBPcapインタフェースに流れるパケットをキャプチャします。インタフェースを指定する画面で複数のインタフェースが表示される場合はいずれかがBluetoothアダプターに繋がっているので、試行して探し当ててください。

Wiresharkでのキャプチャを開始した後、パソコンからスマートフォンへBluetooth経由で適当なファイルを送信します。ここでは筆者が出先で春先に撮影した、菜の花畑のてんとう虫の写真 test.jpg を使いました。


転送を完了したのち、キャプチャを終了します。Wiresharkのディスプレイフィルタに”btrfcomm”を指定すると、以下のようにBluetoothでのファイル送受信に限定したパケットが表示されます。

執筆時点で公開されているバージョンのWireshark(v3.4.5)のデフォルト設定ではパケットの完全な自動解析はできません。しかし、ペイロードを調べると、“Bluetooth RFCOMM Protocol”のあとに先ほど送信した写真のファイル名 test.jpg のUnicode文字列が見つかりました。
BluetoothのRFCOMM プロトコル[2]は接続する機器同士の論理的な伝送チャネルL2CAP上でRS-232Cシリアルポートの転送機能をエミュレートするものです。今回はチャネル番号12上で送信されていたことが、パケットキャプチャ結果からわかります。
tsharkコマンドを使ってRFCOMMプロトコルが運んだペイロードをraw.binファイルに抽出します。なお、以降の解析ではLinux (Ubuntu 20.04 LTS)を用いています。
$ tshark -r btrfcomm.pcapng -Y "btrfcomm.channel==12" -T fields -e data | tr -d '\n' | xxd -r -ps > raw.bin
RFCOMMプロトコルを介したファイルの送受信にはOBEXプロトコル[3]が用いられます。OBEXプロトコルの解析はWiresharkでもできますが今回の目的には十分でないため、raw.binに記録されたOBEXプロトコルのペイロードから送信したファイルのバイナリを抽出する簡単な解析用Pythonプログラムを用意しました。なお、想定外のペイロードの対処はしていないので注意してください。
解析用プログラム: decobex.py
#!/usr/bin/env python3
import struct
import sys
def decode_obex(raw_packet_file):
with open(raw_packet_file, 'rb') as f:
body = b''
body_length = 0
name = ''
end_of_body = False
while not end_of_body:
# REQUEST
print('REQUEST: ', end='')
(command, length) = struct.unpack('>BH', f.read(3))
final_bit = (command >> 7) & 0x01
command &= 0x7f
if command == 0x00 and final_bit == 0x01:
# Connect
print('Connect')
payload = f.read(length - 3)
elif command == 0x01 and final_bit == 0x01:
# Disconnect
print('Disconnect')
payload = f.read(length - 3)
elif command == 0x02:
# Put
print('Put ', end='')
begin_pos = f.tell()
end_pos = begin_pos + length - 3
while f.tell() < end_pos:
(hi,) = struct.unpack('B', f.read(1))
if hi == 0x01:
# Name header
(name_length,) = struct.unpack('>H', f.read(2))
name = f.read(name_length - 3).decode('utf-16be').rstrip('\x00')
print(f'name={name} ', end='')
elif hi == 0x48:
# Body header
(body_fragment_length,) = struct.unpack('>H', f.read(2))
print(f'body_fragment={body_fragment_length} ', end='')
body += f.read(body_fragment_length - 3)
break
elif hi == 0x49:
# End-of-body header
(body_fragment_length,) = struct.unpack('>H', f.read(2))
print(f'body_fragment={body_fragment_length} ', end='')
print(f'end-of-body ', end='')
body += f.read(body_fragment_length - 3)
end_of_body = True
break
elif hi == 0xc3:
# Length header
(body_length,) = struct.unpack('>I', f.read(4))
print(f'body_length={body_length} ', end='')
else:
raise Exception(hi)
print('')
else:
raise Exception(command)
# RESPONSE
print('==> RESPONSE: ', end='')
(command, length) = struct.unpack('>BH', f.read(3))
final_bit = (command >> 7) & 0x01
command &= 0x7f
payload = f.read(length - 3)
if command == 0x10:
# Continue
print('Continue')
elif command == 0x20:
# OK, Success
print('OK, Success')
else:
raise Exception(command)
if len(body) == body_length and name != '':
with open(name, 'wb') as fout:
fout.write(body)
if __name__ == '__main__':
decode_obex(sys.argv[1])
この解析用プログラムを実行すると、ペイロードから送信したファイルが復元できました。
$ ./decobex.py raw.bin
REQUEST: Connect
==> RESPONSE: OK, Success
REQUEST: Put name=test.jpg body_length=59321 body_fragment=8051
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=8077
==> RESPONSE: Continue
REQUEST: Put body_fragment=2832 end-of-body
==> RESPONSE: OK, Success
3.Windows Performance Recorder (WPR) によるロギング
> wget https://github.com/Microsoft/busiotools/raw/master/bluetooth/tracing/BluetoothStack.wprp -outfile BluetoothStack.wprp
> wpr.exe -start BluetoothStack.wprp!BluetoothStack -filemode
その後、Wiresharkの例と同様にファイル転送を実行し、以下のコマンドでキャプチャを終了します。
> wpr.exe -stop BthTracing.etl
キャプチャした結果が格納されているイベントファイルBthTracing.etlをWindows Performance Analyzer (WPA) [6]で開きます。WPAはWindows SDK
[7]の一部としてマイクロソフト社から提供されているので、解析用のマシンにあらかじめインストールする必要があります。
WPAの画面左からSystem ActivityのGeneric Eventsをクリックして、画面右に詳細ログを表示します。”File”でフィルタすると”FileTransferEnd”のログが表示されます。ログにはファイルサイズや拡張子が記載されており、完全ではないものの、ファイル送信の事実をつかむことはできます。

Windows 10の標準機能でBluetooth経由のファイル送信ログを取れることは魅力的ですが、これは開発者向けの方法だということに注意してください。
参考までに、Windows 10(Homeを除くエディションのバージョン1511以降)では特定のBluetoothサービス・プロファイルのみ利用を許可する機能があります。これを利用するとBluetoothによるファイル送信だけを不許可とすることが可能です。詳しくはマイクロソフト社の資料[8]を参照してください。
4.まとめ
本稿ではBluetoothを介してパソコンからスマートフォンへ送信されたファイルを、Wiresharkを用いたパケットキャプチャの結果から復元する手法を紹介しました。また、Windows標準機能のWPRを用いたログの取得方法も併せて示しました。Bluetoothにはファイル転送以外にも様々な機能があり、セキュリティ上のリスクとなることから、テレワーク環境下での運用には注意が必要です。
5.参考文献
- [1]
- [2]
- [3]
- [4]
- [5]
- [6]
- [7]
- [8]
執筆者プロフィール
川北 将(かわきた まさる)
セキュリティ技術センター(インテリジェンスチーム・デジタルシャドウ活用チーム)
・セキュアシステム研究所
脅威インテリジェンス分析の研究開発に従事。
SECCON CTF国際大会の決勝戦に参加した過去も。
情報処理安全確保支援士(2021年5月現在)、システム監査技術者
人生の楽しみはうまい酒とうまいパケットキャプチャ。

執筆者の他の記事を読む
アクセスランキング
2025年4月6日~4月12日に読まれた記事のランキング