1 pontos por GN⁺ 2024-08-24 | 1 comentários | Compartilhar no WhatsApp
  • Esta extensão do Ghidra exporta partes de um programa como arquivo-objeto, e o arquivo exportado inclui metadados válidos, como símbolos e tabela de relocação, podendo ser processado novamente no toolchain
  • Os principais usos são patching binário avançado, portabilidade de software, conversão de formatos de arquivo, criação de bibliotecas e a reimplementação de um programa dividindo-o em vários arquivos-objeto em projetos de descompilação
  • As combinações suportadas são COFF x86/x86_64, ELF x86/x86_64/MIPS e OMF x86; COFF MIPS, OMF x86_64 e OMF MIPS não são suportados
  • O usuário seleciona no Listing do Ghidra o intervalo de endereços a extrair, executa o analisador Relocation table synthesizer e depois chama o exportador de arquivo-objeto relocável em File > Export Program…
  • Como a síntese da tabela de relocação depende da precisão do banco de dados do Ghidra, informações incorretas ou ausentes podem quebrar ou omitir relocações, e é mais seguro executar o analisador novamente logo antes da exportação

O que a extensão faz

  • Object file exporter extension for Ghidra é uma extensão do Ghidra que permite exportar partes de um programa como arquivo-objeto
  • O arquivo-objeto gerado inclui metadados válidos, como símbolos e tabelas de relocação
  • Graças a esses metadados, o arquivo-objeto exportado pode ser reutilizado diretamente no toolchain para processamento adicional

Casos de uso

  • Advanced binary patching
    • Em vez de alinhar manualmente as partes originais e modificadas, é possível usar o linker para encaixá-las em conjunto
  • Software ports
    • É possível separar do programa o código independente de sistema e substituir o restante
  • É possível converter um programa ou arquivo-objeto de um formato de arquivo para outro
  • Extração de parte de um programa e criação de biblioteca
    • É possível extrair parte de um programa e reutilizá-la em outro contexto
  • Em projetos de descompilação, é possível dividir o programa em vários arquivos-objeto e reimplementá-lo no estilo Ship of Theseus

Arquiteturas e formatos de arquivo-objeto suportados

  • A matriz de suporte é a seguinte
    • COFF: suporta x86 e x86_64 / não suporta MIPS
    • ELF: suporta x86, x86_64 e MIPS
    • OMF: suporta x86 / não suporta x86_64 nem MIPS

Build e instalação

  • Procedimento de build via CLI
    • Fazer clone do repositório
    • Definir a variável de ambiente GHIDRA_INSTALL_DIR para o diretório de instalação do Ghidra
    • Executar gradle buildExtension
    • O arquivo gerado da extensão do Ghidra será criado no diretório dist/
  • Para baixar o pacote do repositório Maven do GitHub, é necessária autenticação
    • Criar um GitHub classic token com a permissão read:packages e adicionar githubToken=ghp_xxx em ${GRADLE_USER_HOME}/gradle.properties
    • Ou executar gradle installStandaloneDeps para compilar e instalar a dependência vendorizada do submodule
  • Procedimento de instalação
    • Baixar a extensão na releases page ou compilá-la localmente
    • No Ghidra, instalar a extensão em File > Install Extensions…
    • Na janela CodeBrowser, ativar o plugin RelocationTableSynthesizedPlugin em File > Configure > Experimental

Fluxo de uso e cuidados

  • Procedimento básico de uso
    • Selecionar no Listing o conjunto de endereços a extrair
    • Executar o analisador Relocation table synthesizer, fornecido em modo one-shot
    • Em File > Export Program…, chamar o exportador de arquivo-objeto relocável
  • As relocações reconstruídas podem ser vistas em Window > Relocation table (synthesized)
  • Um relatório de avaliação detalhado pode ser ativado definindo a opção Evaluation report policy do analisador Relocation table synthesizer na caixa de diálogo Analysis > Auto Analyze...
  • Não é necessário fazer toda a engenharia reversa do programa antecipadamente antes de usar a extensão
  • Um delinking bem-sucedido geralmente depende bastante dos metadados na parte a exportar e nas referências externas
    • Funções e ponteiros usados como locais de relocação
    • Footprint de símbolos usados como alvos de relocação
    • Referências entre ambos
  • O analisador Relocation table synthesizer depende da precisão do banco de dados do Ghidra
    • Informações imprecisas ou ausentes podem levar a relocações quebradas ou ausentes durante a análise
  • O exportador de arquivo-objeto depende dos resultados da análise do Relocation table synthesizer
    • Em caso de dúvida, é preciso executar o analisador logo antes de exportar o arquivo-objeto para garantir que a tabela de relocação esteja atualizada

