
Este artigo é dedicado a testar a possibilidade de usar a tecnologia Intel Processor Trace (Intel PT) para gravar faixas no modo SMM (System Management Mode). Este trabalho foi realizado como parte do Summer Of Hack 2019. Publicado por @sysenter_eip .
A maioria das ferramentas usadas são escritas por outras pessoas (em particular @d_olex , @aionescu ). O resultado é apenas uma combinação das ferramentas disponíveis para obter o caminho de execução do código no modo SMM para uma placa-mãe específica . No entanto, o material pode ser interessante para quem deseja repetir isso para sua plataforma ou simplesmente está interessado no trabalho do SMM.
Modo de gerenciamento do sistema
O SMM é um modo especial e privilegiado do processador de arquitetura x86, que está disponível enquanto o sistema operacional está em execução, mas é completamente invisível para ele. Ele foi projetado para interação de baixo nível com ferro, gerenciamento de energia, emulação de dispositivos herdados, transição para o modo de suspensão (S3), acesso ao TPM e muito mais. Funciona completamente isolado do sistema operacional. Durante a execução do SMM, o sistema operacional para completamente. O código do programa que é executado nesse modo é armazenado na memória SPI-Flash da placa-mãe e faz parte do firmware do UEFI BIOS.
A mudança para o modo SMM é realizada usando interrupções SMI especiais (System Management Interrupt). Uma das opções para essa interrupção está disponível para uso no anel zero (ou seja, no kernel do SO) - Interrupção SMI no nível do aplicativo (Software SMI). Além disso, vamos nos concentrar nessas interrupções.
Devido ao seu alto privilégio, o SMM é de particular interesse para pesquisas de segurança. O comprometimento do SMM leva a graves violações da integridade e confidencialidade de todo o sistema e, na maioria dos casos, permite injetar código malicioso que não pode ser excluído e não pode ser detectado pelo sistema operacional no firmware do UEFI BIOS.
Rastreio do processador Intel
Uma das armadilhas do processo de depuração para vários aplicativos altamente carregados é a sobrecarga - os custos das ferramentas de depuração. Eles podem ser reduzidos com uma solução habilitada para hardware.
A quinta geração de processadores da Intel (Broadwell) apresentou ao mundo tecnologias como o Intel Processor Trace. Como isso é útil? O Intel PT permite que você obtenha o fluxo completo de execução (fluxo de controle) do aplicativo depurado com sobrecarga mínima (<5%). Ao mesmo tempo, suporta multithreading e pode ajudar a corrigir erros como "condição de corrida" devido a carimbos de data e hora ao gravar o rastreamento do aplicativo. Sem dúvida, a tecnologia Intel PT oferece grandes oportunidades para escrever ferramentas de pesquisa de vulnerabilidades em aplicativos.
Hoje, essa tecnologia é usada em várias ferramentas para rastrear, depurar e avaliar a cobertura de código - tanto em aplicativos no nível do usuário quanto no kernel. Exemplos de ferramentas podem ser encontrados no site da Intel . Uma opção de fuzzer AFL que aproveita o Intel PT está disponível no repositório do PTfuzzer . De projetos recentes, preste atenção ao iptanalyzer .
No entanto, não vimos nenhum trabalho sobre o uso do Intel PT no modo SMM. Como nada nos impede de usar o Intel PT nesse contexto, decidimos descobrir se é possível rastrear o código do Modo de Gerenciamento do Sistema com ele.
Preparação para o trabalho
Resulta do Manual do desenvolvedor da Intel que é impossível ativar o rastreamento do Intel PT no SMM de fora usando meios regulares. Se estava ativo no momento em que o SMI foi acionado, o processador o desativará antes de transferir o controle para o ponto de entrada do manipulador SMI. O único método de ativação é a inclusão voluntária do manipulador SMI pelo próprio código.
Mesmo que o processador não forneça inicialmente essa oportunidade, podemos interceptá-lo e ativar o Intel PT manualmente. No entanto, você precisa determinar de alguma forma que o sistema está pronto para registrar o rastreamento (o endereço do buffer de saída está definido) e também desativar o rastreamento no final da execução do processador (execução da instrução RSM). Caso contrário, o processador desligará o sistema inteiro.
Primeiro de tudo, você precisa acessar o SMRAM (a área da RAM na qual o código executado no modo SMM está localizado). Como essa região de RAM está protegida, não podemos acessá-la a partir do sistema operacional (mesmo isso não pode ser feito com o DMA). Existem vários cenários:
- explore uma vulnerabilidade conhecida no SMM e obtenha o primitivo R / W a partir dele. Isso pode ser um erro de software (uma vulnerabilidade no próprio processador SMI; como regra, no SMM há código suficiente que foi adicionado pelo OEM, portanto as vulnerabilidades não são incomuns), bem como uma configuração de plataforma vulnerável (desbloquear / mover SMRAM);
- para corrigir a imagem UEFI de maneira que tenhamos uma interface para leitura e gravação em endereços arbitrários - um backdoor. Para implementar esta opção, você precisa encontrar uma placa-mãe na qual o Intel Boot Guard esteja desativado ou houver vulnerabilidades que possam contorná-la.
Incorpore seu código no firmware
Apesar do fato de que as vulnerabilidades SMM no código de vários fabricantes são encontradas de tempos em tempos , será melhor se não confiarmos nelas. É mais interessante rastrear o código em um novo firmware e, consequentemente, tentar encontrar vulnerabilidades neles. Já tínhamos a placa-mãe GIGABYTE GA-Q270M-D3H com o Intel Boot Guard desativado; portanto, bastava adicionar um backdoor ao SMM.

