Off by one overflow

Tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Informações Básicas

Ter acesso apenas a um overflow de 1B permite a um atacante modificar o campo size do chunk seguinte. Isso permite manipular quais chunks são realmente freed, potencialmente gerando um chunk que contenha outro chunk legítimo. A exploração é semelhante ao double free ou overlapping chunks.

Existem 2 tipos de vulnerabilidades off by one:

  • Arbitrary byte: Esse tipo permite sobrescrever esse byte com qualquer valor
  • Null byte (off-by-null): Esse tipo permite sobrescrever esse byte apenas com 0x00
  • Um exemplo comum dessa vulnerabilidade pode ser visto no código a seguir, onde o comportamento de strlen e strcpy é inconsistente, o que permite colocar um byte 0x00 no início do chunk seguinte.
  • Isso pode ser explorado com o House of Einherjar.
  • Se estiver usando Tcache, isso pode ser aproveitado para uma situação 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 outras verificações, agora sempre que um chunk é liberado o campo prev_size é comparado com o size configurado nos metadados do chunk, tornando este ataque bastante complexo a partir da versão 2.28.

Code example:

Objetivo

  • Fazer com que um chunk fique contido dentro de outro chunk, de modo que o acesso de escrita sobre esse segundo chunk permita sobrescrever o chunk contido

Requisitos

  • Off by one overflow para modificar o metadado size

Ataque off-by-one geral

  • Alocar três chunks A, B e C (por exemplo tamanhos 0x20), e outro para prevenir consolidação com o top-chunk.
  • Liberar C (inserido na free-list do Tcache de 0x20).
  • Usar o chunk A para overflow em B. Abusar do off-by-one para modificar o campo size de B de 0x21 para 0x41.
  • Agora temos B contendo o free chunk C
  • Free B e alocar um chunk 0x40 (ele será alocado aqui novamente)
  • Podemos modificar o ponteiro fd de C, que ainda está free (Tcache poisoning)

Off-by-null attack

  • 3 chunks de memória (a, b, c) são reservados consecutivamente. Depois, o do meio é liberado. O primeiro contém uma vulnerabilidade off-by-one e o atacante a explora escrevendo um 0x00 (se o byte anterior fosse 0x10 isso faria o chunk do meio indicar que é 0x10 menor do que realmente é).
  • Em seguida, mais 2 chunks menores são alocados dentro do chunk liberado do meio (b), no entanto, como b + b->size nunca atualiza o chunk c porque o endereço apontado é menor do que deveria.
  • Depois, b1 e c são liberados. Como c - c->prev_size ainda aponta para b (agora b1), ambos são consolidados em um único chunk. Contudo, b2 ainda fica preso entre b1 e c.
  • Finalmente, um novo malloc é executado reclamando essa região de memória que na verdade vai conter b2, permitindo ao dono do novo malloc controlar o conteúdo de b2.

Esta imagem explica perfeitamente o ataque:

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

Observações sobre hardening e bypass do glibc moderno (>=2.32)

  • Safe-Linking agora protege cada ponteiro de bin de lista simplesmente ligada armazenando fd = ptr ^ (chunk_addr >> 12), então um off-by-one que só inverte o byte menos significativo do size normalmente também precisa de um heap leak para recomputar a máscara XOR antes de o Tcache poisoning funcionar.
  • A practical leakless trick é “double-protect” um ponteiro: codificar um ponteiro que você já controla com PROTECT_PTR, então reutilizar o mesmo gadget para codificar seu ponteiro forjado de modo que a verificação de alinhamento passe sem revelar novos endereços.
  • Fluxo de trabalho para Safe-Linking + corrupções de um único byte:
  1. Expanda o chunk vítima até que ele cubra completamente um chunk liberado que você já controla (configuração de overlapping-chunk).
  2. Leak qualquer ponteiro do heap (stdout, UAF, struct parcialmente controlada) e derive a chave heap_base >> 12.
  3. Re-encode os ponteiros da free-list antes de escrevê-los—stage o valor codificado dentro dos dados do usuário e memcpy-o depois se você só possui escritas de um único byte.
  4. Combine com Tcache bin attacks para redirecionar alocações para __free_hook ou entradas de tcache_perthread_struct assim que o ponteiro forjado estiver corretamente codificado.

Um helper mínimo para ensaiar o passo de encode/decode enquanto depura exploits modernos:

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

