Hacktricks-skills off-by-one-heap-exploit
How to exploit off-by-one heap overflow vulnerabilities in glibc. Use this skill whenever the user mentions heap exploitation, off-by-one vulnerabilities, glibc heap attacks, tcache poisoning, chunk size corruption, or any scenario where a single-byte write can corrupt heap metadata. Also trigger for CVE-2023-6779, syslog exploits, safe-linking bypasses, or when analyzing heap-based vulnerabilities in CTF challenges or real-world targets.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/libc-heap/off-by-one-overflow/SKILL.MDOff-by-One Heap Overflow Exploitation
This skill guides you through exploiting off-by-one (OBO) heap overflow vulnerabilities in glibc-based systems. An OBO vulnerability allows writing exactly one byte past a buffer's boundary, which can corrupt the
size field of the next heap chunk.
Understanding the Vulnerability
What is an Off-by-One Overflow?
An off-by-one overflow occurs when a program writes one byte beyond the intended buffer boundary. This single byte can corrupt the
size field of the adjacent heap chunk, enabling:
- Chunk size manipulation: Modify how much memory a chunk claims to occupy
- Overlapping chunks: Make one chunk contain another, enabling arbitrary writes
- Tcache poisoning: Redirect allocations to attacker-controlled addresses
- Double-free scenarios: Create conditions for memory reuse attacks
Two Types of OBO Vulnerabilities
- Arbitrary byte OBO: Can overwrite the boundary byte with any value (0x00-0xFF)
- Null byte OBO (off-by-null): Can only write 0x00 to the boundary
- Common in
/strlen
mismatchesstrcpy - Can be exploited with House of Einherjar technique
- With tcache, can create double-free conditions
- Common in
General Attack Methodology
Step 1: Setup the Heap Layout
Allocate chunks in a specific arrangement to enable the attack:
[ Chunk A (0x20) ] [ Chunk B (0x20) ] [ Chunk C (0x20) ] [ Chunk D (0x20) ]
- Allocate three chunks (A, B, C) of the same size (e.g., 0x20)
- Allocate a fourth chunk (D) to prevent consolidation with the top chunk
- This creates a controlled heap layout for exploitation
Step 2: Create the Overflow Condition
- Free chunk C: This inserts it into the tcache free-list (for 0x20 chunks)
- Overflow from A into B: Use the OBO vulnerability to write one byte past A's boundary
- Corrupt B's size field: Change B's size from 0x21 to 0x41 (or similar)
Step 3: Create Overlapping Chunks
After corrupting B's size:
[ Chunk A ] [ Chunk B (now claims 0x40, contains C) ] [ Chunk D ]
- Chunk B now "contains" the freed chunk C
- C is still in the tcache free-list
- This creates the overlapping condition needed for exploitation
Step 4: Trigger the Overlap
- Free chunk B: This consolidates B with its "contained" chunk C
- Allocate a 0x40 chunk: This allocation will reuse the B+C space
- Modify C's fd pointer: Since C is still in tcache, you can poison it
Step 5: Tcache Poisoning
With C's fd pointer corrupted:
- Free the overlapping chunk: This adds it to tcache
- Poison C's fd: Point it to an address you control (e.g.,
)__free_hook - Trigger allocation: Next malloc will place your fake chunk at the poisoned address
- Achieve arbitrary write: Write shellcode or one_gadget to the target
Modern glibc Hardening (>= 2.32)
Safe-Linking Protection
Modern glibc uses safe-linking to protect tcache pointers:
fd = ptr ^ (chunk_addr >> 12)
This XOR encoding means:
- You need a heap leak to compute the XOR mask
- Single-byte size corruption alone is insufficient
- You must re-encode pointers before writing them
Bypass Strategy
- Grow victim chunk: Make it fully cover a freed chunk you control
- Leak a heap pointer: Use stdout, UAF, or partially controlled struct
- Derive the key:
heap_base >> 12 - Re-encode pointers: Use the protect/reveal helper (see scripts)
- Stage encoded values: Place them in user data, memcpy later
- Combine with tcache attacks: Redirect to
or tcache entries__free_hook
Double-Protect Technique
For leakless exploitation:
- Encode a controlled pointer: Use
on a pointer you already ownPROTECT_PTR - Reuse the gadget: Encode your forged pointer with the same XOR key
- Alignment check passes: No new address revelation needed
Off-by-Null Specific Attack
When you can only write 0x00:
- Allocate three chunks: a, b, c in sequence
- Free the middle chunk (b)
- Overflow from a with 0x00: This reduces b's apparent size
- Allocate two smaller chunks in b: b1 and b2
- Free b1 and c: They consolidate, but b2 remains inside
- Allocate new chunk: It contains b2, giving you control over its content
Practical Exploitation Workflow
1. Identify the Vulnerability
Look for:
- Buffer size calculations that are off by one
/strlen
mismatchesstrcpy- Format string vulnerabilities with INT_MAX truncation
- Off-by-one in array bounds checking
2. Map the Heap Layout
Use
pwndbg or gef:
heap bins heap chunks vmmap
Identify:
- Chunk sizes and positions
- Which chunks are in tcache
- Adjacent chunk relationships
3. Craft the Exploit
Use the helper scripts:
for safe-linking encodingscripts/protect_reveal.py
for exploit scaffoldingscripts/exploit_template.py
4. Test and Iterate
- Start with a simple heap leak
- Progress to size corruption
- Add tcache poisoning
- Finalize with arbitrary write
Real-World Example: CVE-2023-6779
Vulnerability Details
- Location:
in glibc 2.37-2.39__vsyslog_internal() - Trigger:
format strings exceedingsyslog()INT_MAX - Effect: Terminating
corrupts next chunk's size byte\0 - Impact: Root via
overwrite__free_hook
Exploitation Pipeline
- Craft overlong
ident: Makesopenlog()
return adjacent heap buffervasprintf - Call
: Smashes neighbor chunk'ssyslog()
bytesize | prev_inuse - Free and consolidate: Creates overlapping chunk with attacker data
- Corrupt tcache metadata: Aim next allocation at
__free_hook - Overwrite with system/one_gadget: Achieve root
Reproduction Harness
# Fork with gigantic argv[0] # Call openlog(NULL, LOG_PID, LOG_USER) # Call syslog(LOG_INFO, "%s", payload) # where payload = b"A" * 0x7fffffff # Check with: pwndbg heap bins
CTF Writeup Patterns
Bon-nie-appetit (HTB Cyber Apocalypse 2022)
- Vulnerability:
considers next chunk's size fieldstrlen - Technique: General OBO with tcache poisoning
- Result: Arbitrary write primitive
Asis CTF 2016 b00ks
- Vulnerability: OBO leaks heap address via 0x00 byte
- Technique: Fake struct with fake pointers
- Result: Arbitrary write to
with one_gadget__free_hook
PlaidCTF 2015 plaiddb
- Vulnerability: NULL OBO in
functiongetline - Technique: Multi-chunk consolidation with fd poisoning
- Result:
overwrite with one_gadget__malloc_hook
Common Pitfalls
- Tcache interference: Small chunks (<= 0x40) go to tcache, not fastbins
- Safe-linking: Modern glibc requires XOR encoding
- Alignment: Chunk addresses must be properly aligned
- Consolidation: Adjacent free chunks may merge unexpectedly
- Top chunk: Don't let your chunks consolidate with the top chunk
Debugging Tips
With pwndbg
# View heap layout heap chunks heap bins # View specific chunk heap chunk <address> # Watch for changes watch *(size_t*)<chunk_address>
With GDB + gef
heap-analysis heap-context
When to Use This Skill
Use this skill when:
- You encounter an off-by-one vulnerability in a CTF or real target
- You need to corrupt heap metadata with a single-byte write
- You're working with glibc heap exploitation
- You need to bypass safe-linking in modern glibc
- You're analyzing CVE-2023-6779 or similar syslog vulnerabilities
- You need tcache poisoning techniques
- You're dealing with chunk size corruption attacks
Helper Scripts
Use the bundled scripts for:
: Safe-linking encode/decodescripts/protect_reveal.py
: Exploit scaffoldingscripts/exploit_template.py
Run them with:
python scripts/protect_reveal.py --help python scripts/exploit_template.py --help