Diassemblieren
Einleitung
Das Disassemblieren ist der Prozess, bei dem aus Maschinencode wieder menschenlesbarer Assembler-Code erzeugt wird. Es ist ein wichtiges Werkzeug für:
- Debugging: Nachvollziehen, was das Programm tatsächlich macht.
- Reverse Engineering: Verstehen fremder Binärdateien.
- Optimierung: Erkennen ineffizienter Codeabschnitte.
- Lernen: Verstehen, wie Hochsprachen wie C übersetzt werden.
Werkzeuge
Für ARM64 unter Linux – z. B. auf dem Raspberry Pi – bieten sich diese Tools an:
objdump
Teil von `binutils`. Zeigt dir den Assembler-Code aus Objektdateien oder Binärprogrammen an. Dies ist auch meine bevorzugte Art, um etwas zu Diassemblieren.
objdump -d ./program
Optionen:
- `-d` disassembliert ausführbare Abschnitte.
- `-M reg-names=std` (optional): zeigt Standard-Registerbezeichnungen (x0–x30).
- `-M no-aliases` (zeigt die "echten" Instruktionsnamen statt Pseudonyme).
Beispiel:
objdump -d -M reg-names=std ./program
gdb
Mit `gdb` (GNU Debugger) kannst du direkt im Programmfluss disassemblieren.
gdb ./program
(gdb) disassemble _start
Praktische Tipps
- Möchtest du den Code in einem Editor ansehen, kannst du die Ausgabe in eine Datei leiten:
objdump -d -M reg-names=std ./program > program.diassem
- Achte auf die Sectionen: `.text` ist der Code, andere wie `.data` oder `.bss` enthalten keine Instruktionen.
- Prüfe, ob deine Datei nicht mit Strip bearbeitet wurde (`strip` entfernt Debugsymbole).
- Wenn du aus einer reinen Binärdatei (ohne ELF-Header) disassemblieren willst, nutze `objdump` mit `--target binary` und `--architecture aarch64`.
Beispiel:
objdump -D -b binary -m aarch64 your.bin
Worauf achten?
- Alignment: ARM64 nutzt 4-Byte ausgerichtete Instruktionen.
- Branch-Adressen: Bei `b`, `bl`, `cbz`, `ret` und Co. kannst du nachvollziehen, wo dein Programm hin springt.
- System Calls: erkenne z. B. `mov x8, #93; svc 0` als `exit(0)`.
- Speicherzugriffe: beachte, welche Register für Adressierung verwendet werden.
`objdump` im Detail: Parameter und ihre Bedeutung
`objdump` ist ein sehr vielseitiges Werkzeug. Besonders beim Disassemblieren von ARM64-Binärdateien ist es hilfreich, die wichtigsten Optionen zu kennen.
Grundstruktur des Befehls
objdump [OPTIONEN] DATEI
Wichtige Optionen beim Disassemblieren
| Option | Beschreibung |
|---|---|
| `-d` | Disassembliert alle **ausführbaren Segmente** (z. B. `.text`). |
| `-D` | Disassembliert **die gesamte Datei**, auch nicht-ausführbare Abschnitte. Nützlich bei rohen Binärdateien oder unüblichen Formaten. |
| `-M <option>` | Legt das Disassemblierungsformat fest. Mehrere Optionen sind durch Kommas trennbar (z. B. `-M reg-names=std,no-aliases`). |
| `--section=<name>` | Disassembliert **nur eine bestimmte Sektion**, z. B. `.text`. |
| `--start-address=0xADDR` | Beginnt das Disassemblieren **ab dieser Adresse**. |
| `--stop-address=0xADDR` | Hört bei dieser Adresse auf. |
| `--architecture=aarch64` | Setzt die Architektur explizit, z. B. bei rohen Binärdateien. |
| `-b binary` | Interpretiert die Datei als **rohe Binärdaten** (keine ELF-Struktur). |
| `-s` | Rohdaten anzeigen |
Beispiele für ARM64
Einfaches Disassemblieren
objdump -d ./program
Ohne Register-Aliase (z. B. `wzr` statt `w0`)
objdump -d -M no-aliases ./program
Mit klassischen Register-Namen (x0–x30)
objdump -d -M reg-names=std ./program
Nur `.text`-Sektion
objdump -d --section=.text ./program
Disassemblieren roher Binärdaten
Wenn du z. B. ein 1:1-Memory-Dump oder reinen Maschinencode ohne ELF-Struktur hast:
objdump -D -b binary -m aarch64 yourfile.bin
Option `-M` im Detail
`-M` steht für **Disassembler-Modifikatoren** – mehrere können kombiniert werden:
- `no-aliases`
- Zeigt dir "echte" Instruktionen, keine Vereinfachungen.
- > Beispiel: Statt `mov x0, x1` sieht man `orr x0, x1, xzr`.
- `reg-names=std`
- Standardregister (x0–x30, sp, pc).
- `reg-names=raw`
- Zeigt numerische Register: `r0`, `r1`, ...
- `reg-names=apcs`
- Zeigt Register gemäß dem **ARM Procedure Call Standard** (z. B. `a1`, `v1`, `fp`, `lr`).
Die `-s` Option: Rohdaten anzeigen
Die Option `-s` (kurz für `--full-contents`) zeigt den "Hexdump des gesamten Inhalts" der Datei, geordnet nach Sektionen.
Beispiel:
objdump -s ./program
Beispielausgabe:
Contents of section .text:
0000 52800000 d2800021 d2800442 d2800003 R...!...B....
0010 d2800004 8b030084 d2800025 d2800026 .........%...&
Bedeutung:
- Linke Spalte: Offset innerhalb der Sektion.
- Rechte Spalten: Hex-Werte der Bytes.
- Ganz rechts (manchmal): ASCII-Darstellung, wenn druckbar.
Kombination mit `-d` und `-s`
Oft ist es sinnvoll, `-d` und `-s` "gemeinsam" zu nutzen – z. B., um zu sehen, wie ein Maschinenbefehl wirklich kodiert ist:
objdump -ds ./program
Oder: gezielt auf eine bestimmte Sektion fokussieren:
objdump -s --section=.rodata ./program
So kannst du "Strings, Konstanten oder Sprungtabellen" sichtbar machen, die nicht disassembliert, aber trotzdem mitverarbeitet werden.
Zusammenfassung – wann `-s` nützlich ist:
- Debugging von Konstanten/Strings: Gibt Einblick in `.rodata`, `.data`, `.bss` etc.
- Verstehen von Maschineninstruktionen: Hex-Werte → ARM64-Opcode
- Analyse von Binärdateien ohne Symbole: Du siehst trotzdem, "was im Speicher liegt"
- Reverse Engineering: Hex-Dumps helfen, Kontrollstrukturen und Datenlayout zu erkennen
Tipps:
- Nur Funktionssymbol disassemblieren
- Wenn du Symbole hast (nicht `strip` benutzt):
objdump -d ./blink --disassemble=_start
- Wenn deine Datei keine Symbole enthält, verwende zusätzlich `nm` oder `readelf`, um Adressen herauszufinden.
- Kombiniere `objdump` mit `grep` zur gezielten Analyse:
objdump -d ./blink | grep "<_start>"
Diassemblieren
-> objdump -s -d HelloWorld.o > HelloWorld.diassem
Hello.o: file format elf64-littleaarch64
Contents of section .text:
0000 200080d2 e1000058 a20180d2 080880d2 ......X........ 0010 010000d4 000080d2 a80b80d2 010000d4 ................ 0020 00000000 00000000 ........
Contents of section .data:
0000 48656c6c 6f20576f 726c6421 0a Hello World!.
Disassembly of section .text:
0000000000000000 <_start>:
0: d2800020 mov x0, #0x1 // #1 4: 580000e1 ldr x1, 20 <_start+0x20> 8: d28001a2 mov x2, #0xd // #13 c: d2800808 mov x8, #0x40 // #64 10: d4000001 svc #0x0 14: d2800000 mov x0, #0x0 // #0 18: d2800ba8 mov x8, #0x5d // #93 1c: d4000001 svc #0x0
...
negative Zahlen
-> Beispiel: 5 + -3
3 in 1 byte is 0x03 or 0000 0011. Inverting the bits is 1111 1100 Add 1 to get 1111 1101 = 0xFD Now add 5 + 0xFD = 0x102 = 2
movadd.o: file format elf64-littleaarch64
Contents of section .text:
0000 001980d2 812580d2 0200018b a00080d2 .....%.......... 0010 41008092 0200018b 000080d2 a80b80d2 A............... 0020 010000d4 ....
Disassembly of section .text:
0000000000000000 <_start>:
0: d2801900 mov x0, #0xc8 // #200 4: d2802581 mov x1, #0x12c // #300 8: 8b010002 add x2, x0, x1 c: d28000a0 mov x0, #0x5 // #5 10: 92800041 mov x1, #0xfffffffffffffffd // #-3 14: 8b010002 add x2, x0, x1 18: d2800000 mov x0, #0x0 // #0 1c: d2800ba8 mov x8, #0x5d // #93 20: d4000001 svc #0x0