Introdução
Em junho desse ano (2012) 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á tinham 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 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.
Em discussão com Joilson Rabelo[12] e depois confirmado através do blog-post feito pela VUPEN[13], no Windows 7 esse método não funciona, pois o kernel do Windows não permite mapear a última página de memória disponível para o usuário.
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.
• Non-maskable 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, a partir das informações que temos até agora, é possível executar códigos arbitrá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 egd_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 obtém 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.
Referências