HN divulgado: extensão do Ghidra que exporta partes de um programa como arquivo-objeto
(github.com/boricj)- 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_DIRpara 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:packagese adicionargithubToken=ghp_xxxem${GRADLE_USER_HOME}/gradle.properties - Ou executar
gradle installStandaloneDepspara compilar e instalar a dependência vendorizada do submodule
- Criar um GitHub classic token com a permissão
- 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
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-extensionSó 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
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
https://github.com/boricj/ghidra-delinker-extension/blob/mas...
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?
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
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
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?
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”
Por exemplo, pense numa função como
int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }computeOffsetpode 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óriaA 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
computeOffsetainda pode conter uma armadilha de Turing para impedir exatamente esse tipo de tentativaParece 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
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
paholegera arquivos de cabeçalho C compiláveis a partir de informações ELF DWARFAqui, 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
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
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
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