Comme BSH l'a mentionné, votre shellcode ne contient pas les octets de message. Sauter au MESSAGE
label et en appelant le GOBACK
routine juste avant de définir le msg
octet était une bonne décision car l'adresse de msg serait en haut de la pile en tant qu'adresse de retour qui pourrait être sautée à ecx
, où l'adresse de msg est stockée.
Mais le vôtre et le code de BSH ont une légère limitation. Il contient NULL bytes ( \x00 )
qui serait considéré comme une fin de chaîne lorsqu'il serait déréférencé par le pointeur de fonction.
Il existe un moyen intelligent de contourner cela. Les valeurs que vous stockez dans eax, ebx and edx
sont suffisamment petits pour être directement écrits dans les quartets inférieurs des registres respectifs en une seule fois en accédant à al, bl and dl
respectivement. Le quartet supérieur peut contenir une valeur indésirable afin qu'il puisse être xoré.
b8 04 00 00 00 ------ mov $0x4,%eax
devient
b0 04 ------ mov $0x4,%al
31 c0 ------ xor %eax,%eax
Contrairement au jeu d'instructions précédent, le nouveau jeu d'instructions ne contient aucun octet NULL.
Ainsi, le programme final ressemble à ceci :
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
Assemblage et liaison :
$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
y0u sp34k 1337 ? $
Extrayez maintenant le shellcode du binaire hello :
$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done
sortie :
\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
Maintenant, nous pouvons avoir notre programme de pilote pour lancer le 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;
}
Il existe certaines fonctionnalités de sécurité dans les compilateurs modernes comme la protection NX qui empêche l'exécution de code dans le segment ou la pile de données. Nous devons donc spécifier explicitement le compilateur pour les désactiver.
$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher
Maintenant le launcher
peut être invoqué pour lancer le shellcode.
$ ./launcher
y0u sp34k 1337 ? $
Pour les shellcodes plus complexes, il y aurait un autre obstacle. Les noyaux Linux modernes ont ASLR ou Address Space Layout Randomization
Vous devrez peut-être désactiver cela avant d'injecter le shellcode, en particulier lorsqu'il s'agit de débordements de tampon.
[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space
Lorsque vous injectez ce shellcode, vous ne savez pas ce qu'il y a en message
:
mov ecx, message
dans le processus injecté, ça peut être n'importe quoi mais ce ne sera pas "Hello world!\r\n"
puisqu'il se trouve dans la section des données alors que vous ne videz que la section de texte. Vous pouvez voir que votre shellcode n'a pas "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";
C'est un problème courant dans le développement de shellcode, la façon de le contourner est la suivante :
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
Videz maintenant la section de texte :
$ 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 <-+
$
Les lignes que j'ai marquées sont nos "Hello, World!\r\n"
chaîne :
$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!
$
Ainsi, notre wrapper C sera :
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;
}
Testons-le, en utilisant -z execstack
pour activer read-implies-exec (à l'échelle du processus, malgré "stack" dans le nom) afin que nous puissions exécuter du code dans le .data
ou .rodata
rubriques :
$ gcc -m32 test.c -z execstack -o test
$ ./test
Hello wolrd!
Ça marche. (-m32
est également nécessaire sur les systèmes 64 bits. Le int $0x80
L'ABI 32 bits ne fonctionne pas avec les adresses 64 bits telles que .rodata
dans un exécutable PIE. De plus, le code machine a été assemblé pour 32 bits. Il arrive que la même séquence d'octets soit décodée en instructions équivalentes en mode 64 bits, mais ce n'est pas toujours le cas.)
GNU moderne ld
met .rodata
dans un segment distinct de .text
, il peut donc être non exécutable. Auparavant, il suffisait d'utiliser const char code[]
pour mettre du code exécutable dans une page de données en lecture seule. Au moins pour le shellcode qui ne veut pas se modifier.