レンチキュラー・敵対的手法で別の行き先へ誘導するQRコード
(mstdn.social)- Guy Dupontの角度別QRコード実験を出発点として、Christian Waltherがレンズなしでもスキャン条件に応じて別の行き先として読み取られるQRコードを実装した
- 中核構造は、同じサイズのQRコード2つを重ねたあと各ピクセルを分割配置するチェッカーボードマスクである
- QRリーダーが想定ピクセル中心をサンプリングするなら、画像が少し移動したときサンプル地点が片方のコード側へ偏り、別の結果が出る可能性がある
- 実験では縦・斜めの縞模様よりチェッカーボードのほうがうまく機能し、高い誤り訂正レベルが有利だったが、デコーダーごとの差は残っている
- 1枚のQR画像でも、角度、移動、デコーダー実装、色・アルファチャンネルの扱い方によって複数の結果として解釈されうることを示している
レンズなしで作った二重QRコード
- Guy Dupontは「同じ物語の別視点」というアイデアで、角度によって別の出典へ送るQRコードを実験した
- Christian Waltherはこのアイデアから「レンズなしでも可能か」を試し、実際に動作するQR画像を作ったと述べた
- DupontはWaltherの結果について、GitHubとMastodonの2つの行き先を読み取ったと反応した
- Matt Laceyも自分のスマートフォンではMastodon URLのほうがより認識されたが、GitHub URLも読み取れたと述べた
ピクセルを半分ずつ分ける構成
- 基本方式は、同じサイズのQRコード2つを作ってから1枚の画像として重ねること
- 各ピクセルの半分は一方のQRコードから取る
- 残りの半分はもう一方のQRコードから取る
- 以降の説明では、片方のコードを各ピクセルの左上・右下の四分円に見えるようにし、もう片方のコードは残りの領域に見えるようにしたという
- 制作方法としては、Photoshopでチェッカーボードパターンのレイヤーマスクを塗りつぶした可能性が言及されている
スキャン結果が分かれる理由
- Waltherは、QRリーダーが整列ブロックを基準に想定ピクセル位置の中心部をサンプリングすると仮定している
- すべてのピクセルに同じマスクを使い、ピクセル中心がマスク境界上に置かれると、わずかな移動でもサンプル地点が片方のコード領域へ一緒に偏る可能性がある
- その結果、同じ画像が移動量に応じて異なるQRコードとして読み取られうる
- このサンプリング説明は、Walther本人も実際のデコーダー動作を研究した結果ではなく、推測だと明かしている
- あるユーザーはPNGを
zbar-toolsデコーダーに直接入れたとき、コードを検出できなかったと述べた
マスクとデコーダーが生む違い
- Waltherの実験では、縦縞や斜め縞よりチェッカーボードマスクのほうが良い結果を出した
- 最も高い誤り訂正レベルを使ったときによりうまく機能したが、追加実験が必要だという
- 斜め分割のほうが見た目は良いが、実際の動作面ではチェッカーボードが勝っている
- スキャンアプリと標準カメラアプリの間でも、結果の出る頻度に差が報告されている
- あるユーザーは、QRコードスキャナーアプリではMastodonリンクのほうがより頻繁に出て、標準カメラアプリでは両方とも可能に見えたと述べた
- Attie Grandeは、ソフトウェアデコーダー間にも変動性があるようだと述べた
- Hadleyは、スマートフォンをじっと持っているときはスキャンされなかったが、別の角度へ動かし始めると、動いている間は安定してスキャンされたと述べた
変形実験と公開ツール
- Waltherは対話の中で、dualqrcode.com と DualQRCode GitHubリポジトリ をインタラクティブ版として紹介した
- xssfoxは、近い衝突を見つけて異なるビット数を減らすアプローチに言及し、30ビット差を brute force で見つけるのはかなり単純に見えると述べた
- Waltherは、2つのQRコードのマスクパターンを別々に選んで差を減らす実験もしたが、2つのコードに同じマスクを使う場合が最も差が少なかったと述べた
- 元の proof of concept もジェネレーターが自動選択した mask 6 を両方のコードに使っていたため、このケースでは追加の利得はなかったという
- Waltherは、iOSカメラアプリがリアルタイムプレビューではドメインだけを表示し、全内容はタップしないと見せないため、意図的に異なるドメインを選んだと述べた
色・アルファチャンネルと4コード実験
- Nemo Thorxは、3つの方式で表現した
42をそれぞれQRコード化し、これをRGBチャンネルに合成した実験を共有した- 赤は
6x9 - 緑は
XLII - 青は
Forty-two - その後の対話で、アルファチャンネルには
㊷コードがあったと確認された
- 赤は
- Waltherは、その画像でアルファチャンネルを無視すると動作を理解できたと述べた
- Pixel Dunnが色の混合で複数コードを入れようとすると、Waltherは、そのような色ブレンディング方式ではデコーダーが4つの個別コードを分離できず、動作しないだろうと見ている
- Waltherは、移動可能な整列ブロックとピクセル四分円を使って4つのコードを入れたことがあると述べた
- 整列ターゲットをほんの少し動かすと、ピクセルの別の四分円に焦点が合うという
- 透明背景とテーマに応じて2組のコードが切り替わる方式は可能かもしれないが、WaltherのiPhoneは白・黒背景のどちらでも認識できず、手動しきい値処理を適用したときだけ動作したという
1件のコメント
Hacker Newsのコメント
公共の場所にある画面が、現在の利用者の情報に基づいてQRコードをわずかに変えつつ、見た目には大きく違って見えないようにする攻撃が可能そうに思える。
たとえば、半々に混ぜたQRコードを作り、カメラベースの特徴評価のような外部入力で対象を決めたうえで、コードの色を微妙に調整し、望む側として認識される確率を高める、といったやり方だ。
悪用先としては、人口集団ごとに異なるフィードバックフォームを見せて結果を歪めたり、懸賞の当選確率を人種・年齢・外見のような識別特徴に応じて偏らせたり、特定の人だけを別のWi-Fiネットワークや決済ページへ送ったりする方法があり得る。
静的な環境では効果が薄く、一部のユーザーだけを抜き出して利益を得る静的攻撃はすぐには思い浮かばない。公共照明の下でQRコードが動的に変わっていることに、ほとんどの人は気づきにくそうだ。
元記事のハッキング手法が実際に使われそうなのは、攻撃者が正規のQRコードの上に自分のコードを貼る場合だと思う。どちらのコードが読まれるかは多少ランダムなので、一部のユーザーを本来の行き先へ送って検知を遅らせられるが、自分のリンクに入ってくるトラフィックが減る損失を補えるかははっきりしない。
わざわざこうした二重QRコードのように精巧である必要もない。駐車場の「携帯で支払い」案内板に行き、「スキャンして支払い」のQRコードの上に自分のQRコードを貼って、クレジットカード情報が入ってくるのを待てばいい。
もう一つの方法は、完全に平らではないポスターだ。異なるピクセルを少しピラミッド状に突き出させ、それぞれの面を別の色で塗れば、見る方向によってQRコードが違ってスキャンされ得る。
トビリシの主要銀行の一つは、IBAN番号をQRコードで共有できるようにしている。理論上はこうした手法で金を盗めるが、実際には銀行アプリが送金完了前に受取人名を表示するなど、複数の安全策がある。
自分の頭の中では、こういう制御は特定のカードを強制的に選ばせるように働き得るので、まずマジックのほうを思い浮かべた。
自分で試せるようにウェブサイトを作った: https://dualqrcode.com/
Christianが使った正確な方法論はないので、ここでは2つのQRコードを斜め分割パターンで1枚の画像に合成した。同じ位置のパターンが異なる場合はセルを斜めに分け、片側は1つ目のQRコード、もう片側は2つ目のQRコードを表し、両方が黒または白なら単色で塗る。
QRコードの高い誤り訂正能力、特に誤り訂正レベル「H」のおかげで、スキャン角度によってどちらのURLでも読み取れる。ただしUIに書いたように、2つ目のURLのほうに偏ることが多い。
iOSで画像を長押しするとgithub.comへ行くと表示されるのに、プレビュー自体はMastodonになる点がいちばん興味深かった。
QRコードを2回パースして、互いに異なる結果を得ているということのようだ。一部のユーザーを誤誘導するのに使えそうだが、長押しのドロップダウンURLをどれだけの人が見るのかは分からない。
それでスクリーンショットをノートPCにAirDropしてスマホのカメラでスキャンしたり、友人や家族のスマホで自分のスマホ画面上のコードをスキャンしてもらったりという、ばかげたことをしていた。
ところが、カメラロールのスクリーンショットでQRコードを長押しすると、そのままパースしてリンクを開けることを今日知った。以前にも画像を長押ししてみた気がするが、その後に機能が追加されたのか、自分が実際には試していなかったのか、QRコードの変な部分を押していたのかもしれない。いずれにせよ、できると分かってとてもよかった。
意図的にエラーを入れた標準QRコードでも同じことを起こせそうだ。互いにエラーをどう違って直すかさえ突き止めればいい。
誰かに拾われるのを待っているだけのバグバウンティ案件を見つけた気がする。
if someCondition(getFoo()) then doSomethingWith(getFoo())のようなコードや、doSomethingWith(getFoo())の後にdoAnotherThingWith(getFoo())を呼ぶコードが思い浮かぶ。こういうものはfoo := getFoo()で値を一度受け取って使う方法と違って、常にコードの臭いが残る。開始位置から上へスクロールすると、レンチキュラー版を見られる。
かなり強烈だ。自分のiPhoneはどちらか一方に固定される傾向があり、スマホを回すと片方またはもう片方へ行く助けになった。
何度かはMastodonリンクとGitHubリンクが交互にちらつくこともあった。
自分でやってみたいなら、急いで作ったコードを使える。
qrcodePythonパッケージをインストールして、以下のコードを実行すればいい。fooとbarを入れた二重QRコードが実際に出力されるが、HNではUnicodeが使えず、8や0に置き換えると自分のスマホが認識しなかったので、そのまま貼るのは難しい。宝探しに使うと面白そうだ。
QRコードは短縮URLのように悪用されるのにうってつけだ。
本質的に読めないURLなので、どこへ送られるのか、何回リダイレクトされるのかまったく分からない。だから私は使わない。
本当に素晴らしいコンセプトだ。最終目標に集中するなら、URLの行き先でスイッチを置く別のアプローチも可能だ。
ランダム化、ユーザーデータ、その他の基準に応じて別のページへリダイレクトする方法だ。こうした機能を試しつつ実際のステッカーテストまでやってみたいなら、私は複数のSaaS企業と可変ラベルの仕事をしているので、知見を共有したりサンプルを出力したり協業したりできる。