2 Punkte von GN⁺ 2025-01-24 | 1 Kommentare | Auf WhatsApp teilen
  • Ausgehend von Guy Duponts Experimenten mit winkelabhängigen QR-Codes setzte Christian Walther einen QR-Code um, der auch ohne Linse je nach Scan-Bedingungen als anderes Ziel gelesen wird
  • Die Kernstruktur ist eine Schachbrett-Maske, bei der zwei gleich große QR-Codes übereinandergelegt und die einzelnen Pixel aufgeteilt angeordnet werden
  • Wenn ein QR-Reader die erwarteten Pixelzentren abtastet, können sich die Sample-Punkte bei einer kleinen Verschiebung stärker zu einem der Codes verlagern und so ein anderes Ergebnis liefern
  • In den Experimenten funktionierte das Schachbrett besser als vertikale oder diagonale Streifen; ein hohes Niveau der Fehlerkorrektur war vorteilhaft, dennoch bleiben Unterschiede zwischen Decodern
  • Das zeigt, dass auch ein einzelnes QR-Bild je nach Winkel, Verschiebung, Decoder-Implementierung sowie Verarbeitung von Farbe und Alphakanal als mehrere Ergebnisse interpretiert werden kann

Doppelter QR-Code ohne Linse

  • Guy Dupont experimentierte mit QR-Codes, die auf Basis der Idee „verschiedene Perspektiven derselben Geschichte“ je nach Winkel zu unterschiedlichen Quellen führen
  • Christian Walther probierte aus, ob diese Idee „auch ohne Linse“ möglich ist, und erklärte, ein tatsächlich funktionierendes QR-Bild erstellt zu haben
  • Dupont reagierte darauf, dass er in Walthers Ergebnis die beiden Ziele GitHub und Mastodon gelesen habe
  • Auch Matt Lacey sagte, auf seinem Smartphone werde die Mastodon-URL besser erfasst, aber auch die GitHub-URL gelesen

Aufbau mit halbierten Pixeln

  • Das Grundprinzip besteht darin, zwei gleich große QR-Codes zu erzeugen und sie in einem Bild zu überlagern
    • Die eine Hälfte jedes Pixels stammt aus dem einen QR-Code
    • Die andere Hälfte stammt aus dem anderen QR-Code
  • In der weiteren Erklärung heißt es, ein Code sei in den linken oberen und rechten unteren Quadranten jedes Pixels sichtbar gemacht worden, der andere Code in den übrigen Bereichen
  • Als Herstellungsweise wird erwähnt, dass möglicherweise in Photoshop eine Schachbrettmuster-Ebenenmaske gefüllt wurde

Warum die Scan-Ergebnisse auseinandergehen

  • Walther nimmt an, dass ein QR-Reader anhand der Ausrichtungsblöcke den zentralen Bereich der erwarteten Pixelpositionen abtastet
  • Wenn für alle Pixel dieselbe Maske verwendet wird und das Pixelzentrum auf der Maskengrenze liegt, können sich die Sample-Punkte schon bei einer kleinen Verschiebung gemeinsam in den Bereich eines der Codes verlagern
  • Dadurch kann dasselbe Bild je nach Verschiebung als unterschiedlicher QR-Code gelesen werden
  • Walther selbst stellt klar, dass diese Sampling-Erklärung eine Vermutung ist und nicht auf einer Untersuchung des tatsächlichen Decoder-Verhaltens beruht
  • Ein Nutzer sagte, dass beim direkten Einspeisen der PNG-Datei in den zbar-tools-Decoder kein Code erkannt wurde

Unterschiede durch Maske und Decoder

  • In Walthers Experimenten lieferte die Schachbrett-Maske bessere Ergebnisse als vertikale oder diagonale Streifen
  • Mit dem höchsten Niveau der Fehlerkorrektur funktionierte es besser, weitere Experimente seien aber nötig
  • Eine diagonale Aufteilung sieht besser aus, in der praktischen Funktion liegt das Schachbrett jedoch vorn
  • Auch zwischen Scan-Apps und der Standard-Kamera-App wurden Unterschiede in der Häufigkeit der Ergebnisse gemeldet
    • Ein Nutzer sagte, in einer QR-Code-Scanner-App erscheine häufiger der Mastodon-Link, während in der Standard-Kamera-App offenbar beide möglich seien
    • Attie Grande sagte, auch zwischen Software-Decodern scheine es Variabilität zu geben
  • Hadley sagte, der Code sei nicht gescannt worden, solange das Smartphone still gehalten wurde; als es aber aus einem anderen Winkel bewegt wurde, sei der Scan während der Bewegung stabil gelungen

