Browse By

Simplifique – Ofuscador Andoird

Simplifique virtualmente executa um aplicativo para entender seu comportamento e, em seguida, tenta otimizar o código para que ele se comporte de maneira idêntica, mas é mais fácil para um ser humano entender. Cada tipo de otimização é simples e genérico, portanto, não importa qual seja o tipo específico de ofuscação usado.

Antes e depois
O código à esquerda é uma descompilação de um aplicativo ofuscado e o código à direita foi desiludido.

Visão geral
Existem três partes para o projeto: smalivm, simplificar e o aplicativo de demonstração.

  1. smalivm : Fornece uma caixa de proteção de máquina virtual para executar os métodos Dalvik. Depois de executar um método, ele retorna um gráfico contendo todos os possíveis valores de registro e classe para cada caminho de execução. Ele funciona mesmo se alguns valores forem desconhecidos, como E / S de arquivo e rede. Por exemplo, qualquer um ifou switchcondicional com um valor desconhecido resulta em ambas as ramificações serem tomadas.
  2. simplify : Analisa os gráficos de execução do smalivm e aplica otimizações como propagação constante, remoção de código morto, não-reflexão e algumas otimizações de olho mágico. Eles são bastante simples, mas quando aplicados juntos repetidamente, eles descriptografam strings, removem reflexos e simplificam bastante o código. Ele não renomear métodos e classes.
  3. demoapp : Contém exemplos simples e altamente comentados para usar o smalivm em seu próprio projeto. Se você está construindo algo que precisa executar o código da Dalvik, confira.

Uso

usage: java -jar simplify.jar <input> [options]
deobfuscates a dalvik executable
 -et,--exclude-types <pattern>   Exclude classes and methods which include REGEX, eg: "com/android", applied after include-types
 -h,--help                       Display this message
 -ie,--ignore-errors             Ignore errors while executing and optimizing methods. This may lead to unexpected behavior.
    --include-support            Attempt to execute and optimize classes in Android support library packages, default: false
 -it,--include-types <pattern>   Limit execution to classes and methods which include REGEX, eg: ";->targetMethod\("
    --max-address-visits <N>     Give up executing a method after visiting the same address N times, limits loops, default: 10000
    --max-call-depth <N>         Do not call methods after reaching a call depth of N, limits recursion and long method chains, default: 50
    --max-executi   on-time <N>     Give up executing a method after N seconds, default: 300
    --max-method-visits <N>      Give up executing a method after executing N instructions in that method, default: 1000000
    --max-passes <N>             Do not run optimizers on a method more than N times, default: 100
 -o,--output <file>              Output simplified input to FILE
    --output-api-level <LEVEL>   Set output DEX API compatibility to LEVEL, default: 15
 -q,--quiet                      Be quiet
    --remove-weak                Remove code even if there are weak side effects, default: true
 -v,--verbose <LEVEL>            Set verbosity to LEVEL, default: 0

O Building
Building requer que o Java Development Kit 8 (JDK) seja instalado. 
Como este projeto contém submódulos para estruturas Android, clone com--recursive:

git clone --recursive https://github.com/CalebFenton/simplify.git

Ou atualize os submódulos a qualquer momento com:

git submodule update --init --recursive

Então, para construir um único jarro que contenha todas as dependências:

./gradlew fatjar

O jarro Simplify estará em simplify/build/libs/. Você pode testar seu funcionamento simplificando o aplicativo de exemplo ofuscado fornecido . Veja como você executaria (talvez seja necessário alterar simplify.jar):

java -jar simplify/build/libs/simplify.jar -it 'org/cf/obfuscated' -et 'MainActivity' simplify/obfuscated-app.apk

Para entender o que está ficando desofuscado, confira o README do App ofuscado . 

Solução de problemas
Se o Simplify falhar, tente estas recomendações, em ordem:

  1. Apenas segmente alguns métodos ou classes usando a -itopção.
  2. Se a falha é por causa de visitas máxima ultrapassada, tente usar mais elevada --max-address-visits--max-call-depth--max-method-visits.
  3. Tente com -vou -v 2e relate o problema com os registros e um hash do DEX ou do APK.
  4. Tente novamente, mas não quebre o contato visual. Simplificar pode sentir medo.

Se estiver construindo no Windows, e a construção falhar com um erro semelhante a:
 Não foi possível encontrar o tools.jar. Por favor, verifique se C: \ Arquivos de Programas \ Java \ jre1.8.0_151 contém uma instalação válida do JDK.Isso significa que o Gradle não consegue encontrar um caminho JDK adequado. Certifique-se de que o JDK esteja instalado, defina a JAVA_HOMEvariável de ambiente para seu caminho JDK e certifique-se de fechar e reabrir o prompt de comando que você usa para construir. 

Contribuindo
Não seja tímido. Eu acho que a execução virtual e a desofuscação são problemas fascinantes. Qualquer um que esteja interessado é legal e as contribuições são bem-vindas, mesmo que seja apenas para corrigir um erro de digitação. Sinta-se à vontade para fazer perguntas nos problemas e enviar solicitações de recebimento. 

