Come accennato da BSH, il tuo shellcode non contiene i byte del messaggio. Salto allo MESSAGE
label e chiamando GOBACK
routine appena prima di definire il msg
byte è stata una buona mossa in quanto l'indirizzo di msg sarebbe stato in cima allo stack come indirizzo di ritorno che poteva essere saltato a ecx
, dove è memorizzato l'indirizzo di msg.
Ma sia il codice tuo che quello di BSH hanno una leggera limitazione. Contiene NULL bytes ( \x00 )
che verrebbe considerato come fine della stringa quando dereferenziato dal puntatore a funzione.
C'è un modo intelligente per aggirare questo problema. I valori che memorizzi in eax, ebx and edx
sono abbastanza piccoli da poter essere scritti direttamente nei nibble inferiori dei rispettivi registri in una volta sola accedendo a al, bl and dl
rispettivamente. Il nibble superiore può contenere un valore spazzatura in modo che possa essere xored.
b8 04 00 00 00 ------ mov $0x4,%eax
diventa
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
A differenza del set di istruzioni precedente, il nuovo set di istruzioni non contiene alcun byte NULL.
Quindi, il programma finale ha questo aspetto :
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
Assemblaggio e collegamento :
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Ora estrai lo shellcode dal binario hello :
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
uscita:
\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
Ora possiamo avere il nostro programma driver per lanciare lo shellcode.
#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;
}
Esistono alcune funzionalità di sicurezza nei compilatori moderni come la protezione NX che impedisce l'esecuzione di codice nel segmento di dati o nello stack. Quindi dovremmo specificare esplicitamente il compilatore per disabilitarli.
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
Ora il launcher
può essere invocato per lanciare lo shellcode.
$ ./launcher
y0u sp34k 1337 ? $
Per codici shell più complessi, ci sarebbe un altro ostacolo. I moderni kernel Linux hanno ASLR o Address Space Layout Randomization
Potrebbe essere necessario disabilitarlo prima di iniettare lo shellcode, specialmente quando si tratta di overflow del buffer.
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
Quando inserisci questo shellcode, non sai cosa c'è a message
:
mov ecx, message
nel processo iniettato, può essere qualsiasi cosa ma non sarà "Hello world!\r\n"
poiché è nella sezione dati mentre stai scaricando solo la sezione testo. Puoi vedere che il tuo shellcode non ha "Hello world!\r\n"
:
"\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";
Questo è un problema comune nello sviluppo di shellcode, il modo per aggirarlo è questo:
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
Ora scarica la sezione di testo:
$ 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 <-+
$
Le righe che ho contrassegnato sono le nostre "Hello, World!\r\n"
stringa:
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
Quindi il nostro wrapper C sarà:
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;
}
Proviamolo, usando -z execstack
per abilitare read-implies-exec (a livello di processo, nonostante "stack" nel nome) in modo da poter eseguire il codice in .data
o .rodata
sezioni:
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
Funziona. (-m32
è necessario anche sui sistemi a 64 bit. Il int $0x80
L'ABI a 32 bit non funziona con indirizzi a 64 bit come .rodata
in un eseguibile PIE. Inoltre, il codice macchina è stato assemblato per 32 bit. Succede che la stessa sequenza di byte venga decodificata in istruzioni equivalenti in modalità a 64 bit, ma non è sempre così.)
GNU moderno ld
inserisce .rodata
in un segmento separato da .text
, quindi può essere non eseguibile. Prima era sufficiente usare const char code[]
inserire codice eseguibile in una pagina di dati di sola lettura. Almeno per lo shellcode che non vuole modificarsi.