Ausführbare PNGs: Führen Sie Bilder als Programme aus



Dieses Bild und Simultanprogramm



Vor einigen Wochen habe ich über das PICO-8 gelesen , eine fiktive Spielekonsole mit großen Einschränkungen. Von besonderem Interesse für mich ist die innovative Art, ihre Spiele zu verbreiten - die Kodierung ihres PNG-Bildes. Es enthält alles - den Spielcode, die Ressourcen, alles. Das Bild kann alles sein: Screenshots aus dem Spiel, coole Kunst oder einfach nur Text. Um das Spiel zu laden, müssen Sie das Bild an den Eingang des PICO-8-Programms übertragen, und Sie können mit dem Spielen beginnen.



Ich dachte: Wäre es cool, wenn Sie dasselbe mit Programmen unter Linux machen könnten? Nein! Ich verstehe, Sie werden sagen, dass dies eine dumme Idee ist, aber ich habe es trotzdem getan, und unten finden Sie eine Beschreibung eines der dümmsten Projekte, an denen ich in diesem Jahr gearbeitet habe.



Codierung



Ich bin mir nicht ganz sicher, was der PICO-8 macht, aber ich denke, er verwendet wahrscheinlich steganografische Techniken , die Daten in den Rohbytes des Bildes verbergen. Es gibt viele Ressourcen im Internet, die erklären, wie Steganographie funktioniert, aber das Wesentliche ist ganz einfach: Das Bild, in dem Sie Daten ausblenden möchten, besteht aus Bytes und Pixeln. Pixel bestehen aus drei roten, grünen und blauen (RGB) Werten, die als drei Bytes dargestellt werden. Um die Daten ("Nutzdaten") auszublenden, "mischen" wir im Wesentlichen die Nutzdatenbytes mit den Bildbytes.



Wenn Sie einfach die Bytes des Bildes durch die Bytes der Nutzlast ersetzen, werden Bereiche mit verzerrten Farben im Bild angezeigt, da sie nicht mit den Farben des Originalbilds übereinstimmen. Der Trick besteht darin, so unauffällig wie möglich zu sein und Informationen aus heiterem Himmel zu verbergen . Dies kann erreicht werden, indem die Nutzlastbytes auf die Titelbildbytes verteilt werden und in den niedrigstwertigen Bits ausgeblendet werden . Mit anderen Worten, nehmen Sie kleine Änderungen an den Bytewerten vor, damit die Farbänderungen nicht stark genug sind, damit das menschliche Auge sie wahrnehmen kann.



Angenommen, unsere Nutzdaten sind Buchstaben, H



die binär als dargestellt werden 01001000



(72), und das Bild enthält einen Satz schwarzer Pixel.





Die Bits der Eingangsbytes werden auf die 8 Ausgangsbytes verteilt, indem sie im



niedrigstwertigen Bit ausgeblendet werden. Am Ausgang erhalten wir einige Pixel, die etwas weniger schwarz sind als zuvor, aber können Sie den Unterschied erkennen?





Die Pixelfarben wurden leicht angepasst.



Vielleicht kann ein äußerst erfahrener Farbkenner den Unterschied erkennen, aber im wirklichen Leben sind solche winzigen Verschiebungen nur für die Maschine sichtbar. Um unseren streng geheimen Brief zu erhalten H



, müssen Sie nur 8 Bytes des resultierenden Bildes lesen und sie erneut zu 1 Byte zusammensetzen. Natürlich ist es eine dumme Idee, einen einzelnen Buchstaben zu verstecken, aber der Umfang der Übertragung kann frei erhöht werden. Angenommen, Sie senden einen supersektoralen Vorschlag, eine Kopie von War and Peace , einen Link zu Soundcloud, einen Go-Compiler - die einzige Einschränkung ist die Anzahl der im Bild verfügbaren Bytes, da mindestens achtmal mehr davon vorhanden sein müssen als in den Eingabeinformationen.



Programme verstecken