Figura 1. Bancada de testes
Já existe uma estrutura para "infectar" o SMM e trabalhar com um backdoor . É composto por três componentes: o driver UEFI em C, o "infector" e o script do cliente em Python. Para sua operação, você precisa extrair um driver DXE arbitrário (você pode fazer isso usando UEFITool ) e processá-lo com um infector. O módulo original foi substituído por "aprimorado" e o firmware foi carregado na memória SPI (para conveniência de piscar, a unidade flash SPI foi removida da placa).

Figura 2. Chip de memória SPI-Flash
O sistema foi iniciado com sucesso e agora temos acesso total ao SMRAM a partir do Python (um exemplo de uso é fornecido com o backdoor). Como o script do cliente para o backdoor é baseado no CHIPSEC , é necessário conceder acesso ao modo kernel (usamos o driver RWEverything; será conveniente que alguém use seu próprio driver CHIPSEC com a verificação de assinatura desativada no sistema).
Você pode verificar a porta dos fundos solicitando um dump SMRAM.
$ python SmmBackdoor.py -d
Após executar este comando, o arquivo SMRAM_dump_cb000000_cb7fffff.bin será criado contendo o estado atual da SMRAM. Os valores cb000000 e cb7fffff são, respectivamente, os endereços físicos do início e do fim da SMRAM.
Trabalhar com SMRAM de despejo
O despejo SMRAM pode ser carregado em um desmontador ou passado para análise no script smram_parse.py , que extrairá muitas informações úteis para nós. O mais importante para nós serão os endereços dos pontos de entrada SMI. Esses são os endereços das funções para as quais o controle será transferido quando o SMI for acionado. Cada CPU possui seu próprio ponto de entrada.

Figura 3. A saída do script smram_parse
Vamos dar uma olhada no código deles. Como o SMM inicia sua execução no modo real de 16 bits (os primeiros 4 GB de RAM são refletidos no espaço virtual), a primeira coisa que o código faz é alternar para o modo de 64 bits. Ao mesmo tempo, toda a SMRAM está disponível com direitos de gravação e execução, uma vez que apenas um segmento foi criado (existem fornecedores que fazem isso de maneira diferente?).
Não queremos escrever código de 16 bits ou preparar tudo o que é necessário para alternar para o modo de 64 bits por conta própria; portanto, colocaremos nosso interceptador antes de chamar a função SMI manager (essa função determina em qual módulo SMM a execução deve ser transferida dependendo de qual serviço foi chamado ou qual evento aconteceu).

