クソ雑魚ナメクジのためのオーバーウォッチ立ち回りPart2

前回クソ雑魚記事を書いたがその後いくつかノウハウが溜まった。前回の記事ではサッカーで言えば「オフェンスやディフェンスが合ってそれぞれこういう動きをすべきで」みたいな話を書いていたが、もっと基礎的なことが必要だと気がついた。今回は「ボールはこうやって蹴る」的な話を書いていく。

マウスの持ち方

一時期つまみ持ちをしていたがクリックする時にどうしてもぶれてしまうのでかぶせ持ちに落ち着いた。正直持ち方は何でも良いのだが、絶対に崩してはいけないポイントが有る。それは「マウスをまっすぐ持つ」ということだ。僕は最初少し斜めに持っていたために右側の可動域が狭かった。普通ゲーミングマウスならまっすぐ持てばちゃんと左右の可動域が同じくらいになる。また、手首を固定して肘を軸に左右に動かした時ポインタが上下に動かないか確かめると良い。ペイントソフトを使えばチェック出来る。

感度とゲーム内センシ

プロは振り向き15センチあたりが中央値のようだ。他のゲームであれば25センチほどなのでかなり高感度である。これは他ゲーに比べ振り向くことが多いからだと思われる。オーバーウォッチのプロの中でも振り向き25センチを超える人もちらほら居る。初心者ならそのあたりの感度から当てる感覚を掴んでから徐々に感度を上げていくと良いと思う。

肘AIMと手首AIM

一般にこの2つがAIMでよく使われる。僕は両方使うことにした。画面に写っている敵を撃つときは手首AIMで振り向きには肘AIMを使う。また肘AIMは机の上に肘を置くと安定する(それはそう)。机と椅子の高さの相性によっては肘を置きにくい。モニタを左側に置いて体を机に対して斜めにすれば右手肘を比較的楽に置ける。

普段の視点

ちゃんと画面中央を見よう。見ているところと画面中央が合っていないといけない。照準がアイラインにあれば遠くの敵も近くの敵も上下のポインタの動きなしでAIM出来る。自分のプレイの動画を見て照準が床に無いか確かめてみると良い。最初はモニタの位置を上げることで強制的に見ているところと画面中央が合うようにした。多分今ではどの高さでも出来るので必須ではない。

手癖でジャンプをしない

撃ち合いのときはジャンプをするべきではない。敵が当てにくくなる以上に自分が当てづらくなるからだ。特にフラバンなどのコンボの時に手癖でジャンプするのは全く意味がない。なかなか治らないならスペースキーを一時的に取ると治る。

手癖でリロードをしない

一人敵を倒したりチャージショットをした直後によくリロードが入っていた。十分残弾がある時にリロードをするのは射撃機会を逃すだけで意味がない。何発というより後何秒撃ててリロードに何秒掛かるかを把握すると良いと思う。トレーサーなんかは手癖でいい。

アニメーションキャンセル

いくつかの行動のアニメーションは別の行動でキャンセルすることで隙を消す事ができる。例えばゲンジで風斬セカンダリ近接はすべてキャンセルで入る(風切り前にメインセカンダリを使っていると風斬後にセカンダリが入らない)。他にもリロード直後に近接を入れればリロード自体のキャンセルをすることが出来る。このあたりはキャラ毎にとてもたくさんあるので自分の使うキャラはチェックしておきたい。

キャラ毎のテクニック

キャラ毎にこれは出来るようになりたいというテクニックがある。ゲンジであれば風斬セカンダリ近接で一瞬で164ダメ入る。ファラならコンカッシブでの移動。ジャンクラであればメインコンカッションでの200族ワンショットキル。トレモのBotには確実に成功出来るくらいにはしよう。

偏差撃ち

偏差撃ちにはAIMは要らないと宣う人が居るがそんなことはない。よく言われるAIM力と別の能力が少し必要なだけだ。オーバーウォッチではトレーサーとゲンジが5.5m/secであることを除きすべてのキャラが5m/secで移動する。またWikiで各キャラの弾速がわかるので、ある距離で射撃する時にどれだけキャラが動くか計算できる。別に数値で理解しなくてもトレモのBotが等速で動くので適当な距離を開けてヘッショを当てる練習をすればいい。偏差撃ちには2通りの方法がある。1つは「相手がそのままの動きをすると仮定する」ことで、普段はこの方法を採用する。もう1つは「相手の足元に撃つ」ことだ。後者の方法はファラでミリ残りの敵を狩り切る時などに使える。ファラの弾速は35m/secで20mから撃つとほぼ0.5秒で相手まで到達する。0.5秒で移動できる距離は2.5mで、ファラの爆風ダメは2.5mの範囲なので正しく足元に撃てれば必ずダメが入る。

