1 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Der Entwickler betrachtete das Favicon, also das Browser-Tab-Symbol, als Pixeldatenspeicher und führte ein Experiment durch, bei dem kleines HTML in den RGB-Kanälen eines Bildes abgelegt wurde
  • Die Kodierung funktioniert so, dass vor die UTF-8-Bytes des HTML ein 4-Byte-Längenheader gesetzt wird und danach jedes Byte der Reihe nach in die R-, G- und B-Werte der Pixel geschrieben wird
  • Die Demo-Payload ist 208 Byte groß und mit Header 212 Byte, sodass 71 Pixel mit je 3 gespeicherten Byte und ein 9×9-PNG ausreichten
  • Zur Wiederherstellung wird das Favicon-Bild auf ein Canvas gezeichnet, danach liest JavaScript die Pixeldaten aus und setzt die RGB-Werte wieder zu einem Byte-Array zusammen, das als HTML dekodiert wird
  • Die Struktur, bei der allein das Favicon eine Website unabhängig ausführt, ist nicht möglich; es wird zusätzliches Bootstrap-JavaScript benötigt, weshalb es eher ein Grenzexperiment als etwas Praktisches ist

Wie man ein Favicon wie einen Datenspeicher behandelt

  • Ein Favicon ist zwar das kleine Symbol im Browser-Tab, tatsächlich aber eine Bilddatei, die aus Pixeln und Bytes besteht
  • Der Ausgangspunkt des Experiments war Steganografie, in der Demo liegt der Fokus jedoch nicht darauf, wie ein Icon auszusehen, sondern darauf, es als reinen Speicherplatz zu verwenden
  • Gespeichert wird eine kleine HTML-Payload
Website in a Favicon

Everything you're reading right now was decoded from favicon pixels.

  • Das Kodierungsverfahren ist einfach
    • Mit TextEncoder wird das HTML in UTF-8-Bytes umgewandelt
    • Davor wird ein 4-Byte-Header mit der Länge der Payload gesetzt
    • Da Pixel übrig bleiben können, dient der Längenheader dazu, das tatsächliche Ende der Payload zu bestimmen
    • Das erste Byte wird im roten Kanal des ersten Pixels gespeichert, das zweite im grünen, das dritte im blauen
    • Danach werden die weiteren Pixel in derselben Reihenfolge gefüllt, sodass das gesamte HTML-Dokument in Farbwerten steckt
  • Das resultierende Bild sieht visuell wie Rauschen aus

Größe und Wiederherstellungsprozess

  • Die endgültige Größe der Demo ist sehr klein
    • Payload: 208 bytes
    • Gesamtmenge mit Header: 212 bytes
    • Benötigte Pixel: 71 pixels
    • Bildgröße: 9×9 pixels
    • Dateigröße: 239 bytes
    • Auslastung: 87% {p:87}
  • Die Wiederherstellung erfolgt allein mit Browser-Funktionen
    • Das Favicon wird als Bild geladen
    • Das Bild wird auf ein Canvas gezeichnet
    • Mit der Canvas API werden alle Pixel ausgelesen
    • Die RGB-Werte werden wieder zu einem Byte-Array zusammengesetzt
    • Aus den ersten 4 Byte wird die Länge der Payload gelesen
    • Die Payload wird extrahiert und als UTF-8-Text dekodiert
  • Auf der Demo-Seite liest ein Klick auf die Schaltfläche "Render Website" das Favicon ein, stellt das HTML wieder her und ersetzt dann den Seiteninhalt

Grenzen und Alternativen

  • Die größte Einschränkung ist, dass ein Favicon nicht allein eine vollständige Website ausführen kann
    • Das Favicon enthält den Inhalt der Website
    • Ein kleiner JavaScript-Loader zum Dekodieren wird zusätzlich benötigt
    • Ohne JavaScript ist das Favicon nur ein PNG mit Website-Inhalt
  • Der praktische Nutzen ist gering
    • Die speicherbare Datenmenge ist sehr klein
    • Die Seite muss per JavaScript gebootstrapped werden
    • Es gibt viele bessere Wege, kleine HTML-Dokumente zu verteilen
  • Als Alternativen werden genannt: Markup direkt in ein SVG-Favicon einbetten, die PNG-Comment-Chunks tEXt, zTXt, iTXt verwenden oder das ico-Dateiformat nutzen, das Icons in mehreren Auflösungen enthalten kann
  • Demo-Seite: https://www.timwehrle.de/labs/favicon-site/
  • Implementierungscode: https://github.com/timwehrle/favicon