Variantenexperimente und öffentliche Tools

  • Walther stellte im Gespräch dualqrcode.com und das DualQRCode-GitHub-Repository als interaktive Version vor
  • xssfox erwähnte einen Ansatz, nahe Kollisionen zu finden, um die Zahl unterschiedlicher Bits zu verringern, und sagte, eine Differenz von 30 Bit per Brute Force zu finden, sehe ziemlich einfach aus
  • Walther experimentierte damit, für die beiden QR-Codes unterschiedliche Maskenmuster zu wählen, um die Unterschiede zu reduzieren; der Fall mit derselben Maske für beide Codes habe jedoch die geringsten Unterschiede ergeben
    • Auch der ursprüngliche Proof of Concept habe für beide Codes die automatisch vom Generator gewählte mask 6 verwendet, daher habe es in diesem Fall keinen zusätzlichen Vorteil gegeben
  • Walther sagte, die iOS-Kamera-App zeige in der Live-Vorschau nur die Domain an und den vollständigen Inhalt erst nach dem Antippen; deshalb habe er absichtlich unterschiedliche Domains gewählt

Farb- und Alphakanal sowie ein Experiment mit vier Codes

  • Nemo Thorx teilte ein Experiment, bei dem 42 in drei verschiedenen Darstellungen jeweils als QR-Code erstellt und in den RGB-Kanälen zusammengeführt wurde
    • Rot ist 6x9
    • Grün ist XLII
    • Blau ist Forty-two
    • Später wurde im Gespräch bestätigt, dass sich im Alphakanal ein Code für befand
  • Walther sagte, er habe das Verhalten des Bildes verstanden, nachdem er den Alphakanal ignoriert hatte
  • Als Pixel Dunn versuchte, durch Farbmischung mehrere Codes unterzubringen, hielt Walther es für nicht funktionsfähig, weil ein Decoder bei einer solchen Farbüberblendung nicht vier einzelne Codes trennen könne
  • Walther sagte, er habe schon einmal mithilfe beweglicher Ausrichtungsblöcke und Pixelquadranten vier Codes untergebracht
    • Wenn man das Ausrichtungsziel ganz leicht verschiebe, werde auf einen anderen Quadranten des Pixels fokussiert
  • Eine Methode, bei der sich je nach transparentem Hintergrund und Theme zwei Code-Sets unterscheiden, könnte eventuell möglich sein; Walthers iPhone erkannte sie jedoch weder auf weißem noch auf schwarzem Hintergrund und sie funktionierte nur mit manueller Schwellenwertverarbeitung

