Engenharia reversa de uma assinatura ofuscada: 90% é padrão; os 10% fatais estão nos pontos de desvio

Última Atualização: 2026-07-30 10:26:17

Ao abrir um SDK de assinatura ofuscado, a primeira impressão costuma ser enganosa: parece que você caiu em um código esotérico, onde cada linha executa algo impossível de acompanhar.

Depois de passar pelo fluxo completo de engenharia reversa, a realidade geralmente é oposta: 90% do código consiste em algoritmos padrão. O hash é público, a codificação é pública, a cifra de fluxo também é pública. Você nem precisa "entender" essas partes em profundidade: basta identificar o algoritmo, copiá-lo da RFC ou de uma implementação de referência, e nenhum byte ficará diferente.

O perigo está nos 10% restantes: os pontos em que um algoritmo padrão foi alterado de forma quase imperceptível. Uma contagem de rodadas que deveria ser constante vira variável; uma constante-base é trocada por um valor próximo; os índices das palavras em uma rodada são invertidos; a chave é escondida dentro do ciphertext. Cada mudança é pequena, mas basta deixar uma delas passar para sua reimplementação não bater com o alvo — e, como os outros 90% estão certos, você não saberá onde está o erro.

Portanto, o trabalho real ao reverter uma assinatura ofuscada não é ler os 90%, mas localizar os 10% de pontos de desvio. Este artigo mostra como usar um modelo grande para fazer essa busca e por que essa é justamente a etapa em que ele é mais difícil de substituir.

(O pré-requisito — identificar a família do algoritmo, reconhecer "isto é ChaCha" ou "isto é FNV" pelas constantes e pela estrutura — foi abordado no artigo anterior. Aqui, vou partir do princípio de que você já identificou o esqueleto e ir direto à caça dos desvios.)

Por que os desvios passam despercebidos

O cérebro humano tem uma tendência forte ao reconhecer padrões: assim que entende a ideia geral, deixa de examinar os detalhes.

Você vê um bloco de código, as constantes batem com a assinatura do ChaCha, a estrutura parece certa, e o cérebro marca a caixa: "isto é ChaCha20". Então segue em frente. Não confere, byte a byte, cada número de rodadas, cada índice ou o bit menos significativo de cada constante, porque a sensação de "já reconheci" desliga sua atenção.

É exatamente com isso que autores de ofuscação contam. Eles não reescrevem o algoritmo inteiro — isso seria caro demais e sujeito a bugs. Em vez disso, fazem a menor alteração possível em um algoritmo padrão: mudam um número, trocam um índice, acrescentam uma etapa. Essas mudanças são discretas o bastante para escapar da sua percepção de padrões, mas grandes o suficiente para fazer uma reimplementação fiel ao padrão falhar por completo.

É uma guerra de atenção desigual. Quem ofuscou o código só precisa esconder um desvio; você precisa encontrar todos. E seres humanos são naturalmente ruins em "continuar desconfiando de algo que parece correto".

O modelo tem uma vantagem contraintuitiva nesse ponto: ele não sente a satisfação de "já reconheci" que faz a atenção desligar. Se o prompt pedir explicitamente que ele procure desvios, ele verificará os itens um a um sem relaxar no meio do caminho — desde que você use o prompt certo, assunto da Seção 5. Antes disso, vale entender como se apresentam esses quatro tipos de desvio.

Quatro formas comuns de desviar do padrão

Em cada caso abaixo, vou usar um algoritmo público para mostrar como é a implementação padrão e, em seguida, como o desvio costuma aparecer. Em projetos reais, os quatro tipos frequentemente se acumulam no mesmo assinador.

1. De constante para variável: um parâmetro fixo passa a depender da execução

Uma cifra de fluxo padrão tem número fixo de rodadas. O ChaCha20 usa 20 rodadas, sem exceção; esse número aparece fixo em toda implementação compatível — a RFC 8439 deixa isso claro na introdução, observando que descreve apenas o ChaCha de 20 rodadas, enquanto as variantes de 8 e 12 rodadas são definidas em outros documentos. A contagem de rodadas sempre foi uma constante explícita no padrão.