Problemas de relatórios
Inclua um link para o APK ou DEX e o comando completo que você está usando. Isso torna muito mais fácil reproduzir (e assim corrigir ) o seu problema. 
Se você não pode compartilhar a amostra, por favorinclua o hash do arquivo (SHA1, SHA256, etc). 

Estratégias de otimização

Propagação constante
Se um op coloca um valor de um tipo que pode ser transformado em uma constante, como uma string, número ou booleano, essa otimização substituirá essa op pela constante. Por exemplo:

const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
# Decrypts to: "Tell me of your homeworld, Usul."
move-result v0

Neste exemplo, uma string criptografada é descriptografada e colocada em v0. Como as strings são “constantes”, elas move-result v0podem ser substituídas por const-string:

const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."

O Código de Remoção de
Código Morto está inativo se a remoção dele não puder alterar o comportamento do aplicativo. O caso mais óbvio é se o código está inacessível, por exemplo if (false) { // dead }). Se o código estiver acessível, ele pode ser considerado morto se não afetar nenhum estado fora do método, ou seja, não tem efeito colateral . Por exemplo, o código pode não afetar o valor de retorno do método, alterar qualquer variável de classe ou executar qualquer IO. Isso é difícil de determinar na análise estática. Felizmente, o smalivm não precisa ser inteligente. Ele simplesmente executa tudo que pode e supõe que existem efeitos colaterais, se não tiver certeza. Considere o exemplo da Propagação Constante:

const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
invoke-static {v0}, Lmy/string/Decryptor;->decrypt(Ljava/lang/String;)Ljava/lang/String;
const-string v0, "Tell me of your homeworld, Usul."

Neste código, o invoke-staticnão mais afeta o valor de retorno do método e vamos supor que ele não faça nada estranho, como gravar bytes no sistema de arquivos ou em um soquete de rede, para que ele não tenha efeitos colaterais. Pode simplesmente ser removido.

const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."

Finalmente, o primeiro const-stringatribui um valor a um registrador, mas esse valor nunca é usado, isto é, a atribuição está morta. Também pode ser removido.

const-string v0, "Tell me of your homeworld, Usul."

Huzzah! 

Unreflection
Um grande desafio com a análise estática do Java é a reflexão. Não é possível saber que os argumentos são para métodos de reflexão sem fazer uma análise cuidadosa do fluxo de dados. Existem maneiras inteligentes e inteligentes de fazer isso, mas o smalivm faz isso apenas executando o código. Quando encontrar uma invocação de método refletida, como:

invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;

Ele pode conhecer os valores de v0v1v2. Se tiver certeza de quais são os valores, ele pode substituir a chamada Method.invoke()por uma chamada de método real não refletida. O mesmo se aplica a pesquisas de campo e classe refletidas. 

Peephole
Para tudo que não se encaixa perfeitamente em uma categoria específica, há otimizações de olho mágico. Isso inclui remover check-castops inúteis , substituir Ljava/lang/String;-><init>chamadas por const-stringe assim por diante. 

Exemplo de desofuscação

antes da otimização

.method public static test1()I
    .locals 2

    new-instance v0, Ljava/lang/Integer;
    const/4 v1, 0x1
    invoke-direct {v0, v1}, Ljava/lang/Integer;-><init>(I)V

    invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I
    move-result v0

    return v0
.end method

Tudo isso faz é v0 = 1

Depois da Propagação Constante

.method public static test1()I
    .locals 2

    new-instance v0, Ljava/lang/Integer;
    const/4 v1, 0x1
    invoke-direct {v0, v1}, Ljava/lang/Integer;-><init>(I)V

    invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I
    const/4 v0, 0x1

    return v0
.end method

move-result v0é substituído por const/4 v0, 0x1. Isso ocorre porque existe apenas um valor de retorno possível intValue()Ie o tipo de retorno pode ser uma constante. Os argumentos v0v1são inequívocos e não mudam. Ou seja, há um consenso de valores para cada caminho de execução possível em intValue()I. Outros tipos de valores que podem ser transformados em constantes:

  • números – const/4const/16, etc.
  • cordas – const-string
  • classes – const-class

Após a remoção do código morto

.method public static test1()I
    .locals 2

    const/4 v0, 0x1

    return v0
.end method

Como o código acima const/4 v0, 0x1não afeta o estado fora do método (sem efeitos colaterais), ele pode ser removido sem alterar o comportamento. Se houver uma chamada de método que tenha escrito algo no sistema de arquivos ou na rede, ela não poderá ser removida porque afeta o estado fora do método. Ou se test()Itomar um argumento mutável, como um LinkedList, qualquer instrução que o acessasse não poderia ser considerada morta. 
Outros exemplos de código morto:

  • atribuições não referenciadas – atribuindo registros e não os usando
  • instruções não alcançadas / inacessíveis – if (false) { dead_code(); }

Leitura Adicional

Baixar simplificar

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *

Este site utiliza o Akismet para reduzir spam. Fica a saber como são processados os dados dos comentários.