1 Kommentare

 
GN⁺ 2025-01-24
Hacker-News-Kommentare
  • Es scheint ein Angriff möglich zu sein, bei dem ein Bildschirm an einem öffentlichen Ort den QR-Code anhand von Informationen über den aktuellen Nutzer leicht verändert, ohne dass er äußerlich stark anders aussieht.
    Zum Beispiel könnte man einen halb-halb gemischten QR-Code erzeugen, das Ziel anhand externer Eingaben wie einer kamerabasierten Merkmalsbewertung bestimmen und dann die Farbe des Codes minimal anpassen, um die Wahrscheinlichkeit zu erhöhen, dass er als die gewünschte Variante erkannt wird.
    Missbrauchsmöglichkeiten wären etwa, verschiedenen Bevölkerungsgruppen unterschiedliche Feedback-Formulare zu zeigen und so Ergebnisse zu verzerren, Gewinnchancen bei Verlosungen anhand identifizierbarer Merkmale wie Ethnie, Alter oder Aussehen zu verschieben oder nur bestimmte Personen auf ein anderes WLAN-Netz oder eine andere Zahlungsseite zu schicken.
    In statischen Umgebungen wäre der Effekt geringer, und mir fällt nicht sofort ein statischer Angriff ein, bei dem man nur einzelne Nutzer herauspickt und daraus einen Vorteil zieht. Unter öffentlicher Beleuchtung würden die meisten wohl kaum bemerken, dass sich ein QR-Code dynamisch verändert.

    • So etwas ließe sich viel unauffälliger serverseitig erledigen, daher ist mir nicht ganz klar, welchen Mehrwert die Veränderung des QR-Codes bringt.
      Der im Original beschriebene Hack scheint vor allem dort praktisch einsetzbar, wo ein Angreifer seinen eigenen Code über einen legitimen QR-Code klebt. Welcher Code gelesen wird, ist dann einigermaßen zufällig, sodass einige Nutzer weiterhin zum ursprünglichen Ziel geschickt werden und die Entdeckung verzögert wird; ob das den Verlust an Traffic auf den eigenen Link ausgleicht, ist aber unklar.
    • Angriffe, bei denen öffentliche QR-Codes ausgetauscht werden, gibt es bereits in der Praxis.
      Dafür muss man gar nicht so ausgefeilt sein wie mit diesem doppelten QR-Code. Man geht zu einem „Mit dem Handy bezahlen“-Schild auf einem Parkplatz, klebt den eigenen QR-Code über den „Zum Bezahlen scannen“-QR-Code und wartet darauf, dass Kreditkartendaten eingehen.
    • Ähnlich könnte man ein Poster mit QR-Code an die Wand eines Raums hängen, in dem zwei LED-Lampen sind. Wenn die beiden Lampen unterschiedliche Farbtemperaturen haben, kann sich das Dekodierungsergebnis je nachdem unterscheiden, welche Lampe eingeschaltet ist.
      Eine andere Methode wäre ein Poster, das nicht völlig flach ist. Wenn man verschiedene Pixel leicht pyramidenförmig hervorstehen lässt und jede Seite anders einfärbt, könnte der QR-Code je nach Betrachtungsrichtung unterschiedlich gescannt werden.
      Eine der großen Banken in Tbilisi erlaubt es, IBAN-Nummern per QR-Code zu teilen. Theoretisch ließe sich mit solchen Techniken Geld stehlen, praktisch gibt es aber mehrere Sicherheitsvorkehrungen, etwa dass die Banking-App vor Abschluss der Überweisung den Namen des Empfängers anzeigt.
    • Normale Leute würden wohl selbst ziemlich große Änderungen an einem durchschnittlichen QR-Code nicht bemerken. Die meisten maschinenlesbaren Formate sehen für das menschliche Auge wie nicht unterscheidbares weißes Rauschen aus.
    • Gut, dass die Missbrauchsszenarien gleich mit erklärt wurden. Allein vom Setup her wäre ich darauf wohl nicht gekommen.
      In meinem Kopf könnte eine solche Steuerung ähnlich funktionieren wie das Erzwingen einer bestimmten Karte, daher dachte ich zuerst eher an Zaubertricks.
  • Ich habe eine Website gebaut, auf der man es direkt ausprobieren kann: https://dualqrcode.com/
    Da Christian die genaue Methodik nicht beschrieben hat, habe ich hier zwei QR-Codes mit einem diagonalen Teilungsmuster zu einem Bild kombiniert. Wenn sich die Muster an derselben Position unterscheiden, wird die Zelle diagonal geteilt: eine Seite steht für den ersten QR-Code, die andere für den zweiten. Wenn beide schwarz oder beide weiß sind, wird die Zelle einfarbig gefüllt.
    Dank der hohen Fehlerkorrektur von QR-Codes, insbesondere der Fehlerkorrekturstufe „H“, kann je nach Scanwinkel eine der beiden URLs gelesen werden. Wie in der UI erwähnt, tendiert es allerdings häufiger zur zweiten URL.

    • Bei mir hat es nicht funktioniert. Ich habe zwei Links zu BBC und CNN erzeugt und es mit der Android-App „QR & Barcode Scanner“ v2.2.47 sowie der Standardkamera eines iPhone 13 versucht, aber beide konnten ihn nicht lesen.
  • Am interessantesten fand ich, dass iOS beim langen Drücken auf das Bild anzeigt, es gehe zu github.com, die Vorschau selbst aber Mastodon zeigt.
    Das scheint zu bedeuten, dass der QR-Code zweimal geparst wird und dabei unterschiedliche Ergebnisse herauskommen. Das könnte dazu genutzt werden, einige Nutzer in die Irre zu führen; ich weiß aber nicht, wie viele Menschen die URL im Dropdown nach langem Drücken tatsächlich ansehen.

    • Manchmal steht auf dem Bildschirm, den ich gerade auf dem Handy anschaue, selbst ein QR-Code, was ziemlich lästig ist.
      Deshalb habe ich schon dumme Dinge gemacht wie einen Screenshot per AirDrop auf den Laptop zu schicken und ihn dann mit der Handykamera zu scannen, oder Freunde bzw. Familie zu bitten, den Code auf meinem Handybildschirm mit ihrem Handy zu scannen.
      Heute habe ich aber gelernt, dass man in der Kamerarolle auf einem Screenshot einen QR-Code lange drücken kann, woraufhin er direkt geparst und der Link geöffnet wird. Ich glaube, ich hatte früher schon einmal lange auf ein Bild gedrückt; vielleicht wurde die Funktion danach hinzugefügt, vielleicht habe ich es doch nie wirklich ausprobiert, oder vielleicht habe ich auf die falsche Stelle des QR-Codes gedrückt. Jedenfalls ist es sehr erfreulich zu wissen, dass es geht.
    • Es ist gut vorstellbar, dass zwei Teile des Betriebssystems jeweils unterschiedliche QR-Parser verwenden. SmartText nutzt den einen, das Bildsystem einen anderen. Offenbar unterscheiden sich ihre Implementierungen der Fehlerkorrektur ein wenig.
      Dasselbe ließe sich vermutlich auch mit einem Standard-QR-Code erreichen, in den absichtlich Fehler eingebaut wurden. Man müsste nur herausfinden, wie die beiden Parser Fehler unterschiedlich korrigieren.
      Klingt, als hättest du gerade einen Bug-Bounty-Bug gefunden, der nur darauf wartet, eingesammelt zu werden.
    • QR-Codes sollten die Ziel-URL auch als Text anzeigen, damit Nutzer wissen, wohin sie gehen. Das sollte wie eine Art ausdrückliche Zustimmung funktionieren.
    • Unter iOS zeigt langes Drücken als Standardlink „Open“ Mastodon an, im Kontextmenü erscheint aber zusätzlich ein App-Link „Open in Github“.
    • Das wirkt wie ein klassischer Fall, in dem dieselbe Funktion oder Getter-Methode zweimal aufgerufen wird, obwohl beide Aufrufe zwingend dasselbe Ergebnis liefern müssten.
      Ich denke an Code wie if someCondition(getFoo()) then doSomethingWith(getFoo()) oder an einen Aufruf von doSomethingWith(getFoo()), gefolgt von doAnotherThingWith(getFoo()). Im Gegensatz dazu, den Wert einmal mit foo := getFoo() zu holen und dann zu verwenden, bleibt bei so etwas immer ein Code Smell.
  • Wenn man von der Startposition nach oben scrollt, sieht man die Lentikular-Version.

  • Sehr eindrucksvoll. Mein iPhone tendierte dazu, auf eine der beiden Varianten einzurasten, und das Drehen des Telefons half, zur einen oder zur anderen zu gelangen.
    Ein paar Mal flackerten der Mastodon-Link und der GitHub-Link auch abwechselnd auf.

  • Wenn man es selbst ausprobieren möchte, kann man diesen schnell geschriebenen Code verwenden. Das Python-Paket qrcode installieren und den folgenden Code ausführen.
    Es entsteht tatsächlich ein doppelter QR-Code mit foo und bar, aber HN kann kein Unicode, und wenn man es durch 8 oder 0 ersetzt, erkennt mein Handy ihn nicht, daher lässt er sich schwer direkt einfügen.

    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)  
    
  • Für eine Schatzsuche wäre das ziemlich cool.

    • Man könnte es auch so gestalten, dass man die Ergebnisse von zwei oder sogar allen QR-Codes braucht, um am Ende den Code, Link oder Schlüssel zu erhalten.
  • QR-Codes eignen sich ähnlich gut für Missbrauch wie Kurz-URLs.
    Im Grunde ist es eine nicht lesbare URL, sodass man überhaupt nicht weiß, wohin man geschickt wird oder wie oft weitergeleitet wird. Deshalb benutze ich sie nicht.

  • Wirklich ein sehr cooles Konzept. Wenn man sich auf das Endziel konzentriert, wäre auch ein anderer Ansatz möglich: einen Switch am URL-Ziel.
    Dabei würde je nach Zufall, Nutzerdaten oder anderen Kriterien auf unterschiedliche Seiten weitergeleitet. Wenn jemand mit so einer Funktion experimentieren und sogar echte Sticker testen möchte: Ich arbeite mit mehreren SaaS-Unternehmen an variablen Labels und kann Einblicke teilen, Muster drucken oder zusammenarbeiten.