J'ai le code NASM de travail suivant:
global _start
section .text
_start:
mov eax, 0x4
mov ebx, 0x1
mov ecx, message
mov edx, 0xF
int 0x80
mov eax, 0x1
mov ebx, 0x0
int 0x80
section .data
message: db "Hello, World!", 0dh, 0ah
qui imprime "Bonjour tout le monde!\n" à l'écran. J'ai également l'encapsuleur C suivant qui contient le code d'objet NASM précédent:
char code[] =
"\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";
int main(void)
{
(*(void(*)())code)();
}
Cependant, lorsque j'exécute le code, il semble que le code assembleur ne soit pas exécuté, mais le programme se termine bien. Des idées?
Merci
Lorsque vous injectez ce shellcode, vous ne savez pas ce qui se trouve à message
:
mov ecx, message
dans le processus injecté, ça peut être n'importe quoi mais ce ne sera pas "Hello world!\r\n"
car il se trouve dans la section des données lorsque 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 commun dans le développement de shellcode, la façon de contourner ce problème 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
Maintenant, videz 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!
$
Notre wrapper C sera donc:
char code[] =
"\xe9\x1e\x00\x00\x00" // jmp 8048083 <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 8048065 <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;
}
Permet de le tester:
$ gcc test.c -o test
$ ./test
Hello wolrd!
$
ça marche.
Comme BSH mentionné, votre shellcode ne contient pas les octets de message. Passer à l'étiquette MESSAGE
et appeler la routine GOBACK
juste avant de définir l'octet msg
était une bonne décision car l'adresse de msg serait en haut de la pile comme retour adresse qui pourrait être ajoutée à ecx
, où l'adresse du 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 fin de chaîne lorsqu'il est 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 écrits directement 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 ? $
Maintenant, extrayez 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
production:
\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 protection NX qui empêche l'exécution de code dans un segment de données ou une pile. 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 codes shell 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 cette option avant d'injecter le shellcode, en particulier lorsqu'il survient à travers des dépassements de tampon.
root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space