Alvo real recente: glibc __vsyslog_internal off-by-one (CVE-2023-6779)

  • Em janeiro de 2024 a Qualys detalhou o CVE-2023-6779, um off-by-one dentro de __vsyslog_internal() que é acionado quando as format strings de syslog()/vsyslog() excedem INT_MAX, de modo que o \0 terminador corrompe o byte menos-significativo do campo size do chunk seguinte em sistemas glibc 2.37–2.39 (Qualys advisory).
  • Pipeline de exploit para o Fedora 38:
  1. Criar um ident excessivamente longo em openlog() para que vasprintf retorne um buffer no heap adjacente a dados controlados pelo atacante.
  2. Chamar syslog() para sobrescrever o byte size | prev_inuse do chunk vizinho, freeá-lo e forçar consolidação que cause overlap com os dados do atacante.
  3. Usar a visão sobreposta para corromper metadata de tcache_perthread_struct e direcionar a próxima alocação para __free_hook, sobrescrevendo-o com system/um one_gadget para root.
  • Para reproduzir a escrita corruptora em um harness, fork com um argv[0] gigantesco, chame openlog(NULL, LOG_PID, LOG_USER) e então syslog(LOG_INFO, "%s", payload) onde payload = b"A" * 0x7fffffff; pwndbg’s heap bins mostra imediatamente o overwrite de um byte.
  • O Ubuntu acompanha o bug como CVE-2023-6779, documentando a mesma truncagem para INT que torna isto um off-by-one primitive confiável.

Other Examples & References

  • https://heap-exploitation.dhavalkapil.com/attacks/shrinking_free_chunks
  • Bon-nie-appetit. HTB Cyber Apocalypse CTF 2022
  • Off-by-one por causa de strlen que considera o campo size do chunk seguinte.
  • Tcache é usado, então um ataque off-by-one genérico funciona para obter um arbitrary write primitive com Tcache poisoning.
  • Asis CTF 2016 b00ks
  • É possível abusar de um off by one para leak um endereço do heap porque o byte 0x00 do fim de uma string é sobrescrito pelo campo seguinte.
  • Arbitrary write é obtido abusando da escrita off by one para fazer o ponteiro apontar para outro lugar onde uma fake struct com fake pointers será construída. Então, é possível seguir o ponteiro dessa struct para obter arbitrary write.
  • O endereço do libc é leakado porque se o heap é extendido usando mmap, a memória alocada pelo mmap tem um offset fixo a partir do libc.
  • Finalmente o arbitrary write é abusado para escrever no endereço de __free_hook com um one gadget.
  • plaidctf 2015 plaiddb
  • Existe uma vulnerabilidade NULL off by one na função getline que lê linhas de input do usuário. Essa função é usada para ler a “key” do conteúdo e não o conteúdo.
  • No writeup 5 chunks iniciais são criados:
  • chunk1 (0x200)
  • chunk2 (0x50)
  • chunk5 (0x68)
  • chunk3 (0x1f8)
  • chunk4 (0xf0)
  • chunk defense (0x400) para evitar consolidação com o top chunk
  • Então os chunks 1, 5 e 3 são freed, então:

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

- Então, abusando do chunk3 (0x1f8) o null off-by-one é usado para escrever o prev_size para `0x4e0`.
- Repare como os tamanhos dos chunks inicialmente alocados 1, 2, 5 e 3 mais os headers de 4 desses chunks somam `0x4e0`: `hex(0x1f8 + 0x10 + 0x68 + 0x10 + 0x50 + 0x10 + 0x200) = 0x4e0`
- Depois, o chunk 4 é freed, gerando um chunk que consome todos os chunks até o começo:
- ```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 ]

- Então, `0x200` bytes são alocados preenchendo o chunk 1 original
- E outros 0x200 bytes são alocados e chunk2 é destruído e portanto não há nenhum fucking leak e isso não funciona? Talvez isso não devesse ser feito
- Depois, aloca outro chunk com 0x58 "a"s (sobrescrevendo chunk2 e alcançando chunk5) e modifica o `fd` do fast bin chunk de chunk5 apontando-o para `__malloc_hook`
- Então, um chunk de 0x68 é alocado de modo que o fake fast bin chunk em `__malloc_hook` seja o fast bin chunk seguinte
- Finalmente, um novo fast bin chunk de 0x68 é alocado e `__malloc_hook` é sobrescrito com um endereço de `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]
> Aprenda e pratique 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;">\
> Aprenda e pratique 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;">
> Aprenda e pratique 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>Supporte o HackTricks</summary>
>
> - Confira os [**planos de assinatura**](https://github.com/sponsors/carlospolop)!
> - **Junte-se ao** 💬 [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga**-nos no **Twitter** 🐦 [**@hacktricks_live**](https://twitter.com/hacktricks_live)**.**
> - **Compartilhe truques de hacking enviando PRs para o** [**HackTricks**](https://github.com/carlospolop/hacktricks) e [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) repositórios do github.
>
> </details>