Simplify はアプリを仮想的に実行してその動作を理解し、同じように動作するが人間にとって理解しやすいようにコードの最適化を試みます。各最適化タイプはシンプルかつ汎用的なため、特定のタイプの難読化が使用されるかどうかは問題ではありません。
左側のコードは難読化されたアプリを逆コンパイルしたもので、右側のコードは難読化が解除されています。
このプロジェクトには、smalivm、simplify、デモ アプリの 3 つの部分があります。
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 jar はsimplify/build/libs/
にあります。提供されている難読化されたサンプル アプリを簡素化することで、動作をテストできます。実行方法は次のとおりです ( simplify.jar
変更が必要な場合があります)。
java -jar simplify/build/libs/simplify.jar -it " org/cf/obfuscated " -et " MainActivity " simplify/obfuscated-app.apk
何が難読化解除されるのかを理解するには、難読化されたアプリの README を確認してください。
Simplify が失敗した場合は、次の推奨事項を順番に試してください。
-it
オプションを使用して、少数のメソッドまたはクラスのみをターゲットにします。--max-address-visits
、 --max-call-depth
、および--max-method-visits
を使用してみてください。-v
または-v 2
を試し、ログと DEX または APK のハッシュで問題を報告してください。Windows 上でビルドすると、次のようなエラーが発生してビルドが失敗します。
tools.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 }
) です。コードが到達可能な場合、メソッドの外部の状態に影響を与えない場合、つまり副作用がない場合、そのコードは無効であるとみなされる場合があります。たとえば、コードはメソッドの戻り値に影響を与えたり、クラス変数を変更したり、IO を実行したりすることはできません。これは静的解析では判断することが困難です。幸いなことに、smalivm は賢い必要はありません。実行できることはすべて愚かにも実行し、確信が持てない場合は副作用があると想定します。定数伝播の例を考えてみましょう。
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 の静的分析における大きな課題の 1 つはリフレクションです。注意深いデータ フロー分析を行わない限り、引数がリフレクション メソッドに適しているかどうかを知ることはできません。これを行うにはスマートで賢い方法がありますが、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
の可能な戻り値が 1 つだけであり、戻り値の型を定数にできるためです。引数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 ファイルで参照できます。