Diassemblieren: Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
KKeine Bearbeitungszusammenfassung |
||
| (4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
== | == 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. | |||
<syntaxhighlight lang="shell"> | |||
objdump -d ./program | |||
</syntaxhighlight> | |||
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: | |||
<syntaxhighlight lang="shell"> | |||
objdump -d -M reg-names=std ./program | |||
</syntaxhighlight> | |||
=== gdb === | |||
Mit `gdb` (GNU Debugger) kannst du direkt im Programmfluss disassemblieren. | |||
<syntaxhighlight lang="shell"> | |||
gdb ./program | |||
(gdb) disassemble _start | |||
</syntaxhighlight> | |||
== Praktische Tipps == | |||
* Möchtest du den Code in einem Editor ansehen, kannst du die Ausgabe in eine Datei leiten: | |||
<syntaxhighlight lang="shell"> | |||
objdump -d -M reg-names=std ./program > program.diassem | |||
</syntaxhighlight> | |||
* 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: | |||
<syntaxhighlight lang="shell"> | |||
objdump -D -b binary -m aarch64 your.bin | |||
</syntaxhighlight> | |||
== 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 === | |||
<syntaxhighlight lang="shell"> | |||
objdump [OPTIONEN] DATEI | |||
</syntaxhighlight> | |||
=== Wichtige Optionen beim Disassemblieren === | |||
{| class="wikitable" | |||
|- | |||
! 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 ==== | |||
<syntaxhighlight lang="shell"> | |||
objdump -d ./program | |||
</syntaxhighlight> | |||
==== Ohne Register-Aliase (z. B. `wzr` statt `w0`) ==== | |||
<syntaxhighlight lang="shell"> | |||
objdump -d -M no-aliases ./program | |||
</syntaxhighlight> | |||
==== Mit klassischen Register-Namen (x0–x30) ==== | |||
<syntaxhighlight lang="shell"> | |||
objdump -d -M reg-names=std ./program | |||
</syntaxhighlight> | |||
==== Nur `.text`-Sektion ==== | |||
<syntaxhighlight lang="shell"> | |||
objdump -d --section=.text ./program | |||
</syntaxhighlight> | |||
==== Disassemblieren roher Binärdaten ==== | |||
Wenn du z. B. ein 1:1-Memory-Dump oder reinen Maschinencode ohne ELF-Struktur hast: | |||
<syntaxhighlight lang="shell"> | |||
objdump -D -b binary -m aarch64 yourfile.bin | |||
</syntaxhighlight> | |||
=== 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: ==== | |||
<syntaxhighlight lang="shell"> | |||
objdump -s ./program | |||
</syntaxhighlight> | |||
==== Beispielausgabe: ==== | |||
<syntaxhighlight lang="shell"> | |||
Contents of section .text: | Contents of section .text: | ||
0000 | 0000 52800000 d2800021 d2800442 d2800003 R...!...B.... | ||
0010 | 0010 d2800004 8b030084 d2800025 d2800026 .........%...& | ||
</syntaxhighlight> | |||
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: | |||
<syntaxhighlight lang="shell"> | |||
objdump -ds ./program | |||
</syntaxhighlight> | |||
Oder: gezielt auf eine bestimmte Sektion fokussieren: | |||
<syntaxhighlight lang="shell"> | |||
objdump -s --section=.rodata ./program | |||
</syntaxhighlight> | |||
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): | |||
: <syntaxhighlight lang="shell"> | |||
objdump -d ./blink --disassemble=_start | |||
</syntaxhighlight> | |||
* Wenn deine Datei keine Symbole enthält, verwende zusätzlich `nm` oder `readelf`, um Adressen herauszufinden. | |||
* Kombiniere `objdump` mit `grep` zur gezielten Analyse: | |||
: <syntaxhighlight lang="shell"> | |||
objdump -d ./blink | grep "<_start>" | |||
</syntaxhighlight> | |||
== Negative Zahlen beim Disassemblieren mit `objdump` == | |||
== | === 1. Immediates werden hexadezimal dargestellt === | ||
`objdump` zeigt "sofortige Werte (Immediates)" standardmäßig in "Hexadezimalform" – unabhängig davon, ob es sich ursprünglich um negative Zahlen handelte. | |||
* Beispiel (Assemblercode): | |||
: <syntaxhighlight lang="asm"> | |||
mov x0, #-1 | |||
</syntaxhighlight> | |||
* Disassemblierung: | |||
: <syntaxhighlight lang="shell"> | |||
mov x0, #0xffffffffffffffff | |||
</syntaxhighlight> | |||
- `#-1` wird als `0xFFFFFFFFFFFFFFFF` dargestellt. | |||
- Das ist die **Zweierkomplement-Darstellung** von `-1` in 64 Bit. | |||
=== 2. Offsets bei Sprüngen und Speicherzugriffen === | |||
"Branches", "LOAD/STORE-Offsets" oder Stackzugriffe können negative Offsets haben. Ob `objdump` das als negativ darstellt, hängt vom Befehl ab – oft wird auch hier nur der "Zweierkomplementwert" in Hex gezeigt. | |||
* Beispiel: | |||
: <syntaxhighlight lang="asm"> | |||
ldur x0, [sp, #-16] | |||
</syntaxhighlight> | |||
* Disassemblierung: | |||
: <syntaxhighlight lang="shell"> | |||
ldur x0, [sp, #-0x10] | |||
</syntaxhighlight> | |||
Hier ist `objdump` klug genug, das tatsächlich als "negativen Offset" anzuzeigen! | |||
Aber: Das klappt "nicht bei allen Instruktionen"! Manchmal musst du selbst das Offset interpretieren. | |||
=== 3. Unsignierte Darstellung in Datenfeldern === | |||
Bei Verwendung von `objdump -s` (Hexdump der Daten) werden "alle Bytes als unsigned" dargestellt – also es gibt "keine negativen Zahlen" im Dump selbst, nur 0–255 (0x00–0xFF). | |||
* Beispiel: | |||
: <syntaxhighlight lang="C"> | |||
.int32 -42 | |||
</syntaxhighlight> | |||
: <syntaxhighlight lang="shell"> | |||
objdump -s -j .data a.out | |||
</syntaxhighlight> | |||
* Disassemblierung: | |||
: <syntaxhighlight lang="shell"> | |||
d6 ff ff ff | |||
</syntaxhighlight> | |||
Das ist `0xFFFFFFD6`, also `-42` als "signed 32-Bit" im Zweierkomplement. | |||
Aktuelle Version vom 10. April 2025, 17:01 Uhr
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>"
Negative Zahlen beim Disassemblieren mit `objdump`
1. Immediates werden hexadezimal dargestellt
`objdump` zeigt "sofortige Werte (Immediates)" standardmäßig in "Hexadezimalform" – unabhängig davon, ob es sich ursprünglich um negative Zahlen handelte.
- Beispiel (Assemblercode):
mov x0, #-1
- Disassemblierung:
mov x0, #0xffffffffffffffff
- `#-1` wird als `0xFFFFFFFFFFFFFFFF` dargestellt. - Das ist die **Zweierkomplement-Darstellung** von `-1` in 64 Bit.
2. Offsets bei Sprüngen und Speicherzugriffen
"Branches", "LOAD/STORE-Offsets" oder Stackzugriffe können negative Offsets haben. Ob `objdump` das als negativ darstellt, hängt vom Befehl ab – oft wird auch hier nur der "Zweierkomplementwert" in Hex gezeigt.
- Beispiel:
ldur x0, [sp, #-16]
- Disassemblierung:
ldur x0, [sp, #-0x10]
Hier ist `objdump` klug genug, das tatsächlich als "negativen Offset" anzuzeigen!
Aber: Das klappt "nicht bei allen Instruktionen"! Manchmal musst du selbst das Offset interpretieren.
3. Unsignierte Darstellung in Datenfeldern
Bei Verwendung von `objdump -s` (Hexdump der Daten) werden "alle Bytes als unsigned" dargestellt – also es gibt "keine negativen Zahlen" im Dump selbst, nur 0–255 (0x00–0xFF).
- Beispiel:
.int32 -42
objdump -s -j .data a.out
- Disassemblierung:
d6 ff ff ff
Das ist `0xFFFFFFD6`, also `-42` als "signed 32-Bit" im Zweierkomplement.