Kleinstes Docker-Image - Weniger als 1000 Byte

Hinweis perev. : Der Autor dieses Materials ist Architekt bei Barclays und der britische Open Source-Enthusiast Ian Miell. Ziel ist es, ein praktisches Docker-Image (mit einer "schlafenden" Binärdatei) zu erstellen, das nicht heruntergeladen werden muss, sondern einfach per Copy & Paste kopiert werden muss. Durch Versuch, Irrtum und Experimentieren mit dem Assembler-Code erreicht er das Ziel, indem er ein Bild mit einer Größe von weniger als einem Kilobyte vorbereitet.



Hier ist es (in base64 codiert)
H4sICIa2A1sCA2IA7Vrrbts2FFYL7M9+7QUGGNyfDYhtkuJFFLAhWZOhBYJmaLMOWBAEFC+xVlkyJLpYEBjdY+0l+k6jfGvqtkEWp2qD8TMg8vAqnsNzDg9lQhhmEjHDhY4zgWJBBUQJ5ZnCGAubMUQMyhJqoRRMJxYbo7Q2CedYxlQO/myqMroeEEHICIngApspxohEKI4h5DHmGEUQQw7jqAejDjBtnKz9q2w7zubi7gkugazVKHdGuWltQArkWDMCdoCqSpufg/QSPK4aV8pxW+nL96uxzMu39G+NqRe5PeekGj13Oi9BamXRmCtl1dS9X2jqel147C7W+aOJKd8dZ04dlcqsSw7KVyA9Ab/uHT/+cTht6mFRKVkMmywv0yv0mnxbMc8sSP8Apzvg0ViDtJwWxQ54Mpbny5W9qIrp2DSrmt+r+mVenu/ny+UelK6+mFR56VYtjsqfp3mxHupQZqZYdp/NGeo850x99r9j7QloyWEz8kvpK//47vuymvzQ29vf79m8MKnIaIa8bUmwRdByw6TKREIoIzE3xBrjrY7MGDUilomQ3GrNrFaIKqSZ4lkvL3tD12sn/IQCrI10xtcC7C1kH9I+xseQpYilRAwoZ5AI9IcfWFfqpRfzK1M3eeUZDRAfQDGAfc/jHTDKG1fVXiInlzcfctnwLPP9Vszs9VXvUzFy5jlZV5WzTbtN3cWkZWkhL/yS2gXm1p7lumkl24wkpv51FbYcU0EZy7SV0ucEZowkiCjvLbAVikCaGUqhyjT0c0Lj/YrElmmSWANOZ7MooHPwRCiLRaJEzBXKFGTCy49lUHNKjEigVdD6H4uTzPj9wzDCSawU0TQT2ujhjVwjgZzSj/n/eX7D/xPm/T8N/v/Ll/+Lg2fPnxw93eL85xFvyB9Rn4TzXwdAAxiMYLD/t9f/7eM/xDja1P+YBf3vKP7L2+PnttsA/IfjcQiE7nkgdH18Ey4O7pjdH7ygmX0p9n8eFA5aG3pb+0/eP/9jzFmw/13AdTBHK3/OPx7/Ic4X8qecQ9K244QG/98JXh8c/vLwwYM1/TD6KWqpv6LdOb37gT67URKterTpVxu1V9PXq3lW1d8skn++9Y83f4cDeEBAQMBnwliWuTWNu8l33G38/3X3fzGk79wFQ4S4Lwr+vwOcXIJHy4ANkLv4L4APcJ6ZSXUsz+efh1xaSOf3VxstHS6+H/nSu4s6wOns9OugxrdG7WXV5K6qc9NEn0n/ESab+s9o0P+O7v9ce1WzVNI7uAiczYI6BgQEBNwD/AvqV/+XACoAAA==

Wie bin ich dazu gekommen?


Ein Kollege zeigte einmal ein Docker-Image, mit dem er Kubernetes-Cluster testete. Er hat nichts getan: Er ist einfach untergelaufen und hat darauf gewartet, dass du ihn tötest.

„Schau, es dauert nur 700 Kilobyte! Wirklich schneller Download! ”

Und dann wurde ich neugierig, welches minimale Docker-Image ich erstellen konnte. Ich wollte eine haben, die in base64 codiert und mit einfachem Kopieren und Einfügen buchstäblich überall hin gesendet werden kann. Da das Docker-Image nur eine TAR-Datei und die TAR-Datei nur eine Datei ist, sollte alles funktionieren.

Winzige Binärdatei


Zunächst brauchte ich eine sehr kleine Linux-Binärdatei, die nichts bewirkt. Es braucht ein wenig Magie - und hier sind zwei wunderbare, informative und lesbare Artikel zum Erstellen kleiner ausführbarer Dateien:


Ich brauchte nicht "Hello World", sondern ein Programm, das nur schläft und auf x86_64 läuft. Ich habe mit einem Beispiel aus dem ersten Artikel begonnen:

  SECTION .data msg: db "Hi World",10 len: equ $-msg SECTION .text global _start _start: mov edx,len mov ecx,msg mov ebx,1 mov eax,4 int 0x80 mov ebx,0 mov eax,1 int 0x80 

