<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Obfuscation on fr0sttt</title><link>https://fr0sttt.dev/tags/obfuscation/</link><description>Recent content in Obfuscation on fr0sttt</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="https://fr0sttt.dev/tags/obfuscation/index.xml" rel="self" type="application/rss+xml"/><item><title>Evasão por de-otimização: quando o código se disfarça de si mesmo</title><link>https://fr0sttt.dev/p/evas%C3%A3o-por-de-otimiza%C3%A7%C3%A3o-quando-o-c%C3%B3digo-se-disfar%C3%A7a-de-si-mesmo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://fr0sttt.dev/p/evas%C3%A3o-por-de-otimiza%C3%A7%C3%A3o-quando-o-c%C3%B3digo-se-disfar%C3%A7a-de-si-mesmo/</guid><description>&lt;h1 id="o-problema-com-a-solução-que-todo-mundo-usa"&gt;O problema com a solução que todo mundo usa
&lt;/h1&gt;&lt;p&gt;A maioria das ferramentas de evasão de antivírus resolve o problema da mesma forma: esconde o payload dentro de uma casca, e essa casca o descriptografa em memória na hora da execução. Packers, shellcode encoders, obfuscators — todos trabalham com alguma variação desse modelo. O resultado é um binário que parece inofensivo no disco mas que, em algum momento, escreve o código real numa região de memória e pula para ela.&lt;/p&gt;
&lt;p&gt;Esse padrão tem um nome no mundo de detecção: self-modifying code em regiões RWE. E ele ficou velho.&lt;/p&gt;
&lt;p&gt;Ferramentas como Moneta e PE-sieve são construídas exatamente para caçar esse comportamento. Elas varrem a memória do processo em tempo de execução, procuram regiões com permissões de leitura, escrita e execução simultaneamente, e analisam o que está dentro delas. Se o código que está sendo executado não corresponde ao que foi carregado do disco, o jogo acabou. A detecção não depende de assinatura, não depende de heurística de comportamento — ela depende de uma contradição matemática que o próprio processo cria ao rodar.&lt;/p&gt;
&lt;p&gt;O problema não é que a técnica seja fraca. É que ela é estruturalmente detectável. Não importa quão sofisticado seja o packer, o momento em que ele escreve o payload descriptografado numa região executável, ele cria uma evidência que não pode ser apagada sem desfazer o próprio trabalho.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-pergunta-que-muda-o-enquadramento"&gt;A pergunta que muda o enquadramento
&lt;/h2&gt;&lt;p&gt;Se o problema é o código que se modifica em tempo de execução, a solução óbvia é: e se o código não precisar se modificar?&lt;/p&gt;
&lt;p&gt;É aí que a de-otimização de código de máquina entra como abordagem. A ideia não é esconder o payload dentro de outra coisa. É transformar o próprio payload de forma que ele permaneça semanticamente idêntico, mas estruturalmente irreconhecível — e fazer isso de forma estática, antes da execução, sem criar nenhuma região RWE, sem nenhuma escrita em memória em runtime.&lt;/p&gt;
&lt;p&gt;O binário que vai para o disco já é o binário final. Não há descriptografia, não há automodificação. Há apenas instruções de máquina que fazem exatamente o que sempre fizeram, escritas de uma forma que nenhuma ferramenta de análise estática vai reconhecer como familiar.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="como-a-transformação-funciona"&gt;Como a transformação funciona
&lt;/h2&gt;&lt;p&gt;O código de máquina não é uma linguagem com uma única forma de expressar cada operação. A arquitetura x86, em particular, tem redundância suficiente para representar o mesmo cálculo de dezenas de formas diferentes, todas igualmente válidas para o processador. A de-otimização explora isso de forma sistemática, usando transformações matemáticas que preservam o resultado mas destroem qualquer padrão reconhecível.&lt;/p&gt;
&lt;p&gt;A primeira técnica é o particionamento aritmético. Uma instrução simples como &lt;code&gt;ADD RAX, 5&lt;/code&gt; pode ser substituída por uma sequência que calcula o mesmo valor através de operações intermediárias: &lt;code&gt;ADD RAX, 3&lt;/code&gt;, seguido de &lt;code&gt;ADD RAX, 2&lt;/code&gt;. O resultado é idêntico. Os bytes no binário são completamente diferentes. E a sequência pode ser particionada de formas diferentes a cada execução da transformação, garantindo que o mesmo payload nunca produza o mesmo padrão de bytes duas vezes.&lt;/p&gt;
&lt;p&gt;A segunda é a transformação polinomial. Constantes numéricas, em vez de aparecerem diretamente como valores imediatos nas instruções, são representadas como resultados de expressões polinomiais que o processador calcula em tempo de execução. O valor &lt;code&gt;0x1337&lt;/code&gt;, por exemplo, pode ser derivado de &lt;code&gt;(x² + 3x - 7)&lt;/code&gt; para algum &lt;code&gt;x&lt;/code&gt; escolhido em tempo de transformação. O processador chega no mesmo lugar, mas o padrão de bytes que chegou lá não tem nenhuma relação visual com o destino.&lt;/p&gt;
&lt;p&gt;A terceira é o inverso lógico, fundamentado nas leis de De Morgan. Operações como &lt;code&gt;AND&lt;/code&gt; e &lt;code&gt;OR&lt;/code&gt; podem ser reescritas usando suas equivalências lógicas invertidas: &lt;code&gt;NOT (NOT A OR NOT B)&lt;/code&gt; é matematicamente idêntico a &lt;code&gt;A AND B&lt;/code&gt;. Em código de máquina, isso se traduz em substituir instruções lógicas por sequências de instruções que o processador executa de forma diferente mas que produzem o mesmo estado nos registradores ao final.&lt;/p&gt;
&lt;p&gt;A quarta é o particionamento lógico, que divide operações de máscara e manipulação de bits em sequências menores que preservam o efeito combinado. Uma máscara aplicada em uma instrução pode ser decomposta em duas ou três operações intermediárias, nenhuma das quais se parece com a original.&lt;/p&gt;
&lt;p&gt;Combinadas, essas transformações são capazes de reescrever até 95% das instruções de um binário sem alterar seu comportamento. O binário resultante passa por análise estática como se fosse código gerado por um compilador diferente — porque, de certa forma, é.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="por-que-isso-é-diferente-do-que-já-existe"&gt;Por que isso é diferente do que já existe
&lt;/h2&gt;&lt;p&gt;Obfuscação de código de máquina não é nova. O que diferencia essa abordagem é a ausência de padrões reconhecíveis na própria transformação.&lt;/p&gt;
&lt;p&gt;Técnicas tradicionais de obfuscação inserem NOPs, jumps inúteis ou chamadas de função que não fazem nada. Isso cria um padrão diferente, mas ainda detectável: ferramentas de análise aprenderam a reconhecer sequências de instrução que não fazem sentido para um compilador normal. O código parece estranho de uma forma específica, e essa estranheza específica virou assinatura.&lt;/p&gt;
&lt;p&gt;A de-otimização por transformações matemáticas não cria código estranho. Cria código que parece simplesmente diferente — como se tivesse sido compilado com flags de otimização diferentes, ou gerado por um compilador menos comum. As sequências resultantes são semanticamente corretas, sintaticamente válidas e estruturalmente plausíveis. Não há nada para um analisador estático apontar como anômalo, porque nada é anômalo. É apenas código fazendo o que código faz.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="onde-a-técnica-tem-limite"&gt;Onde a técnica tem limite
&lt;/h2&gt;&lt;p&gt;Honestidade técnica exige dizer que isso resolve um problema específico dentro de um problema maior.&lt;/p&gt;
&lt;p&gt;A de-otimização ataca a camada estática da detecção: assinaturas de bytes, análise de padrões no binário, fingerprinting de compiladores. Ela não faz nada pela camada dinâmica, que é onde EDRs modernos como CrowdStrike, SentinelOne e Microsoft Defender for Endpoint colocam a maior parte do seu peso.&lt;/p&gt;
&lt;p&gt;Um payload que sobrevive à análise estática ainda vai ser observado em tempo de execução. Se ele faz &lt;code&gt;VirtualAlloc&lt;/code&gt; seguido de escrita de shellcode seguido de criação de thread, o EDR vai detectar o comportamento independente de como o código que gerou esses syscalls estava escrito no disco. A telemetria via ETW e os hooks em user-mode capturam o que o processo faz, não como ele parece.&lt;/p&gt;
&lt;p&gt;Isso não invalida a técnica. Significa que ela pertence a uma cadeia de evasão, não é a cadeia inteira. Em alvos sem EDR maduro — que ainda representam a maioria dos ambientes em engagements reais — a de-otimização sozinha já representa uma superfície de detecção estática praticamente nula. Em alvos com EDR de última geração, ela precisa ser combinada com evasão de comportamento: syscalls diretas para evitar hooks em user-mode, sleep masking para esconder o payload em memória entre operações, e técnicas como module stomping para eliminar a dicotomia entre o que está no disco e o que está em memória.&lt;/p&gt;
&lt;p&gt;A de-otimização resolve o problema para o qual ela foi projetada. O trabalho de um operador ofensivo competente é entender exatamente qual problema está sendo resolvido em cada camada — e não confundir uma peça do puzzle com o puzzle inteiro.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Ferramentas de detecção evoluem ao encontrar padrões. A de-otimização por transformações matemáticas ataca justamente a capacidade de formar padrões: cada transformação é parametrizada diferente, cada binário produzido é único, e a diversidade do output não é acidental — é o produto de escolhas matemáticas deliberadas. Isso não garante invisibilidade eterna. Garante que o custo de criar assinaturas para esse tipo de evasão sobe de forma desproporcional ao custo de aplicar as transformações. Em segurança, esse tipo de assimetria importa.&lt;/p&gt;</description></item></channel></rss>