bypassing-binary-exploitation-mitigations

star 618

Methodology for identifying and defeating common binary hardening mitigations during authorized exploitation — ASLR, PIE, stack canaries, NX/DEP, and RELRO — by leaking addresses, brute-forcing entropy, abusing forked-process behavior, and selecting the right code-reuse primitive for the protections in place.

xalgord By xalgord schedule Updated 6/6/2026

name: bypassing-binary-exploitation-mitigations description: Methodology for identifying and defeating common binary hardening mitigations during authorized exploitation — ASLR, PIE, stack canaries, NX/DEP, and RELRO — by leaking addresses, brute-forcing entropy, abusing forked-process behavior, and selecting the right code-reuse primitive for the protections in place. domain: cybersecurity subdomain: binary-exploitation tags:

  • binary-exploitation
  • mitigation-bypass
  • exploit-development version: '1.0' author: xalgorix license: Apache-2.0

Bypassing Binary Exploitation Mitigations

When to Use

  • At the start of any authorized binary exploitation engagement, to profile which protections (ASLR, PIE, canary, NX/DEP, RELRO) are active and plan the exploit path accordingly.
  • When a working memory-corruption primitive is blocked by a mitigation: a canary aborts the process, NX prevents shellcode, PIE/ASLR randomizes gadget addresses, or Full RELRO makes the GOT read-only.
  • When deciding what to leak (libc pointer, PIE base, canary) and how (format string, ret2plt, arbitrary read, brute force in a forked server).

Critical: Concepts/Steps Most Often Missed

  • Each mitigation is bypassed independently — enumerate them all first. checksec output drives everything. A binary can be PIE + canary + Full RELRO + NX, and you must defeat each separately (leak base, leak/forge canary, avoid GOT writes, use ROP).
  • Canary low byte is a NUL. On x64 the 8-byte canary's least significant byte is 0x00; on x86 the 4-byte canary's LSB is 0x00. String functions stop on it, so a printed canary leak shows only 7 (or 3) random bytes — account for the NUL when re-inserting it.
  • Forked servers share the canary and PIE slide. fork() without execve() means children inherit the parent's canary, so you can brute-force the canary (and PIE/stack addresses) one byte at a time using a crash-vs-success oracle. execve() after fork() defeats this.
  • Full RELRO does not protect everything. The main binary's GOT is read-only, but loaded shared libraries (libc) are only Partial RELRO. Modern targets removed __malloc_hook/__free_hook (glibc 2.34+), so pivot to other writable pointers, libc GOT, vtables, or SROP/ROP-into-libc.
  • PIE base ends in 000. A leaked code pointer's low 12 bits are the page offset; the base is leak & ~0xfff. If your computed base does not end in 000, your leak or offset is wrong.
  • vsyscall/vDSO and fixed kernel maps escape ASLR. 0xffffffffff600000 vsyscall has fixed addresses; on some arm64 Android kernels the linear map base is fixed, defeating KASLR with no leak.

How to CONFIRM

Run checksec --file ./vuln (and readelf -l/-d) and record each protection. Confirm a leak is valid before building on it: a leaked PIE/libc base must be page-aligned (& 0xfff == 0); a leaked canary's LSB must be 0x00. Confirm a canary bypass by sending the leaked canary back in the overflow and observing the process return normally (no *** stack smashing detected ***). Confirm NX status with readelf -W -l ./vuln | grep GNU_STACK (an E flag = exec stack).

Workflow

Step 1: Enumerate Protections

pwn checksec --file ./vuln
# RELRO, Stack Canary, NX, PIE — note each one

readelf -W -l ./vuln | grep GNU_STACK     # NX: RW (no E) = non-exec stack
readelf -d ./vuln | grep BIND_NOW          # present => Full RELRO
cat /proc/sys/kernel/randomize_va_space    # 0=off, 1=conservative, 2=full ASLR

Step 2: Defeat ASLR / PIE (Leak an Address)

from pwn import *
elf = context.binary = ELF('./vuln')

# Option A: format-string arbitrary read of a GOT entry to leak libc
payload  = p64(elf.got['puts']) + b'%7$s'      # adjust offset
# Option B: ret2plt — call puts(puts@got) to leak puts's runtime address
rop = ROP(elf)
rop.puts(elf.got['puts']); rop.call(elf.symbols['main'])  # leak then re-loop

# After leak:
leak = u64(io.recvline().strip().ljust(8, b'\x00'))
libc = elf.libc; libc.address = leak - libc.symbols['puts']
assert libc.address & 0xfff == 0, "bad leak / wrong offset"
# For PIE: elf.address = code_leak - known_offset

For 32-bit local targets with low entropy, brute force is viable: loop libc base across 0xb7000000..0xb8000000 step 0x1000, or use a NOP sled in env vars. Remote: brute-force usleep(10) address by watching for a 10s delay.

Step 3: Defeat the Stack Canary

# Forked-server byte-by-byte brute force (children share the canary)
canary = b'\x00'                                  # known NUL LSB
for pos in range(7):                              # remaining 7 bytes on x64
    for guess in range(256):
        io = remote(HOST, PORT)
        io.send(b'A'*buf_len + canary + bytes([guess]))
        if b'smashing' not in io.recvall(timeout=1):  # survived => correct
            canary += bytes([guess]); io.close(); break
        io.close()