Figura 4. Colocação para enganchar
A maneira mais fácil de assumir o controle é substituir o endereço do expedidor pelo nosso. Todos os pontos de entrada têm o mesmo código, portanto, o patch precisa ser repetido para cada um.
Nota: Em relação à localização do código interceptador. Como a estrutura da SMRAM não é totalmente conhecida por nós, escolhemos um pedaço aleatório de memória zero próximo a um dos pontos de entrada, onde colocamos o código interceptador. A melhor opção seria adicionar o módulo SMM ao firmware, que a UEFI colocaria legalmente na SMRAM, para não se preocupar que algo importante seja substituído pelo nosso código.
Implementando um SMI Manager Interceptor
Vamos designar o que exatamente vamos fazer dentro do nosso interceptador. Primeiro, precisamos determinar se o Intel PT foi ativado antes de passar para o SMM. É sabido pela documentação da Intel que cada processador possui sua própria base SMBASE (MSR 0x9E) e seu próprio espaço para armazenar o estado do processador (área SMM Save State) no momento da transição para o SMM.

Figura 5. Layout do SMBASE
Determinamos o status da Intel PT
No SMM Save State, o valor do registro MSR IA32_RTIT_CTL, responsável pelo gerenciamento do rastreamento Intel PT, deve ser salvo. Infelizmente, o Intel Manual não indica onde o processador salva o estado do bit IA32_RTIT_CTL.TraceEn no momento da transição para o SMM (se o rastreamento está ativado, zero bit). No entanto, podemos determinar isso sozinhos despejando o SMM Save State duas vezes: com e sem o rastreamento ativado.
Usamos a ferramenta WinIPT para ativar o rastreamento no processo do interpretador Python (pid 1337 ), alocando 2 ^ 12 (4096) bytes ao buffer de rastreamento e, em seguida, executando o script SmmBackdoor.py dentro do interpretador (argumento 0 é um sinalizador, para nós eles não são importante, porque no SMM você ainda precisa forçar suas configurações de rastreamento).
$ ipttool.exe --start 1337 12 0
Comparando os instantâneos SMRAM, determinamos a localização do registro IA32_RTIT_CTL na estrutura SMM Save State. Ele é armazenado no deslocamento SMBASE + 0xFE3C. O estado do bit IA32_RTIT_CTL.TraceEn é a principal condição para a reativação do Intel PT no SMM. O campo nesse deslocamento é marcado como Reservado no Intel Developer Manual.

