2 ポイント 投稿者 GN⁺ 2025-01-24 | 1件のコメント | WhatsAppで共有
  • Guy Dupontの角度別QRコード実験を出発点として、Christian Waltherがレンズなしでもスキャン条件に応じて別の行き先として読み取られるQRコードを実装した
  • 中核構造は、同じサイズのQRコード2つを重ねたあと各ピクセルを分割配置するチェッカーボードマスクである
  • QRリーダーが想定ピクセル中心をサンプリングするなら、画像が少し移動したときサンプル地点が片方のコード側へ偏り、別の結果が出る可能性がある
  • 実験では縦・斜めの縞模様よりチェッカーボードのほうがうまく機能し、高い誤り訂正レベルが有利だったが、デコーダーごとの差は残っている
  • 1枚のQR画像でも、角度、移動、デコーダー実装、色・アルファチャンネルの扱い方によって複数の結果として解釈されうることを示している

レンズなしで作った二重QRコード

  • Guy Dupontは「同じ物語の別視点」というアイデアで、角度によって別の出典へ送るQRコードを実験した
  • Christian Waltherはこのアイデアから「レンズなしでも可能か」を試し、実際に動作するQR画像を作ったと述べた
  • DupontはWaltherの結果について、GitHubMastodonの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.comDualQRCode 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件のコメント

 
