How to use keyboard/mouse for PS4 part2 -How does Bluetooth work?-

How to use keyboard/mouse for PS4 part1 -Sniffing HCI-UART- - aki33524’s blog

今回は前回に引き続き、Bluetoothの簡単な説明を行う。

Bluetoothについて調べると他の分野に比べて資料が少なくて驚くと思う。

仕様書はまだ平易な英語で書かれているし、検索もやりやすいが膨大(3000Pくらい)で筋だった理解を得るのは難しい。適当な二次資料を読みながら仕様書で確認するのが良い。

とは言え僕自身Bluetoothのことは殆ど分かっていないし、結局上手く行っていない。あまりに上手く行かないのでとりあえず記事を書くことにした。

上記から分かるように、この記事の内容は間違っているものとして扱って欲しい。加えてBluetoothを一般に書くと広範すぎるため、DS4に特化した説明になると思う(これは僕がある機能がDS4固有なのかそうでないのかすら判断がついていないからでもある)。

General

Bluetoothの初期接続は大きく分けて3つのフェイズに分かれる。Inquiry/Page, SDP, Pairing/Bondingである。

Inquiry/Page

InquiryはWifiにおけるBeaconのような機能である。PS4がスキャンをしてDS4が応答を返す。これには自身がコントローラーであることなどの情報がも含まれる。

Pageは機器同士のコネクションを張る機能。

SDP

Service Discovery Protocol(SDP)とはあるBluetoothバイスがどのような機能をサポートしているかを通知するためのプロトコルである。pageが終わった後にPS4がrequestを送ってくるので、適切なSDPを返す必要がある。ただ、SDPの長さは本来L2CAP MTUの制限である672byte以内になるはずなのだが、DS4は700byte以上送る必要がある。これは最初LinuxのKernel moduleを直接書き換えてre-compileすることで対応していた。実際にDS4が送っているSDPをsniffしてみるとパケットを分割して送信していた(L2CAPがMTUをオーバーした時の分割をサポートする)。

http://www.psdevwiki.com/ps4/DS4-BT#DS4

Pairing/Bonding

よく「ペアリング」は「機器の登録」として捉えられるが、実際には鍵交換や認証のフェイズを意味するらしい。それらを保存することをBondingと呼ぶようだ。

よく目にするペアリングではPINコードという4桁もしくは6桁のパスワードを入力するが、DS4ではSecure Simple Pairing(SSP)という仕組みで動的にLink Keyを生成して鍵交換を行う。僕が見た仕組みでは互いに生成した乱数値をECDHで交換し、それをhash化することでLink Keyを生成するらしい。この手法では「互いの機器が自分だけで鍵を設定する事ができない」ようになっており、鍵固定化攻撃を妨げている(後述するがDS4ではここに穴がある)。

DS4-USB

ここまで読んで不思議に思った人も居るかと思う。普通にPS4で遊ぶ時にコントローラーでSSPによるペアリング処理を行うことは無いからだ。

実はDS4では二通りのペアリング手法を用意しており、SSPによるペアリングは裏技的機能である。やり方もPSボタンとShareボタンを同時に長押といういかにも裏技らしい方法となっている。

普通、初めて使うDS4は有線でPS4に接続する。この時にペアリング的な処理が行われている(Bluetoothの仕様に則っては居ないと思うが本質的にはまさしくペアリング)。

DS4を接続した際、DS4のBluetoothPS4に登録されていない場合、PS4が自身のBluetoothMACBluetoothではBD_ADDRなどと呼ぶ。Ethernetと同じで6byteであり、ベンダーコードも一致するようではあるが。)とLink Keyを送信する。

これにより鍵交換と機器の登録が行われ、次の接続からはコントローラーの電源を入れればInquiryやSDP, Pairing処理を端折って通信を行う事ができる。

http://www.psdevwiki.com/ps4/DS4-USB#Class_Requests

ここで注目すべきはPS4が鍵をそのまま送りつけて来るということである。この仕様だと簡単にman-in-the-middleで鍵をrelayすることが出来る。GIMXはこの手法を用いている。

しかし、実はUSBのman-in-the-middleには(決して高級なわけではないが)少々特殊なデバイスが必要となる。普通PCについているUSBポートはホスト側であり、デバイス側に偽装することが出来ないからだ。例えばRaspberryPiZeroなどはデバイスモードに対応している。

ところで今回の目的は特殊なデバイスを用いずにDS4をエミュレーションすることであった。GMIXの手法を使うことは出来ない。 BluetoothではUSBのように物理的なホスト/デバイスの区別がない。理論的には可能なはずだ。

Bluez

Bluezは現在Linuxで使われているそびえ立つクソBluetoothプロトコルスタックである。ややこしいBluetoothをsocket通信で扱える便利モジュールであるはずだった。

残念ながらBluezを使ってデバイスのエミュレーションをするのはとんでもなく面倒な作業である。どうやらBluezはGUIからホスト側でBluetoothバイスを扱うことに注力しているようで、実際にsocketでガリガリいじろうとすると謎の挙動のオンパレードだった。

これは僕が悪いのかPS4が悪いのかBluezが悪いのかBluetoothバイスが悪いのかわからない。ただ、Bluezには一切のドキュメントが存在しないし(bluetoothdのコードとkernelのコードと仕様書を読み続けるハメになる)、hcidumpで読めているl2capのconnection requestをガン無視して一生socketをacceptしてくれないしマジでクソ。

というか多分Bluezはガリガリ弄ることを想定していないらしい(だからドキュメントもないのだろう)。

例えばさっき述べたように700byteのSDPを送信しようとすると(本当は何かやり方があるはずだけど)、bluetooth.koのコードを弄ってMTUを無視して送信する必要があった(と言っても下の箇所をコメントアウトするだけ)。

github.com

hcidumpやwiresharkで見ると送れているはずのhci packetが遅れていないのか謎の挙動をするしもう何もわからん。

一応不安定なりにたまにペアリングが出来る時があり、上手く行った時の動画を載せておく。

www.youtube.com

多分ここまで200時間位かかっていて、うち150時間くらいはBluetoothとBluezと闘っていた。本当にクソ。

一応上手く行っていないコードを載せておく。誰か問題点を指摘してくれ頼む。setup処理はBluetoothの諸々の設定をしているだけでさほど重要ではないと思う。hcidumpではl2capのconnection requestが送られて来ているにも関わらず383行目のacceptが返ってこない。もう意味がわからん。

Bluezを捨ててBtstack(組み込み向けのプロトコルスタック)を使ったほうが良い気がしているのだけど、ここしばらくずっとBluetoothの学習(という名の無駄)に時間が割かれていて疲れた。

gist.github.com