Off by one overflow

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Información básica

Tener acceso solo a un 1B overflow permite a un atacante modificar el campo size del siguiente chunk. Esto permite manipular qué chunks son realmente freed, potencialmente generando un chunk que contiene otro chunk legítimo. La explotación es similar a double free o overlapping chunks.

Hay 2 tipos de vulnerabilidades off by one:

  • Arbitrary byte: Este tipo permite sobrescribir ese byte con cualquier valor
  • Null byte (off-by-null): Este tipo permite sobrescribir ese byte solo con 0x00
  • Un ejemplo común de esta vulnerabilidad puede verse en el siguiente código donde el comportamiento de strlen y strcpy es inconsistente, lo que permite establecer un byte 0x00 al inicio del siguiente chunk.
  • Esto puede ser explotado con House of Einherjar.
  • Si se usa Tcache, esto puede aprovecharse para una situación de double free.
Off-by-null ```c // From https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/off_by_one/ int main(void) { char buffer[40]=""; void *chunk1; chunk1 = malloc(24); puts("Get Input"); gets(buffer); if(strlen(buffer)==24) { strcpy(chunk1,buffer); } return 0; } ```

Entre otras comprobaciones, ahora cada vez que un chunk está libre se compara el tamaño previo con el tamaño configurado en los metadatos del chunk, lo que hace que este ataque sea bastante complejo desde la versión 2.28.

Code example:

Goal

  • Hacer que un chunk quede contenido dentro de otro para que escribir sobre ese segundo chunk permita sobrescribir el chunk contenido

Requirements

  • Off by one overflow para modificar la información de tamaño en los metadatos

General off-by-one attack

  • Allocate three chunks A, B and C (say sizes 0x20), and another one to prevent consolidation with the top-chunk.
  • Free C (inserted into 0x20 Tcache free-list).
  • Use chunk A to overflow on B. Abuse off-by-one to modify the size field of B from 0x21 to 0x41.
  • Now we have B containing the free chunk C
  • Free B and allocate a 0x40 chunk (it will be placed here again)
  • We can modify the fd pointer from C, which is still free (Tcache poisoning)

Off-by-null attack

  • 3 chunks of memory (a, b, c) are reserved one after the other. Then the middle one is freed. The first one contains an off by one overflow vulnerability and the attacker abuses it with a 0x00 (if the previous byte was 0x10 it would make he middle chunk indicate that it’s 0x10 smaller than it really is).
  • Then, 2 more smaller chunks are allocated in the middle freed chunk (b), however, as b + b->size never updates the c chunk because the pointed address is smaller than it should.
  • Then, b1 and c gets freed. As c - c->prev_size still points to b (b1 now), both are consolidated in one chunk. However, b2 is still inside in between b1 and c.
  • Finally, a new malloc is performed reclaiming this memory area which is actually going to contain b2, allowing the owner of the new malloc to control the content of b2.

This image explains perfectly the attack:

https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks

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 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:

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

Objetivo real reciente: glibc __vsyslog_internal off-by-one (CVE-2023-6779)

  • En enero de 2024 Qualys detalló CVE-2023-6779, un off-by-one dentro de __vsyslog_internal() que se dispara cuando las cadenas de formato de syslog()/vsyslog() exceden INT_MAX, de modo que el terminador \0 corrompe el byte de menor peso del campo size del chunk siguiente en sistemas glibc 2.37–2.39 (Qualys advisory).
  • Su exploit pipeline para Fedora 38:
  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.
  • Para reproducir la escritura corruptora en un harness, forkea con un argv[0] gigantesco, llama a openlog(NULL, LOG_PID, LOG_USER) y luego a syslog(LOG_INFO, "%s", payload) donde payload = b"A" * 0x7fffffff; los heap bins de pwndbg muestran inmediatamente la sobrescritura de un solo byte.
  • Ubuntu registra el bug como CVE-2023-6779, documentando la misma truncación de INT que convierte esto en una primitiva off-by-one fiable.

