Off by one overflow

Tip

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Ucz się i ćwicz Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Wsparcie dla HackTricks

Podstawowe informacje

Posiadając jedynie 1B overflow, atakujący może zmodyfikować pole size następnego chunka. Pozwala to manipulować tym, które chunk są faktycznie zwalniane, potencjalnie generując chunk, który zawiera inny prawidłowy chunk. Eksploatacja jest podobna do double free lub overlapping chunks.

Istnieją 2 rodzaje podatności off by one:

  • Arbitrary byte: Ten rodzaj pozwala nadpisać ten bajt dowolną wartością
  • Null byte (off-by-null): Ten rodzaj pozwala nadpisać ten bajt jedynie wartością 0x00
  • Typowy przykład tej podatności można zobaczyć w poniższym kodzie, gdzie zachowanie strlen i strcpy jest niespójne, co pozwala ustawić bajt 0x00 na początku następnego chunka.
  • This can be expoited with the House of Einherjar.
  • Jeśli używany jest Tcache, można to wykorzystać do sytuacji 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; } ```

Między innymi, teraz za każdym razem gdy chunk jest zwolniony, poprzedni rozmiar jest porównywany z rozmiarem zapisanym w metadanych chunka, co sprawia, że ten atak jest dość skomplikowany od wersji 2.28.

Przykład kodu:

Cel

  • Spraw, aby jeden chunk był zawarty w innym chunku, tak aby zapis do tego drugiego chunka pozwalał nadpisać zawarty chunk

Wymagania

  • Off by one overflow umożliwiający modyfikację informacji o rozmiarze w metadanych

Ogólny off-by-one attack

  • Zarezerwuj trzy chunki A, B i C (np. rozmiary 0x20), oraz jeszcze jeden, aby zapobiec konsolidacji z top-chunk.
  • Zwolnij C (wstawiony do 0x20 Tcache free-list).
  • Użyj chunka A, aby przepełnić B. Wykorzystaj off-by-one, aby zmodyfikować pole size chunka B z 0x21 na 0x41.
  • Teraz mamy B zawierający wolny chunk C
  • Zwolnij B i zaalokuj chunk 0x40 (zostanie umieszczony tutaj ponownie)
  • Możemy zmodyfikować wskaźnik fd z C, który jest wciąż wolny (Tcache poisoning)

Off-by-null attack

  • Trzy chunki pamięci (a, b, c) są zarezerwowane kolejno jeden po drugim. Następnie środkowy jest zwolniony. Pierwszy zawiera podatność off by one overflow, a atakujący wykorzystuje ją przez zapis 0x00 (jeśli poprzedni bajt miał wartość 0x10, spowodowałoby to, że środkowy chunk będzie wskazywał, że jest o 0x10 mniejszy niż w rzeczywistości).
  • Następnie w zwolnionym środkowym chunku (b) alokowane są jeszcze 2 mniejsze chunki, jednakże b + b->size nigdy nie aktualizuje chunka c, ponieważ wskazywany adres jest mniejszy niż powinien być.
  • Następnie b1 i c są zwalniane. Ponieważ c - c->prev_size wciąż wskazuje na b (teraz b1), oba są skonsolidowane w jeden chunk. Jednak b2 wciąż pozostaje pomiędzy b1 a c.
  • Na końcu wykonywany jest nowy malloc, odzyskujący ten obszar pamięci, który w rzeczywistości zawiera b2, co pozwala właścicielowi nowego malloca kontrolować zawartość b2.

This image explains perfectly the attack:

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

Wzmocnienia glibc i uwagi dotyczące obejść (>=2.32)

  • Safe-Linking teraz chroni każdy singly linked bin pointer przez przechowywanie fd = ptr ^ (chunk_addr >> 12), więc off-by-one, który zmienia tylko najmłodszy bajt size, zazwyczaj potrzebuje również heap leak, aby ponownie obliczyć maskę XOR zanim Tcache poisoning zadziała.
  • 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.

Minimalny helper do przećwiczenia kroku kodowania/odkodowywania podczas debugowania nowoczesnych exploitów:

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

Niedawny cel w rzeczywistym świecie: glibc __vsyslog_internal off-by-one (CVE-2023-6779)

  • W styczniu 2024 Qualys opisał CVE-2023-6779, off-by-one wewnątrz __vsyslog_internal(), który wyzwala się, gdy format stringi syslog()/vsyslog() przekraczają INT_MAX, więc znak kończący \0 korumpuje najmniej znaczący bajt pola size następnego chunku na systemach z glibc 2.37–2.39 (Qualys advisory).
  • Ich exploit pipeline na Fedora 38:
  1. Sporządzić zbyt długi ident dla openlog() tak, aby vasprintf zwrócił heap buffer obok danych kontrolowanych przez atakującego.
  2. Wywołać syslog(), aby nadpisać sąsiedni bajt size | prev_inuse, zwolnić go i wymusić konsolidację, która nachodzi na dane atakującego.
  3. Użyć nachodzącego widoku do skorumpowania metadanych tcache_perthread_struct i skierować następne przydzielenie pod __free_hook, nadpisując je system/a one_gadget dla roota.
  • Aby odtworzyć zapis powodujący korupcję w harnessie, fork z gigantycznym argv[0], wywołać openlog(NULL, LOG_PID, LOG_USER) a potem syslog(LOG_INFO, "%s", payload) gdzie payload = b"A" * 0x7fffffff; pwndbg’s heap bins natychmiast pokaże jednobajtowe nadpisanie.
  • Ubuntu śledzi ten błąd jako CVE-2023-6779, dokumentując to samo obcięcie do INT, które czyni to wiarygodnym off-by-one prymitywem.

Other Examples & References

  • https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks
  • Bon-nie-appetit. HTB Cyber Apocalypse CTF 2022
  • Off-by-one spowodowany przez strlen uwzględniający pole size następnego chunku.
  • Tcache jest używany, więc ogólne ataki off-by-one działają, żeby uzyskać arbitralny write prymityw przez Tcache poisoning.
  • Asis CTF 2016 b00ks
  • Można wykorzystać off-by-one do leak adresu z heap ponieważ bajt 0x00 na końcu stringa jest nadpisywany przez następne pole.
  • Arbitrary write uzyskuje się przez wykorzystanie off-by-one do ustawienia wskaźnika na inne miejsce, gdzie zbudowany zostanie fałszywy struct z fałszywymi wskaźnikami. Następnie można podążać za wskaźnikiem tego structu, aby uzyskać arbitrary write.
  • Adres libc jest leakowany, ponieważ jeśli heap jest rozszerzony przy użyciu mmap, pamięć alokowana przez mmap ma ustalony offset względem libc.
  • W końcu arbitrary write jest wykorzystany do zapisania do adresu __free_hook z one_gadget.
  • plaidctf 2015 plaiddb
  • Istnieje NULL off-by-one w funkcji getline, która czyta linie wejścia od użytkownika. Funkcja ta jest używana do czytania “key” zawartości, a nie samej zawartości.
  • W writeupie tworzy się 5 początkowych chunków:
  • chunk1 (0x200)
  • chunk2 (0x50)
  • chunk5 (0x68)
  • chunk3 (0x1f8)
  • chunk4 (0xf0)
  • chunk defense (0x400) aby uniknąć konsolidacji z top chunk
  • Następnie chunk 1, 5 i 3 są zwalniane, więc:

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

- Potem, wykorzystując chunk3 (0x1f8) null off-by-one jest nadużyty do zapisania prev_size na `0x4e0`.
- Zauważ jak rozmiary początkowo alokowanych chunków1, 2, 5 i 3 plus nagłówki 4 z tych chunków sumują się do `0x4e0`: `hex(0x1f8 + 0x10 + 0x68 + 0x10 + 0x50 + 0x10 + 0x200) = 0x4e0`
- Następnie chunk 4 jest zwalniany, generując chunk który pochłania wszystkie chunky aż do początku:
- ```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 ]