Ausführen:

 nasm -f elf64 hw.asm -o hw.o ld hw.o -o hw strip -s hw 

Es ergibt sich eine Binärzahl von 504 Bytes .

Es ist jedoch nicht erforderlich, "Hello World" zu verwenden. Zuerst habe ich festgestellt, dass die Abschnitte " .data oder " .text nicht erforderlich sind und das Laden von Daten nicht erforderlich ist. Darüber hinaus befasst sich die obere Hälfte des Abschnitts _start mit der Textausgabe. Als Ergebnis habe ich den folgenden Code ausprobiert:

 global _start _start: mov ebx,0 mov eax,1 int 0x80 

Und es wurde bereits mit 352 Bytes kompiliert.

Dies ist jedoch nicht das gewünschte Ergebnis, da das Programm einfach seine Arbeit beendet und wir es zum Schlafen brauchen. Als Ergebnis zusätzlicher Untersuchungen stellte sich heraus, dass der Befehl mov eax das Prozessorregister mit der entsprechenden Linux- mov eax füllt und int 0x80 den Aufruf selbst int 0x80 . Dies wird hier ausführlicher beschrieben.

Und hier habe ich die gewünschte Liste gefunden. Syscall 1 ist exit , und wir brauchen syscall 29:pause . Das folgende Programm stellte sich heraus:

 global _start _start: mov eax, 29 int 0x80 

Wir haben weitere 8 Bytes gespeichert: Die Kompilierung ergab ein Ergebnis von 344 Bytes , und jetzt ist es eine geeignete Binärdatei für uns, die nichts tut und ein Signal erwartet.

In Hexen graben


Es ist Zeit, eine Kettensäge zu besorgen und sich mit der Binärdatei zu befassen ... Dafür habe ich Hexer verwendet , was im Wesentlichen für Binärdateien mit der Fähigkeit, Hexes direkt zu bearbeiten, vim ist. Nach langwierigen Experimenten bekam ich daraus:



… das hier:



Dieser Code macht dasselbe, aber beachten Sie, wie viele Zeilen und Leerzeichen noch übrig sind. Während meiner Arbeit ließ ich mich von einem solchen Dokument leiten, aber im Großen und Ganzen war es ein Weg des Versuchs und Irrtums.

Die Größe hat sich also auf 136 Bytes verringert.

Weniger als 100 Bytes?


Ich wollte wissen, ob ich noch weiter gehen könnte. Nachdem ich dies gelesen hatte, ging ich davon aus, dass es möglich sein würde, 45 Bytes zu erreichen, aber leider! - Nein. Die dort beschriebenen Tricks sind nur für 32-Bit-Binärdateien konzipiert, für 64-Bit-Binärdateien jedoch nicht.

Das Beste, was ich geschafft habe, war, diese 64-Bit-Version des Programms in meinen Systemaufruf zu integrieren:

 BITS 64 org 0x400000 ehdr: ; Elf64_Ehdr db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 0x3e ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndx ehdrsize equ $ - ehdr phdr: ; Elf64_Phdr dd 1 ; p_type dd 5 ; p_flags dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_align phdrsize equ $ - phdr _start: mov eax, 29 int 0x80 filesize equ $ - $$ 

Das resultierende Bild ist 127 Bytes . In diesem Zusammenhang habe ich aufgehört, die Größe zu reduzieren, aber ich akzeptiere die Vorschläge.

Kleiner Hafenarbeiter


Jetzt, da es eine Binärdatei gibt, die endloses Warten implementiert, bleibt es, sie in ein Docker-Image einzufügen.

Um jedes mögliche Byte zu speichern, habe ich eine Binärdatei mit einem Dateinamen aus einem Byte - t - erstellt und in die Dockerfile Datei Dockerfile , wodurch ein fast leeres Bild erstellt wurde:

 FROM scratch ADD t /t 

Beachten Sie, dass die Dockerfile keine CMD Dockerfile , da dies die Größe des Bildes erhöhen würde. Zu Beginn müssen Sie den Befehl über die Argumente an docker run .

Als Nächstes erstellte der docker save eine TAR-Datei, die dann mit maximaler GZIP-Komprimierung komprimiert wurde. Das Ergebnis ist eine tragbare Docker-Image-Datei mit einer Größe von weniger als 1000 Byte:

 $ docker build -tt . $ docker save t | gzip -9 - | wc -c 976 

Ich habe auch versucht, die Größe der TAR-Datei durch Experimentieren mit der Docker-Manifestdatei zu reduzieren, aber vergebens: Aufgrund der Besonderheiten des TAR-Formats und des GZIP-Komprimierungsalgorithmus führten solche Änderungen nur zum Wachstum des endgültigen GZIP. Ich habe andere Komprimierungsalgorithmen ausprobiert, aber gzip erwies sich als das Beste für diese kleine Datei.

PS vom Übersetzer


Lesen Sie auch in unserem Blog:

Source: https://habr.com/ru/post/de413959/


All Articles