撃ち合いでの移動

撃ち合いでの移動には3種類あると思う。1つ目は左右に動いて相手の射撃を躱すためのもの。2つ目は相手の動きをミラーして相対的にAIMしやすくするためのもの。3つ目は大きくポインタを動かす必要がある時にその方向に移動してポインタの移動量を小さくするためのもの。

右手と左手を協調させる

先に述べた左右に動きながらの射撃は「自分はどちらに動くか分かるが相手は分からない」からこそアドバンテージが生まれる。左右に動きながら一点を撃つ練習をしなければ意味がない。慣れると無意識で出来る。

当たるのを確認する前に撃つ

偏差が必要なキャラを使っている時に一発撃って当たか確認してから撃つのを繰り返していたが、それをやると発射レートが落ちるので確認せず撃つべき。強いて言えばファラは当たると爆風でノックバックが発生して着地狩り出来るのでそれを狙っているならアリ。

キルを取る

パッと見えるキャラを撃つのは全く意味がない。きちんと(一人で)フォーカスして狩り切らないといけない。VCがあればチームレベルでのフォーカスが出来るので強い。特にDPSをプレイする時はキルを取ることが期待される。

フォーカスの順番

基本的には「狩り切る時間の短い順」で良い。これを適用するとヒーラーDPSタンクの順になる。タンクでも体力がミリなら狙うべきだがVCで連携してないと難しいところ(体力がそもそも分からない)。後は真っ先に自分のアンチ(というより自分を狙えるキャラ?ファラであればソルジャー)を狙うのも良いと思う。

最大瞬間火力を覚える

各キャラが出せる持続的なダメージ量と瞬間的なダメージ量は大きく異なる。例えばゲンジが好例で、1秒で200ダメ出せるコンボがある一方でメインのDPSは100/sec程度しか無い。よく使うキャラの瞬間火力とそのコンボの練習はしておこう。特に近接を入れるとお手軽に30ダメ入るので忘れないように。

タンクの意義

200族を一人ずつフォーカスして狩ることを意識すれば分かるが、タンクが居ないと全くゲームにならない。その理由は200族だけだとフォーカスに対して弱すぎる(前線を維持できない)からだ。大抵のDPSの平均ダメージは(命中精度を考えると)100/secにも満たないが、瞬間的に100~200程度に達する。タンクであれば瞬間ダメを食らってもさほど痛く無いのでそのまま居座れるが、200族なら次に同じ瞬間ダメを食らったら死ぬため下がるしか無い。結果前線がじわじわ下げられて維持できない。加えて追撃にも耐えられない。

自分が楽でもチームが辛い事がある

相手にファラが居てジャンクラやリーパーを出している時など、どれだけ自分が刺さっていると思っていても味方がキツイ時がある。きちんとキルログを見てなぜ戦況が悪いか把握しよう。僕はよくファラを使うが、敵がウィンストンを出した時は大抵味方に負担が掛かることに気がついた。個人的にはDVAより相性が悪いと思っている。

メダルは意味がない

先の延長だが、自分がメダルを持っているのは味方に負担がかかっているだけなこともままある。ある時敵のモイラがとても下手に見えた試合があったのだが、彼は5金だった。死にかけの味方をヒールせずにダメージを出していればチーム内順位は上がる。このゲームではキルアシストもキルに数えられるためあまり当てにならない。ファイナルブロウ値はまだ気にする価値があると思う。

弱いキャラを使うべきではない

誤解を恐れずに言えばアナやソンブラといった弱いキャラは使うべきではない。トレーサーも余程上手かったとしても今の環境でキャリーは難しいと思う。Overbuffで各レート毎のキャラの勝率が見られるのでそこで低いキャラは控えたほうが良いと思う。例えばソンブラはプラチナ帯まで全キャラ最下位の勝率だし、アナはサポートの中で最下位である。トレーサーはアタッカーの中ではソンブラに次いで低い。確かに高レートであれば刺さるキャラ達ではあるのだけど、使い方が難しい上に正しく使ってもキャリーがし辛い(味方が上手いことを前提としたキャラなので)。

ピックブールを広くする

各ロール2キャラは使えるべきと言うが、正直もっと無いと厳しい。タンクヒーラーDPSのどれか1つは3キャラ以上使えるようになりたい。選ぶキャラの指針はメインで使うキャラのアンチのアンチ、またそのアンチのアンチである。現状DPSならファラとリーパーをよく使うのだが、その両方のアンチであるソルマクウィドウあたりのアンチはゲンジである。だからゲンジを使えれば強いと思うのだけど難しいんだなぁ……。各ロールで相手のこのキャラが暴れてたらこのキャラ出そうというくらい出来ていると強い。

