Simplify виртуально запускает приложение, чтобы понять его поведение, а затем пытается оптимизировать код так, чтобы оно действовало идентично, но было легче для понимания человеком. Каждый тип оптимизации прост и универсален, поэтому не имеет значения, какой конкретный тип обфускации используется.
Код слева представляет собой декомпиляцию запутанного приложения, а код справа уже деобфусцирован.
Проект состоит из трех частей: smalivm, упрощения и демо-приложения.
if
или switch
с неизвестным значением приводит к выбору обеих ветвей. 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
Для сборки требуется установленный Java Development Kit 8 (JDK).
Поскольку этот проект содержит подмодули для платформ Android, клонируйте их с помощью --recursive
:
git clone --recursive https://github.com/CalebFenton/simplify.git
Или обновите подмодули в любое время с помощью:
git submodule update --init --recursive
Затем, чтобы создать один jar-файл, содержащий все зависимости:
./gradlew fatjar
Банка Simplify будет находиться в папке simplify/build/libs/
. Вы можете проверить его работу, упростив предоставленный запутанный пример приложения. Вот как вы это запустите (возможно, вам придется изменить simplify.jar
):
java -jar simplify/build/libs/simplify.jar -it " org/cf/obfuscated " -et " MainActivity " simplify/obfuscated-app.apk
Чтобы понять, что происходит деобфускации, ознакомьтесь с README приложения Obfuscated App.
Если Simplify не сработал, попробуйте следующие рекомендации по порядку:
-it
только для нескольких методов или классов.--max-address-visits
, --max-call-depth
и --max-method-visits
.-v
или -v 2
и сообщите о проблеме с помощью журналов и хеша DEX или APK.Если сборка в Windows завершается неудачно с ошибкой, похожей на:
Не удалось найти инструменты.jar. Убедитесь, что C:Program FilesJavajre1.8.0_151 содержит действительную установку JDK.
Это означает, что Gradle не может найти правильный путь к JDK. Убедитесь, что JDK установлен, задайте для переменной среды JAVA_HOME
путь к JDK и обязательно закройте и снова откройте командную строку, которую вы используете для сборки.
Не стесняйся. Я считаю, что виртуальное выполнение и деобфускация — увлекательные проблемы. Любой, кто заинтересован, автоматически становится крутым, и вклад приветствуется, даже если это просто исправление опечатки. Не стесняйтесь задавать вопросы в задачах и отправлять запросы на включение.
Пожалуйста, укажите ссылку на APK или DEX и полную команду, которую вы используете. Это значительно упрощает воспроизведение (и, следовательно, устранение ) вашей проблемы.
Если вы не можете поделиться образцом, укажите хеш файла (SHA1, SHA256 и т. д.).
Если операция помещает значение типа, который можно превратить в константу, например строку, число или логическое значение, эта оптимизация заменит эту операцию константой. Например:
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
В этом примере зашифрованная строка расшифровывается и помещается в v0
. Поскольку строки являются «константизируемыми», move-result v0
можно заменить 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."
Код мертв, если его удаление не может изменить поведение приложения. Наиболее очевидный случай — если код недоступен, например if (false) { // dead }
). Если код доступен, его можно считать мертвым, если он не влияет ни на какое состояние вне метода, т. е. не имеет побочного эффекта . Например, код не может влиять на возвращаемое значение метода, изменять какие-либо переменные класса или выполнять какие-либо операции ввода-вывода. Это трудно определить в статическом анализе. К счастью, смайливму не обязательно быть умным. Он просто тупо выполняет все, что может, и предполагает наличие побочных эффектов, если не может быть уверен. Рассмотрим пример из 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."
В этом коде invoke-static
больше не влияет на возвращаемое значение метода, и давайте предположим, что он не делает ничего странного, например, не записывает байты в файловую систему или сетевой сокет, поэтому не имеет побочных эффектов. Его можно просто удалить.
const-string v0, "VGVsbCBtZSBvZiB5b3VyIGhvbWV3b3JsZCwgVXN1bC4="
const-string v0, "Tell me of your homeworld, Usul."
Наконец, первая const-string
присваивает значение регистру, но это значение никогда не используется, т. е. присвоение недействительно. Его также можно удалить.
const-string v0, "Tell me of your homeworld, Usul."
Ура!
Одной из основных проблем статического анализа Java является отражение. Просто невозможно понять, что аргументы относятся к методам отражения, не проведя тщательного анализа потока данных. Есть умные и хитрые способы сделать это, но smalivm делает это, просто выполняя код. Когда он находит отраженный вызов метода, например:
invoke-virtual {v0, v1, v2}, Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
Он может знать значения v0
, v1
и v2
. Если он уверен, каковы значения, он может заменить вызов Method.invoke()
фактическим неотраженным вызовом метода. То же самое относится к отраженным поискам полей и классов.
Для всего, что не вписывается в конкретную категорию, есть оптимизация в глазок. Это включает в себя удаление бесполезных операций check-cast
, замену вызовов Ljava/lang/String;-><init>
на const-string
и так далее.
.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
Все это делает 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
move-result v0
заменяется на const/4 v0, 0x1
. Это связано с тем, что существует только одно возможное возвращаемое значение для intValue()I
, а тип возвращаемого значения можно сделать константой. Аргументы v0
и v1
однозначны и не изменяются. Другими словами, существует консенсус значений для каждого возможного пути выполнения intValue()I
. Другие типы значений, которые можно превратить в константы:
const/4
, const/16
и т. д.const-string
const-class
.method public static test1 () I
.locals 2
const/4 v0 , 0x1
return v0
.end method
Поскольку код выше const/4 v0, 0x1
не влияет на состояние вне метода (нет побочных эффектов), его можно удалить без изменения поведения. Если был вызов метода, который записывал что-то в файловую систему или сеть, его нельзя было удалить, поскольку он влияет на состояние вне метода. Или, если test()I
принимает изменяемый аргумент, например LinkedList
, любые инструкции, обращающиеся к нему, не могут считаться мертвыми.
Другие примеры мертвого кода:
if (false) { dead_code(); }
Этот инструмент доступен по двойной лицензии: коммерческой, подходящей для проектов с закрытым исходным кодом, и лицензии GPL, которую можно использовать в программном обеспечении с открытым исходным кодом.
В зависимости от ваших потребностей вы должны выбрать один из них и следовать его политике. Подробная информация о политиках и соглашениях для каждого типа лицензии доступна в файлах LICENSE.COMMERCIAL и LICENSE.GPL.