Otros ejemplos & referencias

  • https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks
  • Bon-nie-appetit. HTB Cyber Apocalypse CTF 2022
  • Off-by-one because of strlen considering the next chunk’s size field.
  • Tcache is being used, so a general off-by-one attacks works to get an arbitrary write primitive with Tcache poisoning.
  • Asis CTF 2016 b00ks
  • Es posible abusar de un off-by-one para leak una dirección del heap porque el byte 0x00 del final de una cadena es sobrescrito por el campo siguiente.
  • 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.
  • La dirección de libc se leakea porque si el heap se extiende usando mmap, la memoria asignada por mmap tiene un offset fijo respecto a libc.
  • Finally the arbitrary write is abused to write into the address of __free_hook with a one gadget.
  • 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.
  • En el writeup se crean 5 chunks iniciales:
  • chunk1 (0x200)
  • chunk2 (0x50)
  • chunk5 (0x68)
  • chunk3 (0x1f8)
  • chunk4 (0xf0)
  • chunk defense (0x400) to avoid consolidating with top chunk
  • Luego se liberan los chunks 1, 5 y 3, así que:

[ 0x200 Chunk 1 (free) ] [ 0x50 Chunk 2 ] [ 0x68 Chunk 5 (free) ] [ 0x1f8 Chunk 3 (free) ] [ 0xf0 Chunk 4 ] [ 0x400 Chunk defense ]

- Luego, abusando de chunk3 (0x1f8), el null off-by-one se explota escribiendo el prev_size a `0x4e0`.
- Fíjate cómo los tamaños de los chunks inicialmente asignados 1, 2, 5 y 3 más los headers de 4 de esos chunks suman `0x4e0`: `hex(0x1f8 + 0x10 + 0x68 + 0x10 + 0x50 + 0x10 + 0x200) = 0x4e0`
- Then, chunk 4 is freed, generating a chunk that consumes all the chunks till the beginning:
- ```python
[ 0x4e0 Chunk 1-2-5-3 (free) ] [ 0xf0 Chunk 4 (corrupted) ] [ 0x400 Chunk defense ]

[ 0x200 Chunk 1 (free) ] [ 0x50 Chunk 2 ] [ 0x68 Chunk 5 (free) ] [ 0x1f8 Chunk 3 (free) ] [ 0xf0 Chunk 4 ] [ 0x400 Chunk defense ]

- Entonces, se asignan `0x200` bytes llenando el chunk original 1
- Y se asignan otros 0x200 bytes y chunk2 es destruido y por lo tanto no hay ningún maldito leak y esto no funciona? Quizás esto no debería hacerse
- Luego, asigna otro chunk con 0x58 "a"s (sobrescribiendo chunk2 y alcanzando chunk5) y modifica el `fd` del fast bin chunk de chunk5 apuntándolo a `__malloc_hook`
- Luego, se asigna un chunk de 0x68 de modo que el fake fast bin chunk en `__malloc_hook` sea el siguiente fast bin chunk
- Finalmente, se asigna un nuevo fast bin chunk de 0x68 y `__malloc_hook` se sobrescribe con la dirección de un `one_gadget`

## Referencias

- [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)

> [!TIP]
> Aprende y practica Hacking en AWS:<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)<img src="../../../../../images/arte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">\
> Aprende y practica Hacking en GCP: <img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)<img src="../../../../../images/grte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
> Aprende y practica Hacking en Azure: <img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">[**HackTricks Training Azure Red Team Expert (AzRTE)**](https://training.hacktricks.xyz/courses/azrte)<img src="../../../../../images/azrte.png" alt="" style="width:auto;height:24px;vertical-align:middle;">
>
> <details>
>
> <summary>Apoya a HackTricks</summary>
>
> - Revisa los [**planes de suscripción**](https://github.com/sponsors/carlospolop)!
> - **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **síguenos en** **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Comparte trucos de hacking enviando PRs a los** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositorios de github.
>
> </details>