敵になるだけ近づく

一般に敵より自分の得意レンジが遠い場合、相手のレンジより少しだけ離れた位置で戦うのが良い。当たり前だが近づいた方が当てやすく火力が出るためだ。例えばファラは状況によってはかなり敵の近くまで近づいていい。逆に敵の得意レンジの方が遠い場合、一気に近づけたりタンクを伴って距離を詰められない場合を除いてガン逃げすべき。

裏取りはすべきではない

裏取り戦法はとてもむずかしい上に低レートではあまり刺さらないと思う。というのも、低レートでは1ピックの重みが小さいためだ。高レートであれば人数差がついた瞬間にちゃんと攻めるしきちんと狩りきれる技術が備わっているが、低レートではそうではないため。むしろ裏取りで慢性的に火力差が付く欠点のほうが強く出る。

魔境は存在しない?

魔境というのは英語でElo Hellと呼ばれる。とはいえ、これは下手人間が言い訳として用いているだけという指摘も多い。LoLという別ゲームの話だが、いくつか参考になる。

いかに Elo hell(魔境)から脱出するか | once majigomi, always majigomi

[Learn ****] Why I'm 1800+ and how to get here - League of Legends Community

重要なのは「レートを上げる」というのは「自分がキャリーして勝たないといけない」ということを意識することだ。 Skill separates good players from great players. Understanding separates good players from bad players.は至言だと思う。

クソ雑魚ナメクジのためのオーバーウォッチ立ち回り

解説記事というのは往々にして人間向けであって、ブロンズと言った知性を感じさせないレート帯に向けてのものではない。 ここではブロンズの人間が出来ていないであろう事柄を書いた。

ここに書いてあることは極めて自明なことしか無い。しかしそれを徹底できないからこそクソ雑魚なのだ(自戒)。 なお僕はこれを書いている時点でブロンズです(は?)。有益な指摘を歓迎します。

キルログを見よう

いきなり自明。しかし本当に出来ていますか。任意のタイミングで敵味方誰が残っているか管理出来ていますか。混戦時に出来ていますか。 実際混戦時にキルログを見るのは極めて難しいと思う。VCがあると無敵。

全て管理するのは難しい。特に重要な情報だけでも覚えよう。 敵味方が何人ずつ前線に居るか(枚数差)。 相手、味方のヒーラーが落ちているか。 自分のアンチキャラが落ちているか。 例えばファラのULTは上手いD.Vaが居れば突っ込んできて潰される。

ピックを見よう

最初のピックを見ている人は多いと思う。しかし敵がキャラを変えたのに気がついていますか。敵が大量落ちした時などは特にピックが変わりやすいので確認しよう。

タンクは不可欠

僕は最初タンクが重要という感覚が皆無だった(僕はそもそもFPSどころかゲームをあまりやった事がありません)。

タンクの重要性を理解するにはまずFPSは陣地ゲー的な側面があることに気付く必要があった。 自明なことだが、撃ち合いは囲んだ方が強い(エイムが簡単になる、囲まれている方はフォーカスが合わない)。 例えばソルジャーに高台を取られて横から撃たれている状況下において誰もそれを潰しに行かない事がある。結果はもちろん惨敗する(一方的に瞬間200dpsを出し続けられる)。 よくFPSでは「高台が強い」と言われるようだ。これはタイマン同士で被断面積が減るという問題より、どちらかと言えば囲い込めるというのが理由だと思う。

さて、FPSは陣地ゲーである。この時耐久力の高い(かつ多くの場合近接で強い)タンクキャラは「強い」陣地となる。 つまり、タンクキャラ周辺はちょっとのことでは崩されない。

先の例で言えば「弱い」陣地であるソルジャーが高台を取っているなら「強い」陣地であるD.Vaで突っ込めば終わる話なのだ(そしてその後こちらのDPSキャラが高台を取れば優勢を取れる)。

加えてこのゲームの基本的なルールを思い出そう。陣地をとったり陣地を進める(ペイロード)ゲームである。 陣地ゲーなのに「強い」陣地が居なければゲームにならない。これがタンクが不可欠な理由である。

敵と味方の位置を把握しよう

ぶっちゃけヒーラー、VCアリとかでも無い限り完全に把握することは出来ないと思うけど出来ることは多い。

