Engenharia reversa
Introdução ao Ghidra com um binário benigno que você compila
Compile uma verificação de licença de brinquedo em C de duas formas e use o Ghidra para achar a string, seguir referências e comparar com seu código.
Entenda o que a compilação descarta, a diferença entre desassembladores e descompiladores e os limites legais antes de analisar um binário.
Engenharia reversa é o trabalho de descobrir como algo foi construído a partir da análise da coisa pronta. Em software, a coisa pronta costuma ser um programa compilado: um executável, uma biblioteca ou uma imagem de firmware. O código-fonte não está disponível ou nunca foi publicado, então você começa pelo código de máquina e volta até entender o que o programa faz e por quê.
As pessoas fazem isso por motivos variados, e a maioria é comum. Um engenheiro de segurança confere o binário de um fornecedor antes de confiar nele. Um desenvolvedor tenta fazer um formato de arquivo fechado funcionar com uma ferramenta aberta. Um estudante quer ver o que um compilador produz de fato. Este artigo explica o que a compilação descarta, a diferença entre análise estática e dinâmica, desassembladores e descompiladores, os motivos legítimos para esse trabalho e os limites legais que você precisa conhecer antes de começar. O próximo artigo da série faz uma primeira análise real.
Um compilador transforma código legível por humanos em instruções que o processador executa. O resultado está correto para a máquina e diz pouco sobre a intenção. Várias coisas desaparecem no caminho:
for vira uma comparação e um salto. Um if vira um salto condicional para outro ponto do arquivo.Cada perda deixa o passo seguinte mais incerto. O engenheiro reverso reconstrói parte desses detalhes a partir de pistas: as strings que o programa usa, as funções que ele importa do sistema operacional e a forma como os dados passam pelos registradores.
Você pode ver uma versão pequena disso com Python 3.12. O exemplo abaixo é uma função de três linhas com um comentário. Bytecode do Python não é código de máquina nativo, então trate isso como analogia, não como modelo da saída do C. Mesmo assim, mostra o ponto.
# Teaching constant, not real access control.
def check_pin(user_pin):
training_pin = 2468 # the comment never reaches the bytecode
return user_pin == training_pinCompile e desmonte com estes comandos:
python -c "import py_compile; py_compile.compile('check_pin.py', cfile='check_pin.pyc', doraise=True)"
python -c "import marshal, dis; data = open('check_pin.pyc','rb').read()[16:]; code = marshal.loads(data); fn = [c for c in code.co_consts if hasattr(c,'co_code')][0]; print('name:', fn.co_name, '| constants:', fn.co_consts, '| locals:', fn.co_varnames); dis.dis(fn)"Na máquina onde este artigo foi conferido, a saída foi:
name: check_pin | constants: (None, 2468) | locals: ('user_pin', 'training_pin')
2 0 RESUME 0
3 2 LOAD_CONST 1 (2468)
4 STORE_FAST 1 (training_pin)
4 6 LOAD_FAST 0 (user_pin)
8 LOAD_FAST 1 (training_pin)
10 COMPARE_OP 40 (==)
14 RETURN_VALUEDuas coisas chamam atenção. O comentário sumiu: uma busca pelo texto dele no arquivo compilado não encontra nada. Os nomes sobreviveram, porque o Python os mantém no objeto de código. Um programa C compilado normalmente não mantém nenhum dos dois, então o mesmo exercício em um binário nativo é mais difícil. Use isso como modelo mental, não como promessa para todas as linguagens.
Os métodos de engenharia reversa se dividem em dois grupos amplos.
A análise estática examina o arquivo sem executá-lo. Você lê as instruções, lista imports e strings, mapeia funções e referências cruzadas e monta um modelo do programa. O arquivo nunca é executado, mas suas ferramentas ainda o interpretam, então mantenha-as atualizadas e trabalhe em uma máquina isolada quando a amostra puder ser hostil. O trabalho estático é mais lento, mas é repetível, e a maioria das pessoas o faz primeiro.
A análise dinâmica executa o programa sob observação. Um depurador o pausa em breakpoints para você ler registradores e memória. Um rastreador registra chamadas de sistema, acesso a arquivos ou conexões de rede. Um sandbox mostra o que muda no disco. O trabalho dinâmico mostra o comportamento real, inclusive valores que só existem em tempo de execução, mas significa executar código em que você talvez não confie. Faça isso em uma máquina virtual descartável, sem contas reais, sem pastas compartilhadas, sem dados sensíveis e, de preferência, sem rede.
A maior parte do trabalho real combina os dois. Uma passada estática encontra a função que verifica uma senha. Uma execução no depurador mostra então qual é, de fato, a entrada e a saída.
Um desassemblador converte bytes em instruções assembly para uma arquitetura de processador específica. A tradução é mecânica: um padrão de bytes vira um mnemônico, como mov, cmp ou jne, com seus operandos. O resultado corresponde exatamente à máquina, mas continua de baixo nível. Ler milhares de instruções para entender uma funcionalidade leva muito tempo.
Um descompilador vai além. Ele tenta produzir pseudocódigo de nível mais alto, geralmente parecido com C, a partir dos mesmos bytes. Ele reconstrói limites de funções, o fluxo de controle, como if e while, e tipos de dados aproximados. A saída é uma interpretação, não uma cópia do código-fonte original. Pode errar tipos, unir coisas que estavam separadas ou inventar uma estrutura que nunca existiu. Um bom hábito é ler o código descompilado ao lado do desassembly e confiar no assembly quando os dois divergirem.
O Ghidra é uma das ferramentas gratuitas mais usadas que fazem as duas coisas. É um framework de engenharia reversa de software criado e mantido pela Research Directorate da Agência de Segurança Nacional dos Estados Unidos, licenciado sob Apache-2.0, e roda em Windows, macOS e Linux. Suporta muitas arquiteturas de processador e formatos de arquivo, e pode ser estendido com Java ou Python. O README pede JDK 25 (64 bits) para executar uma versão oficial. Existem outras ferramentas, comerciais e abertas, e as ideias deste artigo valem para todas elas.
A engenharia reversa tem alguns usos legítimos.
Interoperabilidade. Seu software precisa trocar dados com um programa ou formato fechado, e o fornecedor não publica os detalhes. A União Europeia trata esse caso diretamente, como explica a seção jurídica.
Pesquisa de vulnerabilidades. Você examina um software que tem autorização para testar, encontra falhas e as reporta para que o fornecedor corrija. Normalmente segue a divulgação coordenada: um relato privado, um prazo para correção e só depois os detalhes públicos.
Análise de malware. Defensores desmontam um arquivo suspeito para saber o que ele faz, quais sistemas toca e como detectá-lo. Boa parte do conhecimento público sobre ameaças vem desse trabalho.
Aprendizado. Ler um programa compilado é uma das melhores formas de entender como computadores executam código: layout de memória, convenções de chamada e a distância entre o que você escreve e o que de fato roda.
Verificação de alegações. Você pode querer checar o que um programa envia pela rede ou guarda localmente, sobretudo quando há privacidade em jogo. Isso toca as mesmas questões legais de qualquer outra análise.
Esta é uma visão geral, não aconselhamento jurídico. As leis mudam de país para país, e contratos podem acrescentar condições que valem em qualquer lugar. Três camadas importam mais.
1. Termos de licença. A maioria das licenças comerciais proíbe engenharia reversa, salvo quando a lei permite. Licenças de código aberto funcionam de outro jeito e costumam permitir ler e modificar o código, mas ainda têm condições. Ler um código-fonte que você recebeu não é o mesmo que fazer engenharia reversa de um binário, e nenhum dos dois é o mesmo que invadir um serviço. Antes de analisar um produto que você não escreveu, leia a licença.
2. União Europeia. A Diretiva 2009/24/CE sobre a proteção jurídica de programas de computador inclui, no artigo 6, uma exceção de interoperabilidade. Em resumo, um usuário legítimo, ou alguém agindo por ele, pode descompilar um programa quando isso é indispensável para obter as informações necessárias para fazer um programa criado de forma independente funcionar com outros. Há condições. A informação não pode já estar facilmente disponível, o trabalho deve se limitar às partes do programa necessárias à interoperabilidade, e a informação não pode ser usada para outros fins nem compartilhada além do que a interoperabilidade exige. Ela também não pode servir para desenvolver um programa substancialmente semelhante, e a exceção não pode prejudicar sem razão os interesses legítimos do titular dos direitos. O artigo 6 trata de interoperabilidade, não de curiosidade geral.
3. Estados Unidos. A seção 1201 do Digital Millennium Copyright Act proíbe burlar medidas tecnológicas que controlam o acesso a obras protegidas. Essa proibição é separada da violação de direitos autorais, então uma pessoa pode quebrar a regra sem copiar nada. A lei prevê exceções, entre elas a seção 1201(j), que cobre certos testes de segurança sob condições definidas. O Copyright Office também conduz, a cada três anos, um processo de regulamentação que pode acrescentar exceções temporárias. A página dele sobre a seção 1201, linkada nas fontes, descreve o processo. Na data da conferência, a décima rodada de regulamentação já havia começado, e as exceções renovadas nela valem por três anos, de outubro de 2027 a outubro de 2030. Leia a lei e as regras vigentes antes de contar com qualquer exceção. Vale o texto, não um resumo.
Outros países têm regras próprias. Trate as três camadas acima como exemplos de como as perguntas são formuladas, não como um mapa da sua jurisdição.
Disso decorrem três pontos práticos. Analise software que você possui, seus próprios builds ou material que você tem permissão para testar. Mantenha o trabalho de teste dentro de sistemas que você controla ou para os quais tem autorização por escrito. Se um projeto tiver risco jurídico, consulte um advogado antes de começar, não depois de publicar.
A forma mais segura de aprender é com o seu próprio código. Escreva um programa pequeno, compile e analise o resultado. Você conhece o código-fonte, então pode conferir cada palpite com a verdade. Você também evita as questões legais que vêm com software de terceiros e o risco de executar uma amostra desconhecida.
Duas regras mantêm a prática limpa. Nunca execute binários desconhecidos na máquina do dia a dia. Nunca aponte ferramentas de análise ou varreduras para sistemas que você não possui ou para os quais não tem autorização por escrito, incluindo sites que parecem inofensivos. O próximo artigo, uma primeira análise no Ghidra com um binário que você compila, percorre um primeiro projeto com um programa que você mesmo constrói.
Aprenda o processador que você está estudando, normalmente x86-64 ou ARM64 em computadores modernos, e como o sistema operacional passa argumentos para funções. Compile o mesmo arquivo C pequeno duas vezes, com um compilador C em que você confie, uma com informações de depuração e outra sem símbolos, e compare o que sobra. Leia a documentação do Ghidra e o README do projeto. Depois, siga para o artigo prático que vem em seguida.
Engenharia reversa
Compile uma verificação de licença de brinquedo em C de duas formas e use o Ghidra para achar a string, seguir referências e comparar com seu código.