Concepts
generalPIE & ASLR (position-independent code)
Why modern binaries load at a random base address every run, how position-independent code uses RIP-relative addressing to cope, and what that means for reversing and exploitation.
ASLR (Address Space Layout Randomization) is an OS mitigation that loads the stack, heap, shared libraries — and, with PIE, the executable itself — at a randomised base address on every run. PIE (Position-Independent Executable) is the compiler/linker mode that makes the main program able to run at any base, which is what lets ASLR randomise it too.
Why code has to be position-independent
If the load address isn't known until runtime, code can't hard-code absolute addresses for its own functions and globals. PIE solves this on x86-64 with RIP-relative addressing: operands are encoded as a signed offset from the current instruction pointer, so the same machine code works at any base.
; non-PIE: absolute address baked into the instruction
mov eax, DWORD PTR [0x404060] ; global at a fixed VA
; PIE: address computed relative to RIP
mov eax, DWORD PTR [rip + 0x2f1a] ; global = (next insn) + 0x2f1a
lea rdi, [rip + 0x1c0] ; &string, position-independentOn AArch64 the same job is done by the adrp + add/ldr pair, which forms an
address relative to the program counter.
Static vs runtime addresses
This is the practical headache for reverse engineers:
- The addresses you see in a disassembler are static (file/preferred VAs).
- At runtime everything is shifted by a random load base (the "slide").
- Runtime address = static address + base. To correlate a debugger's live
addresses with your static analysis, find the base (e.g. from
/proc/<pid>/maps, the debugger's module list, orvmmap) and subtract it.
Reverse-engineering & exploitation notes
- A binary full of
[rip + …]/lea …, [rip + …]is PIE; one with bare absolute addresses ([0x4xxxxx]) is a classic non-PIE executable, which always loads at the same base — much easier to script against. - ASLR is the reason an information leak is the first step of most modern
exploits: you must leak one runtime address to defeat the randomisation before
a hard-coded
0x4011...payload would work again. - PIE is required for the main executable to be randomised; shared libraries
(
.so/PIC) have always been position-independent. Check withfile(pie executablevsexecutable) orreadelf -h(Type: DYN= PIE,EXEC= non-PIE). - The randomised global/function references in PIE go through the GOT/PLT for external symbols; understanding both together explains how a PIE binary resolves anything outside itself.
See also: GOT & PLT · Addressing modes · Memory protection (NX / W^X).