Stack Canaries
Last updated
Last updated
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
StackGuard insere um valor especial conhecido como canário antes do EIP (Extended Instruction Pointer), especificamente 0x000aff0d
(representando null, newline, EOF, carriage return) para proteger contra estouros de buffer. No entanto, funções como recv()
, memcpy()
, read()
, e bcopy()
permanecem vulneráveis, e não protege o EBP (Base Pointer).
StackShield adota uma abordagem mais sofisticada do que o StackGuard, mantendo uma Global Return Stack, que armazena todos os endereços de retorno (EIPs). Essa configuração garante que qualquer estouro não cause danos, pois permite uma comparação entre os endereços de retorno armazenados e os reais para detectar ocorrências de estouro. Além disso, o StackShield pode verificar o endereço de retorno em relação a um valor limite para detectar se o EIP aponta fora do espaço de dados esperado. No entanto, essa proteção pode ser contornada por técnicas como Return-to-libc, ROP (Return-Oriented Programming), ou ret2ret, indicando que o StackShield também não protege variáveis locais.
-fstack-protector
:Esse mecanismo coloca um canário antes do EBP, e reorganiza variáveis locais para posicionar buffers em endereços de memória mais altos, impedindo que sobrescrevam outras variáveis. Ele também copia de forma segura os argumentos passados na pilha acima das variáveis locais e usa essas cópias como argumentos. No entanto, não protege arrays com menos de 8 elementos ou buffers dentro da estrutura de um usuário.
O canário é um número aleatório derivado de /dev/urandom
ou um valor padrão de 0xff0a0000
. Ele é armazenado em TLS (Thread Local Storage), permitindo que espaços de memória compartilhados entre threads tenham variáveis globais ou estáticas específicas da thread. Essas variáveis são inicialmente copiadas do processo pai, e os processos filhos podem alterar seus dados sem afetar o pai ou irmãos. No entanto, se um fork()
for usado sem criar um novo canário, todos os processos (pai e filhos) compartilham o mesmo canário, tornando-o vulnerável. Na arquitetura i386, o canário é armazenado em gs:0x14
, e em x86_64, em fs:0x28
.
Essa proteção local identifica funções com buffers vulneráveis a ataques e injeta código no início dessas funções para colocar o canário, e no final para verificar sua integridade.
Quando um servidor web usa fork()
, ele permite um ataque de força bruta para adivinhar o byte do canário um por um. No entanto, usar execve()
após fork()
sobrescreve o espaço de memória, negando o ataque. vfork()
permite que o processo filho execute sem duplicação até que tente escrever, momento em que uma duplicata é criada, oferecendo uma abordagem diferente para a criação de processos e manipulação de memória.
Em binários x64
, o cookie do canário é um 0x8
byte qword. Os primeiros sete bytes são aleatórios e o último byte é um byte nulo.
Em binários x86
, o cookie do canário é um 0x4
byte dword. Os primeiros três bytes são aleatórios e o último byte é um byte nulo.
O byte menos significativo de ambos os canários é um byte nulo porque será o primeiro na pilha vindo de endereços mais baixos e, portanto, funções que leem strings pararão antes de lê-lo.
Vazar o canário e depois sobrescrevê-lo (por exemplo, estouro de buffer) com seu próprio valor.
Se o canário for duplicado em processos filhos, pode ser possível forçar um byte de cada vez:
Se houver algum vazamento interessante ou vulnerabilidade de leitura arbitrária no binário, pode ser possível vazá-lo:
Sobrescrevendo ponteiros armazenados na pilha
A pilha vulnerável a um estouro de pilha pode contém endereços para strings ou funções que podem ser sobrescritos a fim de explorar a vulnerabilidade sem precisar alcançar o canário da pilha. Confira:
Modificando tanto o canário mestre quanto o da thread
Um estouro de buffer em uma função com threads protegida com canário pode ser usado para modificar o canário mestre da thread. Como resultado, a mitigação é inútil porque a verificação é usada com dois canários que são os mesmos (embora modificados).
Além disso, um estouro de buffer em uma função com threads protegida com canário poderia ser usado para modificar o canário mestre armazenado no TLS. Isso ocorre porque pode ser possível alcançar a posição de memória onde o TLS é armazenado (e, portanto, o canário) através de um bof na pilha de uma thread. Como resultado, a mitigação é inútil porque a verificação é usada com dois canários que são os mesmos (embora modificados). Esse ataque é realizado na descrição: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads
Confira também a apresentação de https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015 que menciona que geralmente o TLS é armazenado por mmap
e quando uma pilha de thread é criada, ela também é gerada por mmap
, de acordo com isso, o que pode permitir o estouro como mostrado na descrição anterior.
Modificar a entrada GOT de __stack_chk_fail
Se o binário tiver Partial RELRO, então você pode usar uma escrita arbitrária para modificar a entrada GOT de __stack_chk_fail
para ser uma função dummy que não bloqueia o programa se o canário for modificado.
Esse ataque é realizado na descrição: https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/
Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)