O desvio consiste em transformar essa contagem fixa em algo calculado dinamicamente a partir da chave. A lógica de quarter-round continua a mesma, mas a quantidade de rodadas executadas passa a depender de determinados bytes da chave. Mude a chave, e a contagem de rodadas muda junto.

Por que isso passa despercebido: você reconheceu a estrutura de quarter-round, reconheceu as constantes σ, concluiu que é "ChaCha20" e copiou 20 rodadas. Nem chegou a verificar se a variável que controla o loop é uma constante ou uma expressão — afinal, em todo ChaCha que você já viu, ela é constante.

O sinal desse tipo de desvio é simples: onde deveria haver uma constante, aparece uma expressão dependente da entrada. Uma análise de contraevidências bem orientada verifica especificamente situações do tipo: "a implementação padrão fixa um valor aqui, mas esta o calcula".

Como confirmar com testes diferenciais: execute deliberadamente duas chaves que diferem em apenas um byte. Se a diferença na saída for muito maior do que a influência esperada de um único byte, esse byte provavelmente está alimentando algum parâmetro global — como a contagem de rodadas — em vez de apenas ser aplicado por XOR ao keystream.

2. Perturbação de constante: uma constante-base é substituída por um valor próximo

O hash FNV-1a tem dois números mágicos públicos: a offset basis e o prime. Toda implementação correta de FNV-1a usa exatamente esses dois valores, publicados no padrão — a página de referência do FNV, mantida pelo coautor Landon Curt Noll, informa que a offset basis de 32 bits é 2166136261 e que o prime é 16777619, dígito por dígito.

O desvio: trocar uma dessas constantes-base por um valor vizinho, que difere do padrão por uma alteração mínima. A estrutura geral do hash permanece idêntica — XOR, multiplicação e loop continuam corretos —, mas a base inicial sofreu uma mudança quase invisível.

Por que humanos deixam isso passar: este é o mais traiçoeiro dos quatro. Você reconhece a estrutura do FNV, vê uma constante grande parecida com a offset basis e conclui: "FNV-1a padrão". Não compara a constante bit a bit com o valor oficial — quem confere de novo um número mágico que já "reconheceu"?

Esse tipo de desvio quase só é detectável por testes diferenciais, porque inspecionar números grandes a olho é muito pouco confiável. O método é: use uma entrada conhecida, rode-a na implementação padrão e na caixa-preta alvo, e compare os resultados. Se a estrutura é a mesma, mas as saídas diferem, o problema quase certamente está em uma constante-base. Então entregue a constante suspeita ao modelo e peça que ele a compare com o valor padrão — uma máquina é muito mais confiável nisso do que você.

O valor do modelo aqui é bem concreto: ele se lembra da offset basis padrão do FNV-1a até o último bit; você não. Basta perguntar "esta constante é exatamente igual à offset basis padrão do FNV-1a?", e ele aponta a diferença na hora.

3. Ajuste estrutural: uma etapa do algoritmo padrão é alterada localmente

Cada double round do ChaCha contém 8 quarter rounds: os 4 primeiros atuam nas colunas, e os 4 seguintes nas diagonais. As palavras processadas por cada quarter round seguem um conjunto fixo de índices definido pelo padrão (RFC 8439 §2.3 detalha a função de bloco e exatamente quais palavras do estado cada rodada manipula).

O desvio é trocar discretamente um ou dois índices de palavras em uma das rodadas. A grande maioria das rodadas continua padrão; apenas em algum ponto do meio, uma palavra que deveria ser manipulada é substituída por outra. Ainda parece ChaCha e continua executando sem erro, mas o keystream produzido é completamente diferente do ChaCha padrão.

Por que humanos não percebem: os índices de quarter-round formam uma longa sequência numérica — oito grupos como (0,4,8,12)(1,5,9,13)…. O olhar passa por eles e só confirma "certo, rodadas de coluna e diagonais", sem verificar se os quatro números de cada grupo estão exatamente nas posições previstas pelo padrão. Esconder uma alteração em uma sequência de índices que já cansa os olhos é uma ótima estratégia.

