Introdução
Em junho desse ano o time de segurança do sistema operacional FreeBSD publicou um alerta de
segurança sobre uma vulnerabilidade descoberta por Rafal Wojtczuk que afeta todas as versões 64 bits.
Essa vulnerabilidade não só afeta o sistema operacional FreeBSD mas também vários sistemas
operacionais e sistemas de virtualização[1] disponíveis, com exceção do OpenBSD 5.0 e o Linux que
já tinha corrigido a vulnerabilidade desde 2006[3].
Nesse post será explicado a natureza da vulnerabilidade e como ela pode ser utilizada para obter
execução de códigos no kernel. Apesar de já existir exploits públicos para Windows [4] e para FreeBSD[5] [6], um exploit para FreeBSD também será apresentado nesse artigo.
Assume-se que o leitor saiba como funciona exploração de vulnerabilidades em kernel, sistemas
operacionais e suas estruturas, principalmente IDT[7] e como realizar debugging do kernel.
Os experimentos realizados nesse artigo foram realizados utilizando a versão 9.0 RELEASE do
FreeBSD.
A vulnerabilidade
Esse artigo tem como foco o padrão 64 bits desenvolvido pela AMD, chamada de AMD64[8] ou x86-
64 que também é utilizado pela Intel.
A CPU tem vários modos de operação, sendo os dois principais: o modo mais privilegiado que é o
modo em que o kernel é executado e chamado de ring0 e o menos privilegiado, chamado de ring3, o
modo em que os programas comum do dia a dia são executados como browsers e editores de texto.
Quando uma aplicação que está sendo executada em ring3 precisa realizar uma operação privilegiada,
um conjunto de operações precisam ser realizadas para mudar o modo de operação para o modo mais
privilegiado.
O padrão AMD64 adicionou um novo conjunto de instruções para realizar a troca de contexto, chamada de fastsyscall[10] e é basicamente composto por duas instruções: 'SYSCALL' e 'SYSRET'.
A instrução 'SYSCALL' é utilizada para passar do modo menos privilegiado para o mais privilegiado (ring3→ ring0), invocar o kernel, e a instrução 'SYSRET' o processo inverso, do mais privilegiado para o menos privilegiado, retornando para modo usuário.
A instrução 'SYSCALL' é utilizada para passar do modo menos privilegiado para o mais privilegiado (ring3→ ring0), invocar o kernel, e a instrução 'SYSRET' o processo inverso, do mais privilegiado para o menos privilegiado, retornando para modo usuário.
A vulnerabilidade existe devido a um erro na implementação da instrução 'SYSRET' nos processadores
AMD64 da Intel que é utilizada na troca de contexto (ring0 → ring3) em sistemas 64 bits, por isso essa
vulnerabilidade só afeta sistemas 64 bits que estejam utilizando processadores da Intel.
A AMD também adicionou um novo tipo de endereçamento chamado de endereço canônico[11]. Esse
tipo de endereçamento exige que os bit 47 ao 63 sejam iguais para serem utilizados como endereço de
memória, não satisfazendo essa igualdade, o endereço é considerado inválido. É utilizado a faixa de
endereço 0x0000000000000000 até 0x00007fffffffffff e 0xffff800000000000 até 0xffffffffffffffff, qualquer endereço de memória fora desses dois limites é um endereço não canônico.
Quando algum tipo de acesso a um endereço de memória não canônico for realizado, uma exceção é
gerada e tratada pelo sistema operacional. A diferença no processador da Intel que gerou a
vulnerabilidade é que, em determinada situação, o sistema operacional irá tratar essa exceção no modo
mais privilegiado da CPU diferente do processador da AMD que irá tratar no modo menos privilegiado.
A criticidade dessa sútil diferença entres os processadores é que no momento que o gerenciador de
exceção for executado, o sistema operacional já restaurou os valores dos registradores para valores
controláveis pelo usuário, entre esses registradores o mais interessante do ponto de vista de um atacante
é a pilha, registrador %rsp na arquitetura AMD64.
Portanto, quando a instrução 'SYSRET' for encontrada e executada a CPU deverá realizar basicamente
as seguintes ações:
1 → Mudar o modo de operação da CPU do mais privilegiado para o menos privilegiado;
2 →Redirecionar o fluxo de execução de instrução para o endereço armazenado no registrador
%rcx;
O que ocorre com o processador da Intel é o processo inverso, o passo 2 é executado antes do passo 1.
A forma utilizada para acionar essa vulnerabilidade foi alterar o valor do registrador %rcx para um
endereço não canônico e após isso executar a instrução 'SYSRET'. É permitido armazenar valores não canônico no registrador %rcx por ele ser um registrador de propósito geral.
Apesar de não ser o único, o método utilizado por alguns expoits públicos para FreeBSD consiste em
mapear a última página de memória canônica disponível ao usuário, e colocar a instrução 'SYSCALL'
nos últimos bytes alocados. Assim, após a execução da instrução e a passagem do controle para o
kernel, ao executar a instrução 'SYSRET', o registrador %rcx terá o valor não canônico. Como pode ser
visto na imagem abaixo. Clique na imagem para uma melhor visualização.
![]() |
| Figura 1: Acionando a vulnerabilidade |
Houve modificações no manual da Intel endereçando o problema. Abaixo trecho retirado do
manual[14] que especifica que o sistema operacional precisa realizar validações para evitar que esse
problema ocorra, modificação realizada em Agosto de 2012, dois meses após a publicação da
vulnerabilidade.
The SYSRET instruction does not modify the stack pointer (ESP or RSP). For that reason, it is necessary for software to switch to the user stack. The OS may load the user stack pointer (if it was saved after SYSCALL) before executing SYSRET; alternatively, user code may load the stack pointer (if it was saved before SYSCALL) after receiving control from SYSRET.
If the OS loads the stack pointer before executing SYSRET, it must ensure that the handler of any interrupt or exception delivered between restoring the stack pointer and successful execution of SYSRET is not invoked with the user stack. It can do so using approaches such as the following:
• External interrupts. The OS can prevent an external interrupt from being delivered by clearing EFLAGS.IF before loading the user stack pointer.
• Nonmaskable interrupts (NMIs). The OS can ensure that the NMI handler is invoked with the correct stack by using the interrupt stack table (IST) mechanism for gate 2 (NMI) in the IDT (see Section 6.14.5,“Interrupt Stack Table,” in Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A).
• General-protection exceptions (#GP). The SYSRET instruction generates #GP(0) if the value of RCX is not canonical.
The OS can address this possibility using one or more of the following approaches:
— Confirming that the value of RCX is canonical before executing SYSRET.
— Using paging to ensure that the SYSCALL instruction will never save a non-canonical value into RCX.
— Using the IST mechanism for gate 13 (#GP) in the IDT.
Executando códigos arbitrários no kernel
Como, apartir das informações que temos até agora, é possível executar códigos arbritários? Ao acionar
a vulnerabilidade o gerenciador de exceção nos processadores Intel 64 bits será executado em ring0.
Precisamos fazer com que a exceção seja executada, fazendo com que o processador tente acessar
alguma região de memória numa área não canônica como descrito anteriormente. Durante a execução
do gerenciador de exceção valores são escritos no endereço que está no registrador %rsp que é
controlado pelo usuário (e daí que a vulnerabilidade pode ser explorada). Podemos alterar o registrador
%rsp para apontar para alguma estrutura do kernel que, durante a execução do gerenciador de exceção
será sobrescrita com valores controlados pelo usuário. Como pode ser visto na imagem a baixo. Clique na imagem para uma melhor visualização.
Então o que foi feito para obter execução de códigos é colocar o endereço de determinada entrada da
IDT no registrador %rsp e alterar o valor do registrador %rdx para um valor que, ao sobrescrever os
valores originais, modificará os campos gd_hioffset e gd_looffset da entrada 14 da IDT que é a entrada
referente ao Page Fault Exception(#PF) para o endereço de uma função que será responsável por
escalar os privilégios do usuário atual, reparar todo o estrago feito da IDT e retornar para modo usuário
com sucesso. Como os campos responsáveis por armazenar o endereço da função original que será
executada foi sobrescrito pelo endereço da função kernel_code (0x400b50), nosso código pode ser
executado a qualquer momento que uma exceção do tipo #PF for acionada. Devido a forma que foi
utilizada para acionar a vulnerabilidade, um exceção #PF é gerada automaticamente durante a execução
do gerenciador de exceção original devido a alguns registradores estar com seus valores incoerentes.
Como mencionado acima, após ganharmos execução de código precisamos restaurar os valores
originais da IDT que foram corrompidos para evitar um crash e reiniciar o sistema e depois disso
elevar o privilégio do usuário atual para root. Para podermos fazer isso precisamos saber o endereço da
estrutura que armazena as informações do usuário, há várias formas de conseguir isso, a que foi utilizada no exploit é mencionada nesse artigo em [15]. A vantagem de utilizar esse método é que
podemos obter o endereço da estrutura antes mesmo de acionar a vulnerabilidade, diferente do método
utilizado nos exploits públicos que obtem esse endereço após ganhar execução de código no kernel. O
último passo que o exploit deve fazer é retornar para modo usuário após de ter feito tudo que queríamos
no kernel, isso é feito colocando no registrador %rcx o endereço de uma função do exploit que checará
se realmente os privilégios foram elevados e finalizar o processo, no exploit é a função done a responsável por isso.
Conclusão
Particularmente, essa foi umas das vulnerabilidades mais interessantes do ano, inclusive foi nomeada
para o prêmio Pwnie Awards[16] na categoria de melhor vulnerabilidade que permite escalação de
privilégios, infelizmente não ganhou. O motivo que mais chamou a atenção nessa vulnerabilidade, além
da sua complexidade, é o fato dela existir há muito tempo, como mencionado na introdução que no
Linux foi corrigida desde 2006. Aqui[17] há uma discussão em húngaro bastante interessante com detalhes da vulnerabilidade e outra forma de como a exploração pode ser feita.
Código disponível aqui.