囲い込まれないようにしよう FPSは陣地ゲーという意識を持とう。俯瞰した時に敵と味方を色分けするとどうなるかを考えると良い。ふと思ったけどスプラトゥーンってこっから生まれたのかな。

目に見えない敵に意識を配ろう 例えば敵マクリーやリーパーが見えない時は裏取りULTを警戒しよう。

味方がついてきているか確認しよう タンクをやる時は必須。単身特攻してヒーラーが付いてこないと文句を言うハメになる。

判断を早くしよう

実際は「予測」をしようというのが適切。 上級者は多分頭の回転が速いというより経験による予測と決め打ちが適切。

2人以上の差が付いたら退却、集合

一般化すると1.5倍以上程度の人数差が付いたら。よほどの能力差が無い限りこの人数差で勝つことは出来ない(そして同レートでそれは無い)。 誰も集合しない?リスポ地点で待って集合ラジチャしたら流石に大体の味方は意図を察してくれる。

攻撃サイドの場合はワンチャン特攻をしよう

僕が以前やっていたゲームはリスポーンがなかった(オーバーウォッチと比べ極めて死亡コストが重い)。その為8割勝てると思った時しか突っ込まなかった。 しかしオーバーウォッチでは何回死んでもいい。3割勝てる勝負を4,5回やれば8割どこかで勝てる。 それで言えば勝率5割と言うのはたった勝率2割の勝負を4回やるより低い。

時間ギリギリになるまで何もしないのをやめよう

先も書いたが3割勝てる勝負を4,5回やればよい(この集団同士の当たりをウェーブという)。 「のこり30秒」アナウンスが鳴るまでマトモにULT使わないのやめよう。さっさと使っていけ。

ULTのタイミング

低レートだと溜めすぎる人が多い気がするので積極的に吐いていこう。

一般にULTは有利/不利な状態で吐いても意味がない。 不利からイーブンにしてULTを無駄にするより集合してイーブンからULTを吐いて有利にすべきである。 これは攻撃/防御のどちらかで変わるかも。防御サイドなら不利でも吐くべきかもしれない。 しかしよく時間がギリギリだからとリスポする味方を待たずにULTを吐いてしまう人がいる。焦らずにみんな揃ってからULTを吐くべき(時間ちゃんと見ような)。 低レートだと有利な状況で吐いて確実に殲滅するのは意味があるかも。殲滅能力が低いので敵のリスポが間に合って泥沼化するよりは良い。

攻撃系ULT

リーパー、ファラなど

確実に2人以上落としたい。自分が死に得ることも多いので3人落とせればサイコー。

協力系ULT

ザリア、ハルトなど

味方が援護してくれる場合でないと意味がない。 ULT連絡をして、吐く前にしっかり後ろをチェック。

支援系ULT

ルシオゼニヤッタなど

敵のカウンター、味方のULTのタイミングに合わせてダメ押しとして使う。 ピックと敵味方のULTゲージを大まか把握しておく。 誰がULTを吐いたら合わせるかを決めておくと良い。 例えば敵ザリア、ハルトULTのカウンター、味方のハルトULTのダメ押し(シャター決める時はそのまま前に出るのでハルトが死ぬシーンがままある)。

異端ULT

メトラ

低レートだと死にやすいのでテレポーターが吉。

上手く行った戦法にこだわらない

上手く行かなかった戦法を続ける人はそう居ないと思う。 しかし、最初に上手く行った戦法を続ける人は居る。

例えば相手が最初盾キャラが多い時にジャンクラリーパーを出していれば多分圧倒できる。しかしその後ファラを出されたら惨敗する。 いつも柳の下にどじょうが居るわけではないのだ。

アンチキャラを知ろう

相手の最も暴れているキャラのアンチキャラを出そう。

変に2-2-2に拘ったりせずに自分で対抗できるピックにしたり柔軟に対応しよう。 そもそも2-2-2と言うのはバランスが良い(敵が最善を尽くした時に負けにくい)編成であって、ピックが壊れやすい低レードだと多少壊れることより誰も猛威を振るう敵キャラに対抗できないほうが不味い。

自分でやろう

ヒーラーが居ない、タンクが居ない、誰もタレット壊さないetc...

お前がやるんだよ!!!!!!!

それでもダメだと思うならパーティ組んだりVC組んだりしよう

vermagicとmodversionsを偽装する方法(Dirty Hack、備忘録)

LinuxではKernelをコンパイルした際、異なるversionのmoduleをインストールして不具合が起きないようにチェック機構を設けている。

これは通常、正しいversionのlinux-headerを指定することで解決するのが筋である。しかし、Raspberry Piで同じことをやろうとしたがなかなかうまく行かなかった。