# Or: leak the canary via format string / arbitrary read, then replay it.
payload = b'A'*offset + canary + b'B'*8 + rop_chain

Alternative bypasses: overwrite stack-stored pointers before the canary (pointer redirecting); in threaded apps overwrite the master canary in TLS; with Partial RELRO + arbitrary write, neuter __stack_chk_fail's GOT entry.

Step 4: Defeat NX and RELRO (Pick the Code-Reuse Primitive)

# NX on: no shellcode -> code reuse. Make a page RWX with mprotect then jump in.
rop = ROP(elf)
rop.mprotect(elf.bss() & ~0xfff, 0x1000, 7)        # PROT_RWX
payload = flat({offset: rop.chain(), offset+len(rop.chain()): asm(shellcraft.sh())})
  • Partial RELRO: overwrite a .got.plt entry or use ret2dlresolve; overwrite .fini_array/atexit pointers.
  • Full RELRO: GOT is read-only — target libc's (Partial-RELRO) GOT, C++ vtables, or do pure ROP/SROP into libc.
  • SROP (scarce gadgets): forge a sigframe, call sys_rt_sigreturn to load all registers, e.g. mprotect then run stack shellcode.
  • CET/IBT hardened: switch from ROP to JOP/COP (gadgets ending in jmp/call [reg]).

Key Concepts

Concept Description
ASLR Randomizes stack/heap/libs/mmap; randomize_va_space 0/1/2. 32-bit has low entropy (brute-forceable).
PIE Randomizes the binary's own base; bypass by leaking one code address (base = leak & ~0xfff).
Stack canary Random value before saved RIP/EBP; LSB is NUL. Bypass by leak, replay, or forked brute force.
NX / DEP Marks stack/heap non-executable; bypass with ROP/ret2libc/ret2syscall or mprotect-to-RWX.
Partial RELRO .got.plt writable -> GOT overwrite / ret2dlresolve still possible.
Full RELRO Entire GOT read-only (BIND_NOW); pivot to libc GOT, vtables, hooks, or ROP-into-libc.
vsyscall/vDSO Fixed (non-ASLR) addresses usable as a stable ret/syscall source.
Master canary forging In threaded apps, overwrite the TLS-stored master canary so the equal-vs-equal check passes.

Tools & Systems

Tool Purpose
checksec Enumerate RELRO/canary/NX/PIE in one command.
readelf / objdump Confirm GNU_STACK perms, BIND_NOW, GOT/PLT and .fini_array layout.
pwntools ELF, ROP, libc.address, fmtstr_payload, brute-force loops, shellcraft, process/remote.
gdb + pwndbg/GEF vmmap for page perms and vsyscall, verify leaks/canary, inspect TLS (fs:0x28).
ROPgadget / ropper Find gadgets for ret2libc/ret2syscall/SROP/JOP once a base is known.
one_gadget Single-shot shell gadget after a libc leak (avoids GOT writes under Full RELRO).

Common Scenarios

Scenario 1: PIE + NX + Partial RELRO

Leak a code pointer via format string, rebase the binary, then ROP using the binary's own gadgets; overwrite a .got.plt entry or ret2dlresolve to reach system.

Scenario 2: Canary + forked network service

Service forks per connection. Brute-force the 8-byte canary byte-by-byte (≤256 tries/byte) using crash vs. clean response, then overflow past the replayed canary into a ROP chain.

Scenario 3: Full RELRO + NX + ASLR

GOT is read-only and stack is non-exec. Leak libc via ret2plt puts, then ROP-into-libc system("/bin/sh") or a one_gadget — no GOT write needed.

Scenario 4: Low-entropy 32-bit + ASLR

Brute-force libc base locally, or stuff a large NOP sled into environment variables and jump to a guessed stack address repeatedly until execution lands in the sled.

Output Format

## Mitigation Assessment & Bypass Finding

**Target**: ./vuln (amd64)
**Protections**: PIE: yes | Canary: yes | NX: enabled | RELRO: Full | ASLR: 2 (full)

### Bypass Chain
1. ASLR/PIE: leaked libc via ret2plt puts@got -> base 0x7f...000 (page-aligned, confirmed)
2. Canary: leaked via format string %15$p -> 0x..00 (NUL LSB confirmed), replayed in overflow
3. NX/Full RELRO: pure ROP-into-libc system("/bin/sh") (no GOT write required)

### Proof
Process returned cleanly past canary; system("/bin/sh") yielded an interactive shell.

### Impact
All deployed mitigations bypassed -> reliable remote code execution.

### Recommendation
1. Keep Full RELRO, PIE, canaries, and NX enabled (they raise cost but are not individually sufficient).
2. Re-randomize the canary on fork (avoid fork-without-execve patterns in network daemons).
3. Adopt CET/shadow-stack (x86) or PAC/BTI (ARM) to break ROP/JOP.
4. Eliminate the root memory-corruption bug and the info-leak primitives that enable base/canary recovery.
Install via CLI
npx skills add https://github.com/xalgord/xalgorix --skill bypassing-binary-exploitation-mitigations
Repository Details
star Stars 618
call_split Forks 109
navigation Branch main
article Path SKILL.md
More from Creator