Off by one overflow

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

Avoir seulement accès à un débordement de 1B permet à un attaquant de modifier le champ size du chunk suivant. Cela permet de manipuler quels chunks sont réellement libérés, générant potentiellement un chunk qui contient un autre chunk légitime. L’exploitation est similaire à double free ou à des overlapping chunks.

Il existe 2 types de vulnérabilités off by one :

  • Arbitrary byte : Ce type permet d’écraser cet octet avec n’importe quelle valeur
  • Null byte (off-by-null) : Ce type permet d’écraser cet octet uniquement avec 0x00
  • Un exemple courant de cette vulnérabilité peut être vu dans le code suivant où le comportement de strlen et strcpy est incohérent, ce qui permet de placer un octet 0x00 au début du chunk suivant.
  • Ceci peut être exploité avec le House of Einherjar.
  • Si vous utilisez Tcache, cela peut être exploité pour aboutir à une situation 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; } ```

Parmi d’autres vérifications, désormais chaque fois qu’un chunk est free la previous size est comparée avec la size configurée dans le chunk de metadata, rendant cette attaque assez complexe à partir de la version 2.28.

Code example:

Objectif

  • Faire en sorte qu’un chunk soit contenu à l’intérieur d’un autre chunk afin qu’un accès en écriture sur ce deuxième chunk permette d’écraser le chunk contenu

Prérequis

  • Off by one overflow pour modifier les informations de size dans les metadata

Attaque off-by-one générale

  • Allouer trois chunks A, B et C (par exemple tailles 0x20), et un autre pour empêcher la consolidation avec le top-chunk.
  • Free C (inséré dans la 0x20 Tcache free-list).
  • Utiliser le chunk A pour overflow sur B. Abuser de l’off-by-one pour modifier le champ size de B de 0x21 à 0x41.
  • Désormais B contient le chunk free C
  • Free B et allouer un chunk 0x40 (il sera placé ici à nouveau)
  • On peut modifier le pointeur fd de C, qui est toujours free (Tcache poisoning)

Attaque off-by-null

  • 3 chunks de mémoire (a, b, c) sont réservés les uns à la suite des autres. Ensuite le chunk du milieu est free. Le premier contient une vulnérabilité off by one et l’attaquant l’abuse en écrivant 0x00 (si l’octet précédent était 0x10 cela ferait que le chunk du milieu indiquerait qu’il est 0x10 plus petit qu’il ne l’est réellement).
  • Ensuite, 2 autres chunks plus petits sont alloués dans le chunk middle freed (b), cependant, comme b + b->size ne met jamais à jour le chunk c parce que l’adresse pointée est plus petite qu’elle ne devrait l’être.
  • Ensuite, b1 et c sont freed. Comme c - c->prev_size pointe toujours vers b (b1 désormais), les deux sont consolidés en un seul chunk. Cependant, b2 est toujours à l’intérieur, entre b1 et c.
  • Enfin, un nouveau malloc est effectué récupérant cette zone mémoire qui va en réalité contenir b2, permettant au propriétaire du nouveau malloc de contrôler le contenu de b2.

This image explains perfectly the attack:

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

Notes sur le durcissement moderne de glibc et les contournements (>=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.

Un helper minimal pour répéter l’étape d’encodage/décodage lors du débogage d’exploits modernes:

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

Cible récente dans le monde réel : glibc __vsyslog_internal off-by-one (CVE-2023-6779)

  • En janvier 2024, Qualys a détaillé CVE-2023-6779, un off-by-one dans __vsyslog_internal() qui se déclenche lorsque les chaînes de format de syslog()/vsyslog() dépassent INT_MAX, de sorte que le caractère de terminaison \0 corrompt l’octet de poids faible du champ size du chunk suivant sur les systèmes glibc 2.37–2.39 (Qualys advisory).
  • Leur pipeline d’exploit pour Fedora 38 :
  1. Créer un ident openlog() trop long pour que vasprintf retourne un buffer heap adjacent à des données contrôlées par l’attaquant.
  2. Appeler syslog() pour écraser l’octet size | prev_inuse du chunk voisin, le libérer, et forcer une consolidation qui chevauche les données de l’attaquant.
  3. Utiliser la vue chevauchée pour corrompre les métadonnées tcache_perthread_struct et orienter l’allocation suivante vers __free_hook, en l’écrasant avec system/un one_gadget pour root.
  • Pour reproduire l’écriture corruptrice dans un harness, forkez avec un argv[0] gigantesque, appelez openlog(NULL, LOG_PID, LOG_USER) puis syslog(LOG_INFO, "%s", payload)payload = b"A" * 0x7fffffff ; le heap bins de pwndbg montre immédiatement l’écrasement d’un seul octet.
  • Ubuntu suit le bug sous CVE-2023-6779, documentant la même troncation INT qui en fait une primitive off-by-one fiable.

Autres exemples et références

  • https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks
  • Bon-nie-appetit. HTB Cyber Apocalypse CTF 2022
  • Off-by-one causé par strlen qui prend en compte le champ size du chunk suivant.
  • Tcache est utilisé, donc une attaque off-by-one générale permet d’obtenir une primitive d’écriture arbitraire via Tcache poisoning.
  • Asis CTF 2016 b00ks
  • Il est possible d’abuser d’un off-by-one pour leak une adresse depuis le heap parce que l’octet 0x00 de la fin d’une chaîne est écrasé par le champ suivant.
  • Une écriture arbitraire est obtenue en abusant de l’écriture off-by-one pour faire pointer un pointeur vers un autre endroit où une fausse struct avec de faux pointeurs sera construite. Ensuite, il est possible de suivre le pointeur de cette struct pour obtenir une écriture arbitraire.
  • L’adresse libc est leakée parce que si le heap est étendu via mmap, la mémoire allouée par mmap a un offset fixe par rapport à libc.
  • Enfin, l’écriture arbitraire est utilisée pour écrire à l’adresse de __free_hook avec un one_gadget.
  • plaidctf 2015 plaiddb
  • Il existe une vulnérabilité NULL off-by-one dans la fonction getline qui lit les lignes de l’entrée utilisateur. Cette fonction est utilisée pour lire la “key” du contenu et non le contenu.
  • Dans le writeup, 5 chunks initiaux sont créés :
  • chunk1 (0x200)
  • chunk2 (0x50)
  • chunk5 (0x68)
  • chunk3 (0x1f8)
  • chunk4 (0xf0)
  • chunk defense (0x400) pour éviter la consolidation avec le top chunk
  • Ensuite, les chunks 1, 5 et 3 sont libérés, donc :

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

- Puis, abusant du chunk3 (0x1f8), le NULL off-by-one est exploité pour écrire le prev_size à `0x4e0`.
- Notez comment les tailles des chunks initialement alloués 1, 2, 5 et 3 plus les en-têtes de 4 de ces chunks égalent `0x4e0` : `hex(0x1f8 + 0x10 + 0x68 + 0x10 + 0x50 + 0x10 + 0x200) = 0x4e0`
- Puis, chunk 4 est libéré, générant un chunk qui englobe tous les chunks jusqu'au début :
- ```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 ]

- Ensuite, 0x200 octets sont alloués remplissant le chunk original 1
- Et encore 0x200 octets sont alloués et chunk2 est détruit et donc il n'y a pas de fucking leak et ça ne fonctionne pas ? Peut-être que cela ne devrait pas être fait
- Ensuite, il alloue un autre chunk avec 0x58 "a"s (écrasant chunk2 et atteignant chunk5) et modifie le `fd` du fast bin chunk de chunk5 en le pointant vers `__malloc_hook`
- Puis, un chunk de 0x68 est alloué de sorte que le faux fast bin chunk dans `__malloc_hook` soit le fast bin chunk suivant
- Enfin, un nouveau fast bin chunk de 0x68 est alloué et `__malloc_hook` est écrasé avec une adresse `one_gadget`

## Références

- [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]
> Apprenez et pratiquez le hacking 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;">\
> Apprenez et pratiquez le hacking 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;">
> Apprenez et pratiquez le hacking 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>Soutenir HackTricks</summary>
>
> - Vérifiez les [**plans d'abonnement**](https://github.com/sponsors/carlospolop) !
> - **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez-nous sur** **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Partagez des astuces de hacking en soumettant des PR au** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) dépôts github.
>
> </details>