HyperAI
Back to Headlines

Spigot generiert gefälschte JPEGs, um Crawler zu täuschen.

vor 13 Tagen

Am 25. März 2025: Fälschen eines JPEGs Ich habe schon eine Weile über Spigot gesprochen. Spigot ist eine kleine Webanwendung, die eine gefälschte Hierarchie von Webseiten dynamisch generiert, indem sie Markov-Ketten verwendet, um Unsinnsinhalte zu erzeugen, die aggressiven Webcrawlers zum Verzehr angeboten werden. Seither liefert Spigot seit einigen Monaten über eine Million Seiten pro Tag und ich habe nicht wirklich aufgepasst, was es treibt. Allerdings werfe ich hin und wieder einen Blick auf die Protokolle, um zu sehen, welche Crawlers darauf zugreifen. Leider verstecken sich zwei der aktivsten Crawlers durch extrem komplizierte Methoden, indem sie zufällige und unwahrscheinliche Browsersignaturen (zum Beispiel Firefox Version 134.0, 64 Bit, auf Windows 98!) erzeugen und von zufälligen Adressen aus zugreifen. Es scheint wahrscheinlich, dass dies über ein Botnetz geschieht – illegale Ausnutzung tausender Geräte. Seufz. Wenn ich einen aktivsten Crawler identifizieren kann, füge ich ihn der Liste auf der Startseite von Spigot hinzu, um das Zugriffsverhalten über die Zeit zu verfolgen. Vor ein paar Wochen bemerkte ich einen neuen Aktivsten: ImageSiftBot. Keine der von Spigot generierten Seiten enthielt Bilder, aber ImageSiftBot schlug mit Tausenden von Anfragen pro Stunde zu, in verzweifelter Suche nach Bildern. Mir tat sein sinnloses Streben leid, und ich begann darüber nachzudenken, wie ich ihm entgegenkommen könnte. Mein Hauptziel für Spigot ist, dass es sich effizient auf meinem Server verhält, ohne zu viel CPU-Leistung zu verbrauchen. Dynamische Bildgenerierung ist in diesem Kontext eine Herausforderung, da Kompression in Echtzeit sehr ressourcenintensiv ist. Das ist ineffizient, wenn man nur temporäre Daten erzeugt. Daraufhin kam ich zum folgenden Gedankengang: Kompression erhöht in der Regel die Entropie eines Bitstroms. Wenn eine Datei nicht wie zufälliger Inhalt aussieht, ist sie komprimierbar. Optimal komprimierte Daten sind nahezu nicht von zufälligen Daten zu unterscheiden. JPEGs sind gut komprimiert. Daher wird die komprimierte Datenmenge in einem JPEG zufällig aussehen, oder? Wenn ich eine Vorlage für eine JPEG-Datei hätte, die die strukturierten Teile (Informationen über Größe, Farbtiefe usw.) enthält und Markierungen für die Bereiche mit stark komprimierten Daten, könnte ich etwas erzeugen, das wie ein JPEG aussieht, indem ich die "komprimierten" Bereiche einfach mit zufälligen Daten fülle. Dies ist eine CPU-sparende Operation. Der Empfänger würde eine Datei sehen, die wie ein JPEG aussieht, und die zufälligen Daten als etwas behandeln, das dekomprimiert werden muss. Ich informierte mich über die Struktur von JPEG-Dateien und stellte fest, dass sie recht komplex sein können. Aber das ist nicht entscheidend. Eine JPEG-Datei besteht aus Chunks. Jeder Chunk hat ein Marker und eine Länge (manchmal implizit null, manchmal nur durch Lesen des Chunks bestimmt, indem nach dem nächsten Marker gesucht wird). Daher ist das Parsen einer JPEG-Datei relativ einfach. Ich habe viele JPEGs zur Verfügung. Was wäre, wenn ich eine Reihe existierender Dateien scannen würde, die "Kommentar"-Chunks verworfen, nur die Längen der "Pixel"-Chunks notiere und den Rest behalte? Wie groß wäre das Ergebnis? Ich habe aktuell 514 JPEGs auf meiner Website, die insgesamt etwa 150 Megabyte an Daten umfassen. Wenn ich alle scannen und nur die strukturierten Chunks beibehalte sowie die Längen der "Pixel"-Chunks notiere, beträgt das Resultat weniger als 500 Kilobyte. Das gibt mir 514 realistische Vorlagen in verschiedenen Größen und Farbtiefen. Das Generieren eines JPEGs würde dann darauf hinauslaufen: Wahl einer Vorlage basierend auf den vorhandenen JPEGs. Ersetzen der Pixel-Daten durch zufällige Daten. Setzen des zufälligen Seeds des Generators auf Grundlage der URL, um konsistente Bilder bei wiederholtem Laden zu erzeugen. Das war's! Ich erstellte einige Testcodes und stellte fest, dass es nicht ganz so einfach ist. Reale Pixeldaten sind nicht vollständig zufällig – sie sind Huffman-kodiert und haben eine gewisse Struktur. Wenn ich die Pixeldatenbereiche mit rein zufälligen Daten fülle, bemerkt der Decoder Fehler. Allerdings akzeptieren die meisten JPEG-Anzeigeprogramme meine zufälligen Daten und zeigen ein Bild, obwohl das JPEG fehlerhaft ist. Selbst wenn die Decoder Fehler bemerken, müssen sie die Daten trotzdem herunterladen und versuchen, sie zu dekomprimieren, bevor sie erkennen, dass sie defekt sind. Das erhöht die Kosten für die Crawlers, was mich freut. Die Abbildung am Anfang dieser Seite wurde dynamisch von meinem Code generiert. Ihr Browser wird sie höchstwahrscheinlich anzeigen, obwohl sie ein fehlerhaftes JPEG ist. Zurück zur Effizienz: Wie schnell kann ich diese zufälligen Bilder erzeugen? Ich verwende Vorlagen basierend auf Bildern von meiner Website. Die Bilder sind meist optimiert für das Web und haben eine Größe von etwa 1280x960 Pixeln und 200-300 Kilobytes. Eine schnelle Test zeigt, dass ich auf meinem Webserver mit dieser Methode (in Python) etwa 900 solche Bilder pro Sekunde erzeugen kann. Das sind etwa 190 Megabyte pro Sekunde und erheblich schneller als die Internet-Verbindung meines Servers. Prima! Ich habe diesen Prozess in Spigot integriert, und nun enthalten rund 60% der von Spigot generierten Seiten ein zufälliges JPEG. Wie bei Spigot wird der Zufallsgenerator für jedes Bild mit einem Wert aus der URL initialisiert. Auch wenn das Bild dynamisch generiert wird, erhalten Sie bei einem Neuladen das gleiche Bild. ImageSiftBot ist sehr zufrieden damit und hat heute etwa 15,000 zufällige Bilder heruntergeladen. Ich erwarte, dass es seine Rate in den nächsten Tagen erhöhen wird, während es mehr Links findet. Die Bots von Meta, Amazon und GPT sind ebenfalls begeistert! Ich muss die Python-Klasse, die dies durchführt, noch aufräumen, werde sie aber bald veröffentlichen. Sie besteht aus weniger als 100 Zeilen Code (aber könnte mehr Kommentare gebrauchen!). [2025-03-26] Jetzt veröffentlicht auf GitHub [2025-03-28] Nach intensiver Beschäftigung mit Huffman-Codes habe ich eine Bitmaske für die generierten Pixeldaten hinzugefügt. Jedes generierte Byte mit 0x6D zu "UND"-en, stellt sicher, dass keine Sequenzen von drei oder mehr 1s im Bitstrom auftreten. Dadurch sinkt die Wahrscheinlichkeit, ein JPEG mit ungültigen Huffman-Codes zu erzeugen, von über 90% auf weniger als 4%, ohne dass viel mehr CPU-Leistung benötigt wird. Das Ziel ist, die Generierung der zufälligen Daten so billig wie möglich für mich zu gestalten und gleichzeitig die Kosten für die missbräuchlichen Webcrawler zu erhöhen. Obwohl es technisch machbar wäre, perfekte Huffman-Streams zu erzeugen, würde dies zusätzliche CPU-Ressourcen verbrauchen, ohne einen signifikanten Vorteil zu bieten.

Related Links

Hacker News