次善の策としてはmodprobe -fでチェックを無視できるはずなのだがこれも上手く動かない(無視されてるらしい?)

そこで無理やりどうにかすることを考えた。

kernel/module.c

moduleに関する処理はkernel/module.c中にある。これを読んだ。

チェックはsetup_load_info中のcheck_modstruct_versionで行われる

github.com

これはkernel optionでCONFIG_MODVERSIONSが有効であった場合、常に1を返す(チェックが無い)。 CONFIG_MODVERSIONSが無効であった場合、該当するkernel module中から.modinfoと__versionsというsectionを探し正しい値かどうかを確かめる。

そこで.modinfoと__versionsを正しいmoduleから引っ張ってきて差し替える。

具体的にはobjcopy --only-sectionで正しいmoduleから抜き出し、objcopy --remove-section --add-sectionで差し替え、objcopy --set-section-flagsでsectionのflagを調整する。

どう考えてももっと良い手法があるはずだけどRaspberry Piは色々めんどい。

How to use keyboard/mouse for PS4 part4 -challenge response-

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

How to use keyboard/mouse for PS4 part2 -How does Bluetooth work?- - aki33524’s blog

How to use keyboard/mouse for PS4 part3 -Btstack is GOD- - aki33524’s blog

DS4は非正規製品の流通を防ぐため、認証の機構を備えている。このパートではそれがどのような仕組みなのかを解説する。完全に理解したと思っているのに認証が通ってくれない。USB snifferが欲しい……

General

DS4の認証はChallenge-Responseベースである。1分に1回程度の割合でランダムなデータが送られて来て、それに対する適切なデータを送り返さなければPS4はそのコントローラーからの入力を無視するようになっている。

しかし解析Wikiでは「どうやらChallenge-Responseらしい」程度の情報しか載っておらず、実際に何が行われているのかはわからなかった。何故仕組みが分かっていないのに現在(デバイスを用いるとはいえ)コンバータが存在するかと言えば、「どうやらこのChallenge-ResponseはRelayしてパスすることが出来る」ということが分かっているからだった。

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

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

What are these data?

確かに、ファームウェアも読めないような認証の仕組みを明らかにするのは不可能に思える。しかし、暗号についての幾つかの知見があれば自然な類推で仕組みを明らかにすることが出来る。ここでは僕がどのようにして仕組みを知ったかを順に書いていく。冗長かも知れないが僕の実験の楽しさ(と苦しさ)の片鱗を味わってもらいたい。

さて、最初の取っ掛かりはWikiにあるPacket 0x0d is padded with zeros, except bytes 58 and 60 (both are 0x01).だった。こういう風に書かれると何のことだか分からないが、58-60byteが0x010001になっていると言われると暗号徒であれば察するところがあると思う。そう、0x010001とはRSAのeとして広く用いられている数である。

Challenge-ResponseでRSAが関係すると言われればまず間違い無く署名である。Challengeデータをコントローラーの内部にある秘密鍵で署名してResponseとして返す。これなら筋が通る。

eがResponseの中に入っているからにはもう1つの秘密鍵であるnも入っているはずである。実際にデータを見ると幾つかの連続したデータ列と0-paddingがある。具体的には16byteのデータ列1つ、256byteのデータ列3つ、そしてeがResponseに含まれるデータだった。

長さから考えて256byteのデータのどれかがnである。今度はこれを特定しよう。

まず、何回かのChallenge-Responseを眺めると2つの256byteの列は固定だった。ランダムなデータは間違い無くResponseデータであるし、公開鍵は変化することが無いのでこの内の1つがnである。残る列が何かと言えばこれはnを署名した列だと推測できる(そうでなければ公開鍵ごと偽造すれば簡単に破ることが出来る)。

どちらがnだろうか。これを確かめるのは簡単で、素因数分解をしてみれば良い。nは非常に大きな素数p, qの積からなるため小さい素因数を持たない。実際、片方の列は10000までの素因数を持たないにも関わらず、Miller-Rabin素数判定によって合成数であることが分かった。極めてn的な数である。

n, e, Challengeを手に入れたが、ここからどうやってChallengeを署名しているか特定する必要があった。これは幸いにも一般に署名に使われるPKCS1 PSSだった。Challengeをsha256でハッシュ化した後適切なパディングを施してd乗 mod nをする。

実際に得られたn, e, Challenge, Responseを使ってPKCS1 PSSでverifyすると通った。めでたい。

ところでn, e, nの署名(と思われるデータ)はペアリングをやり直しても不変だった。これはnの署名がLink KeyなどのPS4-DS4間で共有されるデータに依存しないことを意味する。