Figura 6. Marcando que os campos estão reservados
Escrevendo código de shell
Não queríamos configurar o Intel PT dentro do SMM por conta própria, pois isso complicaria nosso código de shell (por exemplo, estando no SMM, seria difícil alocar uma grande parte da RAM para que não fosse usada pelo próprio sistema operacional). Portanto, decidimos usar o rastreador já configurado e simplesmente "ignorá-lo" no SMM, especialmente porque ele já tem a função de salvar o rastreamento em um arquivo.
Como usamos o WinIPT para essa finalidade, que na época não suportava o rastreamento do código do kernel (CPL == 0), era óbvio que mesmo quando o rastreamento era incluído no SMM, nada aparecia no log, pois o código SMM era executado na CPL = 0 . Precisamos modificar alguns filtros para que o rastreador possa funcionar durante todo o tempo gasto no SMM. Listamos tudo o que precisa ser verificado e instalado:
- O rastreamento com CPL = 0 deve estar ativado.
- O rastreamento para CPL> 0 deve estar ativado (opcional).
- Os intervalos de IP válidos para gravação de eventos devem ser desativados.
- IA32_RTIT_STATUS.PacketByteCnt deve ser redefinido.
- A filtragem CR3 deve estar desativada.
Algumas palavras devem ser ditas sobre PacketByteCnt. Esse contador determina em que ponto você precisa inserir pacotes de sincronização (uma sequência de vários comandos PSB) no rastreamento. Precisamos redefinir esse contador, caso contrário, durante o processamento do rastreamento, o momento de entrar no SMM será perdido e o rastreamento começará de um local aleatório quando o PSB for gerado naturalmente.
Abaixo está o código de shell que usamos:
sub rsp, 0x18 ; this will align stack at 16 byte boundary (in case SMM ; code uses align dependent instructions) mov qword ptr ss:[rsp+0x10], rcx ; need to save rcx for SMI_Dispatcher mov ecx, 0x9E ; MSR_IA32_SMBASE rdmsr test byte ptr ds:[rax+0xFE3C], 0x1 ; Save State area contains saved ; IA32_RTIT_CTL.TraceEn je short @NoTrace call @Trace_Enable mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall ; (first argument in rcx) mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax call @Trace_Disable add rsp, 0x18 ret @NoTrace: mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax add rsp, 0x18 ret @Trace_Disable: mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov rax, qword ptr ss:[rsp+0x10] ; restore IA32_RTIT_STATUS wrmsr mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov rax, qword ptr ss:[rsp+0x8] ; restore IA32_RTIT_CTL wrmsr ret @Trace_Enable: mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov qword ptr ss:[rsp+0x8], rax ; save IA32_RTIT_STATUS and edx, 0xFFFF0000 ; IA32_RTIT_STATUS.PacketByteCnt = 0 wrmsr mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov qword ptr ss:[rsp+0x10], rax ; save IA32_RTIT_CTL and eax, 0xFFFFFFBF ; IA32_RTIT_CTL.CR3Filter = 0 or eax, 0x5 ; IA32_RTIT_CTL.OS = 1; IA32_RTIT_CTL.User = 1; and edx, 0xFFFF0000 ; IA32_RTIT_CTL.ADDRx_CFG = 0 wrmsr ret
Esse código deve ser colocado na SMRAM e a transição para o gerente SMI deve ser corrigida para ir ao nosso código. Tudo isso é feito usando o SmmBackdoor.
Trabalhar com a faixa
O interceptor do gerente SMI nos permitiu escrever o primeiro rastreio de código do SMM. O comando a seguir pode solicitar ao WinIPT que salve o rastreamento em um arquivo:
$ ipttool.exe --trace 1337 trace_file_name
Desativando o rastreio em um processo:
$ ipttool.exe --stop 1337
Você pode tentar desmontar o rastreamento usando o utilitário dumppt da libipt .
$ ptdump.exe --no-pad ./examples/trace_smm_handler_33 > ./examples/trace_smm_handler_33_pt_dump.txt
Exemplo de saída:

Figura 7. O primeiro caminho da instrução SMM
Podemos ver alguns endereços, no entanto, é extremamente difícil usar essas informações, pois elas são de nível muito baixo.
Para obter uma aparência mais legível, existe um utilitário ptxed (da libipt) que converte o rastreamento em um log de instruções executadas do assembler. Obviamente, teremos de fornecer ao utilitário um despejo de memória SMRAM, pois o log IPT não contém informações sobre os valores das células da memória ou sobre quais instruções foram executadas; Ele contém apenas informações sobre quais alterações ocorreram no fluxo de controle.
$ ptxed.exe --pt tracesmm_12 --raw SMRAM_dump_cb000000_cb7fffff.bin:0xcb000000 > tracesmm_12_ptasm