- Potem alokowane jest `0x200` bajtów wypełniając oryginalny chunk 1
- I kolejne `0x200` bajtów jest alokowane i chunk2 zostaje zniszczony i dlatego nie ma żadnego fucking leak i to nie działa? Może tego nie powinno się robić
- Potem alokuje się kolejny chunk z 0x58 "a"s (nadpisując chunk2 i sięgając chunk5) i modyfikuje `fd` fast bin chunku chunk5 wskazując go na `__malloc_hook`
- Następnie alokowany jest chunk o rozmiarze 0x68, więc fałszywy fast bin chunk w `__malloc_hook` staje się następnym fast bin chunkiem
- W końcu, nowy fast bin chunk 0x68 jest alokowany i `__malloc_hook` zostaje nadpisany adresem `one_gadget`

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

> [!TIP]
> Ucz się i ćwicz 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;">\
> Ucz się i ćwicz 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;">
> Ucz się i ćwicz 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>Wsparcie dla HackTricks</summary>
>
> - Sprawdź [**plany subskrypcyjne**](https://github.com/sponsors/carlospolop)!
> - **Dołącz do** 💬 [**grupy Discord**](https://discord.gg/hRep4RUj7f) lub [**grupy telegramowej**](https://t.me/peass) lub **śledź** nas na **Twitterze** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Dziel się trikami hackingowymi, przesyłając PR-y do** [**HackTricks**](https://github.com/carlospolop/hacktricks) i [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repozytoriów na githubie.
>
> </details>