Wie BSH erwähnt, enthält Ihr Shellcode die Nachrichtenbytes nicht. Springen zu MESSAGE
Label und Aufruf der GOBACK
Routine kurz vor der Definition von msg
byte war ein guter Schachzug, da die Adresse von msg oben auf dem Stack als Rücksprungadresse stehen würde, die in ecx
eingefügt werden könnte , wo die Adresse von msg gespeichert ist.
Aber sowohl Ihr als auch der Code von BSH hat eine leichte Einschränkung. Er enthält NULL bytes ( \x00 )
die als Ende der Zeichenfolge betrachtet würde, wenn sie vom Funktionszeiger dereferenziert wird.
Es gibt einen cleveren Weg, dies zu umgehen. Die Werte speichern Sie in eax, ebx and edx
sind klein genug, um durch Zugriff auf al, bl and dl
auf einmal direkt in die unteren Nibbles der jeweiligen Register geschrieben zu werden Das obere Halbbyte kann Junk-Wert enthalten, sodass es xored werden kann.
b8 04 00 00 00 ------ mov $0x4,%eax
wird
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
Im Gegensatz zum vorherigen Befehlssatz enthält der neue Befehlssatz kein NULL-Byte.
Das fertige Programm sieht also so aus:
global _start
section .text
_start:
jmp message
proc:
xor eax, eax
mov al, 0x04
xor ebx, ebx
mov bl, 0x01
pop ecx
xor edx, edx
mov dl, 0x16
int 0x80
xor eax, eax
mov al, 0x01
xor ebx, ebx
mov bl, 0x01 ; return 1
int 0x80
message:
call proc
msg db " y0u sp34k 1337 ? "
section .data
Zusammenbauen und Verlinken :
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Extrahieren Sie nun den Shellcode aus der Hello-Binärdatei :
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
Ausgabe:
\xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20
Jetzt können wir unser Treiberprogramm haben, um den Shellcode zu starten.
#include <stdio.h>
char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
"\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
"\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
"\x01\xcd\x80\xe8\xe2\xff\xff\xff"
"\x20\x79\x30\x75\x20\x73\x70\x33"
"\x34\x6b\x20\x31\x33\x33\x37\x20"
"\x3f\x20";
int main(int argc, char **argv) {
(*(void(*)())shellcode)();
return 0;
}
In modernen Compilern gibt es bestimmte Sicherheitsfunktionen wie den NX-Schutz, der die Ausführung von Code in Datensegmenten oder Stacks verhindert. Daher sollten wir den Compiler explizit angeben, um diese zu deaktivieren.
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
Jetzt die launcher
kann aufgerufen werden, um den Shellcode zu starten.
$ ./launcher
y0u sp34k 1337 ? $
Bei komplexeren Shellcodes gäbe es eine weitere Hürde. Moderne Linux-Kernel haben ASLR oder Address Space Layout Randomization
Möglicherweise müssen Sie dies deaktivieren, bevor Sie den Shellcode einfügen, insbesondere wenn es durch Pufferüberläufe geht.
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
Wenn Sie diesen Shellcode einfügen, wissen Sie nicht, was bei message
ist :
mov ecx, message
im injizierten Prozess kann es alles sein, aber es wird nicht "Hello world!\r\n"
sein da es sich im Datenabschnitt befindet, während Sie nur den Textabschnitt ausgeben. Sie können sehen, dass Ihr Shellcode nicht "Hello world!\r\n"
hat :
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";
Dies ist ein häufiges Problem in der Shellcode-Entwicklung, der Weg, es zu umgehen, ist folgender:
global _start
section .text
_start:
jmp MESSAGE ; 1) lets jump to MESSAGE
GOBACK:
mov eax, 0x4
mov ebx, 0x1
pop ecx ; 3) we are poping into `ecx`, now we have the
; address of "Hello, World!\r\n"
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
MESSAGE:
call GOBACK ; 2) we are going back, since we used `call`, that means
; the return address, which is in this case the address
; of "Hello, World!\r\n", is pushed into the stack.
db "Hello, World!", 0dh, 0ah
section .data
Sichern Sie nun den Textabschnitt:
$ nasm -f elf shellcode.asm
$ ld shellcode.o -o shellcode
$ ./shellcode
Hello, World!
$ objdump -d shellcode
shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: e9 1e 00 00 00 jmp 8048083 <MESSAGE>
08048065 <GOBACK>:
8048065: b8 04 00 00 00 mov $0x4,%eax
804806a: bb 01 00 00 00 mov $0x1,%ebx
804806f: 59 pop %ecx
8048070: ba 0f 00 00 00 mov $0xf,%edx
8048075: cd 80 int $0x80
8048077: b8 01 00 00 00 mov $0x1,%eax
804807c: bb 00 00 00 00 mov $0x0,%ebx
8048081: cd 80 int $0x80
08048083 <MESSAGE>:
8048083: e8 dd ff ff ff call 8048065 <GOBACK>
8048088: 48 dec %eax <-+
8048089: 65 gs |
804808a: 6c insb (%dx),%es:(%edi) |
804808b: 6c insb (%dx),%es:(%edi) |
804808c: 6f outsl %ds:(%esi),(%dx) |
804808d: 2c 20 sub $0x20,%al |
804808f: 57 push %edi |
8048090: 6f outsl %ds:(%esi),(%dx) |
8048091: 72 6c jb 80480ff <MESSAGE+0x7c> |
8048093: 64 fs |
8048094: 21 .byte 0x21 |
8048095: 0d .byte 0xd |
8048096: 0a .byte 0xa <-+
$
Die Zeilen, die ich markiert habe, sind unsere "Hello, World!\r\n"
Zeichenkette:
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
Unser C-Wrapper wird also sein:
char code[] =
"\xe9\x1e\x00\x00\x00" // jmp (relative) <MESSAGE>
"\xb8\x04\x00\x00\x00" // mov $0x4,%eax
"\xbb\x01\x00\x00\x00" // mov $0x1,%ebx
"\x59" // pop %ecx
"\xba\x0f\x00\x00\x00" // mov $0xf,%edx
"\xcd\x80" // int $0x80
"\xb8\x01\x00\x00\x00" // mov $0x1,%eax
"\xbb\x00\x00\x00\x00" // mov $0x0,%ebx
"\xcd\x80" // int $0x80
"\xe8\xdd\xff\xff\xff" // call (relative) <GOBACK>
"Hello wolrd!\r\n"; // OR "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
// "\x6f\x72\x6c\x64\x21\x0d\x0a"
int main(int argc, char **argv)
{
(*(void(*)())code)();
return 0;
}
Testen wir es mit -z execstack
um read-implies-exec (prozessweit, trotz "stack" im Namen) zu aktivieren, damit wir Code in .data
ausführen können oder .rodata
Abschnitte:
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
Es klappt. (-m32
ist auch auf 64-Bit-Systemen erforderlich. Die int $0x80
32-Bit-ABI funktioniert nicht mit 64-Bit-Adressen wie .rodata
in einer ausführbaren PIE-Datei. Außerdem wurde der Maschinencode für 32-Bit zusammengestellt. Es kommt vor, dass dieselbe Folge von Bytes im 64-Bit-Modus in äquivalente Anweisungen dekodiert wird, aber das ist nicht immer der Fall.)
Modernes GNU ld
setzt .rodata
in einem separaten Segment von .text
, kann also nicht ausführbar sein. Früher reichte es aus, const char code[]
zu verwenden um ausführbaren Code in eine Seite mit schreibgeschützten Daten einzufügen. Zumindest für Shellcode, der sich nicht selbst modifizieren will.