
Android仮想マシンおよび難読化解除ツール
Simplify はアプリを仮想的に実行してその動作を理解し、その後コードを最適化して、同じように動作しながらも人間にとって理解しやすくなるようにします。各最適化タイプはシンプルで汎用的なので、使用されている難読化の具体的な種類は問いません。
左側のコードは難読化されたアプリの逆コンパイル結果で、右側のコードは難読化が解除されています。
このプロジェクトには3つの部分があります:smalivm、simplify、およびデモアプリです。
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でビルドしていて、次のようなエラーでビルドが失敗する場合:
Could not find tools.jar. Please check that C:\Program Files\Java\jre1.8.0_151 contains a valid JDK installation.
これはGradleが適切なJDKパスを見つけられないことを意味します。JDKがインストールされていることを確認し、JAVA_HOME 環境変数をJDKパスに設定し、ビルドに使用するコマンドプロンプトを閉じて再度開いてください。
遠慮しないでください。仮想実行と難読化解除は魅力的な問題だと思います。興味がある人は自動的にクールで、タイポ修正のような小さなものでも貢献を歓迎します。Issueで質問したり、プルリクエストを送信したりしてください。
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 })。到達可能なコードであっても、メソッド外部の状態に影響を与えない場合(つまり、副作用がない場合)、デッドコードと見なされる可能性があります。例えば、コードがメソッドの戻り値に影響を与えず、クラス変数を変更せず、I/Oも行わない場合などです。これは静的解析では判断が難しいです。幸い、smalivmは賢くなろうとする必要はありません。可能なものはすべて愚直に実行し、確信が持てない場合は副作用があると仮定します。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, 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
これが行っているのは v0 = 1 だけです。
.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 は const/4 v0, 0x1 に置き換えられます。これは、intValue()I の可能な戻り値が1つしかなく、戻り値の型を定数にできるためです。引数 v0 と v1 は曖昧でなく、変更されません。つまり、intValue()I におけるすべての可能な実行パスについて値の一致があります。定数に変換できる他のタイプの値:
const/4、const/16 などconst-stringconst-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 ファイルにあります。