Obfuscatorは、ソースコードを機能を変更せずに読み取り困難な形式に変換するツールです。Obfuscatorは、知的財産の保護、コード分析の困難化、リバースエンジニアリングの防止に使用されます。Android Developers Documentationによると、R8およびProGuardによる難読化は、Androidアプリケーションのプロダクションビルドの標準的なステップです。
重要なポイント
Obfuscatorは、難読化を実行するプログラムです。読み取り可能なコードを、機能的には同等だが人間には読めないコードに変換します。難読化ツールの主なタスクは、識別子のマングリング、デバッグ情報の削除、制御フローの難読化、文字列の暗号化です。
難読化は暗号化ではありません。暗号化されたコードは、復号なしでは実行できません。難読化されたコードはJVM、ART、またはJavaScriptエンジン上で直接実行されますが、人間が理解するのは非常に困難です。難読化は絶対的な保護を提供しません — 決意のある専門家は、難読化解除ツールやランタイムデバッグを通じてロジックを常に復元できます。
最初の商用難読化ツールProGuardは、2002年にJavaアプレット用のツールとして登場しました。Android(2008)の成長に伴い、ProGuardはモバイル開発の標準となりました。2018年、GoogleはAndroid Gradle Plugin 3.4のProGuardの代替としてR8をリリースしました。R8はProGuardより2〜3倍高速で、SSA(Static Single Assignment)レベルでの深い最適化により、よりコンパクトなバイトコードを生成します — これはデータフロー分析を可能にする中間表現形式です。
Web開発では、難読化はシンプルなミニファイア(YUI Compressor、2007)から複雑なASTトランスフォーマー(Obfuscator.io、2016)へと進化しました。最新のJavaScript難読化ツールは、制御フローフラットニング、不透明述語(常に真または偽であるが、分析者には明白ではない条件)、ランタイムでの自己復号を伴う文字列暗号化を使用します。Jscrambler(2012)は、難読化をデバッガー保護やDRMメカニズムと統合しています。
適用範囲は広範囲です。モバイル開発では、難読化ツールはAPKデコンパイラ(jadx、APKTool、dex2jar)によるコード盗難から保護します。Web開発では、JavaScript難読化がアルゴリズム、APIキー、クライアント側のビジネスロジックを保護します。ライブラリやSDKでは、難読化により競合他社によるコードの使用を防ぎます。
| 手法 | 難読化前 | 難読化後 |
|---|---|---|
| クラスの名前変更 | NetworkManager | a |
| メソッドの名前変更 | sendRequest() | b() |
| 文字列の暗号化 | "API_KEY" | decrypt("x9fK2p") |
| 条件の難読化 | if (a > b) | if (a > b ? true : false) |
Identifier Manglingは最も一般的な手法です。クラス、メソッド、フィールド、変数の名前が短く情報のない文字列(a、b、c、aa、ab)に置き換えられます。これにより、コードの各要素の目的を理解することが困難になります。ProGuardとR8は異なる型(クラスA、フィールドA、メソッドA)に同一の名前を使用し、分析をさらに複雑にします。
制御フロー難読化は、コード構造を変更して線形シーケンスを非明示的にします。デッドブランチが追加され、条件が反転され(if (a)の代わりにif (!a))、goto風の演算子(ラベル付きのbreak/continue)が挿入されます。これにより、デコンパイラやデバッガーによる分析が非常に時間のかかるものになります。
// ソースコード
function authenticate(token) {
const url = "https://api.example.com/auth";
const headers = { Authorization: "Bearer " + token };
return fetch(url, { method: "POST", headers });
}// Obfuscator.io highモード適用後
const _0x4f2e = ["https://api.example.com/auth",
"Authorization", "Bearer ", "POST"];
(function(_0x5a3b, _0x4f2e) {
const _0x1c2d = function(_0x3e4f) {
while (--_0x3e4f) {
_0x5a3b["push"](_0x5a3b["shift"]());
}
};
_0x1c2d(++_0x4f2e);
}(_0x4f2e, _0x1c2d));
function _0x1c2d(_0x5a3b, _0x4f2e) {
return _0x4f2e[_0x5a3b];
}
function _0x3e4f(_0x1c2d) {
const _0x5a3b = _0x1c2d(0, "https://api.example.com/auth");
const _0x4f2e = { Authorization: "Bearer " + _0x1c2d };
return fetch(_0x5a3b, { method: "POST", headers: _0x4f2e });
}Obfuscator.ioは、文字列配列、配列をシャッフルする自己呼び出し関数を追加し、すべての識別子の名前を変更し、文字列を配列インデックスに置き換えました。元の5行のコードは20行以上の読み取り不能な行に変わりましたが、authenticate(token)機能は完全に保持されています。AST分析による難読化解除は可能ですが、時間を要します。
ProGuardは、2002年から使用されているJavaおよびAndroid向けのクラシックな難読化ツールです。ProGuardは3つのタスクを実行します:圧縮(未使用のクラスとメソッドの削除)、最適化(バイトコードの最適化)、難読化(識別子の名前変更)。ProGuardは、ライブラリの除外ルールを含むproguard-rules.proファイルを介してAndroid Gradle Pluginに統合されます。
R8は、AGP 3.4以降Android Gradle Pluginに含まれているよりモダンな難読化ツールです。R8はProGuardと同じ機能を実行しますが、より高速で(スクラッチからKotlinで記述)、より効率的です(ART Runtime向けのバイトコード最適化が優れています)。R8はProGuardと同じproguard-rules.proファイルを使用して設定されます。R8を有効にするには、build.gradleでminifyEnabled trueを設定するだけで十分です。
マッピングファイルは、R8/ProGuardの出力で、クラス、メソッド、フィールドの元の名前と難読化された名前の対応を含みます。マッピングファイルはクラッシュレポートの分析に重要です。これがないと、スタックトレースにはcom.example.app.MainActivity.onCreateの代わりにa.a.bが表示されます。Firebase CrashlyticsとSentryは自動的にマッピングファイルをアップロードし、レポート内の元の名前を復元します。
マッピングファイルは、新しいアプリケーションリリースごとにFirebaseまたはSentryにアップロードする必要があります。マッピングファイルが失われたりアップロードされなかった場合、難読化後のすべてのクラッシュレポートが読めなくなります。Android Gradle Pluginは、マッピングファイルをbuild/outputs/mapping/release/mapping.txtに自動的に保存します。Firebaseは、リリースビルド中にマッピングをアップロードするCrashlytics Gradle Pluginを使用します。
// app/build.gradle — R8による難読化
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
"proguard-android-optimize.txt"),
"proguard-rules.pro"
}
}
}# proguard-rules.pro — 保持ルール
# Gsonのデータモデルを保持
-keep class com.example.model.** { *; }
# Retrofitインターフェースのクラスを保持
-keep,allowobfuscation interface com.example.api.*
# 公開アクティビティを難読化しない
-keep class * extends android.app.Activity {
public protected *;
}
# プロダクションのログを削除
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(...);
public static int v(...);
public static int d(...);
}proguard-rules.proの-keepルールは重要です — これらがないと、R8はリフレクション(Gson、Retrofit、Room)を介して使用されるクラスとメソッドを削除または名前変更します。assumentSideEffectsは、プロダクションコードからLog.vおよびLog.dの呼び出しを削除します。Gson、Retrofit、OkHttpなどのライブラリは、AAR内のproguard.txtに既製のルールを提供します。
Obfuscator.ioは、識別子の名前変更、文字列暗号化、制御フローフラットニング、デバッガー保護をサポートする最も人気のあるオープンソースのJavaScript難読化ツールです。設定はJSON設定またはCLIを介して行われます。無料版は基本的な手法をサポートします。Enterprise版は、多態的コードと自己保護を追加します。
Jscramblerは、高度な保護を備えた商用のJavaScript難読化ツールです:多態的変換(実行ごとに新しい難読化コードを生成)、デバッガー保護(DevToolsの検出)、スクリーンショット保護(自己防御)、有効期限メカニズム(特定の日付以降、コードは機能しなくなります)。Jscramblerは、バンキングアプリケーションやDRMシステムで使用されています。
// obfuscate.js — Obfuscator.io設定
const JavaScriptObfuscator = require("javascript-obfuscator");
const fs = require("fs");
const code = fs.readFileSync("app.js", "utf8");
const result = JavaScriptObfuscator.obfuscate(code, {
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
numbersToExpressions: true,
simplify: false,
stringArray: true,
stringArrayThreshold: 0.8,
debugProtection: true,
disableConsoleOutput: true,
});
fs.writeFileSync("app.obfuscated.js", result.code);パラメータ Obfuscator.io:controlFlowFlattening:0.75は75%のブロックで制御フローを難読化します。stringArray:trueは文字列を配列に移動します。debugProtectionはDevToolsの開始を防ぎます。disableConsoleOutputはconsole.logを削除します。しきい値が高いほど、難読化時間とコードサイズが大きくなりますが、分析はより困難になります。
難読化はランタイム分析から保護しません。攻撃者はデバッガー(Frida、Objection、Xposed)でアプリケーションを実行し、メソッドをリアルタイムで傍受できます。難読化は静的解析(APKの逆コンパイル、バイトコードの読み取り)から保護しますが、動的解析からは保護しません。ランタイム保護には追加の対策が必要です:SSL Pinning、Root Detection、Integrity Verification。
アプリケーションサイズは難読化後に20〜50%増加する可能性があります。制御フロー難読化はデッドブランチを追加し、条件を重複させます — これによりバイトコードサイズが増加します。文字列暗号化は短い文字列リテラルをdecrypt()呼び出しに置き換え、これもサイズを増加させます。モバイルアプリケーションでは、APKサイズがGoogle Playでのコンバージョンに直接影響するため、これは重要です。
パフォーマンスも影響を受けます。制御フロー難読化は追加のチェックと分岐を追加し、メソッドの実行時間が5〜15%増加します。文字列暗号化は、文字列アクセスのたびにdecrypt呼び出しを追加します。パフォーマンスが重要な関数(AndroidのonDraw、Reactのrender)では、-keepルールを介して難読化を無効にする必要があります。
よくある質問
暗号化は、コードを復号なしでは実行不能にします — 実行には復号化器が必要です。難読化は、コードを読み取り不能にしますが、直接実行可能にします。暗号化はより強力な保護を提供しますが、復号化ローダーが必要であり、それ自体が分析される可能性があります。
難読化解除は可能ですが、労力を要します。jadx、JEB Decompiler、UnConfuserなどのツールは、部分的な難読化解除でバイトコードを復元します。元の名前を含む元のソースコードの完全な復元は不可能です — 名前は不可逆的に失われます。最新の難読化ツール(R8、ProGuard)は、自動難読化解除に耐性があります。
Google Playは難読化を必須としていませんが、build.gradleのminifyEnabledを介して強く推奨しています。難読化なしのアプリケーションはAPKToolやjadxで簡単に逆コンパイルでき、APIキーの盗難、改ざん、海賊版に対して脆弱になります。ほとんどの大規模アプリケーションはR8またはProGuardを使用しています。
クラッシュレポートは、難読化後に難読化された名前(com.example.app.MainActivityの代わりにa.b.c)を含みます。復元にはR8/ProGuardによって生成されたマッピングファイルが使用されます。自動スタックトレース難読化解除のために、マッピングファイルをFirebase CrashlyticsまたはSentryにアップロードする必要があります。
文字列暗号化は、文字列リテラル(APIキー、URL、メッセージ)を暗号化データとランタイムでのdecrypt関数呼び出しに置き換えます。これにより、機密性の高い文字列が逆コンパイルされたコード内での単純な検索によって読み取られるのを防ぎます。R8/ProGuardは、-encryptstringsルールを介して文字列暗号化をサポートしています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。