Shellcode ist ein Begriff aus der Programmierung und bezeichnet einen zumeist sehr kleinen Patch von in Opcodes umgewandelten Assemblerbefehlen, mit denen beabsichtigt wird, ein Programm oder System zu manipulieren, oder für nicht vorgesehene Zwecke auszunutzen. Dabei wird oft versucht, eine Shell zu starten, daher auch der Name. Shellcodes haben ihren Ursprung in Pufferüberlauf- und anderen Code-Injektions-Attacken, sie können aber auch bei Software-, insbesondere Penetrationstests zum Einsatz kommen, beim Experimentieren und in der Didaktik.
Erstellen von Shellcodes
Zur Erzeugung von Shellcode kann der auszuführende Befehl in C geschrieben und mit einem Compiler übersetzt werden. Das erzeugte Programm wird nun disassembliert (rückübersetzt) und die Funktionsweise des Programms in Assemblersprache nachprogrammiert. Viele Instruktionen können aber weggelassen oder verkürzt werden. Bei vielen Exploits darf im Shellcode kein 0-Byte enthalten sein, weil dieses in C das String-Ende markiert. Im Allgemeinen müssen weitere Hindernisse umgangen werden, beispielsweise werden nur Buchstaben und Zahlen zugelassen oder die Groß- und Kleinschreibung verändert, oder es müssen bestimmte Offsets eingehalten werden, was etwa durch Auffüllen mit mehr oder minder kreativen Ketten von Nulloperationen (sog. NOP Slides) erreicht werden kann.
Anstatt eigenen Code auszuführen, was nicht immer möglich ist (zum Beispiel bei Verwendung von Speicherschutz), kann man auch direkt zu gewünschten Funktionen springen, die beispielsweise im Programm selber oder einer geladenen Bibliothek, beispielsweise der libc vorhanden sind. Dieses Verfahren wird return into libc genannt.
Beispiel
Lokaler execve(/bin/sh) Shellcode
Der Assembler-Code (x86-Architektur):
void main() {
__asm__("
jmp 0x2a # 3 bytes - springt direkt vor den String
popl %esi # 1 byte - Adresse des Strings wird in esi geladen
movl %esi,0x8(%esi) # 3 bytes - die Adresse des Strings wird in den Speicher geschrieben
movb $0x0,0x7(%esi) # 4 bytes - der String wird nullterminiert
movl $0x0,0xc(%esi) # 7 bytes - ein nullpointer für das environment
movl $0xb,%eax # 5 bytes - syscall-nummer in eax
movl %esi,%ebx # 2 bytes - ebx enthält die adresse von "/bin/sh"
leal 0x8(%esi),%ecx # 3 bytes - argumente, ein pointer auf den string und ein nullpointer
leal 0xc(%esi),%edx # 3 bytes - environment
int $0x80 # 2 bytes - interrupt wird ausgelöst
movl $0x1, %eax # 5 bytes - exit-interrupt
movl $0x0, %ebx # 5 bytes - wird vorbereitet
int $0x80 # 2 bytes - interrupt wird ausgelöst
call -0x2f # 5 bytes - ein call zurück, dabei wird der eip auf den Stack gepusht
.string \"/bin/sh\" # 8 bytes
");
}
Der Opcode String:
char shellcode[] =
"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"
"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"
"\xb8\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xd1\xff\xff"
"\xff\x2f\x62\x69\x6e\x2f\x73\x68\x00\x89\xec\x5d\xc3";
Dieser Code ist jedoch nicht sonderlich geschickt, da er Nullbytes enthält und recht lang ist. Zur Vermeidung von „unerwünschten Zeichen“ werden häufig auch Encoder verwendet, welche eine Maskierung und spätere Demaskierung dieser Zeichen ermöglichen und den Shellcode eventuell noch zusätzlich komprimieren. Es gibt auch noch andere Techniken, die Adresse des Strings herauszufinden, als einen „jmp“ oder „call“. Es ist beispielsweise möglich, lediglich /bin/sh
auf den Stack zu pushen. Danach enthält der esp die Adresse.
Literatur
- Jack Koziol: The Shellcoder’s Handbook. Discovering and Exploiting Security Holes. Wiley, Indianapolis IN 2004, ISBN 0-7645-4468-3.
- Jon Erickson: Forbidden Code. mitp, Bonn 2004, ISBN 3-8266-1457-7.
Weblinks
Einzelnachweise
- ↑ Quelle: phrack.org (Memento vom 11. Februar 2008 im Internet Archive)