Nem o modelo resolve isso com uma olhada superficial; você precisa orientá-lo a listar os índices rodada por rodada e compará-los com o ChaCha padrão. É uma tarefa mecânica de conferência — precisamente o tipo de trabalho no qual o modelo não se dispersa, mas um humano pode se dispersar. Peça uma tabela de "índice padrão versus índice real", e o desvio aparece sozinho.

O teste diferencial confirma o diagnóstico: se os dois primeiros tipos foram descartados — contagem de rodadas correta e constantes corretas — e a saída ainda diverge, o problema é estrutural. Faça dump do estado intermediário a cada rodada; a primeira rodada que divergir do ChaCha padrão é a que foi modificada.

4. Material embutido: dados da chave escondidos na saída

Os três primeiros casos mudam o algoritmo; este altera a organização dos dados.

Uma técnica comum é não transmitir a chave de criptografia por um canal separado: ela é dividida e inserida no próprio ciphertext, para que o receptor a extraia pela mesma regra. Uma variante mais astuta torna a posição de inserção variável, calculada a partir do conteúdo dos próprios dados — o ciphertext muda, e o local onde a chave fica escondida muda junto. A camada mais externa então empacota tudo em um Base64 de alfabeto personalizado e um byte de prefixo marcador.

Por que isso engana: você está lutando com o algoritmo de criptografia e não percebe que talvez nem seja preciso quebrar a chave — ela está no ciphertext que você já tem em mãos; você só não sabe em qual trecho ela foi escondida. Iniciantes frequentemente travam aqui, tentando "quebrar" algo cujo plaintext está bem diante deles.

O indício desse tipo de desvio é uma região em um bloco de dados cujas características estatísticas diferem do entorno — uma chave costuma conter bytes aleatórios de alta entropia e, inserida no meio do ciphertext, forma um "segmento estranho" reconhecível. O modelo pode ajudar a analisar "qual intervalo desta saída tem uma distribuição de bytes diferente do restante", localizando os limites do material embutido.

Como confirmar: se você encontrar a regra de inserção — por exemplo, algum módulo sobre a soma de bytes —, valide-a com alguns pares conhecidos de entrada e saída, resolvendo a fórmula para trás para obter a posição de inserção e então verificando-a no sentido direto. O modelo pode ajudar a deduzir a regra a partir de poucas amostras, mas a palavra final sobre ela estar certa continua sendo do assert.

A análise de contraevidências é onde o modelo de raciocínio realmente faz diferença

Nos quatro tipos de desvio, há um padrão em comum: reconhecer a família do algoritmo — a hipótese — é fácil; encontrar o desvio — a contraevidência — é difícil.

A etapa da hipótese quase qualquer modelo consegue executar. As constantes σ do ChaCha, a estrutura do FNV: qualquer modelo que tenha visto os dados de treinamento os reconhece. O que realmente separa os modelos é a análise de contraevidências: se o modelo consegue e está disposto a continuar investigando um algoritmo que acabou de reconhecer para dizer "mas esta parte não bate com o padrão".

Um modelo mais fraco perde a coragem nesse ponto. Depois de reconhecer "isto é ChaCha20", sua análise de contraevidências tende a se degradar em uma repetição da hipótese com outras palavras: "a implementação segue a estrutura padrão do ChaCha20, empregando o clássico quarter round...". É tudo uma reformulação da hipótese, sem checar de fato um único desvio. Uma análise assim não orienta seu próximo passo.

A análise de contraevidências de um modelo forte de raciocínio é outra coisa. Ele escreve: "a hipótese é ChaCha20, mas há três desvios em relação à implementação padrão: primeiro, a contagem de rodadas é controlada por uma expressão dependente da chave, enquanto o ChaCha20 padrão usa 20 rodadas fixas; segundo, os índices de palavras na rodada N não correspondem à rodada diagonal padrão; terceiro...". Cada ponto indica um desvio específico e verificável. Uma análise dessas é a checklist de testes diferenciais que você escreverá no Estágio 3.

Por isso vale colocar a identificação da família do algoritmo na camada de raciocínio — e testá-la por conta própria antes de se comprometer. A diferença na qualidade das contraevidências entre modelos determina diretamente quantos testes inúteis você escreverá e quantos desvios de rota fará.

