
Um hipervisor para fuzzing construído com WHVP e Bochs
Olá! Bem-vindo ao applepie! Esta é uma ferramenta projetada para fuzzing, introspecção e encontrar bugs! Este é um hypervisor que utiliza a API Windows Hypervisor Platform presente em versões recentes do Windows (especificamente, foi desenvolvido e testado no Windows 10 17763). O Bochs é usado para fornecer introspecção profunda e emulação de dispositivos.
A API Windows Hypervisor Platform (WHVP) é um conjunto de APIs para acessar as capacidades de hypervisor do Hyper-V. Essa API facilita a implementação de uma máquina virtual totalmente em espaço de usuário, sem necessidade de drivers ou permissões especiais.
Este é um projeto em rápido desenvolvimento. Provavelmente twittarei quando novos recursos forem lançados antes de serem documentados.
Gosto de ter coisas físicas para meus projetos:
Esta é uma ferramenta projetada para fuzzing e introspecção durante a pesquisa de segurança. Usando um hypervisor, técnicas comuns de fuzzing podem ser aplicadas a qualquer alvo, kernel ou userland. Este ambiente permite fuzzing de sistemas inteiros sem necessidade de acesso ao código fonte do alvo. No nível do hypervisor, a cobertura de código pode ser coletada e, se necessário, a emulação do Bochs pode ser usada para fornecer introspecção arbitrária em um ambiente de emulação. Essas informações de cobertura podem ser usadas para avaliar a eficácia dos casos de fuzz. Um caso de fuzz que causou um aumento na cobertura pode ser salvo por ser um caso interessante. Essa entrada pode ser usada posteriormente, sendo incrementada por novas corrupções.
O fuzzing de snapshots é o uso principal desta ferramenta. Onde você tira um snapshot de um sistema em um determinado estado e o salva. Esse snapshot pode então ser carregado para fuzzing, onde um caso de fuzz é injetado e o sistema é retomado. Como a VM pode ser reiniciada de forma muito barata, a VM pode ser reiniciada com frequência. Se o Word leva 5 segundos para inicializar, mas você pode tirar um snapshot no momento em que ele lê seu arquivo, você pode reduzir o caso de fuzz apenas ao que é relevante para uma entrada. Isso permite um loop de fuzzing muito apertado sem necessidade de ter acesso ao código fonte. Como as VMs são sistemas completamente separados, muitas podem ser executadas em paralelo para permitir escalabilidade para todos os núcleos.
Atualmente, esta ferramenta suporta apenas a coleta de cobertura de código, download dinâmico de símbolos para Windows e análise de símbolos/módulos para alvos Windows também. Adicionar suporte para fuzzing será em breve.
Considerando que já escrevi quase todos os recursos aqui antes (cobertura, fuzzing, reinicializações rápidas, etc). Eu espero que este projeto se torne rapidamente pronto para fuzzing, a menos que eu me distraia :D
Estou mirando o final de janeiro para cobertura (concluído!), feedback, listagens de módulos (concluído!), listas de processos, reinicializações rápidas e suporte a símbolos (concluído!). O que o tornaria um fuzzer muito capaz.
O principal alvo suportado é o Windows 10 moderno. Os alvos Windows têm download de símbolos da loja de símbolos. Isso permite cobertura simbólica em alvos Windows imediatamente. No entanto, o código é escrito de forma que a iluminação (enlightenment) do Linux possa ser facilmente adicionada.
Sem qualquer iluminação, qualquer SO que inicialize ainda pode ser submetido a fuzzing e a cobertura básica pode ser coletada.
Antes de relatar problemas de suporte a SO, valide se o problema está no hypervisor/alterações no Bochs tentando inicializar seu alvo usando o Bochs pré-construído padrão sem hypervisor. O Bochs não é comumente usado e pode frequentemente ter bugs que quebram até mesmo tarefas comuns como inicializar o Linux. Especialmente com as rápidas mudanças internas nos usos de CPUID/MSR com as mitigações Spectre/Meltdown sendo incorporadas aos SOs.
Consulte a página de issues no Github para uma lista de problemas. Já coloquei algumas. Alguns desses precisam ser resolvidos rapidamente antes do início do desenvolvimento de fuzzing.
Para compilar isso, você precisa de algumas coisas:
Instale o Visual Studio 2017 e certifique-se de que está atualizado. Estamos usando algumas APIs, cabeçalhos e bibliotecas de ponta aqui.
Eu estava usando a versão cl.exe: Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64
E versão do SDK 10.0.17763.0
Instale o Rust via https://rustup.rs/. Usei rustc 1.32.0-nightly (b3af09205 2018-12-04)
Certifique-se de instalar a toolchain x86_64-pc-windows-msvc, pois apenas 64-bit é suportado para este projeto.
Certifique-se de que cargo está no seu PATH. Isso deve ser o padrão.
Baixe o Python https://www.python.org/ e certifique-se de que ele está no seu PATH para que python possa ser invocado.
Instale o Cygwin 64-bit (https://www.cygwin.com/setup-x86_64.exe) especificamente em C:\cygwin64. Ao instalar o Cygwin, certifique-se de instalar os pacotes autoconf e make.
Vá em "Ativar ou desativar recursos do Windows" e marque a caixa de seleção ao lado de "Hyper-V" e "Windows Hypervisor Platform". Isso requer, é claro, que seu computador suporte Hyper-V.
Este guia de processo de instalação foi verificado no seguinte:
Clean install of Windows 10, Build 17763
rustc 1.33.0-nightly (8e2063d02 2019-01-07)
Microsoft (R) C/C++ Optimizing Compiler Version 19.16.27025.1 for x64
Visual Studio Community 2017 version 15.9.4
applepie commit `f84c084feb487e2e7f31f9052a4ab0addd2c4cf9`
Python 3.7.2 x64
git version 2.20.1.windows.1



autoconf)make)git clone https://github.com/gamozolabs/applepiepython build.py
Este processo de compilação inicial pode levar cerca de 2 minutos; em uma máquina moderna, provavelmente 20 a 30 segundos.
Apenas execute python build.py a partir do diretório raiz deste projeto. Ele deve verificar a sanidade do ambiente e tudo deve "simplesmente funcionar".
Execute python build.py clean para limpar os binários do Bochs e do Rust.
Execute python build.py deepclean para remover completamente todos os binários do Bochs e do Rust, também remove toda a configuração do Bochs. Use isso se você reconfigurar o Bochs de alguma forma.
Leia sobre a configuração do Bochs para descobrir como configurar seu ambiente. Temos alguns requisitos, como sync=none, ips=1000000, e atualmente suporte apenas para único processador. Isso é imposto dentro do próprio código para garantir que você não atire no próprio pé.
Use as configurações incluídas bochservisor_test\bochsrc.bxrc e bochservisor_test_real\bochsrc.bxrc como exemplos. bochservisor_test_real é provavelmente a configuração mais atualizada que você deve consultar como referência.
Os alvos Windows têm iluminação de lista de módulos, que nos permite ver as listagens de todos os módulos no contexto em que estamos executando. Com isso, podemos converter os endereços de instrução para módulo + offset. Esse módulo + offset ajuda a manter as informações de cobertura entre casos de fuzz onde o estado do ASLR muda. Também permite que o módulo seja colorido em uma ferramenta como IDA para ver visualmente qual código foi atingido.
Para alvos Windows, os símbolos serão baixados dinamicamente da loja de símbolos usando seu _NT_SYMBOL_PATH e usando symchk. Sem symchk no caminho, falhará silenciosamente. Com símbolos, uma versão legível da cobertura pode ser salva para visualização. Além disso, com símbolos privados, a cobertura pode ser convertida em source:line para que o código fonte possa ser colorido.
Ok, não há muitos testes, mas existe bochservisor_test, que é um pequeno SO que apenas verifica se tudo inicializa com o hypervisor.
Há também bochservisor_test_real, que é uma configuração que uso para coisas como Windows/Linux. Esta é provavelmente a que será atualizada com mais frequência.
Este código adiciona uma pequena quantidade de código ao Bochs para permitir acesso modular ao contexto da CPU, mapeamento de físico do convidado para a memória de suporte e avanço (stepping) tanto do estado do dispositivo quanto da CPU.
O código principal que você deseja examinar está em lib.rs no projeto Rust bochservisor.
No loop principal da CPU do Bochs, usamos LoadLibrary() para carregar a DLL bochservisor. Esta DLL exporta uma rotina que é o loop da CPU em Rust que será invocado.
O Bochs passará uma estrutura para esta rotina bochs_cpu_loop que conterá ponteiros de função para obter informações do Bochs e para avançar o estado do dispositivo e da CPU dentro dele.
Quando ocorre MMIO ou E/S, o hypervisor será interrompido com uma falha de memória ou uma falha de instrução de E/S. Embora o WHVP forneça uma API de emulação, ela é realmente deficiente e insuficiente.
Em vez disso, usamos o Bochs, que já está presente, e avançamos algumas instruções. Mantendo o estado da CPU do hypervisor sincronizado com o Bochs, podemos alternar dinamicamente entre hypervisor e emulação a qualquer momento (ou pelo menos deveríamos ser capazes).
Isso significa que o estado completo do hypervisor está sempre sincronizado com o Bochs e, portanto, coisas como snapshots do Bochs devem funcionar normalmente e poderiam ser inicializados sem o hypervisor (exceto talvez algum estado CPUID que precisa ser armazenado nas informações do snapshot).
Quando ocorre MMIO ou E/S, executamos um certo número de instruções sob emulação, em vez de apenas emular uma. Devido aos custos de API de entrar e sair do hypervisor, e à probabilidade de operações MMIO semelhantes ocorrerem próximas umas das outras, avançamos algumas instruções. Isso nos permite reduzir a sobrecarga da API e diminui a frequência de VMEXIT. Este é um número ajustável, mas o que está no código provavelmente está lá por um motivo.
Lidamos com interrupções de uma forma realmente interessante. Em vez de agendar interrupções para serem entregues ao hypervisor, lidamos com todas as interrupções na própria emulação do Bochs. Coisas como exceções que ocorrem inteiramente dentro do hypervisor, é claro, não são tratadas pelo Bochs.
Isso também nos dá recursos que o WHVP não suporta, como SMIs (para SMM). A BIOS do Bochs usa SMM por padrão e, sem suporte a SMI, é necessário construir uma BIOS personalizada. Fiz isso na minha primeira iteração disto... não recomendo.
Este projeto foi projetado para fuzzing, no entanto, é tão novo (apenas alguns dias) que não possui nenhum desses recursos.
Algumas das primeiras coisas a vir serão:
Potencialmente, poderíamos ter os dispositivos do Bochs rodando em uma thread em loop em tempo real, e outra thread rodando o hypervisor. Eventos assíncronos seriam comunicados via IPC e permitiriam que os dispositivos fossem atualizados enquanto a execução está no convidado.
Atualmente, tudo acontece em uma única thread, o que significa que o hypervisor deve sair em intervalos para garantir que possamos avançar os dispositivos. É como se tivéssemos escrito nosso próprio escalonador.
Isso pode ser um pouco mais rápido, mas também aumenta a complexidade e adiciona potencial para problemas de concorrência. É difícil dizer se isso algum dia acontecerá.
Não tenho certeza de qual método usarei para coletar cobertura de código, mas haverá pelo menos algumas opções. Variando de preciso a rápido, etc. Todos esses mecanismos de cobertura serão de nível de sistema e não exigirão código fonte ou símbolos dos alvos.
Análise de estruturas do SO para obter informações primitivas, como listagens de processos, listas de módulos, etc. Isso seria então usado para consultar PDBs e obter informações de símbolos.
Relatar falhas de maneira significativa. Idealmente, minidumps seriam bons, pois poderiam ser carregados e processados no WinDbg. Isso pode ser bastante fácil, pois DMPs são apenas memória física e contexto do processador, que já temos.
Tenho algumas técnicas interessantes para análise de causa raiz de bugs que foram historicamente bem-sucedidas. Pretendo trazê-las para cá.
Rastreando páginas sujas e restaurando apenas as coisas modificadas, devemos ser capazes de redefinir VMs muito rapidamente. Isso nos dá a capacidade de realizar fuzzing em velocidades máximas em todos os núcleos de um alvo de sistema. Isso é semelhante ao que fiz no falkervisor, então já está pensado e projetado. Só precisa ser portado para cá.
Fuzzing extremamente rápido que cancela a execução quando ocorre MMIO ou E/S. Isso permite que todo o tempo de CPU seja gasto no hypervisor e nenhum tempo de emulação. Isso tem a desvantagem de não suportar coisas como E/S de disco durante um caso de fuzz, mas é bom.
Alguns dos conceitos centrais deste projeto são modificações mínimas absolutas no Bochs. Isso nos permite manter a parte do Bochs deste repositório atualizada.
O objetivo também é mover o máximo de código possível para Rust e DLLs para tornar o sistema muito mais modular e seguro. Isso espera reduzir as chances de criar bugs de corrupção tolos no próprio hypervisor, causando resultados de fuzz inválidos.
Atualmente, o hypervisor é uma DLL e pode ser trocado sem alterações no Bochs (a menos que a API FFI mude).
Alterações adicionais no próprio Bochs devem ser documentadas claramente, e farei um documento para isso em breve para rastrear as alterações no Bochs que devem ser portadas e reavaliadas com as atualizações do Bochs.