この考察から、Replayしても認証は上手く動くのである(と思っていた)。

Challenge-Response over USB

しかしここからが大変だった。

普通、先述の認証はBluetooth通信で行われる。そこでBluetooth通信をMITMすることで認証をReplayしようと思っていた。 しかし実際に書くと分かるがBtstackを使って複数のコネクションを管理するのは極めて大変な作業である。加えて1つのBluetoothドングルではPS4とChallenge-Response Oracle用DS4と同時に繋ぐとパケットロスが生じる。ドングルを2つ用意するのも手だが、他の方法を考えた。

実は新しい基板のDS4はUSBでの通信に対応している。そしてその設定をするページには「USBケーブルを使って通信する場合マイクが使えなくなります」とと言った文言がある。これは実はとても示唆に富んでいる。何故なら、DS4において「音声信号は必ずBluetooth上でやり取りされる」からだ。これは@ka1l氏に基板の写真を見てもらった時に指摘されていて、つまりは物理的に不可能なようだ。

僕はこの時までずっとUSBで操作の入力だけやり取りして他の音声や認証情報はBluetoothを使うと信じていた。しかし、音声情報が使えないということは一切Bluetoothを使わない可能性が高いと考えられる。つまり、Challenge-ResponseをUSBで行うことが出来る可能性が高いと考えた。

実際、USBで通信する設定を有効にしたコントローラーのBluetooth通信のログを見ると一切パケットが流れていなかった。

幸いにもGET_REPORT/SET_REPORTはUSBのHIDにもある概念で(と言うよりBluetoothがUSBから輸入した?)、ReportIDなども一致した。少し逸れるがLinuxカーネルには何故かDS4用のドライバが入っており、これを無効にしなければcontrolでの通信が出来なかった(結局必要なかったがデバドラの仕組みを調べたりした)。

果たしてGET_REPORT/SET_REPORTの0xf0, 0xf1, 0xf2のデータを適切にUSBでやり取りすると、きちんと署名されたデータが返ってきた。勝利を確信した瞬間である。

What's wrong?

しかしここからも大変なのだ。

0xf0, 0xf1, 0xf2のデータ以外にもGET_REPORT/SET_REPORTには0x03, 0x04のデータがやり取りされている。PS4からDS4に32byteのデータを送信してDS4が40byte返す。これはランダムなデータで内容も分かっていない。全く重要で内容に見える。

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

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

ところがこのデータを返さなければChallenge-Response用の通信もロックされてタイムアウトしてしまう。しばらく考えたが結局何のデータなのか分からず、urandomで生成したデータを流したが(少なくともそれを流した瞬間には)問題無いように見えた。

何故このデータもreplayしないのかと思うだろう。それはDS4のUSB通信ではReportID 0x03が存在しないからだ。これに相当するデータがあるのかないのか、あるならどのIDでやり取りしているのか覗いてみたいがUSB Snifferは存外大変らしい(金で殴れば解決する)。

雑な0x03, 0x04のデータを流して今度こそ解決したかと思われた。認証はタイムアウトせず全てのデータが流れきった。しかし最後のデータが渡った瞬間に入力が無視された。明らかに認証でハネられている。

どうやら考え残しているところがあるようだ。

一番怪しいのは0x03, 0x04だが、他にも可能性はある。

コントローラーのBDADDRに偽装してみたがこれもダメだった。

認証時にLink Keyを用い無いと考えていたが、PKCS1 PSSはパディングに乱数値を入れるため、この箇所にLink Keyに依存した情報が入っているのかもしれない。そう考えて同じLink Keyを使っている2つのResponseに入っている乱数値を取り出してみたが異なる値だった。

どれか他のReport IDが0x03, 0x04の代わりをしているかと探ったがこれもダメだった。

かなり煮詰まっているので記事に書いた(記事に書くと何故か進捗が生まれるジンクス、内容を自分で整理出来るからだろうか)。

Demo

Responseを返し切るまでは入力が受理されるところ。

www.youtube.com

How to use keyboard/mouse for PS4 part3 -Btstack is GOD-

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

How to use keyboard/mouse for PS4 part2 -How does Bluetooth work?- - aki33524’s blog

Part2で少し話に出したが、Btstackを使ってみると一気に進捗が出た。Bluezなんて一生使わねえぞ。

Bluez vs Btstack

Bluezは現在Linuxで採用されているBluetoothプロトコルスタックである。標準であるからには優れたものだと思っていたが実際にはそびえ立つクソだった。料理を作ろうとしたらチェーンソーを渡されてしかもたまに突然歯が逆回転したりするような感じ。