Como funciona

  • Um arquivo-objeto é composto por três partes
    • Bytes de seção relocável
    • Tabela de símbolos

      • Tabela de relocação
      • O que o linker faz ao criar um executável a partir de vários arquivos-objeto
      • Posiciona as seções na memória
      • Calcula os endereços dos símbolos no espaço de endereçamento virtual
      • Aplica relocações aos bytes da seção com base nos endereços finais dos símbolos
      • Normalmente, quando esse processo termina, a tabela de relocação é descartada
      • Se os símbolos de depuração não forem mantidos, a tabela de símbolos também é descartada, restando apenas os bytes de seção não relocáveis
      • Esta extensão pode recriar esses dados por meio de uma análise cuidadosa, permitindo delinkar o programa de volta para um arquivo-objeto

1 comentários

 
GN⁺ 2024-08-24
Comentários no Hacker News
  • Que bom ver isso por aqui. Acho que é um projeto realmente muito legal, e ajudei a adicionar suporte a MS COFF
    Só que meu PR inicial era bem inferior ao suporte a ELF que já existia, então, se aparecer algum problema, provavelmente a culpa pode ser minha. Mesmo assim, dá para ver que está melhorando
    Ainda não usei em nenhum trabalho grande, mas a coisa mais divertida que fiz foi delinkar um executável Hello World compilado com Visual Studio 2003, relinkar de novo com GCC+glibc no Linux x86 e depois relinkar mais uma vez com MinGW+msvcrt
    Qualquer coisa maior que Hello World ainda é difícil, e como eu também não conheço muito bem o Ghidra, ainda não encontrei uma boa forma de escolher o intervalo a ser delinkado em binários grandes
    Por coincidência, o pacote derivado do Nixpkgs desta ferramenta foi mesclado hoje, então no NixOS unstable dá para instalar com ghidra.withExtensions. O caminho é ghidra-extensions.ghidra-delinker-extension
    Só que saiu uma versão nova há alguns dias e eu não fiz rebase do PR, então no momento ainda está na versão antiga, mas pretendo enviar uma atualização em breve

    • Uma forma de rastrear o alvo a ser delinkado é usar pastas e fragmentos na árvore do programa
      Por exemplo, se você tiver um programa no Ghidra em que descobriu os nomes e os intervalos dos vários arquivos-objeto que compunham o executável original, pode clicar com o botão direito nessa pasta ou fragmento > Select Addresses para selecionar tudo de uma vez
      Tanto o analisador de síntese de relocação quanto a exportação também podem ser automatizados por script ou pelo gerenciador de árvore do programa, reduzindo a necessidade de selecionar manualmente o intervalo desejado e executar o analisador e a exportação à mão
  • Parece bem interessante e me deu vontade de voltar a olhar um projeto de engenharia reversa de jogos que abandonei há alguns anos
    Seria ótimo ter um exemplo completo mostrando até o fim como usar isso e como aproveitar o resultado gerado

  • Fico curioso sobre quanto trabalho dá descobrir quais seções do executável exportar
    Para um jogo Win32 relativamente moderno, ali por volta de 2008~2015, é realista exportá-lo como arquivos-objeto e depois compilar/linkar de volta um executável completo em poucas horas?

    • Desde que você não corte no meio de variáveis ou funções, dá para exportar com bastante liberdade, e não é obrigatório seguir exatamente os limites originais dos arquivos-objeto
      O que exportar já é outra questão, e exige conhecimento do programa. Com símbolos de depuração fica muito mais fácil, e mesmo sem eles, quando você já montou um banco de dados do Ghidra preciso o bastante para permitir a exportação, em geral já passa a ter uma boa noção de onde está cada coisa
      No caso de uso do post de submissão, a primeira issue foi aberta no começo de julho, e em meados de agosto já havia um executável relinkado funcionalmente idêntico
      Só que naquela época ainda havia muitos bugs a corrigir na exportação COFF e também havia pontos a ajustar no analisador i386, então a expectativa é que hoje outras pessoas esbarrem menos nesses mesmos problemas
      Não sei dizer quanto tempo levaria, mas, a menos que você tenha símbolos de depuração e esteja realmente com muita sorte, a chance é que leve mais do que algumas horas. Um engenheiro reverso experiente talvez consiga fazer algo rodar nesse tempo, mas pode travar no meio da primeira tela de carregamento, e é o tipo de trabalho em que você talvez nem saiba quanto vai demorar até terminar
  • Parece muito legal. Talvez um dia isso ajude a desmontar programas existentes com mais facilidade para usá-los do jeito que você quiser
    Há pesquisas como o LLM Compiler da Meta e decompilação com LLM, e dividir programas em partes e substituir algumas delas pode ser uma área interessante, rica em dados, para LLMs explorarem e melhorarem por conta própria
    Também parece haver muitos tokens interessantes escondidos aí do ponto de vista de treinamento, além de muita coisa que poderia ser feita com isso

  • Sinceramente, isso soa como mágica. Preciso entender como isso é possível

    • Em termos simples, um arquivo-objeto é composto por três partes: bytes de seção relocáveis, uma tabela de relocação e uma tabela de símbolos
      Quando o linker cria um executável a partir de vários arquivos-objeto, ele posiciona as seções na memória, calcula os endereços dos símbolos no espaço de endereçamento virtual e depois aplica as relocações aos bytes das seções com base nos endereços finais dos símbolos
      O truque do delinking é descobrir onde essas relocações foram aplicadas e revertê-las para recuperar bytes relocáveis. Depois, com base no que foi revertido, gerar a tabela de relocação e a tabela de símbolos, empacotar tudo e obter um arquivo-objeto
      A parte realmente difícil é a análise para encontrar os pontos de relocação. A maior parte disso fica a cargo do Ghidra, mas é necessário transformar referências em pontos de relocação. Em x86 é relativamente fácil; em MIPS é um pesadelo. Criei essa extensão para automatizar tanto a coleta dos dados necessários quanto a serialização do arquivo-objeto
    • Não deve ser fácil em todas as arquiteturas de CPU, mas também não é algo tão absurdo quanto parece. Basicamente, a diferença entre um arquivo-objeto e um executável ou biblioteca compartilhada não é tão grande
      Fora das plataformas da Microsoft, em muitos casos o formato de arquivo real chega a ser o mesmo, como no caso do ELF
      A grande diferença é a relocação. Arquivos-objeto contêm informações detalhadas de relocação, enquanto executáveis normalmente não. Imagens executáveis do Windows têm apenas as relocações mínimas que apontam os endereços de código e dados que precisam ser ajustados quando o executável é relocado, e a própria imagem só pode ser relocada como um todo em relação ao endereço-base da imagem
      Já arquivos-objeto contêm relocações no nível de símbolo. Para reconstruir essa informação com precisão, é preciso anexar ao disassembly informações bem precisas sobre os símbolos
      Outra grande diferença é que o arquivo-objeto ainda não foi linkado. Nenhum símbolo foi resolvido. Isso, na verdade, é até mais fácil de corrigir: durante o delinking, se um símbolo estiver fora do escopo atual, em geral basta convertê-lo em símbolo não resolvido. Depois, ao relinkar, outro arquivo-objeto ou biblioteca precisará fornecer esse símbolo para que a ligação seja refeita
      Há também diferenças menores, como não parecer haver ponto de entrada, mas isso não tem grande importância
      Portanto, o limite em que se recorta um arquivo-objeto a partir de uma imagem executável ou objeto compartilhado é, na prática, arbitrário. Quando o programa foi compilado originalmente, ele foi dividido em unidades de tradução, mas na etapa de linkedição esses limites não importam tanto. Claro que, se você quiser uma decompilação correspondente, errar os limites do arquivo-objeto pode tornar isso muito difícil, então é melhor descobri-los quando possível
      Já lidei com esse problema na prática, mas posso estar errando alguns detalhes, então vale tomar isso apenas como referência. Tentei escrever um post de blog sobre arquivos-objeto, e já existem alguns textos bons sobre isso
  • Fico me perguntando se esse processo é totalmente seguro. O sucesso é sempre garantido, ou a análise opera de forma conservadora?
    Por exemplo, se faltar algum fragmento, dado ou funcionalidade no ELF, o delinking falha?

    • A resposta é complicada
      Meu analisador depende de um banco de dados do Ghidra preciso, pelo menos para a parte que se quer exportar. Fiz um bom esforço para registrar em log vários problemas que precisam ser corrigidos, mas não dá para ver o que não existe
      Em especial, referências ausentes e variáveis truncadas não são detectadas e podem levar a comportamentos indefinidos bem estranhos
      Há formas de rastrear alguns desses problemas. O melhor método que encontrei até agora é relinkar o executável para outro endereço-base e evitar mapear a faixa de endereços do programa original. Assim, pontos de relocação absoluta que passaram despercebidos causam falha de segmentação e podem ser depurados. Mas isso só funciona se o alvo tiver MMU
      Variáveis truncadas são especialmente difíceis de rastrear se você não suspeitar delas antes. Isso porque o que acaba sendo corrompido é a memória após a variável truncada. Casos em que um inteiro é confundido com um ponteiro também são difíceis de rastrear. O valor inteiro passa a depender do endereço em que o símbolo-alvo foi colocado, então o comportamento do programa fica inconsistente, e isso é ainda pior em programas carregados em regiões muito baixas do espaço de endereçamento
      Ainda assim, se o banco de dados do Ghidra for preciso o suficiente, e você reexportar usando o mesmo formato de arquivo-objeto original e depois usar a mesma plataforma e a mesma toolchain, é possível fazer delinking com sucesso de megabytes de código e dados do programa. Se o linker conseguiu fazer, eu diria que deveria ser possível desfazer
      Por outro lado, a história muda se você começar a fazer delinking cruzado, por exemplo pegando um executável ELF Linux i386 e gerando um arquivo-objeto COFF para usar numa toolchain Windows i386. Se a exportação conseguir representar as relocações, você talvez obtenha um arquivo-objeto relocável funcional, mas ainda terá de lidar com incompatibilidades de ABI. É possível, mas eu não recomendaria como primeiro projeto
      Resumindo, dependendo do que você estiver fazendo e da precisão do banco de dados do Ghidra, a experiência pode variar de “simplesmente funciona” até “rezar por misericórdia a Cthulhu”
    • Se a intenção da pergunta era essa, então não, não é totalmente seguro, nem poderia ser
      Por exemplo, pense numa função como int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }
      computeOffset pode ser arbitrariamente complexa e, se você quiser ofuscar, pode até ser feita de propósito para dificultar a análise. Nada impede essa função de acessar posições arbitrárias da memória
      A menos que se tente absolutamente todas as entradas possíveis, não há como saber se ela acessa símbolos que o linker já definiu na memória, e computeOffset ainda pode conter uma armadilha de Turing para impedir exatamente esse tipo de tentativa
  • Parece interessante conectar isso a uma ideia que eu já tinha imaginado antes, mas nunca cheguei a implementar: gerar arquivos de cabeçalho a partir de informações de depuração e, se necessário, pedir para um LLM organizá-los

    • Já existem algumas tentativas nessa linha. Para o Microsoft Program Database, existe isto:
      https://github.com/wbenny/pdbex
      Quanto à parte de organizar com LLM, ainda não parece haver muitos casos de grande sucesso aplicando modelos de LLM à engenharia reversa. Até me pergunto se esta talvez seja uma área em que as limitações da arquitetura dos LLMs fiquem mais evidentes
      Não sou especialista, mas se eu tivesse que apostar, em muitos casos de uso de engenharia reversa, modelos de difusão talvez fossem mais interessantes
      Não é exatamente a mesma coisa, mas o Binary Ninja tem um recurso chamado Sidekick que tenta organizar a desmontagem com LLM. Pessoalmente, não achei muito impressionante, mas pode ser útil para alguém
    • pahole gera arquivos de cabeçalho C compiláveis a partir de informações ELF DWARF
      Aqui, LLMs não parecem muito relevantes. Ou o arquivo de cabeçalho contém corretamente todos os tipos exportados do executável com seus valores originais e, portanto, é utilizável, ou está errado ou incompleto. Fazer um LLM inventar algo a mais não ajuda
      O Ghidra também tem uma função nativa para exportar estruturas de dados, e isso pode ser gerado a partir de estruturas DWARF. Clique com o botão direito -> Export to C header
    • Há alguns anos, eu criei uma ferramenta para gerar e inserir automaticamente um fuzzer com reconhecimento de tipos para APIs em C a partir de informações DWARF: https://github.com/intel/fffc
      Parte disso incluía a geração de cabeçalhos e, depois, a geração de mutadores que podiam ser ajustados para respeitar restrições de tipo
      Se acrescentássemos LLMs, talvez desse para usá-los para nomear coisas como estruturas anônimas, mas não sei se isso seria uma boa ideia. Algo mais interessante talvez fosse fazer um LLM resumir em linguagem natural restrições de tipo conhecidas para fins de documentação
    • Isso é um pouco diferente, mas já pensei em melhorar a experiência de depuração gerando símbolos de depuração para o arquivo-objeto exportado com base no conteúdo do banco de dados do Ghidra
      Ainda não implementei isso porque, até agora, consegui me virar sem. Além disso, isso soa como uma toca de coelho bem profunda, e a toca de coelho em que já estou metido já é grande o bastante
  • Parece realmente muito legal, e também se relaciona com uma ideia que eu tinha para modding de jogos. A série de posts do blog sobre decompilação de Tenchu também foi ótima

    • Preciso voltar a esse projeto algum dia. Tive sessões demais seguidas rastreando versões e precisei dar uma pausa, e no meio disso a side quest de delinking continua crescendo sem controle
  • Não tenho uso imediato para isso no que estou fazendo agora, mas parece ser uma ferramenta que teria sido realmente útil no passado
    Espero ter tempo ou oportunidade para testar em breve