Zurück zu unserer Vorstellung von ausführbaren Linux-Dateien im Image. Wenn Sie sich ausführbare Dateien einfach als Bytes vorstellen, ist klar, dass sie genau wie beim PICO-8 in Bildern versteckt werden können.



Bevor ich dies implementierte, entschied ich mich, meine eigene Steganographie-Bibliothek und ein Tool zu schreiben , das das Codieren und Decodieren von Daten in PNG unterstützt. Natürlich gibt es viele fertige steganografische Bibliotheken und Tools, aber ich lerne am besten, wenn ich mein eigenes Ding mache.



$ stegtool encode \

--cover-image htop-logo.png \

--input-data /usr/bin/htop \

--output-image htop.png

$

$ echo "Super secret hidden message" | stegtool encode \

--cover-image image.png \

--output-image image-with-hidden-message.png

$ stegtool decode --image image-with-hidden-message.png

Super secret hidden message






Da alles in Rust geschrieben ist , war es überhaupt nicht schwierig, dies in WASM zu kompilieren, sodass Sie selbst experimentieren können .



Jetzt können wir Daten einbetten, indem wir Bildern ausführbare Dateien hinzufügen. Aber wie führen wir sie?



Führen Sie das Image aus



Der einfachste Weg wäre, einfach das obige Tool auszuführen, die decode



Daten in einer neuen Datei auszuführen , die Rechte mit zu ändern chmod +x



und sie dann auszuführen. Es wird funktionieren, aber es wird zu langweilig. Ich wollte etwas im PICO-8-Stil tun - wir übergeben ein PNG-Bild an eine Entität, und es erledigt den Rest.



Wie sich jedoch herausstellt, können Sie nicht einfach einen beliebigen Satz von Bytes in den Speicher laden und Linux anweisen, dorthin zu springen ... zumindest nicht direkt. Allerdings können Sie ein paar einfache Tricks , um es getan.



memfd_create



Nach dem Lesen dieses Beitrags wurde deutlich, dass Sie eine Datei im Speicher erstellen und als ausführbar markieren können.



Wäre es nicht schön, einfach einen Speicherblock zu nehmen, die Binärdaten dort zu schreiben und auszuführen, ohne den Kernel zu patchen, execve (2) im Userland neu zu schreiben oder die Bibliothek in einen anderen Prozess zu laden?


Diese Methode verwendet den Systemaufruf memfd_create (2) , um eine Datei im Namespace /proc/self/fd



Ihres Prozesses zu erstellen und die benötigten Daten mit zu laden write



. Ich habe ziemlich viel Zeit damit verbracht, die libc- Bindungen mit Rust herauszufinden, damit alles funktioniert, und es war schwierig für mich, die übergebenen Datentypen zu verstehen. Die Dokumentation zu diesen Rust-Bindungen hat nicht viel geholfen.



Es gelang mir jedoch, etwas zum Laufen zu bringen.



unsafe {
    let write_mode = 119; // w
    // create executable in-memory file
    let fd = syscall(libc::SYS_memfd_create, &write_mode, 1);
    if fd == -1 {
        return Err(String::from("memfd_create failed"));
    }

    let file = libc::fdopen(fd, &write_mode); 

    // write contents of our binary
    libc::fwrite(
        data.as_ptr() as *mut libc::c_void, 
        8 as usize,
        data.len() as usize,
        file,
    );
}
      
      





Ein Aufruf /proc/self/fd/<fd>



des übergeordneten Elements als untergeordnetes Element reicht aus, um Ihre Binärdatei auszuführen.



let output = Command::new(format!("/proc/self/fd/{}", fd))
    .args(args)
    .stdin(std::process::Stdio::inherit())
    .stdout(std::process::Stdio::inherit())
    .stderr(std::process::Stdio::inherit())
    .spawn();
      
      





