Esta semana, a NSA (
Agência de Segurança Nacional ) de repente fez um presente para a humanidade, abrindo fontes de sua estrutura de engenharia reversa de software. Comunidade de engenheiros reversos e especialistas em segurança com grande entusiasmo começou a explorar o novo brinquedo. De acordo com o feedback, é uma ferramenta realmente incrível, capaz de competir com soluções existentes, como IDA Pro, R2 e JEB. A ferramenta é chamada Ghidra e os recursos profissionais estão cheios de impressões dos pesquisadores. Na verdade, eles tinham um bom motivo: nem todos os dias as organizações governamentais fornecem acesso a suas ferramentas internas. Eu, como engenheiro reverso profissional e analista de malware, também não podia passar. Decidi passar um fim de semana ou dois e ter uma primeira impressão da ferramenta. Eu havia brincado um pouco com a desmontagem e decidi verificar a extensibilidade da ferramenta. Nesta série de artigos, explicarei o desenvolvimento do complemento Ghidra, que carrega um formato personalizado, usado para resolver a tarefa CTF. Como é uma estrutura grande e eu escolhi uma tarefa bastante complicada, dividirei o artigo em várias partes.
No final desta parte, espero configurar o ambiente de desenvolvimento e criar um módulo mínimo, capaz de reconhecer o formato do arquivo WebAssembly e sugerir o desmontador correto para processá-lo.
Vamos começar com a descrição da tarefa. No ano passado, a empresa de segurança FireEye sediou o concurso CTF, chamado flare-on. Durante o concurso, os pesquisadores tiveram que resolver doze tarefas, relacionadas à engenharia reversa. Uma das tarefas foi pesquisar o aplicativo da Web, construído com o WebAssembly. É um formato executável relativamente novo e, até onde eu sei, não há ferramentas perfeitas para lidar com isso. Durante o desafio, tentei várias ferramentas para derrotá-lo. Esses eram scripts simples do github e descompiladores conhecidos, como IDA pro e JEB. Surpreendentemente, parei no chrome, que fornece um bom desmontador e depurador para o WebAssembly. Meu objetivo é resolver o desafio com o ghidra. Vou descrever o estudo da maneira mais completa possível e fornecer todas as informações possíveis para reproduzir meus passos. Talvez, como pessoa que não tenha muita experiência com o instrumento, eu possa entrar em alguns detalhes desnecessários, mas é assim que é.
A tarefa que vou usar para estudo pode ser baixada do
site de desafio do flareon5. Há o arquivo 05_web2point0.7z: arquivo criptografado com uma palavra assustadora
infectada . Existem três arquivos no arquivo morto: index.html, main.js e test.wasm. Vamos abrir o arquivo index.html em um navegador e verificar o resultado:

