Simplify exécute virtuellement une application pour comprendre son comportement, puis essaie d'optimiser le code afin qu'il se comporte de manière identique mais soit plus facile à comprendre pour un humain. Chaque type d'optimisation est simple et générique, le type spécifique d'obscurcissement utilisé n'a donc pas d'importance.
Le code de gauche est une décompilation d'une application obscurcie, et le code de droite a été désobscurci.
Le projet comprend trois parties : smalivm, simplifier et l'application de démonstration.
if
ou switch
conditionnel avec une valeur inconnue entraîne la prise des deux branches. 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-execution-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
La construction nécessite l'installation du Java Development Kit 8 (JDK).
Parce que ce projet contient des sous-modules pour les frameworks Android, soit clonez avec --recursive
:
git clone --recursive https://github.com/CalebFenton/simplify.git
Ou mettez à jour les sous-modules à tout moment avec :
git submodule update --init --recursive
Ensuite, pour construire un seul pot contenant toutes les dépendances :
./gradlew fatjar
Le pot Simplify sera dans simplify/build/libs/
. Vous pouvez tester son fonctionnement en simplifiant l'exemple d'application obscurcie fourni. Voici comment vous l'exécuteriez (vous devrez peut-être modifier simplify.jar
) :
java -jar simplify/build/libs/simplify.jar -it " org/cf/obfuscated " -et " MainActivity " simplify/obfuscated-app.apk
Pour comprendre ce qui est désobscurci, consultez le fichier README de l'application Obfuscated.
Si Simplify échoue, essayez ces recommandations, dans l’ordre :
-it
.--max-address-visits
, --max-call-depth
et --max-method-visits
plus élevées.-v
ou -v 2
et signalez le problème avec les journaux et un hachage du DEX ou de l'APK.Si vous construisez sous Windows et que la construction échoue avec une erreur similaire à :
Impossible de trouver tools.jar. Veuillez vérifier que C:Program FilesJavajre1.8.0_151 contient une installation JDK valide.
Cela signifie que Gradle est incapable de trouver un chemin JDK approprié. Assurez-vous que le JDK est installé, définissez la variable d'environnement JAVA_HOME
sur votre chemin JDK et assurez-vous de fermer et de rouvrir l'invite de commande que vous utilisez pour créer.
Ne soyez pas timide. Je pense que l'exécution virtuelle et la désobscurcissement sont des problèmes fascinants. Toute personne intéressée est automatiquement cool et les contributions sont les bienvenues, même s'il s'agit simplement de corriger une faute de frappe. N'hésitez pas à poser des questions dans les numéros et à soumettre des demandes de tirage.
Veuillez inclure un lien vers l'APK ou DEX et la commande complète que vous utilisez. Cela facilite grandement la reproduction (et donc la résolution ) de votre problème.
Si vous ne pouvez pas partager l'échantillon, veuillez inclure le hachage du fichier (SHA1, SHA256, etc.).
Si une opération place une valeur d'un type qui peut être transformé en constante telle qu'une chaîne, un nombre ou un booléen, cette optimisation remplacera cette opération par la constante. Par exemple:
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
Dans cet exemple, une chaîne chiffrée est déchiffrée et placée dans v0
. Puisque les chaînes sont "constantisables", le move-result v0
peut être remplacé par une 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."
Le code est mort si sa suppression ne peut pas modifier le comportement de l'application. Le cas le plus évident est celui où le code est inaccessible, par exemple if (false) { // dead }
). Si le code est accessible, il peut être considéré comme mort s'il n'affecte aucun état en dehors de la méthode, c'est-à-dire qu'il n'a aucun effet secondaire . Par exemple, le code ne peut pas affecter la valeur de retour de la méthode, modifier les variables de classe ou effectuer des E/S. C’est une question difficile à déterminer en analyse statique. Heureusement, smalivm n'a pas besoin d'être intelligent. Il exécute bêtement tout ce qu'il peut et suppose qu'il y a des effets secondaires s'il ne peut pas en être sûr. Prenons l'exemple de Constant Propagation :
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."
Dans ce code, l' invoke-static
n'affecte plus la valeur de retour de la méthode et supposons qu'elle ne fasse rien de bizarre comme écrire des octets sur le système de fichiers ou sur une socket réseau, elle n'a donc aucun effet secondaire. Il peut simplement être supprimé.
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."
Enfin, la première const-string
attribue une valeur à un registre, mais cette valeur n'est jamais utilisée, c'est-à-dire que l'affectation est morte. Il peut également être supprimé.
const-string v0, "Tell me of your homeworld, Usul."
Huzzah !
L'un des défis majeurs de l'analyse statique de Java est la réflexion. Il n'est tout simplement pas possible de savoir si les arguments sont en faveur des méthodes de réflexion sans effectuer une analyse minutieuse du flux de données. Il existe des moyens intelligents et intelligents de procéder, mais smalivm le fait en exécutant simplement le code. Lorsqu'il trouve un appel de méthode reflété tel que :
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
Il peut connaître les valeurs de v0
, v1
et v2
. S'il est sûr des valeurs, il peut remplacer l'appel à Method.invoke()
par une véritable invocation de méthode non réfléchie. Il en va de même pour les recherches de champs et de classes réfléchies.
Pour tout ce qui ne rentre pas clairement dans une catégorie particulière, il existe des optimisations de judas. Cela inclut la suppression des opérations check-cast
inutiles, le remplacement des appels Ljava/lang/String;-><init>
par const-string
, et ainsi de suite.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
move-result v0
return v0
.end method
Tout cela ne fait que v0 = 1
.
.method public static test1 () I
.locals 2
new-instance v0 , L java/lang/Integer ;
const/4 v1 , 0x1
invoke-direct { v0 , v1 }, L java/lang/Integer ; -> <init> ( I ) V
invoke-virtual { v0 }, L java/lang/Integer ; -> intValue () I
const/4 v0 , 0x1
return v0
.end method
Le move-result v0
est remplacé par const/4 v0, 0x1
. En effet, il n’existe qu’une seule valeur de retour possible pour intValue()I
et le type de retour peut devenir une constante. Les arguments v0
et v1
sont sans ambiguïté et ne changent pas. C'est-à-dire qu'il existe un consensus de valeurs pour chaque chemin d'exécution possible à intValue()I
. Autres types de valeurs pouvant être transformées en constantes :
const/4
, const/16
, etc.const-string
const-class
.method public static test1 () I
.locals 2
const/4 v0 , 0x1
return v0
.end method
Étant donné que le code ci-dessus const/4 v0, 0x1
n'affecte pas l'état en dehors de la méthode (pas d'effets secondaires), il peut être supprimé sans modifier le comportement. S'il y avait un appel de méthode qui écrivait quelque chose sur le système de fichiers ou le réseau, il ne pourrait pas être supprimé car il affecte l'état en dehors de la méthode. Ou si test()I
prenais un argument mutable, tel qu'un LinkedList
, toutes les instructions qui y accédaient ne pouvaient pas être considérées comme mortes.
Autres exemples de code mort :
if (false) { dead_code(); }
Cet outil est disponible sous une double licence : une commerciale adaptée aux projets fermés et une licence GPL utilisable dans les logiciels open source.
En fonction de vos besoins, vous devez en choisir un et suivre ses politiques. Un détail des politiques et des accords pour chaque type de licence est disponible dans les fichiers LICENSE.COMMERCIAL et LICENSE.GPL.