Btstackはとてもシンプルな組み込み向けのシンプルなプロトコルスタックである。組み込み向けとは言うものの、libusbをインストールすることでUNIX-basedの環境にも対応するし、daemonもあるようだ。料理を作ろうとしたら小さなナイフを幾つか渡された感じ。決して高機能ではないし、ナイフの選択には少しばかり注意が必要だが思い通りの動作が出来る。

Btstackは組み込み向けであるからして、kernelの機能を使わない。その分デバッグや細かい制御がとても簡単に行える。ドキュメントも存在しているし、例示も充実している。Keyboardエミュレータまで存在するのは驚いた。Bluezが何故クソかと言えば、これらが全て満たされないからだ。kernelに依存するのは良いが、それが謎の挙動をするのだからクソでしか無い。ドキュメントは本当に一切無いし、最も良い例示はbluetoothdのコードである。加えて難解なkernelのコードを読む必要もある。

もちろんBluezだって良いところはある。どうやらBluezは様々なデバイスをホスト側で扱うことに注力しているようだ。加えて、ユーザーがGUIで簡単に設定できるようにしており(むしろCUIはなおざりらしい)、一般ユーザーが使う分には便利。今回は用途が違った。

Btstack

BtstackはBluetoothの仕様に従い階層化したイベント駆動で動く。各階層のやり取りは独自の(仮想化した)HCI packetを使って行うようだ。パケットログが若干汚くなるがそこまで気にならないから良しとしよう。多分ログに保存しない方法もあるはず。

正しい作法なのかわからないが、実装されているコードは極めて簡素であるから自分でカスタマイズした。DS4はHuman Interface Device(HID)プロトコルでやり取りする。Btstackのhid_device.cはL2CAPのDatapacketに関して一切イベントを送出しない。しかし、DS4で接続した直後に飛んでくるGET_REPORTに答えなければ入力待ち状態にならないため、新しいイベントを送出するように書き換えた。

SDP

SDPは適切に設定すれば勝手に分割されて送信される。これはPart1で作ったsnifferで取り出したSDPデータを分割した(HIDとDIDの2つのserviceが入っており、1つずつ設定する必要があるため)。これはWikiに載っている内容と少し食い違っているからで、ProductIDから違う。新しいバージョンのコントローラーはUSBによるキー入力に対応しており、別のデバイスとして認識されるようだ。

GET_REPORT

先述したように、PS4に接続した直後に飛んでくるGET_REPORTに答えなければならない。Wikiに載っている情報はおそらく以前のバージョンのコントローラー(Aug 3 2013)で、実際にsniffするとこの箇所が2016になっていた。ちょうど発売時期と一致する。

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

0x06と0xa3に応答するのはいかにも大切そうだが、あまり解析されていない0x02のreportを返すのも大切だった。これを返して初めてコントローラーは応答待ちになる。これもWikiのデータとは食い違っていた。これがコントローラーそれぞれによって違うのか基板レベルなのかは謎。

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

Demo

www.youtube.com

ThinkpadBluetoothでDS4として登録する。その後数秒おきに◯ボタンを押している。最後に注目して欲しいのだが接続が解除されている。これはThinkpadから解除したのではなくPS4側から解除されている。

実はPS4はライセンスされたコントローラーしか使えない仕組みが導入されている。次パートではそれについて解説する。もし新しいコントローラーで仕様変わってたら死ねる……

と思ってたけど今冷静に動画見たらこれはただ単に自分で接続切ってるだけだった……

www.youtube.com

◯を一回だけ押して後は上下を交互に押し続けるようにした。どうやらchallengeに応答しないと入力が無視されるだけで接続が切られるわけでは無いようだ。

そもそも何故動的に入力を作ってないかというと、CRCをCでパッと書くのが面倒だから(Pythonで10000メッセージくらい作って流してる)。

Bluetooth Class

Bluetoothは幾つかの消費電力の区分でクラス分けされている。どうやらX220に内蔵されているBluetoothはClass-3なのかとても弱くて接続が安定しなかった。今日Class-1(なんと理論上100mまで通信可能)を買ってきたら安定した。

DS4で使われているAR3002はSupports both Class-2 (up to +4 dBm) and Class-1 (up to +10 dBm) operationとあり比較的強い電波強度である。しかしClass-1を名乗るには100mW以上必要だと理解していたけど10mWまでに見える、どういうことだ。100mWは上限であって、Class-2以上であれば良いようだ。