1 Kommentare

 
GN⁺ 4 시간 전
Hacker-News-Kommentare
  • Man könnte doch statt über Pixel zu gehen einfach ein SVG-Favicon verwenden, das Markup direkt darin speichern und anschließend extrahieren.
    Man packt etwas wie hello HN! in favicon.svg, verwendet es als SVG-Favicon und extrahiert es dann in den Dokument-Body.

    • Eher nicht im Sinne von „Warum macht man das nicht stattdessen?“, sondern eher als „Es gibt auch eine interessante Variante“. Beides sind Arten, aus Spaß, Neugier und Forscherdrang mit Technik zu spielen, und der Ansatz, etwas in Pixeln zu speichern, hat den Charme einer Rube-Goldberg-Maschine.
    • Ich bin der Autor, und ja, diese Methode ist natürlich praktischer. Ich wollte die Payload aber nicht als versteckten Text in einer XML-Datei haben, sondern in echten Pixeldaten „lebendig“ machen :)
    • Reguläre Ausdrücke, igitt. Man kann es einfach korrekt als XML im richtigen Namespace kodieren und so wieder einlesen.
      Oder man liefert die SVG-Datei direkt aus und bettet HTML ein. Theoretisch sollte man es definieren und dann verwenden können, aber in der Praxis scheinen weder Firefox noch Chromium das innerhalb eines Favicons richtig zu verarbeiten, was schade ist.
    • Wenn ich schon mein persönliches Windmühlengefecht austrage: [\s\S] kann man kürzer und präziser als [^] schreiben.
    • SVG kann Rasterbilder als base64-kodierte Bytes einbetten.
      Man könnte das Experiment also noch eine Ebene weiter treiben: ein SVG-Favicon, darin ein kodiertes Rasterbild, und in dessen Bytes wiederum kodiertes HTML. Das wäre zumindest eine ziemlich verwirrende CTF-Stufe.
  • Natürlich ist das keine neue Idee. Zum Beispiel hat schon 2000 jemand deCSS in einem Favicon gespeichert.
    https://web.archive.org/web/20010408040524if_/http://decss.z...
    Extrahieren lässt es sich mit dd bs=1 skip=2238 < favicon.ico.

  • Es stimmt auch nicht, dass man „immer noch einen kleinen Bootstrap-Loader zum Dekodieren des Bildes braucht“. Mit einem HTML/PNG-Polyglot lässt sich alles in einer einzigen Datei unterbringen, und mit neueren Formaten wie WebP könnte die Kompression heute sogar noch besser sein.
    https://web.archive.org/web/20120801001616/http://daeken.com...

  • Wenn man Nutzer über mehrere Domains umleitet, kann man auch den Favicon-Cache als Speicher verwenden. Das wurde schon einmal als potenzielles Fingerprinting-Risiko vorgeschlagen[0], und wenn Browser den Cache selbst im privaten Modus naiv wiederverwenden, könnte das zur Nutzerverfolgung über Browser-Profile hinweg missbraucht werden.
    [0]: https://www.schneier.com/blog/archives/2021/02/browser-track...

    • War das nicht schon behoben, oder zumindest größtenteils?
    • Als ich den Originalbeitrag gelesen habe, war mein erster Reflex: „Das wird bestimmt fürs Fingerprinting verwendet.“ Ich frage mich, ob Anti-Fingerprinting-Maßnahmen auch die Kombination aus Favicon und Canvas API berücksichtigen.
      Der Link zur Supercookie-Seite ist leider tot.
  • PNG hat Kommentar-Chunks vom Typ tEXt, zTXt, iTXt. In eine äußerlich völlig normale Bilddatei kann man also beliebig viel Inhalt stopfen. Zugegeben, der Spaßfaktor ist etwas geringer.

  • Ist das Timing Zufall? Ich habe gerade vor einer Stunde, genauer gesagt 30 Minuten vor diesem Beitrag, eine Website gepostet, die mein Aktienportfolio in URL + Favicon speichert.
    https://news.ycombinator.com/item?id=48606396

  • Das passt wirklich gut zu dieser Denkweise: Auch Monitore sind Speicher, Tastaturen sind Speicher, und Forenbeiträge sind Speicher.
    Wenn man über die Zeit beim Bearbeiten Variationen einstreut, die Markov gefallen würden, kommt da ziemlich viel Speicherkapazität zusammen. Außerdem sind Kommentare manchmal auch sozial interessant, also ist es ein Speicher mit Doppelnutzen.
    Niemand weiß, ob das Hähnchenauflauf-Rezept von irgendwem in Wahrheit der Handle einer kunstvoll konstruierten GUID ist und scherzhaft gesagt auf tausend verschiedene Forenbeiträge verweist. Ich frage mich, ob der Autor PoC||GTFO kennt, denn das ist definitiv eine Technik, die man tief in den heiligen Büchern der Alchemist Owls finden würde.

    • Code im Code. Räder in Rädern.
  • Dieser aggressiv abgehackte Stil, der offensichtlich wie von einem LLM erzeugt wirkt, war wirklich sehr schwer zu lesen.

    • Ich habe mich vor ein paar Monaten auf Medium schon einmal über genau so einen Stil beschwert. Der Autor dort antwortete, dass dies der bevorzugte Stil sei, wenn man davon ausgeht, auf kleinen Smartphone-Bildschirmen gelesen zu werden. Das ergibt bis zu einem gewissen Grad Sinn. Ob jener oder dieser Text KI-generiert ist, weiß ich nicht.
    • Etwa auf halber Strecke war ich fest überzeugt, am Ende käme die Wendung „Dieser Beitrag selbst war die ganze Zeit im Favicon der Website gespeichert“, was die kurzen, abgehackten Sätze erklärt hätte. Als ich gemerkt habe, dass das nicht kommt, war ich ehrlich enttäuscht. Eine verpasste Chance.
    • Mir hat diese Art zu schreiben gefallen. Ich schreibe selbst manchmal ähnlich und habe nie ein LLM benutzt, um meine Texte zu erzeugen. Ich habe sogar bei der Arbeit genau so geschrieben.
      Auf mich wirkt es einfach so, als wolle der Autor direkt zum Punkt kommen. Er scheint zu wissen, dass Leute bei zu viel Text nur noch drüberfliegen.
    • Es ist schon eine Weile her, dass ich auf HN einer Einordnung als KI-generierter Stil nicht zugestimmt habe. Höchstens könnte ein LLM für einen ersten Entwurf verwendet worden sein, aber das Endergebnis wirkt ziemlich menschlich.
      it’s/its wurde verwechselt, But. steht als Ein-Wort-Satz da, HTML wurde nicht großgeschrieben, und in Klammern steht „okayy“. Das soll keine Kritik am Autor sein; im Gegenteil, ich fand es gerade angenehm, diese kleinen Unsauberkeiten zu sehen, aus denen Blogposts bestehen.
    • Der Text war fesselnd und angenehm zu lesen.
  • Mich hat das an Inigos real pixel coding erinnert: https://www.youtube.com/watch?v=FvS_DG8yIqQ
    Ein 256-Byte-Intro, das in Photoshop durch Platzieren von Pixeln erstellt und als exe gespeichert wurde.

  • Fun Fact: Jedes Inline-SVG kann als Favicon verwendet und unverändert im HTML-Dokument belassen werden.
    Damit kann man sogar Emojis direkt als Favicon verwenden. Auf HN werden Emojis allerdings nicht angezeigt.

    • Nur als Hinweis: Wenn man dabei #rrggbb-Farbcodes oder url(#id)-Links verwenden will, muss man # als %23 escapen. Sonst wird es als URL-Fragment geparst und der SVG-Code an dieser Stelle abgeschnitten.