A personal device audit of a Eonon aftermarket Android car stereo (Topway TS18 platform), run like a mini pentest engagement — recon → static analysis → sandboxed dynamic verification → reporting. Real engagement, real finding: the unit is carrying a dropper tied to an active malware campaign (BADBOX / MoYu Group). Written up here because the whole arc — “I don’t trust this Chinese box” turning into a confirmed live C2 chain — is exactly the kind of finding a pentest report exists to capture, and the techniques (static Android RE, obfuscated-payload decoding, passive OSINT instead of live contact, sandbox construction) generalize past this one device.
Scope and authorization
Device owned by the operator (self-engagement, no third-party authorization needed — this methodology is for your own hardware or an explicit engagement, never someone else’s device). Scope was read-only analysis: dump accessible files, decompile apps, verify firmware hashes, and identify indicators of compromise. Explicitly out of scope: contacting C2 infrastructure with the real device identity, running any decoded payload, flashing or modifying the unit. The rule followed throughout: never run an unknown sample on your real machine or network.
Target profile
| Field | Value |
|---|---|
| Platform | Unisoc UIS8581A (8× Cortex-A55 @ 1.6 GHz), PowerVR GE8322 GPU |
| OS | Android 10, kernel 4.14.133 |
| Vendor stack | Topway TS18 (uis8581a2h10_Automotive-user), rebadged as Eonon |
| Installed build | TS18.2.2_20240402.213649, MCU Ts18.1.1-100-8F-A5489D-240424 |
| FOTA server | iotapi.abupdate.com (plain HTTP download host: iotdown-jd.mayitek.com) |
| App store | DoFun market, osappstore.cdn.cardoor.cn |
Methodology
- Recon (passive): read on-device “About” screens, pulled version/build strings from photos, searched XDA/vendor mirrors for matching firmware — no device contact needed for this phase.
- Firmware acquisition and hash verification: downloaded a same-build firmware package from a third-party mirror, compared its SHA-256 against the value the unit’s own FOTA server reported for that version (mismatch, by design — the official package was a delta update, not a full image; documented rather than assumed malicious).
- Static triage of a full file-system dump (USB-accessible folders pulled by the operator) —
file,strings, size/entropy sampling, looking for installed-package lists, cached APKs, and app logs rather than reading every file blind. - APK decompilation:
jadx+apktoolagainst every cached/pushed APK, focused on manifests (permissions, exported components, shared UID) before diving into code. - Obfuscated payload extraction: the suspicious app’s embedded second stage was XOR-encoded across split byte arrays reassembled at runtime — wrote a small Python script to statically replicate that reassembly (same XOR key schedule, same byte-array layout) without ever executing the app..
- Firmware image unpack:
lpunpack/simg2imgon thesuper.imgdynamic-partition blob,debugfsto listsystem/vendorpartitions read-only, searched for the dropper’s strings to establish whether it ships in the image or arrives only via push (it’s push-only — not in the factory image). - Passive OSINT on IOCs:
dig/whoisagainst the C2 domains (resolution + registration dates only, no HTTP requests), then cross-referenced against public threat-intel write-ups. This is the step that turned “a weird obfuscated loader” into “a documented campaign”. - Sandbox construction for anything requiring execution-adjacent tooling (decompilers, not the sample itself):
bwrap --unshare-allwith no network, no$HOME, read-only sample mounts, dedicated output dir.
Findings
🔴 Critical — Active malware dropper installed (com.tw.jar1)
What: com.tw.jar1 v1.11 is present in the unit’s installed-package list. Static decompilation showed an obfuscated loader (XOR-encoded strings, class names scrambled) that decrypts an embedded jar at runtime and loads it via reflection — a deliberate change from the app’s own v1.8, which called the same ad-SDK openly (ADMOYU.start(...)). The decoded second stage (package com.xgw.f) phones home to task.mymoyu.shop/cpc/api/client/active, polls four rotating domains (a1.ty4523.space, a1.vrr8345.site, a1.ty54fgd435.my, a1.ue886578433.online) for a sdk.jar third stage, falls back to an Alibaba OSS config bucket, and class-loads whatever it downloads.
Confirmed via third-party intel: this exact chain is documented. Nokia Deepfield’s report describes the identical host.dex (package com.xgw.f, sha256 prefix ccce124a… — matching the hash of the payload extracted here) hitting the same endpoint and domains. Kaspersky/Securelist and VicOne independently name com.tw.jar1 (“JarService”) as the first-stage dropper in a campaign targeting DoFun/Topway head units specifically, attributed with high confidence to MoYu Group, linked to the BADBOX botnet (per HUMAN Security’s BADBOX 2.0 disclosure, over 1M compromised off-brand Android devices). The third stage (not recovered in this engagement) is reported elsewhere as a clicker/residential-proxy module (“zhima”) — ad fraud plus using the device as an exit node for someone else’s traffic.
Delivery vector: not preinstalled in the factory firmware image (confirmed absent from both system.img and vendor.img after unpacking the official-channel firmware). Delivered post-sale by com.tw.core (TWCore), the OEM’s own update/device-management app — which runs as sharedUserId="android.uid.system" and holds INSTALL_PACKAGES/DELETE_PACKAGES/FORCE_STOP_PACKAGES. Per Kaspersky, the mechanism is a malicious MQTT upgrade command sent to TWCore’s broker — the legitimate OEM update channel gets hijacked to push the dropper, no user interaction and no separate exploit required. This is the textbook supply-chain/update-channel trust failure: the thing with install authority is the thing that got abused.
Impact: CVSS-style reasoning — network-adjacent attacker (owns the C2) achieves persistent code execution on a privileged automotive IVI system with phone-state and location permissions, chained to a stage-3 payload capable of arbitrary JS execution (web command per Kaspersky’s IOC list) and further code download (loadlib2). Practically: ad fraud, device used as a reverse proxy for other people’s traffic, and an open door for whatever the operators push next — MQTT commands observed elsewhere in this family include silent app uninstall, HOME launcher replacement, and disabling a competing SIM.
Remediation: block all listed IOCs at the network edge (below). Uninstall com.tw.jar1 if the unit’s package manager allows it — expect TWCore to attempt reinstall, since it still has install authority. Full remediation requires either a firmware rebuild without TWCore/the DoFun push stack, or keeping the unit permanently network-isolated.
🟠 High — OTA channel integrity is weak
FOTA client (com.abupdate.fota_demo_iot) downloads update packages over plain HTTP (iotdown-jd.mayitek.com), with integrity checked only by a SHA-256 the same HTTP-served JSON response supplies — an on-path attacker who can MITM the check call can also just serve a different hash alongside a different package. The server’s checkVersion response additionally carries free-form commands with "method":"write" that the client appears to execute — remote configuration write capability baked into an unauthenticated-looking channel. This is the same class of failure that let the stage-1 dropper in via TWCore/MQTT, just on a different OEM service.
🟠 High — Decoded payload trusts all TLS certificates
The decoded second-stage loader’s HTTPS client (com.xgw.f.k) installs a custom TrustManager/HostnameVerifier that accepts any certificate. Combined with loading and executing whatever sdk.jar it receives with zero signature or hash check, this means anyone who can MITM the unit’s traffic — not just the campaign operators — can supply arbitrary code to it. Ironic given the payload’s own job is to compromise the device; it’s sloppy malware, but the flaw is equally present against a legitimate-looking MITM, which matters for a defender deciding whether network isolation alone is sufficient (it is, if done correctly — this flaw doesn’t create a second path in, it just means a blocked-domain unit isn’t magically safer against an on-path attacker on an untrusted network).
🟡 Medium — SELinux effectively permissive
Crash-dump logs show seccomp disabled by setenforce 0 on every app launch — SELinux is not enforcing on this build. Lowers the bar for any future code execution (including the stage-3 payload, if it lands) to escalate beyond its own app sandbox.
🟡 Medium — Unguarded exported system components
com.tw.carinfoservice (the CAN-bus/vehicle-integration service manager) runs as sharedUserId="android.uid.system" and exports CarService and PermissionService with no permission enforcement beyond a normal-level custom permission — meaning any app installed on the unit, dropper or otherwise, can bind to a system-privileged service that controls vehicle-side integrations. Classic over-broad exported="true" finding, the same class of over-broad exported-component finding, just on the IPC surface instead of the network surface.
🔵 Low — Shared vendor signing key across unrelated apps
Multiple Topway-origin packages (com.tw.carinfoservice, TWCore, others) share one platform signing certificate (CN=John, O=TW, Shenzhen, valid to 2040, SHA1withRSA — itself a weak/disabled algorithm). A single compromised key compromises the trust boundary for the entire app set at once. Worth knowing as a vendor-practice smell even though it wasn’t the entry point here (the dropper was signed by a different, unrelated key — CN=Zheng, O=jk, with a fake 457 “country” — itself a tell, since legitimate platform apps shouldn’t be signed by random third-party certs).
⚪ Informational — Red herring, documented and dismissed
A configuration.txt found in the same file dump was a stock NiceHash OS mining config (BTC address, worker name). Traced back to its actual origin — a USB drive previously used to boot NiceHash OS, unrelated to the head unit — before being ruled out. Worth noting as process, not just outcome: a pentest report should show the dead ends that got checked and cleared, not just the live findings; a USB stick’s provenance mattering is the same “verify before escalating” discipline as not assuming a hash mismatch means a malicious firmware mirror (that mismatch above turned out to be a delta-vs-full-image difference, not tampering).
Indicators of Compromise
Domains (all confirmed live via passive DNS as of this engagement — not dead infrastructure):
| Domain | Role | Resolves to | Registered |
|---|---|---|---|
task.mymoyu.shop |
activation/heartbeat | 10 rotating IPs (Leaseweb, others) | — |
a1.ty4523.space |
loader | 47.76.118.67 (Alibaba Cloud) | 2026-09-02 |
a1.vrr8345.site |
loader | 47.76.118.67 (same IP as above) | 2026-09-02 |
a1.ty54fgd435.my |
loader | 157.90.33.73/74 | 2026-08-10 |
a1.ue886578433.online |
loader | 193.166.255.171 | 2026-08-21 |
terrtest.oss-cn-hangzhou.aliyuncs.com |
config fallback | Alibaba OSS (returned AccessDenied) |
— |
Package/file hashes:
| Item | Hash |
|---|---|
com.tw.jar1 v1.11 APK |
SHA-256 0776f04a36539bfc5e5760c29dbb19f4dc950826b224b27ffdab70c208dbb0ce |
com.tw.jar1 v1.8 APK |
SHA-256 0c9cc75b5ababdff37477723148956990115d8be52107f4b89f98d13f722cc9d |
Decoded stage-2 jar (host.dex equiv.) |
SHA-256 ccce124a28b1c181c0f18ae8bd1b47ea1904e6f108b8d10664838c37262d4640 — matches Deepfield’s published host.dex hash prefix |
Local behavior: payload queries a loopback service on 127.0.0.1:12101 for a device UID — confirms an on-device companion/service component beyond the app itself.
Remediation summary
- Block all domains above at the router/firewall level — the single most effective mitigation available without touching the unit itself.
- Remove the SIM / disable cellular if present; keep the unit off any network you’d mind being used as an exit node.
- Attempt removal of
com.tw.jar1; assume TWCore will try to reinstall it and monitor for recurrence. - Treat
com.tw.coreas the actual trust boundary that failed — any future clean-firmware project should omit it and the DoFun push stack entirely, not just remove the dropper after the fact. - Report the sample/IOCs to the vendor and to Kaspersky/relevant threat-intel trackers — this device is currently live, which the published reports’ “domains now dead” status didn’t capture.
Follow-up: native REA/Ghidra sweep of the companion casting app
A separate app on the same unit (com.zjinnova.zlink, “T-Link5” — the head-unit-side casting receiver for Android Auto Wireless/HiCar/CarLife/AirPlay) was audited with REA’s Ghidra provider, decompiling its bundled native libraries directly rather than relying on strings output alone.
- Commercially shielded, not custom-obfuscated: the APK’s own DEX is a thin loader (
MyWrapperProxyApplication) that decrypts andSystem.load()s the real logic from an encrypted asset at runtime via a named commercial packer (libshellx-super.2019.so, confirmed by string xrefs in the unpacker stub). This is a known class of Chinese app-shielding product, not evidence of malice by itself — but it means the real application logic is unreachable by static analysis, full stop, regardless of tooling. - No
com.tw.jar1-style C2 pattern found in the 9 of ~50 bundled.solibraries checked (prioritized by risk: telemetry, location/camera, auth, casting-protocol libraries). No rotating domains, no hardcoded IPs, no runtime code-fetch-and-load. - Two legitimate but undisclosed telemetry SDKs confirmed via decompiled, cross-referenced strings (not just floating text): Tencent Bugly (
libBugly.so,bugly.qq.com) and Tencent MTA (libMtaNativeCrash_v2.so,com.tencent.stat.StatNativeCrashReport) — a second analytics SDK not previously flagged. - Real privacy-relevant capability, no evidence of active use:
libdmsdpdvgps.so, part of Huawei’s DMSDP distributed-device framework, exportsGpsReceiveLocationData/GpsReceiveDrivingData/GpsReceiveCarBaseInfo— a module whose entire purpose is relaying location and driving-behavior data across linked devices. No external hostname appears in its strings (DMSDP uses its own service-mesh transport, not fixed URLs), so this is a documented capability, not a confirmed exfil channel. - Methodology note:
search_stringsreturning cross-referenced, provider-verified string objects (tied to real code via Ghidra’s analysis) is meaningfully stronger evidence than a rawstringsdump of the same binary — worth defaulting to REA/Ghidra over shellstringswhen a finding needs to hold up in a report, not just inform triage.
Follow-up: sdk.jar (stage 3) — confirmed unavailable via any passive source
Checked whether the undocumented third stage could be obtained without contacting the campaign’s live infrastructure: general web search, the original Deepfield/Kaspersky/VicOne reporting, and MalwareBazaar directly. No public copy exists anywhere. Deepfield’s own researchers never recovered it either — their loader domains were dead at analysis time, so even the primary published source describes sdk.jar’s behavior only by inference from what the first-stage loader does with it, never from its own bytes. Live retrieval from the (now-reactivated) loader domains remains the only way to see it, and remains explicitly out of scope for this engagement.
Follow-up: building a clean firmware image
With the findings confirmed, work began on a debloated firmware image built from the official-channel TS18.2.2_20240904 package (not the third-party mirror used for analysis) — unpacked via lpunpack, edited in place with debugfs -w (no loop-mount/root needed for simple file removal), to be repacked with lpmake once the removal list is finalized and tested.
Key constraint that shaped the approach: TWCore and com.tw.carinfoservice both declare sharedUserId="android.uid.system", co-signed with the vendor’s own platform certificate (not available to us). This rules out patching those apps’ manifests and repacking — a signature mismatch on a sharedUserId app risks a boot failure. Deletion sidesteps the problem entirely: removing a file from /system/priv-app needs no signature validation at all, Android just sees one fewer preinstalled app. Where modification was viable (com.zjinnova.zlink, confirmed to use no sharedUserId), stripping its two telemetry libraries and re-signing with a self-owned key remains an option — a smaller, separately-testable change, not bundled into the main image build.
Removed from the working image (13 APKs, verified via debugfs ls before and after): the malware delivery vector (TWCore), the FOTA client, three DoFun-ecosystem apps, and eight Google/Play-services packages (GmsCore, GooglePartnerSetup, GoogleServicesFramework, Phonesky, Velvet, AndroidAutoStub, ConfigUpdater, GmsSampleIntegration) — a deliberate de-Googling pass, replacing the Play ecosystem with F-Droid (sideloaded from a copy the operator already had) and, later, Aurora Store pulled through F-Droid’s own catalog rather than fetched and trusted blind.
Deliberately kept: the vehicle-integration stack (CAN bus/AC/radio/voice — not implicated in any finding), WebView/Chrome (AOSP-licensed, not Play-Services-gated, assumed present by other system surfaces), and carrier config (closer to AOSP telephony plumbing than Google tracking surface).
Open question carried forward: removing AndroidAutoStub assumes the casting app’s own protocol stack doesn’t depend on it for Android Auto Wireless specifically. Unconfirmed until first boot — the kind of assumption a changelog should surface explicitly rather than discover silently.
Follow-up: auditing the cleaned build found a shared signing key
A permission-reference sweep (/system/etc/permissions, /product/etc/permissions, sysconfig XMLs) of the rebuilt image turned up a finding worth its own entry:
- Dangling references to removed packages are harmless —
SystemConfig/PackageManagerread privapp-permission allow-lists as grants-if-present; a listed-but-absent package is a normal boot-time no-op, not a crash risk. Confirmed by checking what kind of file each reference lived in before concluding this. - Real gap found:
com.dofun.market,com.dofun.variety, andcom.dofun.carassistant.car(ad-supported app store, an ad-laden widget app, and a GPS/trip-logging app) are not part of the factory firmware at all — they exist only in/dataon the live unit, installed post-sale. The same is true ofcom.tw.jar1itself. A system-only firmware flash does not remove any of them — this only became visible by checking whether each suspect package existed as a file in the image, not just by trusting the original removal list was complete. A factory data reset is a required second step, not optional.com.dofun.carsettingis part of the factory image (it’s the real Settings app) and was correctly left alone. - Headline finding: the OS’s default theme engine (
/system/etc/theme/theme.apkand two selectable packs in/system/etc/theme_package/) is signed with the exact same certificate fingerprint as thecom.tw.jar1dropper (CN=Zheng, OU=jkkj, O=jk, L=gd, ST=sz, C=457, SHA-2565B:88:18:77:16:D7:4B:CA:64:1B:35:DA:26:1C:FA:52:F8:64:67:DD:DA:0A:C6:09:90:83:18:4D:F8:42:99:9D). Decompiling all three theme APKs found them individually clean — no exported components, no obfuscation pattern matching the dropper, no embedded hosts, nosharedUserId. The significance isn’t “this file is malware” — static analysis didn’t show that — it’s that the signing key had write access to a component baked into the official factory firmware, not just post-sale push infrastructure. That’s a supply-chain-trust finding distinct from an active-compromise finding, and the right response is to stop trusting the key going forward (treat any future artifact bearing it as deserving the same scrutiny the dropper got), independent of what any single signed file currently contains.
Remediation taken: all three theme APKs stripped of their original signature and re-signed with a newly generated, self-owned key (jarsigner, SHA256withRSA — no apksigner/v2 scheme available in this environment, but none of the three use sharedUserId, so legacy v1 JAR signing was sufficient and needed no vendor key). Verified post-repack by extracting the rebuilt image and re-checking the signer in place, not just the pre-repack file.
Follow-up: v2 build — theme re-signing, a GMC boot logo, and a documented gap
A second repack pass added: the three re-signed theme APKs (above), F-Droid (/system/app/FDroid/FDroid.apk, sideloaded from a copy the operator already had — Aurora Store deliberately left for post-flash install via F-Droid’s own catalog rather than baking in an unverified binary), and a generated 1024×600 24-bit boot-splash BMP built from the operator’s own GMC artwork — this last one stays a separate userdata-level file (.Tcfg/bootlogo.bmp), not part of the flashable package, since the local-updater package format here has no userdata-partition slot to embed it into.
Every untouched file in the package was byte-size-diffed against the original before and after both repack passes — the discipline of proving “nothing else changed” rather than assuming it. A README-CLEAN-BUILD.txt ships inside the package itself, documenting exactly what was removed, restored, re-signed, and — critically — what flashing this package does not fix (the /data-resident apps above), so the gap travels with the artifact instead of living only in chat history.
Follow-up: the data wipe is a separate vendor package, not part of the OTA
Since the firmware-only flash can’t touch /data (above), checked whether the vendor provides a dedicated wipe mechanism through the same local-updater channel rather than relying on the device’s own Settings menu. It does. The same firmware host that served the base build also hosts TS18.2.2_Format+File.zip, explicitly tagged (WIPE), flashed through the identical USB/Key-1 local-updater path as regular firmware — the vendor deliberately keeps data wipes out of the normal OTA package (so routine updates don’t destroy user data by accident) rather than making wiping impossible through that channel.
Verified by extraction before trusting a third-party host for it, same discipline as every other download this engagement: the package contains exactly one file, a 32 MiB blob that turned out to be a freshly formatted, genuinely empty ext4 filesystem labeled "data" — 33,547,704 of its 33,554,432 bytes are literal zero, with only filesystem metadata and an empty lost+found as content. This is close to the ideal case for trusting an unverified download: a structurally blank filesystem image has no space to hide a payload in, which is a stronger safety argument than hash-checking alone would give for an executable or an app.
Minor forensic bonus: the wipe package’s file UUID shares a middle segment (9cbf-22ea) with three of the four files in this engagement’s own (unrelated, newer) firmware package — suggesting that segment is a stable vendor-assigned partition-type tag, not a per-build random value. Weak but free corroborating evidence that a 2020-dated wipe package is still structurally compatible with 2024-era firmware on this platform.
Follow-up: why a full system-image test can’t be done in Waydroid
Before committing to the “do we have a safe way to test this before flashing real hardware” question, checked whether Waydroid (or a similar Android container) could run the modified system.img/vendor.img pair. It can’t, and the reasons are structural, not a tooling gap:
- Waydroid runs Android as a container on the host’s own Linux kernel via binder/ashmem, using generic LineageOS-based system images built for that approach. It does not boot an arbitrary OEM
system.img+vendor.imgpair the way real hardware or a full device emulator would. - Even if it could, the vendor kernel modules bundled in
vendor.img(pvrsrvkm.ko,sprd_camera.ko, etc.) are version-locked to kernel4.14.133viavermagic(confirmed earlier in this engagement) — no desktop Linux host runs that kernel, so those modules couldn’t load regardless. - The vendor HALs (PowerVR GPU, Unisoc audio/camera/radio, the CAN-bus integration) have no equivalent on a desktop host at all; a generic container has nothing to substitute them with.
What’s actually available as pre-flight validation is what this engagement has been doing throughout: structural verification (partition metadata matching via lpdump, extracting the rebuilt image and confirming file-level content rather than trusting the build process), not behavioral/boot testing. The one thing a tool like Waydroid could legitimately help with is testing an individual modified APK’s installability and signature validity in isolation (e.g. the re-signed theme packages) — a much narrower, app-layer-only check that says nothing about whether the image as a whole boots on real hardware.
Follow-up: the backdoor confirmed, full root achieved, and the project’s real ending
Everything from here on happened in a single extended session, on live hardware, after run-as was confirmed to require shell or root UID before even inspecting its argument — which meant Termux’s own app-sandboxed attempt failing earlier was expected, not a dead end. The actual path to shell:
- Wireless ADB, built from nothing but property writes.
ro.secure=1/ro.debuggable=0(a realuserbuild) normally makes this impossible without a prior USB-authorized ADB session. Permissive SELinux let it happen anyway:setprop service.adb.tcp.port 5555thensetprop ctl.start adbdfrom an unprivileged Termux shell startedadbdin TCP mode regardless —ctl.*properties are gated purely by SELinuxproperty_contextson modern Android, and permissive mode doesn’t enforce that gate.adb connect 127.0.0.1:5555from Termux’s own (initially broken, later working)adbclient then produced a genuineshell(uid 2000) session. - From
shell, the backdoor fired for real:run-as "[email protected]" id→uid=0(root). Confirmed, not inferred — the exact vendor-build-identity-shaped string found weeks earlier in a decompiledtoolboxapplet (xupdate_main) turned out to be a genuine, functioning privilege-escalation backdoor, reachable the moment any process reachesshellUID by any means. This is a finding independent of anything else in this engagement — any process that can reach ADB shell on this platform has root, full stop.
Full partition map obtained as root (/dev/block/by-name/, real eMMC device mmcblk0), revealing something the whole prior engagement never knew: this device implements AVB (vbmeta/vbmeta_system/vbmeta_vendor partitions). ro.boot.verifiedbootstate=orange (unlocked) + ro.boot.veritymode=enforcing meant the bootloader itself wouldn’t hard-block custom content, but runtime dm-verity on the mounted partitions still needed neutralizing — standard custom-ROM technique: avbtool make_vbmeta_image --set_hashtree_disabled_flag --set_verification_disabled_flag, written directly to all three vbmeta partitions before anything else.
This fully superseded the entire local-updater/recovery-signature investigation. Every finding about RecoverySystem.installPackage(), the otacerts.zip/”Zhantang” certificate, and the unidentified recovery-partition validation logic — real findings, fully documented above — turned out to be beside the point once root was achieved: direct dd to the real block devices (super at mmcblk0p31, userdata at mmcblk0p44) bypasses that entire verification chain, because nothing in it runs before raw bytes land on the chip. Lesson for the methodology: a software-signature wall that looks unbreakable from inside the OS can become irrelevant the moment a lower-privilege boundary (here, run-as’s own backdoor) is crossed — worth checking what’s actually enforcing a wall before assuming it has to be defeated on its own terms.
Write executed and verified, byte-count-exact on every target (vbmeta×3, super — 2,936,012,800 bytes, zero dropped records — userdata), using the RootSU community build’s system.img (independently verified clean of every project IOC, decompiled root mechanism confirmed attributed/non-obfuscated) combined with this engagement’s own verified-stock vendor.img, substituted in place of a corrupted download. The device boots successfully — confirmed by Russian-language UI (the RootSU build’s actual content), working su in Termux, and a pre-installed Root Explorer, all independently confirming genuine success despite the About screen’s version string staying unchanged (the RootSU author never edited that string — a red herring the operator initially read as failure, resolved by checking three independent content signals instead of one string).
With real root, the original goal — stripping TWCore/DoFun bloat — stopped needing the whole rebuild-and-flash pipeline. Direct live removal (mount -o rw,remount /, delete the APKs, pm uninstall --user 0) replaced hours of offline image surgery. Also closed off cleanly: Magisk was considered and correctly rejected for this specific goal — it solves a problem (no root) this engagement no longer had, at the cost of patching an unexamined boot partition for zero actual benefit toward debloating.
The touch-inversion mystery — the one thread that ran the entire second half of this engagement — had nothing to do with any of it. Ruled out, in order, with real evidence at each step: not firmware content (persisted across stock, custom, and fully-wiped RootSU flashes), not Android’s rotation/window-manager state (dumpsys window showed a completely clean ROTATION_0, display matching physical panel dimensions exactly), not hardware (unit never opened). Confirmed via pointer_location as a genuine raw-coordinate 180° mirror, present before Android’s input pipeline ever sees it — at which point the actual fix turned out to be a standard factory-service gesture baked into the touch controller itself: holding five fingers on the screen simultaneously re-triggers calibration. No file, partition, or line of code in this entire engagement needed to change. The lesson generalizes past this one device: when a symptom survives every software variable you can control changing, and the hardware is confirmed untouched, check for a vendor self-service mechanism before assuming the fix has to be built — especially on cheap consumer hardware, where “undocumented factory gesture” is a genuinely common design pattern, not an edge case.
Lessons for the methodology itself
- An obfuscated loader that changes behavior between point versions (v1.8 calling the ad SDK openly vs. v1.11 hiding the same call behind reflection + decryption) is itself a signal worth flagging before you even know what the payload does — see Malware Persistence and Evasion Techniques.
- Passive OSINT before active contact is what turned this from “suspicious decompiled code” into “confirmed live campaign, here’s the vendor’s internal naming (JarService), here’s the attribution (MoYu/BADBOX), here’s what stage 3 actually does” — all without a single packet sent to the C2. That ordering (recon → static analysis → OSINT correlation → then decide whether live contact is justified and authorized) is the general lesson, not just an Android-specific one.
- A full no-network
bwrapsandbox is cheap enough to build per-engagement that there’s no excuse for decompiling an unknown sample directly on a connected machine, even for “just reading the code.”