Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions src/binary-exploitation/libc-heap/off-by-one-overflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,40 @@ This image explains perfectly the attack:

<figure><img src="../../images/image (1247).png" alt=""><figcaption><p><a href="https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks">https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks</a></p></figcaption></figure>

### Modern glibc hardening & bypass notes (>=2.32)

- Safe-Linking now protects every singly linked bin pointer by storing `fd = ptr ^ (chunk_addr >> 12)`, so an off-by-one that only flips the low byte of `size` usually also needs a heap leak to recompute the XOR mask before Tcache poisoning works.
- A practical leakless trick is to "double-protect" a pointer: encode a pointer you already control with `PROTECT_PTR`, then reuse the same gadget to encode your forged pointer so the alignment check passes without revealing new addresses.
- Workflow for safe-linking + single-byte corruptions:
1. Grow the victim chunk until it fully covers a freed chunk you already control (overlapping-chunk setup).
2. Leak any heap pointer (stdout, UAF, partially controlled struct) and derive the key `heap_base >> 12`.
3. Re-encode free-list pointers before writing them—stage the encoded value inside user data and memcpy it later if you only own single-byte writes.
4. Combine with [Tcache bin attacks](tcache-bin-attack.md) to redirect allocations into `__free_hook` or `tcache_perthread_struct` entries once the forged pointer is properly encoded.

A minimal helper to rehearse the encode/decode step while debugging modern exploits:

```python
def protect(ptr, chunk_addr):
return ptr ^ (chunk_addr >> 12)

def reveal(encoded, chunk_addr):
return encoded ^ (chunk_addr >> 12)

chunk = 0x55555555c2c0
encoded_fd = protect(0xdeadbeefcaf0, chunk)
print(hex(reveal(encoded_fd, chunk))) # 0xdeadbeefcaf0
```

### Recent real-world target: glibc __vsyslog_internal off-by-one (CVE-2023-6779)

- In January 2024 Qualys detailed CVE-2023-6779, an off-by-one inside `__vsyslog_internal()` that triggers when `syslog()/vsyslog()` format strings exceed `INT_MAX`, so the terminating `\0` corrupts the next chunk’s least-significant `size` byte on glibc 2.37–2.39 systems ([Qualys advisory](https://www.qualys.com/2024/01/30/cve-2023-6246/syslog.txt)).
- Their Fedora 38 exploit pipeline:
1. Craft an overlong `openlog()` ident so `vasprintf` returns a heap buffer next to attacker-controlled data.
2. Call `syslog()` to smash the neighbor chunk’s `size | prev_inuse` byte, free it, and force consolidation that overlaps attacker data.
3. Use the overlapped view to corrupt `tcache_perthread_struct` metadata and aim the next allocation at `__free_hook`, overwriting it with `system`/a one_gadget for root.
- To reproduce the corrupting write in a harness, fork with a gigantic `argv[0]`, call `openlog(NULL, LOG_PID, LOG_USER)` and then `syslog(LOG_INFO, "%s", payload)` where `payload = b"A" * 0x7fffffff`; `pwndbg`’s `heap bins` immediately shows the single-byte overwrite.
- Ubuntu tracks the bug as [CVE-2023-6779](https://ubuntu.com/security/CVE-2023-6779), documenting the same INT truncation that makes this a reliable off-by-one primitive.

## Other Examples & References

- [**https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks**](https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks)
Expand All @@ -83,7 +117,7 @@ This image explains perfectly the attack:
- It's possible to abuse an off by one to leak an address from the heap because the byte 0x00 of the end of a string being overwritten by the next field.
- Arbitrary write is obtained by abusing the off by one write to make the pointer point to another place were a fake struct with fake pointers will be built. Then, it's possible to follow the pointer of this struct to obtain arbitrary write.
- The libc address is leaked because if the heap is extended using mmap, the memory allocated by mmap has a fixed offset from libc.
- Finally the arbitrary write is abused to write into the address of \_\_free_hook with a one gadget.
- Finally the arbitrary write is abused to write into the address of `__free_hook` with a one gadget.
- [**plaidctf 2015 plaiddb**](https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/off_by_one/#instance-2-plaidctf-2015-plaiddb)
- There is a NULL off by one vulnerability in the `getline` function that reads user input lines. This function is used to read the "key" of the content and not the content.
- In the writeup 5 initial chunks are created:
Expand Down Expand Up @@ -112,7 +146,10 @@ This image explains perfectly the attack:
- Then, a chunk of 0x68 is allocated so the fake fast bin chunk in `__malloc_hook` is the following fast bin chunk
- Finally, a new fast bin chunk of 0x68 is allocated and `__malloc_hook` is overwritten with a `one_gadget` address

{{#include ../../banners/hacktricks-training.md}}

## References

- [Qualys Security Advisory – CVE-2023-6246/6779/6780](https://www.qualys.com/2024/01/30/cve-2023-6246/syslog.txt)
- [Ubuntu Security – CVE-2023-6779](https://ubuntu.com/security/CVE-2023-6779)
- [Breaking Safe-Linking in Modern Glibc – Google CTF 2022 "saas" analysis](https://blog.csdn.net/2402_86373248/article/details/148717274)

{{#include ../../banners/hacktricks-training.md}}