Figura 8. Lista do Assembler correspondente ao log do IPT
Já parece muito melhor, mas se o código contiver um loop, a saída será entupida com as mesmas instruções.
Definir cobertura de código usando o rastreio
Para obter a visualização da cobertura, escolhemos o plug-in Lighthouse para o IDA Pro, que usa o formato drcov.
Nenhuma ferramenta pronta foi encontrada; portanto, modificamos o ptxed para que também gerasse um arquivo de cobertura no processo. O ptxed com patch está disponível no repositório . Dê uma olhada no histórico de consolidação para determinar exatamente o que foi adicionado.
Após a conclusão do ptxed, o arquivo SMRAM_dump_cb000000_cb7fffff.bin.log aparece, o qual conterá informações de cobertura no formato drcov.
Nota: Há um pequeno problema com a sincronização do desmontador no primeiro PSB. Por uma razão não totalmente clara, se o PSB for gerado antes do PGE (o contador será redefinido para zero antes do rastreamento ser ativado novamente), o ptxed não poderá ser sincronizado nele. Para contornar esse problema, fizemos um pequeno patch. Não está claro se isso é um problema para o próprio ptxed ou se estamos fazendo algo errado, redefinindo IA32_RTIT_STATUS.PacketByteCnt.

Figura 9. Um patch que permite usar o PSB localizado bem em frente ao PGE
Os arquivos de cobertura gerados podem ser baixados no IDA Pro e obter um belo destaque, além de estatísticas sobre a porcentagem de cobertura para cada função.

Figura 10. Plug-in IDA Pro Lighthouse com informações de cobertura de código
Nota: O plug-in Lighthouse funciona um pouco estranhamente em bancos de dados analisados incompletamente (o código executável não está identificado, as funções não foram criadas). Rastreamos esse "problema" para a função get_instructions_slice no arquivo \ lighthouse \ metadata.py, onde ele retorna 0 instruções, mesmo para o endereço em que a função foi criada manualmente. O plugin parece usar o cache e ignorar o novo código específico. Isso pode ser contornado chamando Reanalyze no programa e reabrindo o BID. Somente depois disso o plugin poderá ver o novo código e começar a considerá-lo. Como esse problema é muito inconveniente no caso de um despejo de SMRAM (que na primeira inicialização consiste quase inteiramente de código indefinido), fizemos uma pequena alteração no código do Lighthouse para poder definir manualmente o novo código mais rapidamente.

Figura 11. Mensagem de log adicionada para ajudar a identificar o novo código
Suporte Linux
Como todos os nossos testes foram realizados no Windows 10 x64 (é necessário o ipt.sys, que apareceu no Windows October Creators Update 2018), digamos algumas palavras sobre a possibilidade de implementar isso no Linux.
- Há um módulo perf do kernel Linux que pode executar as mesmas ações do WinIPT (ipt.sys), incluindo a capacidade de rastrear código no modo kernel.
- Como a interface SMM backdoor é baseada na estrutura CHIPSEC de plataforma cruzada, nosso patch funcionará em um sistema Linux sem nenhuma modificação.
Conclusão
Lidamos com êxito com a tarefa de obter um rastreamento de código executado no SMM usando a tecnologia Intel Processor Trace. Um resultado semelhante poderia ser alcançado com a ajuda de equipamentos e softwares caros que não são vendidos para todos. Foi o suficiente para termos em mãos uma placa-mãe e um programador SPI. A velocidade de remoção da pista é realmente impressionante e não há queixas sobre a precisão do resultado.
Esperamos que este artigo ajude outras pessoas a aproveitar a tecnologia Intel PT para investigar e procurar vulnerabilidades no código SMM. Adaptar nosso trabalho a outras placas-mãe não deve causar dificuldades (não se esqueça do Intel Boot Guard). O principal é entender completamente como funciona. A parte mais difícil é determinar como interceptar o despachante SMI e escrever um código de shell para o interceptador. Em nossa versão, endereços "com fio" foram usados, portanto, você deve transferir cuidadosamente o código do shell para outro sistema.
Todas as ferramentas e scripts usados estão disponíveis no repositório no GitHub .