Normalerweise programmieren wir im CoderDojo mit sogenannten höheren Programmiersprachen wie C#, Java, Python oder JavaScript. Auf unterster Ebene versteht ein Computer diese Sprachen aber nicht. Er kann nur mit Maschinensprache umgehen. Diese Befehle kann der Prozessor deines Computers direkt ausführen.
In dieser Übung probieren wir, ein Programm direkt in Maschinensprache zu programmieren. Dazu verwenden wir eine Assemblersprache. Auch wenn du später selten Assembler programmieren wirst, hilft dir diese Übung, besser zu verstehen, was im Hintergrund passiert.
In dieser grundlegenden Übung stehen die einfachsten Grundlagen von Assembler sowie die Tools, die du dafür brauchst, im Mittelpunkt. Später werden wir uns mit mehr Assembler-Kommandos beschäftigen.
In Assembler schreibt man Programme für einen bestimmten Prozessor und ein bestimmtes Betriebssystem. Ein Assemblerprogramm, das man z.B. für einen Windows-Computer mit Intel-Prozessor schreibt, kann man nicht ohne weiteres unter Linux auf einem ARM-Prozessor ausführen.
Diese Übung setzt einen Intel-Prozessor und Linux (z.B. Ubuntu) voraus.
Im CoderDojo wird dir ein Mentor einen Zugang zu einer fertigen Linux-Maschine geben. Zum Zugriff brauchst du PuTTY. Lade die Software herunter, installiere sie und frage deinen CoderDojo-Mentor um die Zugangsdaten.
Wenn du von einem Mentor die Zugangsdaten zu einer fertigen Entwicklungsumgebung bekommen hast, kannst du dieses Kapitel überspringen.
Möchtest du die Entwicklungsumgebung auf deinem Computer haben? Das ist natürlich möglich. Die gesamte, verwendete Software ist Open Source und kostenlos.
Damit Linux bei dir läuft, hast du folgende Möglichkeiten (falls du nicht sicher bist, frag einen CoderDojo Mentor um Hilfe):
Wenn du deinen Ubuntu Linux Server installiert hast, musst du die für die Übung notwendige Software einrichten. Folgendes müssen wir installeren:
sudo apt-get install build-essential
gdb
installiert. Start daher sudo apt-get install gdb
Das gesamte Installationsscript findest du in install-dev-tools.sh.
Hinweise für das Mentorenteam bzw. Coder mit umfangreichem Basiswissen:
docker build -t nasm .
). Dazu noch ein Hinweis: Wenn ihr die Übung von unten in einem Docker Container durchführen wollt, braucht ihr folgendes run Statement: docker run -it --rm --security-opt seccomp=unconfined nasm /bin/bash
. Beachtet die Option --security-opt seccomp=unconfined
. Sie ist nur für gdb
notwendig.Starte PuTTY
Im Feld Host Name (or IP address) gibst du die IP-Adresse deines Ubuntu Linux Servers ein (im CoderDojo bekommst du die IP-Adresse vom Mentorenteam).
Melde dich mit Benutzer und Passwort an (im CoderDojo bekommst du Benutzer und Passwort vom Mentorenteam)
Probiere, ob du den Assembler starten kannst, indem du das Kommando nasm -v
ausführst. Du müsstest die Version von NASM angezeigt bekommen.
Klappt alles? Dann können wir anfangen zu hacken.
Als erstes möchten wir das typische Kennenlernprogramm schreiben, das man in jeder Programmiersprache programmiert: Hello World.
Starte den Editor vim mit dem Kommando vim
. vim ist ein grundlegender Editor, der bei Bedarf auf jeden System installiert werden kann.
Mache dich gemeinsam mit dem Mentorenteam vom CoderDojo mit vim vertraut.
Erstelle mit vim die Datei hello.asm
, indem du das Kommando vim hello.asm
eingibst.
Gib das Beispiel-Assemblerprogramm in die Datei hello.asm
ein.
SECTION .data ; DATEN
msg: db "Hello World",10 ; Diesen Text wollen wir ausgeben
; Die 10 am Ende bedeutet "naechste Zeile".
; Es handelt sich um einen ASCII-Code (Details
; siehe http://www.asciitable.com/)
len: equ $-msg ; Wir berechnen die Laenge des Text, indem
; wir die Speicheradresse von msg von der
; aktuelle Speicheradresse ("$") subtrahieren
SECTION .text ; PROGRAMMCODE
global main ; Das Programm startet bei "main"
main:
mov edx, len ; In edx tragen wir die Laenge ein.
; edx ist ein sogenanntes "Register" (Details siehe
; https://de.wikipedia.org/wiki/Register_(Computer))
mov ecx, msg ; In ecx die Adresse des Textes
mov ebx, 1 ; 1 steht fuer "stdout" = Bildschirm
mov eax, 4 ; 4 steht fuer "Ausgabe"
int 0x80 ; Mit Interrupt 80 hex rufen wir den
; Linux Kernel auf
mov ebx, 0 ; 0 steht fuer "normal beendet"
mov eax, 1 ; 1 steht fuer "programm beenden"
int 0x80
Kompiliere das Programm mit nasm -f elf hello.asm
. Als Ergebnis bekommst du eine Datei hello.o
.
Linke das Programm mit gcc -m32 -o hello hello.o
. Als Ergebnis bekommst du die ausführbare Datei hello
. Faszinierend, wie klein die Datei ist, oder?
Führe dein Programm mit ./hello
aus. Wenn *Hello World" ausgegeben wird, hast du dein erstes Assembler-Programm geschrieben :-)
Hier in paar wichtige Links, die dir helfen, das Programm besser zu verstehen:
Während des CoderDojos kannst du das Programm mit dem Mentorenteam diskutieren.
SECTION .bss
buffer: resb 64
SECTION .text
global main
main:
part1:
mov edx, 64
mov ecx, buffer
mov eax, 3
mov ebx, 0
int 0x80
part2:
mov edx, eax
mov eax, 4
mov ebx, 1
int 0x80
quit:
mov eax, 1
int 0x80
Gib diesen Code in die Datei challenge.asm
ein, kompiliere und linke es wie im vorigen Beispiel gezeigt.
Führe dein Programm mit ./challenge
aus. Macht es was du vermutet hast?
Ein Debugger ist ein Tool, mit dem du Fehler in einem Programm suchen kannst. Er erlaubt dir, das Programm an jeder beliebigen Stelle anzuhalten und Variablen, Register, etc. anzusehen. Probieren wir den Debugger mit unserem Programm challenge aus.
Kompiliere das Programm aus der vorigen Challenge mit nasm -f elf -F dwarf -g challenge.asm
. Als Ergebnis bekommst du eine Datei challenge.o
. Achte auf die neue Optionen -F dwarf -g
. Dadurch fügst du der Zieldatei Daten zum leichteren Debuggen (=Fehlersuchen) hinzu. Näheres zum DWARF Format findest du auf Wikipedia.
Linke das Programm mit gcc -m32 -g -o challenge challenge.o
. Als Ergebnis bekommst du die ausführbare Datei challenge
. Achte auf die neue Option -g
. Sie sorgt dafür, dass die Debug-Informationen in der ausführbaren Datei erhalten bleiben.
Starte den GNU Debugger mit gdb challenge
.
Gib das Kommando list main
ein. Du siehst, dass der Debugger dir dein Programm anzeigen kann, obwohl er nur die ausführbare Datei kennt. list
funktioniert auch wenn du den Quellcode in der challenge.asm
Datei nicht hättest.
Gib das Kommando set disassembly-flavor intel
und anschließend das Kommando disassemble main
ein. Wieder siehst den den Quellcode deines Programms. disassemble
kannst du immer verwenden, auch wenn das Programm ohne Debuginformationen kompiliert wurde.
Jetzt wollen wir zwei Breakpoints setzen. Breakpoints sind stellen, an denen der Debugger die Programmausführung unterbrechen soll, damit wir einen Blick auf z.B. Variablenwerte werfen können. Gib die Kommandos break part1
und break part2
ein. Der Debugger soll also bei part1
und part2
stehenbleiben.
Lass dein Programm jetzt mit run
laufen. Der Debugger müsste sofort bei part1
stehenbleiben.
Schauen wir uns die Variable buffer
an. Mit print &buffer
geben wir die Speicheradresse von buffer
aus. Mit x /64x &buffer
sehen wir uns 64 Bytes im Speicher als Hexadezimalwerte an, die sich an der Adresse von buffer
befinden. Mit x /64s &buffer
wird jedes Byte in ein ASCII-Zeichen umgewandelt. Näheres zu x (für examine) findest du hier. In unserem Fall ist der Speicher, auf den buffer
verweist, leer, also mit lauter Null-Werten gefüllt.
Lassen wir das Programm jetzt mit c
für continue
weiterlaufen. Das Programm erwartet eine Eingabe. Gib einen beliebigen Text ein (z.B. CoderDojo) und drücke die Enter-Taste.
Der Debugger bleibt beim zweiten Breakpoint (part2
) stehen. Probiere nochmals die x
-Kommandos von oben aus. Wie du siehst steht jetzt der eingegebene Text im Speicher an der Adresse, auf die buffer
verweist. Noch ein Tipp dazu: Wenn du einen Text im Speicher ausgeben möchtest, kannst du auch das printf
-Kommando probieren: printf "%s", &buffer
(mehr dazu).
Mit dem info
-Kommando kannst du dir neben vielen anderen Sachen auch die Registerwerte ansehen. Probiere info registers
und info registers eax
.
Würden wir das Programm mit c
jetzt weiterlaufen lassen, würde der eingegebene Text (z.B. CoderDojo) am Bildschirm ausgegeben. Wir wollen aber manuell im Debugger den Text im Speicher ändern. Gib dazu das Kommando p strcpy(&buffer, "Hacked!!!\n")
ein. strcpy
steht für string copy, also für Text kopieren. Wir kopieren dementsprechend den Text Hacked!!! an den Speicher, auf den die Variable buffer
zeigt.
Überprüfe mit dem uns schon bekannten Kommando x /10s &buffer
ob das Überschreiben den Textes im Speicher geklappt hat.
Lass das Programm mit c
(continue) weiterlaufen. Es müsste Hacked!!! ausgeben :-) Geklappt?
Lass uns noch einen Schritt weiter gehen. In höheren Programmiersprachen wie C#, Java, Python etc. hast du schon Schleifen kennengelernt. Der Prozessor kennt auf unterster Ebene keine solchen Schleifen. Man muss durch Sprünge (Jump) Schleifen nachbilden. Das wollen wir an einem Beispiel ausprobieren.
Unsere Aufgabe ist es, ein Programm zu schreiben, bei dem …
Überlege dir, wie der Algorithmus aussehen könnte. Diskutiert das am besten mit eurem Mentorenteam beim CoderDojo bevor ihr die Musterlösung im nächsten Schritt anseht.
Wir haben dir eine Musterlösung zusammengestellt. Gib sie ein. Du musst nicht von vorne beginnen. Wenn du das Beispiel challenge.asm von oben noch hast (download), brauchst du dieses nur erweitern. Anfang (Texteingabe) und Ende (Textausgabe) sind bei beiden Beispielen gleich.
Kompiliere (nasm -f elf -F dwarf -g reverse.asm
) und linke (gcc -m32 -g -o reverse reverse.o
) das Programm. Falls Fehler erscheinen, frag deinen CoderDojo-Mentor oder deine Mentorin um Hilfe.
Führe das Programm aus und kontrolliere, ob der Text richtig umgedreht wird.
Schaue dir jetzt Zeile für Zeile den Code an und versuche, den Algorithmus zu verstehen. Bei Fragen wende dich an das CoderDojo Mentorenteam.
Wir haben nur an der Oberfläche dessen gekratzt, was Assember alles kann. Hier Vorschläge für nächste, selbständige Übungen:
Schreibe ein Programm, bei dem der Benutzer zwei Texte eingeben muss. Anschließend finde heraus, ob die Texte gleich sind und gib eine entsprechende Meldung aus.
Ändere das letzte Beispiel von oben so, dass statt einem umgedrehten Text ein Text herauskommt, bei dem alle Kleinbuchstaben in Großbuchstaben umgewandelt werden.
Viel Spaß beim Hacken!