DS4をペアリングする時にえらく認識されるのが速いのはClassだけが理由ではない。DS4は起動時にいろいろな初期化を行っており、その際にスキャンされやすい(その代わりに消費電力も上がる)設定をする。Bluez向けのコードではこれを実装していたがBtstack向けではまだやっていない。とは言え書かなくても問題無いらしい。Bluez向けはこれを書いた後に不安定になった気もするのであまり手を出したくないという理由もある。

追記

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

How to use keyboard/mouse for PS4 part1 -Sniffing HCI-UART-

If there is someone who wants to read this article in English, please contact me. I'm not good at English, so I don't want to write in English for anybody.

Motivation

最近研究室でPS4オーバーウォッチをやった。PS4FPSは初めてだったのだが、アナログパッドで全くエイムが合わない。規約上問題ない様なのでキーボードとマウスをPS4で使う術を考えた。

PS4で外部デバイスを使う試みはGIMXにより行われているが、デバイスを自作する必要がある。今回はそういったデバイスの自作無しで実現することを目的とする。

現状大凡の目処は立っているものの、細かい問題が解消されず未だ出来ていない。出来た時に記事にしようと思っていたが膨大になりそうなので幾つかのパートに分ける。おそらく4,5パート程度になると思われる。

Sniffing HCI-UART

PS4コントローラー(以下DS4)とPS4本体はBluetooth2.1+EDRで通信をしている。Wifiとは異なり、Bluetoothのプロミスキャスキャプチャはとても困難なようだ。これはどうやらBluetoothスペクトラム拡散方式である周波数ホッピングによるものらしい。

加えて、PS4ではSecure Simple Pairingというプロトコルにより動的に鍵(以下Link Key)を生成しており、これを外部から盗聴することは出来ない。

DS4とPS4の内部に格納されたLink Keyを取り出すことは困難らしい(読み出しされにくい領域に置く)。実はPS4-DS4間のLink Keyを読み出す方法はあるのだが、これに外部デバイスを必要とする(どこかのパートで書く)。

果たしてBluetoothの盗聴は不可能に思われるが、実は物理的なHackでなんとかなる。BluetoothのチップにUARTで通信しているところを直接抜き取れば良い。

HCI UART sniffer – GIMX

この記事に沿って行った。

まず、ボタンの壊れたDS4を購入(2000円程度で買えた)して分解する。

f:id:aki33524:20180212121706j:plain

先の記事の写真と見比べると分かるが基板が違う。DS4は新旧2種があり、どうやらそれによって基板も変わったらしい。@TA7368PG氏によれば3種以上基板があるようだ。

真面目にデータシートと基板を読む。DS4に搭載されているBluetoothチップはAtheros AR3002でこれは10, 11番ピンにTx, Rxが配置されている。

f:id:aki33524:20180213024254p:plain
AR3002 Single Chip Bluetooth v4.0 UART HCI Data Sheetより

基板を眺めるとおそらくデバッグ用のパッドに伸びている。ここに線をはんだ付けする。 f:id:aki33524:20180212132500j:plain f:id:aki33524:20180212154055j:plain

Rx, Tx, GNDを取ってきてブレッドボードに配置する。シリアル通信モジュールは記事に倣いFT232を搭載したものを用いる。ボーレートが3Mbps以上であればなんでも良い。

f:id:aki33524:20180212235152j:plain

その後、これをコンパイルして終わり。

github.com

ただ、このプログラムはTx, Rx用に2つモジュールがなければ動かない。そこでPORT2をコメントアウトしてfd2に-1をセットすると上手く動く。

f:id:aki33524:20180213030800p:plain

小話

ハードウェアに関して全く詳しく無いので@TA7368PG氏と@ka1l氏にTwitterで聞きまくった。ありがとうございました。と言うか最近人々にTwitterで質問しまくってる。

どう考えてもこれを最初にやるべきなのだけど、しばらくsniffer無しで頑張っていた。しかしどうも煮詰まったので止むを得なくPS4ごと買った(これまでPS4を持っていなくて研究室においてあるもので実験していた。流石に人のコントローラーを分解するわけにはいかない)。

基板が違った時はマジで詰んだと思った。

メルカリで落としたコントローラーを分解したら犬の毛が入ってて笑った。

本当は音声端子を剥がしてそこからケーブルを出したかったのだが、はんだ剥がしがつらすぎて諦めた。

Tx, Rx用にモジュールが2つ必要で秋月で注文した。その後5年前に買ったきり一切触っていない回路箱を覗いたら何故かFT232が載ったモジュールがあったのでとりあえず実験した。

snifferの375行目のfd1はどう考えてもfd2のtypo