- initrd wird als Programmeinheit definiert, die der Kernel direkt interpretiert und ausführt, wodurch Linux als eine Art Interpreter neu gedeutet wird
- Mit
kexec, base64 und cpio wird eine rekursive Linux-Distribution gebaut, die sich selbst neu bootet; die initrd führt sich immer wieder selbst aus
- Wenn das
/init-Skript sein eigenes cpio-Image ausgibt, entsteht eine selbstreplizierende initrd in Quine-Form
- Anhand der ELF-Ausführungsstruktur,
ld.so und binfmt_misc wird erläutert, wie sich die Interpreter-Schichtung bis in den Kernel hinein fortsetzt
- Mit
kexec oder QEMU lässt sich auf Linux ein weiteres Linux in endrekursiver Form ausführen, wodurch die Grenzen zwischen Kernel, Virtualisierung und Interpreter experimentell erweitert werden
Reverse Engineering von rkx.gz und die Struktur einer selbstrekursiven initrd
- Der Befehl
curl https://astrid.tech/rkx.gz | gunzip | sudo sh lädt ein 20 MB großes base64-kodiertes Shell-Skript herunter und führt es aus
- Das Skript prüft Root-Rechte und kontrolliert das Vorhandensein von
kexec, base64 und cpio
- Es dekodiert die base64-Daten, erzeugt daraus ein cpio-Archiv namens
r und extrahiert daraus ein Kernel-Image namens k
- Mit
kexec werden k als Kernel und r als Ramdisk geladen und anschließend ausgeführt
- In
r.cpio befinden sich die Dateien /bin, /init und k; k ist ein Linux-6.18.18-Kernel-Image, /init liegt als Shell-Skript vor
/init mountet /proc, bündelt dann das aktuelle Dateisystem per cpio nach /r und startet anschließend per kexec erneut /k und /r
- Das Ergebnis ist eine rekursive Linux-Distribution, die sich selbst fortlaufend neu bootet
Die Sicht auf den Linux-Kernel als Interpreter
- initrd ist nicht nur eine einfache Ramdisk zum Booten, sondern kann als Programm verstanden werden, das der Linux-Kernel interpretiert und ausführt
- Wie bei
curl | sh oder python3 script.py ist auch initrd ein Eingabeprogramm, das vom Kernel ausgeführt wird
- Daher fungiert der Linux-Kernel als Interpreter, der initrd interpretiert
- Diese Struktur ähnelt einer Tail-Call-Optimierung
kexec überschreibt den vorherigen Kernel nicht, sondern lädt und startet ihn in einem neuen Speicherbereich
- Jeder Kernel behält keinen vorherigen Zustand und wird durch einen neuen „Stack-Frame“ ersetzt
Quine und die Selbstreplikation von initrd
- Ein Quine ist ein Programm, das sich selbst ausgibt
- Wenn das
/init-Skript am Ende cat /r ausführt, gibt es ein cpio aus, das mit ihm selbst identisch ist
- In diesem Fall entsteht ein Quine des Linux-initrd-Interpreters
- Da alle Dateien im RAM auf
tmpfs existieren, findet keine tatsächliche Festplatten-I/O statt
ELF, ld.so und die Schichten des Interpreters
- ELF-Binärdateien enthalten im Header den Pfad zum Interpreter (
ld-linux-x86-64.so.2)
- Bei der Ausführung startet der Kernel zunächst
ld.so, und ld.so lädt anschließend die dynamischen Bibliotheken des ELF und führt das Programm aus
- Daher kann auch ELF als eine Art Interpreter-Sprache betrachtet werden
/bin/sh wird von ld.so interpretiert, und ld.so wird direkt vom Kernel interpretiert
ld.so ist ein statisch gelinktes ELF und kann daher direkt vom Kernel ausgeführt werden
- Dadurch entsteht der Basisfall der Interpreter-Schichtung
Ausführung von CPIO über binfmt_misc
- Mit
binfmt_misc lassen sich Dateien mit bestimmten Magic Bytes über einen festgelegten Interpreter ausführen
- Über QEMU kann ein Skript, das CPIO als initrd ausführt, als Interpreter registriert werden
- QEMU bootet eine virtuelle Maschine mit dem angegebenen Kernel und der angegebenen initrd
- Dadurch wird der Interpreter der CPIO-Datei zu dem von QEMU gestarteten Linux-Kernel
Rekursive Interpreter und die „seltsamste Schleife“
- Ein QEMU-basierter Interpreter erzeugt einen neuen Stack-Frame einer Linux-Umgebung
- In einer Struktur, in der auf Linux ein weiteres Linux läuft, ist eine Verschachtelung bis an die Speichergrenze möglich
- Ersetzt man dies durch einen
kexec-basierten Interpreter, wird eine tail-call-optimierte rekursive Linux-Ausführung möglich
- Wenn in
/init binfmt_misc registriert und anschließend /r ausgeführt wird,
ist eine initrd komplett, die sich selbst ausführt
/r ist der nächste Init-Prozess im CPIO-Format und interpretiert bei seiner Ausführung erneut sich selbst
Fazit
- initrd ist nicht nur ein simples Boot-Werkzeug, sondern eine Programmeinheit, die der Linux-Kernel interpretiert
- Mit
kexec und binfmt_misc lässt sich Linux selbst rekursiv wie ein Interpreter ausführen
- Diese Struktur ist ein experimentelles Konzept, das die Grenzen zwischen Kernel, Virtualisierung, Interpreter und selbstreplizierenden Programmen aufweicht
- Der zugehörige Quellcode ist im GitHub-Repository ifd3f/rekexec veröffentlicht
2 Kommentare
Unwissen macht offenbar mutig … Solche Artikel sollte man besser vermeiden.
Hacker-News-Kommentare
Beim Lesen dieses Artikels litt ich unter zu vielen Missverständnissen
Ein cpio-Archiv ist kein Dateisystem. Der Autor verwendet initramfs, das auf tmpfs basiert. Linux kann cpio nach tmpfs extrahieren. Ein Archiv aus Dateien und Verzeichnissen ist für sich genommen kein Programm
Nur weil etwas ähnlich aussieht, ist es nicht dasselbe. Ein Binärprogramm wird auf der CPU ausgeführt, und wenn es einen Interpreter gibt, dann versteckt er sich in der Hardwareumgebung. Das liegt außerhalb des Zuständigkeitsbereichs des Kernels
Um ein Shell-Skript auszuführen, braucht man eine Shell, die dieses Skript interpretiert. Der Autor lässt diesen Teil aus und verwechselt Kernel und Shell-Programm
Linux kann auch ohne initramfs oder ramdisk kompiliert werden und trotzdem den Userspace eines Dateisystems ausführen
Die Formulierung „Linux initrd interpreter“ ist wirklich eine falsche Beschreibung
ld.soELF in den Speicher entpackt und den Entry-Point ausführt oder der Kernel initramfs entpackt und den Entry-Point ausführtIst nicht jedes OS in Kernel-Rechten eine Art Maschinencode-Interpreter?
Der Artikel ist in Ordnung, wenn man ihn als mentales Modell „Linux ist ein Interpreter“ versteht, aber wörtlich genommen ist er falsch
Wenn man es nicht als Interpretation auf CPU-Befehlsebene betrachtet, sondern als Rolle des Kernels beim Orchestrieren von Ausführungsformaten wie ELF, Shebang-Skripten und initramfs, ist es plausibler. Die Verwirrung scheint daher zu kommen, dass zwei Bedeutungen von „Interpreter“ vermischt werden
Der Kernpunkt ist nicht, ob die Metapher stimmt, sondern dass sie zeigt, wie stark das Konzept von „Ausführung“ von der Umgebung abhängt
„Alles ist ein Interpreter?“
Turings Theta Combinator
In einem früheren Artikel der Serie sagte der Autor, er habe sein eigenes VPS-Image gebaut, weil er den Object Storage von Contabo nicht nutzen wollte
Ich denke, irgendwo zwischen dem Extrem, 50 Stunden zu investieren, um 1,50 Dollar im Monat zu sparen, und dem anderen Extrem, 250.000 Dollar für Tokens auszugeben, gibt es einen Mittelweg.
Wenn man sich die Infrastrukturkosten nicht leisten kann, könnte eher ein soziales Problem als mangelnde technische Kompetenz vorliegen. Sich darauf zu versteifen, Doom mit
curlauszuführen, wirkt auf mich nicht produktivIn
man ld.sosteht ausdrücklich, dass der dynamische Linker, der in der.interp-Sektion von ELF gespeichert ist, ausgeführt wird. Schon der Sektionsname ist interessantLinux ist als programmierbare Schnittstelle sehr nützlich. Mit Windows geht das auch, aber Linux scheint dafür besser geeignet zu sein
Ich finde die GUI unter Windows besser, aber auch GNOME oder KDE sind unbequem. Deshalb nutze ich fluxbox, icewm und manchmal xfce oder mate-desktop. Heutzutage bevorzuge ich eine einfache und schnelle Umgebung. Die meisten Aufgaben erledige ich über die Kommandozeile und beim Bearbeiten von Code