Mit diesen Bausteinen in der Hand habe ich ein pngrun- Programm geschrieben , um Bilder auszuführen. Im Wesentlichen wird Folgendes ausgeführt:



  1. Akzeptiert vom steganografischen Tool ein Bild, in das unsere Binärdatei eingebettet ist, und Argumente
  2. Dekodiert es (d. H. Ruft Bytes ab und setzt sie wieder zusammen)
  3. Erstellt eine Datei im Speicher mit memfd_create



  4. Platziert die Bytes einer Binärdatei in einer Datei im Speicher
  5. Ruft die Datei /proc/self/fd/<fd>



    als untergeordneten Prozess auf und übergibt alle Argumente vom übergeordneten Prozess.


Das heißt, Sie können es so ausführen:



$ pngrun htop.png

<htop output>

$ pngrun go.png run main.go

Hello world!






Nach Abschluss wird die pngrun



Datei im Speicher zerstört.



binfmt_misc



Das Tippen ist jedoch jedes Mal ärgerlich.pngrun



Der letzte einfache Trick in diesem sinnlosen Projekt war die Verwendung von binfmt_misc - einem System, mit dem Sie Dateien basierend auf ihrem Dateityp "ausführen" können. Ich denke, diese Funktion wurde hauptsächlich für Interpreter / virtuelle Maschinen wie Java entwickelt. Anstatt zu tippen, geben Sie java -jar my-jar.jar



einfach ein ./my-jar.jar



und dies ruft den Prozess java



zum Ausführen der JAR auf. Die Datei my-jar.jar



muss jedoch zuerst als ausführbar markiert werden.



Das heißt, fügen Sie einen Eintrag für binfmt_misc der pngrun



Lage sein , jede laufen png



mit dem Flag gesetzt x



, das kann dir gefallen:



$ cat /etc/binfmt.d/pngrun.conf

:ExecutablePNG:E::png::/home/me/bin/pngrun:

$ sudo systemctl restart binfmt.d

$ chmod +x htop.png

$ ./htop.png

<output>






Was bedeutet das Projekt?



Nun, es macht nicht wirklich viel Sinn. Die Idee, PNG-Bilder zu erstellen, auf denen Programme ausgeführt werden können, hat mich in Versuchung geführt, und ich habe sie ein wenig entwickelt, aber das Projekt war immer noch interessant. Es ist aufregend, Software als Bilder zu verteilen - denken Sie an die funky Pappkartons der PC-Software mit Grafiken auf der Vorderseite. Warum bringst du sie nicht zurück? (Obwohl es sich nicht wirklich lohnt.)



Das Projekt ist sehr dumm und weist viele Mängel auf, die es völlig bedeutungslos und unpraktisch machen. Der Hauptfehler ist, dass es ein dummes Programm auf der Maschine geben muss, damit es funktioniert pngrun



. Ich bemerkte jedoch einige Kuriositäten in Programmen wie clang



... Ich habe es in dieses lustige LLVM-Logo codiert, und obwohl es gut funktioniert, stürzt es beim Kompilieren ab.





$ ./clang.png --version

clang version 11.0.0 (Fedora 11.0.0-2.fc33)

Target: x86_64-unknown-linux-gnu

Thread model: posix

InstalledDir: /proc/self/fd

$ ./clang.png main.c

error: unable to execute command: Executable "" doesn't exist!






Dies ist wahrscheinlich darauf zurückzuführen, dass die Datei anonym ist, und das Problem kann gelöst werden, wenn ich Interesse daran hatte, sie zu studieren.



Warum ist dieses Projekt sonst so dumm?



Viele Binärdateien sind ziemlich groß, und da sie in Bilder geschrieben werden müssen, müssen die Grafiken groß und die resultierenden Dateien komisch groß sein.



Darüber hinaus besteht die meiste Software nicht nur aus einer ausführbaren Datei, sodass der Traum, PNGs zu verteilen, bei komplexeren Programmen wie Spielen scheitern wird.



Fazit



Dies ist wahrscheinlich das Dümmste Projekt , das ich in diesem Jahr gearbeitet habe, aber es war auf jeden Fall Spaß, habe ich gelernt , über Steganographie memfd_create



, binfmt_misc



und mit Rust ein wenig rumgespielt.



All Articles