GN⁺ 2025-01-24
Hacker Newsのコメント
  • 公共の場所にある画面が、現在の利用者の情報に基づいてQRコードをわずかに変えつつ、見た目には大きく違って見えないようにする攻撃が可能そうに思える。
    たとえば、半々に混ぜたQRコードを作り、カメラベースの特徴評価のような外部入力で対象を決めたうえで、コードの色を微妙に調整し、望む側として認識される確率を高める、といったやり方だ。
    悪用先としては、人口集団ごとに異なるフィードバックフォームを見せて結果を歪めたり、懸賞の当選確率を人種・年齢・外見のような識別特徴に応じて偏らせたり、特定の人だけを別のWi-Fiネットワークや決済ページへ送ったりする方法があり得る。
    静的な環境では効果が薄く、一部のユーザーだけを抜き出して利益を得る静的攻撃はすぐには思い浮かばない。公共照明の下でQRコードが動的に変わっていることに、ほとんどの人は気づきにくそうだ。

    • こういうことはずっと密かにサーバー側で処理できるので、QRコードを改変することで何が加わるのかよく分からない。
      元記事のハッキング手法が実際に使われそうなのは、攻撃者が正規のQRコードの上に自分のコードを貼る場合だと思う。どちらのコードが読まれるかは多少ランダムなので、一部のユーザーを本来の行き先へ送って検知を遅らせられるが、自分のリンクに入ってくるトラフィックが減る損失を補えるかははっきりしない。
    • すでに公開QRコードを差し替える攻撃は実際にある。
      わざわざこうした二重QRコードのように精巧である必要もない。駐車場の「携帯で支払い」案内板に行き、「スキャンして支払い」のQRコードの上に自分のQRコードを貼って、クレジットカード情報が入ってくるのを待てばいい。
    • 似たように、QRコード入りのポスターを、2つのLED電球がある部屋の壁に貼ることもできる。2つの電球の色温度が違えば、どちらの電球が点いているかによってデコード結果が変わる可能性がある。
      もう一つの方法は、完全に平らではないポスターだ。異なるピクセルを少しピラミッド状に突き出させ、それぞれの面を別の色で塗れば、見る方向によってQRコードが違ってスキャンされ得る。
      トビリシの主要銀行の一つは、IBAN番号をQRコードで共有できるようにしている。理論上はこうした手法で金を盗めるが、実際には銀行アプリが送金完了前に受取人名を表示するなど、複数の安全策がある。
    • 普通の人は、平均的なQRコードがかなり大きく変わっても気づかないと思う。ほとんどの機械可読形式は、人の目には区別できない白いノイズのように見える。
    • 悪用例まで説明してくれてよかった。設定だけ見ていたら、そういう用途はあまり思い浮かばなかったと思う。
      自分の頭の中では、こういう制御は特定のカードを強制的に選ばせるように働き得るので、まずマジックのほうを思い浮かべた。
  • 自分で試せるようにウェブサイトを作った: https://dualqrcode.com/
    Christianが使った正確な方法論はないので、ここでは2つのQRコードを斜め分割パターンで1枚の画像に合成した。同じ位置のパターンが異なる場合はセルを斜めに分け、片側は1つ目のQRコード、もう片側は2つ目のQRコードを表し、両方が黒または白なら単色で塗る。
    QRコードの高い誤り訂正能力、特に誤り訂正レベル「H」のおかげで、スキャン角度によってどちらのURLでも読み取れる。ただしUIに書いたように、2つ目のURLのほうに偏ることが多い。

    • 自分の環境では動かなかった。BBCとCNNの2つのリンクを作り、Androidの「QR & Barcode Scanner」v2.2.47アプリとiPhone 13の標準カメラで試したが、どちらも読み取れなかった。
  • iOSで画像を長押しするとgithub.comへ行くと表示されるのに、プレビュー自体はMastodonになる点がいちばん興味深かった。
    QRコードを2回パースして、互いに異なる結果を得ているということのようだ。一部のユーザーを誤誘導するのに使えそうだが、長押しのドロップダウンURLをどれだけの人が見るのかは分からない。

    • たまに、スマホで見ている画面そのものにQRコードが表示されていて困ることがある。
      それでスクリーンショットをノートPCにAirDropしてスマホのカメラでスキャンしたり、友人や家族のスマホで自分のスマホ画面上のコードをスキャンしてもらったりという、ばかげたことをしていた。
      ところが、カメラロールのスクリーンショットでQRコードを長押しすると、そのままパースしてリンクを開けることを今日知った。以前にも画像を長押ししてみた気がするが、その後に機能が追加されたのか、自分が実際には試していなかったのか、QRコードの変な部分を押していたのかもしれない。いずれにせよ、できると分かってとてもよかった。
    • OSの2つの部分がそれぞれ別のQRパーサーを使っている状況は十分想像できる。SmartTextは一つを使い、画像システムはまた別のものを使う、という具合だ。それぞれ誤り訂正の実装が少しずつ違うのだろう。
      意図的にエラーを入れた標準QRコードでも同じことを起こせそうだ。互いにエラーをどう違って直すかさえ突き止めればいい。
      誰かに拾われるのを待っているだけのバグバウンティ案件を見つけた気がする。
    • QRコードは、ユーザーがどこへ行くのか分かるように、対象URLをテキストでも表示すべきだ。一種の明示的な同意のように機能すべきだ。
    • iOSで長押しすると、標準の「Open」リンクはMastodonになり、コンテキストメニューには「Open in Github」アプリリンクも一緒に表示される。
    • 同じ関数やgetterメソッドを2回呼び出していて、その2回の呼び出しが必ず同じ結果を返さなければならない状況の典型例のように思える。
      if someCondition(getFoo()) then doSomethingWith(getFoo()) のようなコードや、doSomethingWith(getFoo()) の後に doAnotherThingWith(getFoo()) を呼ぶコードが思い浮かぶ。こういうものは foo := getFoo() で値を一度受け取って使う方法と違って、常にコードの臭いが残る。
  • 開始位置から上へスクロールすると、レンチキュラー版を見られる。

  • かなり強烈だ。自分のiPhoneはどちらか一方に固定される傾向があり、スマホを回すと片方またはもう片方へ行く助けになった。
    何度かはMastodonリンクとGitHubリンクが交互にちらつくこともあった。

  • 自分でやってみたいなら、急いで作ったコードを使える。qrcode Pythonパッケージをインストールして、以下のコードを実行すればいい。
    foobar を入れた二重QRコードが実際に出力されるが、HNではUnicodeが使えず、80 に置き換えると自分のスマホが認識しなかったので、そのまま貼るのは難しい。

    import qrcode
    
    bar = qrcode.QRCode(border=0)  
    bar.add_data('bar')  
    bar.make()  
    bar_mat = bar.get_matrix()
    
    foo = qrcode.QRCode(border=0)  
    foo.add_data('foo')  
    foo.make()  
    foo_mat = foo.get_matrix()
    
    for l, r in zip(foo_mat, bar_mat):  
        line = ''  
        for lc, rc in zip(l, r):  
            line = line + (lc and '\u2588' or ' ')  
            line = line + (rc and '\u2588' or ' ')  
        print(line)  
        print(line)  
    
  • 宝探しに使うと面白そうだ。

    • 2つ、あるいはすべてのQRコードの結果がそろわないと、最終的なコード・リンク・キーを得られないようにもできる。
  • QRコードは短縮URLのように悪用されるのにうってつけだ。
    本質的に読めないURLなので、どこへ送られるのか、何回リダイレクトされるのかまったく分からない。だから私は使わない。

  • 本当に素晴らしいコンセプトだ。最終目標に集中するなら、URLの行き先でスイッチを置く別のアプローチも可能だ。
    ランダム化、ユーザーデータ、その他の基準に応じて別のページへリダイレクトする方法だ。こうした機能を試しつつ実際のステッカーテストまでやってみたいなら、私は複数のSaaS企業と可変ラベルの仕事をしているので、知見を共有したりサンプルを出力したり協業したりできる。