As quatro camadas que uso nesse fluxo de trabalho:

Estágio

Capacidade necessária

Escolha

model id

Mapeamento estrutural após a divisão

Contexto longo, lê um módulo inteiro de uma vez

Kimi K3

kimi-k3

Identificação da família do algoritmo e contraevidências

Raciocínio forte; capaz de contestar a própria hipótese

Claude Opus 5

claude-opus-5

Renomeação em massa de símbolos

Baixo custo, alta concorrência

Claude Sonnet 5

claude-sonnet-5

Atribuição de diferenças

Raciocínio intermediário, explica com base em bytes específicos

GPT-5.6 Sol

gpt-5.6-sol

A segunda camada é o centro deste artigo. Não aceite minha escolha sem testar: o protocolo é simples.

  1. Escolha 2–3 funções folha do seu próprio bundle ofuscado, incluindo ao menos uma cuja resposta você já conheça, como controle.

  2. Usando o prompt em três partes — "hipótese / evidências / contraevidências" — do artigo anterior, envie a mesma entrada separadamente para claude-opus-5 e gpt-5.6-sol.

  3. Observe apenas a análise de contraevidências: ela realmente verifica os desvios um por um ou apenas repete a hipótese com palavras diferentes? No controle, quantos desvios cada modelo encontrou?

  4. Use a quantidade e a qualidade dos desvios encontrados como critério de escolha.

Você perceberá a diferença em uma única execução — de forma mais direta do que qualquer ranking de benchmark.

O custo de alternar entre modelos é o obstáculo real

São quatro modelos de três fornecedores. A forma ingênua de usar modelos diferentes em etapas diferentes é integrar três SDKs, três esquemas de autenticação e três tratadores de erro. A maioria faz as contas, conclui que não vale o esforço e acaba usando um modelo para tudo — inclusive uma camada fraca em contraevidências para identificar famílias de algoritmos —, acumulando desvios de rota sem entender por quê.

AIReiter elimina essa camada de complexidade: uma chave, uma interface compatível com OpenAI, os quatro modelos disponíveis por trás dela, e alternar entre eles é só mudar o campo model no corpo da requisição.

# Identificação da família do algoritmo + contraevidências: a camada de raciocínio
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<prompt em três partes + função folha + constantes>"}]
  }'

# Renomeação em massa de símbolos: altere um campo
#   "model": "claude-sonnet-5"
# Atribuição de diferenças:
#   "model": "gpt-5.6-sol"

Já usa o SDK da OpenAI? Aponte base_url para https://aireiter.com/api/v1 e não altere mais nada. Usa o SDK da Anthropic? Faça POST /api/v1/messages com a mesma chave.

Em preço, os modelos Claude têm 30% de desconto sobre o valor de tabela, e os modelos GPT saem pela metade do preço. Neste fluxo, o desconto atinge justamente onde importa: a etapa de família do algoritmo exige iterar o prompt repetidas vezes, consultando a mesma função em muitas rodadas, e é a parte com maior densidade de chamadas; a renomeação em massa de símbolos começa na casa das centenas de chamadas. São essas duas etapas que concentram a maior parte do custo.

Conclusão

A verdade sobre reverter assinaturas ofuscadas é esta: a maior parte do código usa algoritmos padrão que você pode copiar sem alterações; o trabalho real é encontrar os pontos em que o padrão foi modificado discretamente.

Os quatro tipos de desvio — de constante para variável, perturbação de constante, ajuste estrutural e material embutido — têm algo em comum: são pequenos demais para a percepção humana de padrões notá-los, mas grandes o suficiente para fazer uma reimplementação falhar por completo. Humanos não são bons em continuar desconfiando do que parece correto; essa é justamente a força de um modelo, quando bem orientado.

Mas o modelo só gera suspeitas; ele não confirma nada. No fim, toda hipótese sobre um ponto de desvio precisa virar um teste diferencial — assunto do artigo sobre testes diferenciais. O modelo entrega uma lista de "pontos que podem ter sido alterados"; o assert informa quais realmente foram.