Linux-Shellcode erklärt: von C zu Bytes
Wie Linux-Shellcode funktioniert — Syscalls, Register und positionsunabhängiger Code — und wie man ihn sicher assembliert, kodiert und testet.
Linux-Shellcode ist klein, weil der Kernel die Hauptarbeit über Syscalls erledigt. Du linkst nicht gegen libc; du richtest Register ein und fragst den Kernel direkt. Wenn das Wort "Shellcode" neu ist, lies zuerst Was Shellcode ist.
Syscalls: x86 vs. x64
Die beiden Architekturen unterscheiden sich in drei Dingen, die für Shellcode wichtig sind:
| x86 | x64 | |
|---|---|---|
| Syscall-Instruktion | int 0x80 | syscall |
| Syscall-Nummer | eax | rax |
| Argumente | ebx, ecx, edx, … | rdi, rsi, rdx, … |
execve ist Syscall 11 auf x86 und 59 auf x64. Nimmst du die Nummer für die falsche Architektur, rufst du etwas Unerwartetes auf — ein häufiger Anfängerfehler.
Positionsunabhängiger Code
Deine Bytes landen an einer Adresse, die du nicht kontrollierst, also kannst du keine Pointer fest kodieren. Der Standardtrick für Strings ist, sie auf den Stack zu schieben und ein Register auf den Stack zeigen zu lassen:
xor eax, eax
push eax ; terminator
push 0x68732f2f ; "//sh"
push 0x6e69622f ; "/bin"
mov ebx, esp ; ebx now points at the string, wherever it is
Keine .data-Sektion, keine absolute Adresse — es funktioniert, egal wo der Code läuft.
Null-Bytes vermeiden
xor eax, eax statt mov eax, 0 ist kein Stil; die zweite Form assembliert mit Null-Bytes, die die stringbasierte Auslieferung brechen. Die ganze Kunst von kompaktem Linux-Shellcode besteht darin, die Logik richtig hinzubekommen und dabei verbotene Bytewerte zu umgehen. Siehe Null-Bytes in Shellcode.
Zu Bytes assemblieren
Schreibe das Assembly, assembliere es und extrahiere dann die .text-Bytes. Die Logik zuerst in C zu prototypisieren ist ein guter Weg, die richtigen Syscalls und Argumente zu finden, bevor du das Assembly von Hand feinabstimmst. Funktionierende Beispiele mit vollständigen Byte-Strings stehen in den Shellcode-Beispielen.
Reihenfolge der Encoder-Pässe
Wenn dein Ziel bestimmte Bytes verbietet, kodierst du — aber die Encoder-Reihenfolge zählt: Ein zweiter Pass kann Bytes wieder einführen, die der erste entfernt hat. Daran scheitern viele; die Begründung steht in Reihenfolge der Encoder-Pipeline.
Testen
Assembliere, kodiere für dein Bad-Char-Set und verifiziere — alles client-seitig — im Shellcodes-Builder, und führe ihn dann nur in einer isolierten Lab-VM aus, die du kontrollierst. Nur autorisiertes Testen: verantwortungsvolle Nutzung.