Bem, é com isso que vou trabalhar. Vamos começar com o estudo html, especialmente porque é a parte mais fácil do desafio. O código html não contém nada, exceto o carregamento do script main.js.
<!DOCTYPE html> <html> <body> <span id="container"></span> <script src="./main.js"></script> </body> </html>
O script também não faz nada complicado, apesar de parecer um pouco mais detalhado. Ele apenas carrega o arquivo test.wasm e o usa para criar uma instância do WebAssembly. Em seguida, ele lê o parâmetro "q" do URL e o passa para a correspondência de método, exportada pela instância. Se a string no parâmetro estiver incorreta, o script mostra a imagem que vimos acima, em termos de desenvolvedores do FireEye chamados "Pile of cocô".
let b = new Uint8Array(new TextEncoder().encode(getParameterByName("q"))); let pa = wasm_alloc(instance, 0x200); wasm_write(instance, pa, a); let pb = wasm_alloc(instance, 0x200); wasm_write(instance, pb, b); if (instance.exports.Match(pa, a.byteLength, pb, b.byteLength) == 1) {
A solução da tarefa é encontrar o valor do parâmetro q que faz a função "combinar" retornar "Verdadeiro". Para fazer isso, vou desmontar o arquivo test.wasm e analisar o algoritmo da função Match.
Não há surpresas, e tentarei fazer isso em Ghidra. Mas primeiro tenho que instalá-lo. A instalação pode (e deve) ser baixada em
https://ghidra-sre.org/ . Como está escrito em Java, quase não há requisitos especiais para instalação, não requer nenhum esforço especial para instalar. Tudo o que você precisa é descompactar o arquivo morto e executar o aplicativo. A única coisa necessária é atualizar o JDK e o JRE para a versão 11.
Vamos criar um novo projeto ghidra (
Arquivo-> Novo Projeto ) e chamá-lo de “wasm” /

Em seguida, adicione ao projeto o arquivo test.wasm (
Arquivo → Importar arquivo ) e veja como o ghidra pode lidar com isso.

Bem, isso não pode fazer nada. Ele não reconhece o formato e não pode desmontar nada, portanto, é absolutamente impotente para lidar com esta tarefa. Finalmente chegamos ao assunto do artigo. Não há mais nada a fazer, mas escrever um módulo, capaz de carregar o arquivo wasm, analisá-lo e desmontar seu código.
Antes de tudo, estudei toda a documentação disponível. Na verdade, existe apenas um documento adequado, mostrando o processo de desenvolvimento de complementos: slides GhidraAdvancedDevelopment. Vou seguir o documento, dando uma descrição detalhada.
Infelizmente, o desenvolvimento de complementos requer o uso de eclipse. Toda a minha experiência com o eclipse é o desenvolvimento de dois jogos de gdx para o Android em 2012. Foram duas semanas cheias de dor e sofrimento, depois das quais eu o apaguei da minha mente. Espero que após 7 anos de desenvolvimento seja melhor do que costumava ser.
Vamos baixar e instalar o eclipse no
site oficial.
Em seguida, instale a extensão para o desenvolvimento ghidra:
Ir para o eclipse
Ajuda → menu
Instalar Novo Software , clique no botão
Adicionar e escolha GhidraDev.zip em / Extensions / Eclipse / GhidraDev /. Instale e reinicie a extensão. A extensão, adiciona modelos ao novo menu do projeto, permite depurar módulos do eclipse e compilar o módulo no pacote de distribuição.
Conforme segue nos documentos dos desenvolvedores, as seguintes etapas devem ser executadas para adicionar o módulo ao processamento do novo formato binário:
- Crie classes, descrevendo estruturas de dados
- Desenvolva o carregador. O carregador deve ser herdado da classe AbstractLibrarySupportLoader . Ele lê todos os dados necessários do arquivo, verifica a integridade dos dados e converte os dados binários em representação interna, preparando-os para análise
- Desenvolva o analisador. O analisador é herdado da classe AbstractAnalyzer . Ele pega as estruturas de dados preparadas pelo carregador e as anota (não tenho muita certeza do que isso significa, mas espero entender durante o desenvolvimento)
- Adicione processador. Ghidra tem uma abstração: Processador. Ele foi escrito em linguagem declarativa interna e descreve o conjunto de instruções, o layout da memória e outros recursos arquitetônicos. Vou abordar este tópico, escrevendo o desmontador.
Agora, quando tivermos toda a teoria necessária, é hora de criar o projeto do módulo. Graças à extensão eclipse GhidraDev instalada anteriormente, temos o modelo do módulo no menu
Arquivo-> Novo projeto .

O Assistente pergunta quais componentes são necessários. Como foi descrito anteriormente, precisaríamos de dois deles: carregador e analisador.

O assistente cria o esqueleto do projeto com todas as partes necessárias: analisador em branco no arquivo WasmAnalyzer.java, carregador em branco no arquivo WasmLoader.java e esqueleto do idioma no diretório / data / languages.

Vamos começar com o carregador. Como foi mencionado, ele deve ser herdado da classe AbstractLibrarySupportLoader e possui três métodos a serem sobrecarregados:
- getName - este método deve ser o nome interno do carregador. O Ghidra usa-o em vários lugares, por exemplo, para ligar o carregador ao processador
- findSupportedLoadSpecs - retorno de chamada, executado, quando o usuário escolhe o arquivo a ser importado. Nesse carregador de retorno de chamada, você deve decidir se é capaz de processar o arquivo e retornar a instância da classe LoadSpec, informando ao usuário como o arquivo pode ser processado.
- load - retorno de chamada, executado, após o arquivo carregado pelo usuário. Nesse método, o carregador analisa a estrutura do arquivo e carrega no ghidra. O descreverá em mais detalhes no próximo artigo
O primeiro e o método mais simples é getName, apenas retorna o nome do carregador
public String getName() { return "WebAssembly"; }
O segundo método a ser implementado é o findSupportedLoadSpecs. É chamado por ferramenta durante a importação do arquivo e deve verificar se o carregador é capaz de processar o arquivo. Se seu método puder retornar o objeto da classe
LoadSpec , informando qual objeto é usado para carregar o arquivo e qual processador desmontará seu código.
O método começa na verificação do formato. Conforme segue a
especificação , os primeiros oito bytes do arquivo wasm devem ser a assinatura “\ 0asm” e a versão.
Para analisar o cabeçalho, criei a classe WasmHeader, implementando a interface
StructConverter , que é a interface base para descrever dados estruturados. O construtor do WasmHeader recebe o objeto
BinaryReader - abstração, usado para ler dados da fonte binária sendo analisada. O construtor usa para ler o cabeçalho do arquivo de entrada
private byte[] magic; private byte [] version; public WasmHeader(BinaryReader reader) throws IOException { magic = reader.readNextByteArray(WASM_MAGIC_BASE.length()); version = reader.readNextByteArray(WASM_VERSION_LENGTH); }
O Loader usa esse objeto para verificar a assinatura do arquivo. Em seguida, em caso de sucesso, procure o processador apropriado. Ele chama a consulta de método da classe
QueryOpinionService e passa o nome do carregador ("Webassembly"). O OpinionService está procurando pelo processador associado a esse carregador e o devolve.
List<QueryResult> queries = QueryOpinionService.query(getName(), MACHINE, null);
Certamente, ele não retorna nada, porque o ghidra não conhece o processador, chamado WebAssembly, e é necessário defini-lo. Como eu disse antes, o assistente criou o esqueleto do idioma no diretório data / languages.

No estágio atual, existem dois arquivos que podem ser interessantes: Webassembly.opinion e Wbassembly.ldefs. O arquivo .opinon define a correspondência entre o carregador e o processador.
<opinions> <constraint loader="WebAssembly" compilerSpecID="default"> <constraint primary="1" processor="Webassembly" size="16" /> </constraint> </opinions>
Ele contém xml simples com poucos atributos. É necessário definir o nome do carregador para atribuir "carregador" e o nome do processador no atributo "processador", ambos são "Webassembly". Nesta etapa, preencherei outros parâmetros com os valores aleatórios. Assim que souber mais sobre a arquitetura do processador Webassembly, vou alterá-los para corrigir os valores.
O arquivo .ldefs descreve os recursos do processador, que devem executar o código do arquivo.
<language_definitions> <language processor="Webassembly" endian="little" size="16" variant="default" version="1.0" slafile="Webassembly.sla" processorspec="Webassembly.pspec" id="wasm:LE:16:default"> <description>Webassembly Language Module</description> <compiler name="default" spec="Webassembly.cspec" id="default"/> </language> </language_definitions>
O atributo "processador" deve ser o mesmo que o processador de atributo do arquivo .opinion. Vamos deixar outros campos intocados. Mas lembre-se da próxima vez que é possível definir a densidade do registro (atributo "tamanho"), arquivo que descreve a arquitetura do processador "processorspec" e o arquivo, contendo a descrição do código na linguagem declarativa especial "slafile". Será útil trabalhar na desmontagem.
Agora, é hora de voltar ao carregador e retornar as especificações do carregador.
Está tudo pronto para o teste. O plug-in para GhidraDev adicionou a opção de execução "
Executar → Executar como → Ghidra " ao eclipse:

Ele executa o ghidra no modo de depuração e implanta o módulo, oferecendo uma grande oportunidade de trabalhar com a ferramenta e, ao mesmo tempo, usar o depurador para corrigir erros no módulo que está sendo desenvolvido. Mas, neste estágio simples, não há razão para usar um depurador. Como antes, vou criar um novo projeto, importar arquivo e ver se meus esforços valeram a pena. Diferentemente da última vez, o arquivo é reconhecido como WebAssembly e o carregador propõe o processador correspondente para ele. Isso significa que tudo funciona e meu módulo é capaz de reconhecer o formato.

No próximo artigo, estenderei o carregador e o farei não apenas reconhecer, mas também descrever a estrutura do arquivo wasm. Acho que, nesta fase, depois que o ambiente for configurado, será fácil fazer isso.
O código do módulo está disponível no repositório do
github .