Cerberus Anti-theft is stalkerware: a reverse engineering
There is an app on Google Play called Lock Screen Protector (com.lsdroid.lsp). It requests accessibility service permissions — the most sensitive permission on Android. Once granted, it reads all screen content, performs gestures, and takes screenshots. It monitors for the power dialog and dismisses it — the phone cannot be turned off. It blocks the notification shade — airplane mode cannot be enabled. When someone tries to power off the device, it captures a screenshot and sends the JPEG bytes to another app: Cerberus.
Contents
Background
Cerberus Anti-theft by LSDroid — not the unrelated banking trojan on Wikipedia’s Cerberus (Android) — is one of the longest-running stalkerware applications in the Android ecosystem. It has been a documented intimate-partner-violence threat since at least 2018, when researchers from Cornell Tech and NYU named it as one of two Play Store apps that “violate Play Store policy by hiding their app icon and showing no notifications, making them as covert as off-store spyware” in their IEEE S&P paper The Spyware Used in Intimate Partner Violence.
Echap’s stalkerware-indicators database — the IOC feed used by MVT, Quad9, AdGuard, TinyCheck, and MISP — has listed Cerberus with 8 package names and the cerberusapp.com domain since its first release. This research led to commit fa248462 on 2026-04-10, which added com.lsdroid.lsp and com.lsdroid.cerberus.enterprise among other indicators to ioc.yaml. Every consumer of the database now sees both halves of the LSP ↔ main IPC pair.
Cerberus has been around since 2011. In April 2013, The Verge argued Google should ship lost-phone features as a built-in Android service, interviewing Luca Sagaria of LSDroid as one of the third-party fillers of that gap — 150,000 licenses sold, two engineers, one customer service representative. The same Luca Sagaria whose personal signing certificate (CN=Luca Sagaria, SHA-256 6B:CE:75:CE) signs the Kids app today. Four months later, in August 2013, Google launched Android Device Manager (now Find Hub, 1B+ Play Store installs) and made remote location, ring, lock, and wipe a free, built-in Android feature. The anti-theft product category Cerberus had been selling into became a platform feature that shipped on every Android phone.
Google knows who these people are. In November 2017, Google emailed LSDroid citing a Malicious Behavior Policy violation for cloaking functionality and linking to external APKs. LSDroid posted the email publicly. In May 2018, the Chatterjee et al. IEEE S&P paper documented that Cerberus “provides all [anti-theft] functionality, along with a remote Android shell in its web portal” and reported the apps to Google. Google did not act on the stalkerware finding. Some months later — LSDroid’s own archived statement places the start in November 2018 — Google removed com.lsdroid.cerberus from the Play Store, citing a different policy: “Apps that cause users to download or install applications from unknown sources outside of Google Play are prohibited.” Google enforced against the off-store-distribution business model, not against the surveillance capability set the paper had named.
LSDroid’s response, on their own website (archived snapshot): they would “not bother to appeal” because “the full-featured app” is “always available on our website.”
In 2020, Cerberus accounted for 52% of all stalkerware detections tracked by F-Secure — the single most detected stalkerware globally. On October 4, 2023, it returned to Google Play under a new package name (com.ssurebrec). LSDroid srl, Milan, same developer account. Their website describes the Play Store version as having “a slightly smaller set of commands.” The direct-download version remains free on cerberusapp.com. The removal-to-return gap was 5 years 5 months.
The Play APKs ship an open-source Android-modding library — HiddenApiBypass, published April 2021 — whose own maintainers explicitly warn developers that shipping it on Google Play is a policy violation.
What “a slightly smaller set of commands” actually means, in the bytecode of the live Play Store APK, is what this writeup documents.
LSDroid SRL on Google’s records
LSDroid is not hiding. The Cerberus Play Store listing at play.google.com/store/apps/details?id=com.ssurebrec displays its developer publicly — “Cerberus • LSDroid srl”, 100K+ downloads, 4.2 stars, 2.28K reviews. The company is named on the listing itself, not behind a shell-name front. The “LS” in LSDroid almost certainly stands for the founder’s initials — Luca Sagaria — paired with “Droid” for Android; the company name itself encodes the personal identity behind it.
The same legal entity is the named beneficiary in Google’s AdMob compliance file. com.surebrec.Login.onCreate calls RewardedInterstitialAd.load() with a hardcoded ad-unit ID ca-app-pub-9848961826628138/7883431316 — Cerberus monetizes the Play Store version with a Google AdMob rewarded-interstitial ad that plays when the user opens the login screen without biometrics. The publisher ID is 9848961826628138. Google’s IAB-standard compliance file at https://realtimebidding.google.com/sellers.json (981,141 publishers as of this writing) maps that publisher ID to LSDroid SRL:
{
"seller_id": "pub-9848961826628138",
"seller_type": "PUBLISHER",
"name": "LSDroid SRL",
"domain": "cerberusapp.com"
}
LSDroid SRL is, in Google’s own public compliance data, the entity being paid for ad impressions served inside Cerberus. In Italian corporate law “SRL” stands for Società a Responsabilità Limitata or a limited liability company. Google Payments has LSDroid SRL’s legal registration, bank details, and billing address on file because Google sends them money.
The same entity signs the APKs. droidsaw signing on com.surebrec v3.7.6 (the WearOS companion downloaded directly from cerberusapp.com) yields a v1 certificate with subject CN=LSDroid srl, C=IT and SHA-256 digest B6:74:AB:75:7F:83:23:C1:07:35:A6:00:BB:06:F9:03:54:F6:8F:4E:E6:1C:71:BA:F2:27:95:FA:2C:C9:11:E6. The same SHA-256 digest also signs com.ssurebrec v1.4.9 (Play Store main) and com.surebrec v3.8.0 standard (direct-download). All three are cryptographically bound to the same RSA-2048 private key held by LSDroid SRL in Milan, Italy.
Two apps, one product
Cerberus (LSDroid srl) ships two versions.
The Google Play version (com.ssurebrec v1.4.9, targetSdk 36, €5/month) passes review, handles billing, and executes 44 remote commands via Firebase Cloud Messaging (FCM): silent front- and rear-camera capture, microphone recording, continuous GPS tracking, device wipe, lockout, launcher-icon concealment, fake shutdown, a wrong-unlock photo trigger, a dial-code re-entry mechanism, and a Wi-Fi-BSSID proximity tracker that fires when the victim’s phone comes within range of specified networks. It delegates accessibility-service permission to a second Play Store app by the same developer, Lock Screen Protector (com.lsdroid.lsp), which reads every screen, captures screenshots, intercepts power-dialog presses, and blocks the notification shade. Together, v1.4.9 + LSP are the deployment on current victims’ phones.
The direct-download version (com.surebrec v3.8.0, targetSdk 26, free) is distributed from cerberusapp.com. On a rooted phone it is a standalone rootkit: Runtime.exec("su") invoked from 33 distinct callers, an interactive WebSocket shell at wss://cerberusapp.com:8443, an SMS command channel that intercepts its own messages before they reach the victim’s inbox, keylogging via a built-in accessibility service labeled “System Framework,” notification suppression, and a SuCommands toolkit that silently grants 16 permissions and blocks uninstallation via reflected Android framework APIs. Same command-and-control (C2) infrastructure, same operator dashboard, same subscription as the Play version.
Threat model: which version a victim is likely running
The threat model relevant to most current victims is the Play Store build (com.ssurebrec v1.4.9 + com.lsdroid.lsp v3.6). It installs cleanly, updates automatically, doesn’t trigger Play Protect, and doesn’t need root. The abuser buys a €5 subscription, installs it during a moment of physical access, grants the permissions, enables device admin, installs Lock Screen Protector alongside it, and hides both from the launcher. The phone looks normal. The abuser controls it from cerberusapp.com. The capabilities documented later in this writeup that explicitly require root — the SuCommands reflected-binder-stub toolkit, /system/etc/cerberus_conf.xml factory-reset survival, status-bar disable from arbitrary code paths — apply only to the v3.8.0 direct-download build on rooted devices.
Silent sign-in is built into the Login activity. com.surebrec.Login.onCreate reads user and pass extras from the launching Intent; when both are present, onStart auto-fills the fields and invokes the login button’s OnClickListener.onClick programmatically. An abuser launching com.ssurebrec/com.surebrec.Login via ADB or a provisioning APK passes credentials as Intent extras and the sign-in completes without any on-screen interaction. The same activity sets a null ActivityManager.TaskDescription(null, null, color) which removes the app from the Android recents screen — a victim swiping through recents after the moment of access sees a blank card instead of the Cerberus login.
On rooted phones with unlocked bootloaders, v3.8.0 writes configuration to /system/etc/cerberus_conf.xml — a system-partition path that survives factory reset. Modern Android’s verified boot prevents the write; on older or unlocked devices, a factory reset does not remove Cerberus.
For detection tools and indicators of compromise (IOCs), both package names matter: com.ssurebrec for current victims, com.surebrec for historical and rooted-device cases. And com.lsdroid.lsp matters for both — it’s the common denominator across all deployment scenarios.
The HiddenApiBypass policy violation
v3.8.0’s rootkit capabilities — silent permission grant, uninstall block, status-bar disable, notification suppression, power-menu control, background-network-throttle bypass — depend on Java reflection against internal Android binder stubs (IPackageManager, IStatusBarService, IPowerManager, IDevicePolicyManager, INotificationManager, INetworkPolicyManager, ILockSettings, IPermissionManager).
Since Android 9 (API 28), reflection on these classes is blocked by default. v3.8.0 targets targetSdk 26, which receives Android’s hidden-API leniency, so its SuCommands reflection chain runs without a bypass library. The Play builds (com.ssurebrec, LSP, Kids) target targetSdk 36, do not ship SuCommands, and do not invoke the reflection chain.
The Play-Store builds nonetheless ship a library whose class-level annotation declares it exists specifically to defeat the Android 9+ hidden-API restriction. The decompiled source tree of com.ssurebrec v1.4.9 contains the file org/lsposed/hiddenapibypass/i.java, whose first declared field is a static final sun.misc.Unsafe. The library is HiddenApiBypass, published by the LSPosed Android-modding project, Apache-2.0-licensed, actively maintained, distributed on Maven Central as org.lsposed.hiddenapibypass:hiddenapibypass. Its class-level @RequiresApi(Build.VERSION_CODES.P) annotation targets Android 9+ — the exact Android versions where the hidden-API restriction exists. Its presence in a Play APK is the policy violation, independent of how narrowly the library is actually used downstream.
A note on naming. The “LSP” acronym appears twice in this writeup. LSPosed is the open-source Android-modding project above. Lock Screen Protector (
com.lsdroid.lsp) is LSDroid’s accessibility-service companion app.
How the library works, and what it silences
The library obtains sun.misc.Unsafe via Unsafe.class.getDeclaredMethod("getUnsafe").invoke(null), uses it to read internal memory offsets inside the Android Runtime (ART), and constructs a reference to the hidden dalvik.system.VMRuntime.setHiddenApiExemptions(String[]) — itself a hidden API, reached via the Unsafe path. It then calls setHiddenApiExemptions("") with an empty-string prefix that matches every class.
The javadoc on setHiddenApiExemptions:
All matching APIs are treated as if they were on the whitelist: access permitted, and no logging.
The “no logging” half matters separately. The bypass also silences Android’s own audit trail for non-SDK-API usage — the telemetry channel Google Play Protect uses to detect hidden-API calls after install. A Play-side scanner on a Cerberus-installed device sees nothing, because the library told the runtime to stop reporting.
What the bypass is used for, and how many Play apps ship it
HiddenApiBypass ships in three of the five Cerberus Play Store apps: com.ssurebrec, com.lsdroid.lsp, and com.lsdroid.cerberus.kids. Persona2 and Enterprise do not ship the library. The v3.8.0 direct-download APK doesn’t ship it either — targetSdk 26 receives Android’s hidden-API leniency, and v3.8.0’s SuCommands reflection chain runs natively on that leniency.
A whole-DEX call-graph search across each of the three Play apps finds exactly one external caller of the renamed setHiddenApiExemptions entry point in each. All three callers follow the same shape:
if (!Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
throw new Exception("Not a Xiaomi device");
}
if (Build.VERSION.SDK_INT >= 28) {
org.lsposed.hiddenapibypass.<X>.b(new String[]{ "" });
}
activated = true;
The trigger differs per app:
com.ssurebrec:Lw0/e;-><init>(FakeShutSetupActivity), called fromFakeShutSetupActivity.onResume()during initial fake-shutdown configuration.com.lsdroid.lsp:LI2/e;-><init>(SettingsActivity), called from LSP’s settings screen.com.lsdroid.cerberus.kids:Lcom/lsdroid/cerberus/kids/f;-><init>(Context), called from the Kids app’s onboarding flow.
In every case the immediately-following use is identical: reflect on android.miui.AppOpsUtils.getApplicationAutoStart(Context, String), a Xiaomi MIUI internal hidden API, to query whether the app has been granted MIUI’s autostart permission. MIUI aggressively kills background apps that lack autostart permission; each Cerberus app needs to know its own autostart-permission state to know whether its persistence is going to work on a Xiaomi device. Each app runs in its own Android process, so each needs its own copy of the library and its own activation.
The demonstrated use is narrow — a single Xiaomi-OEM-specific permission probe per app, gated on MANUFACTURER == "xiaomi", behind a static one-shot flag. The library’s broader capability is gated open (the empty-prefix setHiddenApiExemptions("") call exempts every class once activated), but the only subsequent hidden-API call this writeup observes in any of the three Play apps is the MIUI AppOpsUtils reflection. Whether other call sites exist through Class.forName-style indirection or runtime-loaded code isn’t externally verifiable from static analysis.
Three Play Store apps each ship org.lsposed.hiddenapibypass; each ship requires the README’s dependenciesInfo evasion snippet at build time; each passed Play review without the library being flagged. The find . -path '*org/lsposed/hiddenapibypass/*' grep test runs against any of the three APKs independently, and each grep is a separate dispositive policy citation.
The library’s own README warns against Play deployment
The HiddenApiBypass README.md documents:
Google Play doesn’t allow apps to use hidden APIs, reporting library usage will cause your app to fail app review, you need to disable dependencies info reporting in build.gradle.
The README then publishes the exact build.gradle evasion snippet:
android {
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
}
com.ssurebrec v1.4.9 is live on Play, its APK ships the library, and Play’s automated dependency reporting, apparently, does not flag it. That combination requires applying the evasion snippet at build time — the snippet documented in the library’s own README.
Timeline: the rebuild window and the relisting
Cerberus was removed from Google Play in late 2018 / early 2019, citing the off-store-distribution policy — months after the Chatterjee et al. IEEE S&P paper named it as intimate-partner-violence spyware in May 2018. It returned in October 2023. The five-year gap reflects LSDroid’s choice of when to repackage and resubmit; the surveillance capability set the paper named was achievable in 2018 and remains achievable through documented Android APIs without any hidden-API bypass. The chronology of LSDroid’s actual rebuild window is anchored on certificate-issuance dates, not on library availability:
| Date | Event |
|---|---|
| May 2018 | Chatterjee et al. IEEE S&P paper names Cerberus as IPV spyware; researchers report to Google |
| Late 2018 / early 2019 | Google Play removes com.lsdroid.cerberus, citing off-store-distribution policy — not the stalkerware finding |
| 2018 – 2023 | Cerberus is direct-download-only; no Play presence |
| 2021-04-07 | Initial commit of AndroidHiddenApiBypass published on GitHub |
| 2021 – 2023 | The library matures across Android 10–13; published to Maven Central |
| 2023-01-07 | LSDroid enrolls com.lsdroid.lsp in Play App Signing — earliest mark of bridge-app construction |
| 2023-06-22 | LSDroid SRL issues the disguised RSA-2048 signing certificate 42:1F:4D:1D:… |
| 2023-06-26 | LSDroid SRL issues the clean RSA-2048 signing certificate B6:74:AB:75:… |
| 2023-06-26 → 2023-10-04 | Rebuild window: LSDroid integrates HiddenApiBypass, applies the README’s evasion snippet, submits com.ssurebrec to Play review |
| 2023-10-04 | com.ssurebrec v1.0 goes live on Google Play |
After April 2021, integrating a hidden-API bypass became a single Gradle dependency line — what previously required hand-rolled sun.misc.Unsafe ART-offset code became one import. LSDroid took advantage of that on the way back to Play, but only for one Xiaomi MIUI probe per app (see use-site call graph above); the surveillance capability set the Play apps actually deliver does not depend on hidden-API reflection at all. The cert-issue-to-Play-live interval of roughly three months is consistent with a standard Android app rewrite-and-review cycle: integrate dependencies, rebrand the APK, submit to Play review.
A grep is the enforcement test
Three statements: (1) Google Play’s Malicious Behavior Policy bans non-SDK interface access. (2) Three Cerberus Play APKs (com.ssurebrec v1.4.9, com.lsdroid.lsp v3.6, com.lsdroid.cerberus.kids v1.2.9) each ship org.lsposed.hiddenapibypass; find . -path '*org/lsposed/hiddenapibypass/*' after unzipping any one of them is dispositive. (3) The library’s name is literally “Hidden API Bypass” and the policy bans Play deployment of such tools. The enforcement decision is a grep — a Play engineer verifies it in under a minute without LSDroid cooperation, and three independent Cerberus Play apps fail the same grep.
Runtime gating: which reflected calls actually resolve
Whether the reflected calls succeed at runtime depends on each method’s server-side permission gate inside system_server. IPackageManager.grantRuntimePermission requires GRANT_RUNTIME_PERMISSIONS; IStatusBarService.disable requires STATUS_BAR_SERVICE. A Play-store-signed app does not hold these. On rooted devices, Runtime.exec("su") + app_process invocation of SuCommands runs with system-equivalent UID and the full chain resolves. The reflected-binder-stub call sites live in SuCommands.main() in v3.8.0; they are not present in com.ssurebrec v1.4.9. The device-admin-mediated subset (lockNow, force-password-quality) works on both. The root-gated set lives in Root capabilities (v3.8.0) below.
In the Play APKs the library has two demonstrable effects once activated: (1) the entire process is exempted from hidden-API restrictions — every hidden API in the runtime is reachable from any code path, not just the line that calls the bypass; (2) Android’s hidden-API telemetry is silenced — the channel Play Protect uses to detect hidden-API calls on installed devices reports nothing for the lifetime of the process. The only direct call site externally verifiable from static analysis is the Xiaomi MIUI autostart-permission probe per app. Whether the Play apps reach additional hidden APIs through Class.forName-style runtime string indirection — Cerberus does construct strings at runtime in U2.U1 for other purposes — isn’t externally verifiable.
Analysis scope
| Layer | Target | Source |
|---|---|---|
| Play Store phone app | com.ssurebrec v1.4.9_play (versionCode 285) | Play Store |
| Play Store LSP module | com.lsdroid.lsp v3.6 (versionCode 39) | Play Store |
| Direct phone app (standard) | com.surebrec v3.8.0 (versionCode 333800) | cerberusapp.com |
| Direct phone app (disguised) | com.ssurebrec v3.8.0 (disguised build) | cerberusapp.com |
| WearOS companion (standard) | com.surebrec v3.7.6 (versionCode 283761) | cerberusapp.com |
| WearOS companion (disguised) | com.ssurebrec v3.7.6 (versionCode 283761, disguised build) | cerberusapp.com |
| Enterprise (marketed as MDM) | com.lsdroid.cerberus.enterprise v1.7 (versionCode 17) | Play Store |
| Kids / Child Safety | com.lsdroid.cerberus.kids v1.2.9 (versionCode 331290) | Play Store |
| Persona / “Personal security — Women safe” | com.lsdroid.cerberus.persona2 v1.8 (versionCode 21) | Play Store |
Out of scope: Runtime traffic capture, server-side behavior, active API probing, installation on non-test devices. No server contact attempted. No exploitation. Static analysis only of lawfully obtained APKs from Google Play and cerberusapp.com (public download).
Legal basis: CFAA — no computer access occurs (Van Buren 2021). DMCA 2024 rulemaking security research exemption (through Oct 2027). Washington RCW 9A.90 white hat protection.
Findings
| # | Finding | Scope |
|---|---|---|
| 1 | Three Play Store-distributed APKs (com.ssurebrec, com.lsdroid.lsp, com.lsdroid.cerberus.kids) ship org.lsposed.hiddenapibypass — Malicious Behavior Policy violation, verifiable by find -path '*org/lsposed/hiddenapibypass/*' against any of the three | Play (main + LSP + Kids) |
| 2 | Silent permission grant via reflected IPackageManager — 16 dangerous permissions, no consent dialog | v3.8.0 rooted |
| 3 | Uninstall blocking via reflected IPackageManager.setBlockUninstallForUser | v3.8.0 rooted |
| 4 | Interactive root shell over WebSocket (wss://cerberusapp.com:8443) | v3.8.0 |
| 5 | SMS C2 with abortBroadcast + priority=2147483647 on SMS_RECEIVED — command SMS hidden from victim’s inbox | v3.8.0 |
| 6 | NotificationListener suppresses notifications within ±5 s of a command SMS | v3.8.0 |
| 7 | Remote anti-forensics: preventusbdebug disables ADB | v3.8.0 |
| 8 | Status bar disabled for 1 hour via IStatusBarService.disable() | v3.8.0 rooted |
| 9 | Screen-scrape credential capture via LSP AccessibilityService getText on input fields | Both |
| 10 | Fake shutdown — screen blacks, phone continues recording and uploading | Both |
| 11 | No certificate pinning, no network_security_config.xml | Both |
| 12 | Unsalted SHA-1 password hashing | Both |
| 13 | AES/CBC with static null IV for SMS command encryption and config restore; key derived from raw IMEI or ANDROID_ID with no hashing — 2^53^ to 2^64^ effective entropy claimed as AES-128, recoverable in O(1) from device-side access (the IMEI/ANDROID_ID is directly readable on a co-installed device) | Both |
| 14 | Lock-screen passcode is the first four characters of the device ID — the same ID displayed on the lock screen itself, and readable by any zero-permission app through com.surebrec.IdProvider | Both |
| 15 | Exported ContentProviders (IdProvider, ConfProvider) with no permission guard | Both |
| 16 | Google AdMob embedded in stalkerware — publisher pub-9848961826628138, attributed to LSDroid SRL in Google’s own sellers.json | Both |
| 17 | Unprotected broadcast delivers LSP screenshot JPEGs to the main app — any third-party app on the device can fire the full shutdown-attempt chain with a forged extra | Play Store |
| 18 | Dropbox + Google Drive exfiltration — BACKUP uploads contacts, SMS, call log, photos, videos to the operator’s cloud storage | v3.8.0 |
| 19 | Dial-code re-entry — dialing 23723787 (T9 mapping of CERBERUS, configurable per-install) on the victim’s own keypad launches the admin UI after the launcher icon is hidden | v3.8.0 |
| 20 | Wi-Fi BSSID radar — operator names an SSID; Cerberus reports when the victim’s phone enters or leaves its range; works indoors where GPS doesn’t | Both |
| 21 | Tasker integration gives the operator seven event-triggered macro entry points, including wrong-admin-password, geofence crossings, and significant-motion sensor events, parameterised with live device state | v3.8.0 |
| 22 | System-partition persistence — on rooted devices, config and automation DB install to /system/etc/cerberus_* and survive factory reset | v3.8.0 rooted |
| 23 | Six signing certificates across three custody regimes — three LSDroid-controlled (including the child-monitoring APK still signed by a 2009 RSA-1024 SHA-1 personal cert in Luca Sagaria’s name with every identity field Unknown), three held by Google via Play App Signing | Suite |
| 24 | “Cerberus Enterprise” is marketed as MDM but declares no BIND_DEVICE_ADMIN, ships no DeviceAdminReceiver, and has no device-policy code — the APK is a location tracker with geofencing, not managed-device software | Enterprise |
| 25 | Dual distribution — the same developer, same subscription, and same Firebase C2 backend serve a Play-compliant build and a direct-download rootkit side by side | Both |
| 26 | Five separate Firebase projects across the suite (api-project-999803017449, cerberus-lock-screen-protector, cerberus-enterprise, cerberus-kids, cerberus-persona); three carry FCM command channels (main 44, Enterprise 5, Kids 17 bidirectional); main is additionally backed by a server-side Firebase Realtime Database that persists operator-state on Google’s infrastructure | Suite |
Methodology
Static analysis only, against the nine-APK corpus — no privileged data, no running device, no subpoena, no runtime instrumentation, no server interaction. Tooling: droidsaw (Android APK/DEX decompiler and static analyzer) for manifest, signing, resources, decompile, xrefs, call-graph analysis, source extraction, and audit; Semgrep for policy-violation pattern matching against the extracted source tree; TruffleHog for hardcoded-secret scans; apksigner and openssl for v2/v3 signing-block certificate extraction, modulus comparison (gcd over the two LSDroid SRL public moduli to verify independent RNG draws), and notBefore/notAfter validity dates.
What static analysis cannot show. Claims about runtime state — that setAndAllowWhileIdle alarms actually fire under Doze, that WorkManager periodic uploads occur on the configured cadence, that ConnectivityReceiver actually drains cached surveillance media on next CONNECTED transition, that the bypass library’s empty-prefix setHiddenApiExemptions call exempts the running process — are inferred from the configuration encoded in static bytecode, not observed via runtime instrumentation. Server-side behavior (operator dashboard, license validation, command-and-control delivery) is entirely unobserved; no requests were made to cerberusapp.com during this analysis. Class.forName(stringConcat) reflective indirection cannot be exhaustively traced from static analysis; the surveillance-feature attribution to documented Android APIs is the strongest claim static analysis can make, not a guarantee that no hidden-API access exists. Where the article uses phrasing like “Cerberus does X,” read it as “the bytecode encodes X to happen at runtime under documented Android behavior.”
droidsaw itself was built using Claude Code (running Claude Opus 4.6), which also helped draft this article and the disclosure letters that followed it; AI tooling sits at the engineering layer of this work, not at the analytical-claim layer — every finding in this article is verifiable byte-by-byte against the cited APKs.
Architecture
Cerberus ships as multiple packages across two distribution channels.
Distribution channels
| Channel | Package | Version | targetSdk | Root | Installed on |
|---|---|---|---|---|---|
| Google Play | com.ssurebrec | 1.4.9_play | 36 | No | Victim |
| Google Play | com.lsdroid.lsp | 3.6 | 36 | No | Victim |
| Google Play | com.lsdroid.cerberus.enterprise | 1.7 | 36 | No | Employee (victim) |
| Google Play | com.lsdroid.cerberus.kids | 1.2.9 | 33 | No | Child (victim) + parent (operator) |
| Google Play | com.lsdroid.cerberus.persona2 | 1.8 | 36 | No | Victim |
| cerberusapp.com | com.surebrec (standard) | 3.8.0 | 26 | Yes | Victim |
| cerberusapp.com | com.ssurebrec (disguised) | 3.8.0 | 26 | Yes | Victim |
| cerberusapp.com | com.surebrec (WearOS standard) | 3.7.6 | 28 | No | Abuser |
| cerberusapp.com | com.ssurebrec (WearOS disguised) | 3.7.6 | 28 | No | Abuser |
Package ecosystem
| Package | Role |
|---|---|
com.ssurebrec | Play Store phone app — passes review, handles billing, 44 FCM commands |
com.surebrec | Direct download — full RAT with root, shell, SMS C2, keylogging |
com.surebrec (WearOS) | Watch companion — alarm, emergency mode, Bluetooth proximity (installed on the abuser’s watch) |
com.lsdroid.lsp | Lock Screen Protector — accessibility, anti-power-off, screenshot |
com.lsdroid.cerberus | Package alias for com.surebrec (HIDE / UNHIDE via pm enable/disable) |
com.lsdroid.cerberuss | Package alias for com.surebrec (HIDE / UNHIDE via pm enable/disable) |
com.lsdroid.cerberus.enterprise | Sold as “Enterprise MDM” — shipped APK is a location tracker with geofencing; no device-admin capability |
com.lsdroid.cerberus.kids | Kids / “Child Safety” — app monitoring, screen time, location, notification suppression |
com.lsdroid.cerberus.persona2 | Persona / “Personal security — Women safe” — emergency SMS, GPS streaming, hard dependency on main app |
Component comparison
The Play Store deployment is a pair: com.ssurebrec v1.4.9 and com.lsdroid.lsp v3.6 by the same developer, both required, both installed together during abuser setup. The capability column for the Play side reflects the combined v1.4.9 + LSP deployment, because that is what is actually on a victim’s phone. A row marked “Via LSP” means the capability is supplied by the companion app’s accessibility service, not by the main app’s own bytecode.
| Capability | v3.8.0 (direct) | v1.4.9 + LSP (Play) |
|---|---|---|
Root shell (su) | 50+ calls | Absent |
| WebSocket shell | wss://cerberusapp.com:8443 | Absent |
| SMS C2 channel | Yes (configurable keyword) | Absent |
| Accessibility service | AccService (“System Framework”) | Via LSP (canRetrieveWindowContent + canPerformGestures + canTakeScreenshot) |
| Screen-scrape credential capture | canRequestFilterKeyEvents (key-event filter) | Via LSP (AccessibilityNodeInfo.getText on input fields) |
| Power-off interception + fake shutdown | AccService detects system power dialog | Via LSP (dismiss + broadcast to main app’s ShutdownDialogActivity) |
| Status-bar blocking | IStatusBarService.disable(1 hour) | Via LSP (AccessibilityService overlay) |
NotificationListener | Intercepts + suppresses | Absent |
| SIM change detection | SIMChangeReceiver | Absent |
| SMS interception | READ_SMS, RECEIVE_SMS, abortBroadcast | Absent |
| Contact exfiltration | READ_CONTACTS | Absent |
| Call interception | PROCESS_OUTGOING_CALLS | Absent |
| NFC command trigger | Yes | Absent |
Anti-forensics (preventusbdebug) | Settings.Secure ContentObserver | Absent |
| Uninstall blocking | SuCommands.block_uninstall (reflected) | Device admin only |
What the Play Store version still does
The Play Store version accepts 44 FCM commands including WIPE, FAKE_SHUTDOWN, HIDE, and TAKEPICTURE. It has no certificate pinning. It exports a ContentProvider that leaks the device registration ID to any app on the device.
The FCM command handler declares EXEC_TERM_COMMAND and STARTSHELL — the dispatch code exists, but the execution handlers are absent from the bytecode.
What the direct version adds
The direct version targets SDK 26 (Android 8) and is distributed from cerberusapp.com — neither subject to Play Store’s policy restrictions on SMS / Call Log permissions (which since 2019 require default-handler status or compliance review) nor to Android-level permission tightening at later API levels (e.g., ACCESS_BACKGROUND_LOCATION separated as its own permission at API 29; before that, foreground location implicitly granted background access). It adds READ_SMS, RECEIVE_SMS, SEND_SMS, READ_CALL_LOG, READ_CONTACTS, CALL_PHONE, and MODIFY_PHONE_STATE — permissions the Play Store version doesn’t request.
Signing keys: a mixed-custody picture
The Cerberus suite is signed by six distinct certificates across three custody regimes. Pulling apksigner verify --print-certs across the nine-APK sample, then openssl x509 -noout -subject -dates on each PEM, yields:
| SHA-256 (first 8) | Subject | Issued | Signs | Custody |
|---|---|---|---|---|
6B:CE:75:CE (RSA-1024, SHA-1) | CN=Luca Sagaria, all other fields Unknown | 2009-03-13 | Kids | LSDroid |
| Play App Signing | CN=Android, O=Google Inc. | 2021-10-07 | Enterprise | |
| Play App Signing | CN=Android, O=Google Inc. | 2023-01-07 | LSP | |
42:1F:4D:1D | CN=LSDroid srl, C=IT | 2023-06-22 | Disguised phone (v3.8.0) + disguised WearOS (v3.7.6) | LSDroid |
B6:74:AB:75 | CN=LSDroid srl, C=IT | 2023-06-26 | Play Store main (com.ssurebrec), direct-download v3.8.0 standard, WearOS standard | LSDroid |
| Play App Signing | CN=Android, O=Google Inc. | 2024-02-29 | Persona2 (“Personal security — Women safe”) |
The 2009 personal cert still signs a child-monitoring app in 2026. Luca Sagaria generated a self-issued RSA-1024, SHA-1 developer certificate on 2009-03-13, years before LSDroid SRL was incorporated; every identity field other than the common name reads Unknown. NIST disallowed RSA-1024 for digital-signature generation effective 2014 (SP 800-131A); public CAs stopped issuing SHA-1 code-signing certs by January 2016. Seventeen years on, that same personal cert — not LSDroid SRL’s corporate cert, not a Play App Signing key held by Google — is what signs com.lsdroid.cerberus.kids on children’s phones, valid through 2036-07-29. LSDroid SRL’s name does not appear in the signing chain of its own child-monitoring product.
The cert is self-signed, so the standard PKI revocation path doesn’t apply — there’s no issuing CA to publish a CRL or OCSP response. The validity window remains open until 2036 unless Google blocks new uploads under this cert, refuses to distribute APKs signed by it, or LSDroid migrates Kids to Play App Signing (which would change the signing key going forward and requires LSDroid’s cooperation). The modulus is publicly extractable from any Cerberus Kids APK on Google Play; one apksigner verify --print-certs followed by openssl x509 -text returns the full 1024-bit modulus and the SHA-1 signature value to anyone with the APK on disk.
The direct-download pair is four days apart. The 42:1F:4D and B6:74:AB keys were both issued as CN=LSDroid srl, C=IT in June 2023, 96 hours apart, signing non-overlapping APK distributions: the B6:74:AB key signs the openly-published releases (Play Store main, direct-download v3.8.0 standard, WearOS standard); the 42:1F:4D key signs only the disguised variants. The two keys share no prime factors — they were generated from independent RNG draws, not from a single low-entropy keygen window where two adjacent calls might have shared internal state.
C2 protocol
Primary channel: Google’s Firebase Cloud Messaging
Every command that reaches a victim’s device transits Google’s push-notification infrastructure. The main app com.ssurebrec registers under Firebase project api-project-999803017449; commands arrive via FCM push to FCMMessagingService. Each message contains a message field with the command string and optional param field. The handler dispatches to SurebrecService or BackgroundService via intent extras. The Play Store version exposes 44 commands; the direct version adds SMS-triggered commands for a total of 60+ distinct operations.
The Cerberus suite spans five Firebase projects, not one. Each LSDroid product is configured against its own Firebase project: api-project-999803017449 (main), cerberus-lock-screen-protector (LSP), cerberus-enterprise (Enterprise), cerberus-kids (Kids), cerberus-persona (Persona2). Each project ID is in the corresponding APK’s google-services.json. The five projects’ billing-account ownership on Google’s side is not externally verifiable. Three of the five carry FCM command channels — main (44 commands), Enterprise (5 commands, see below), Kids (17 bidirectional tokens, see Kids subsection). LSP and Persona2 use Firebase only for Analytics + Crashlytics telemetry; their operational paths are HTTP POST to cerberusapp.com and, for LSP, broadcast IPC from the main app.
Main’s api-project-999803017449 is additionally backed by a Firebase Realtime Database (api-project-999803017449.firebaseio.com) — but the device-side APK does not bundle the Firebase Database SDK. The RTDB is server-facing only: LSDroid’s PHP backend writes operator-state into it; the operator’s web dashboard reads from it. Google’s Firebase RTDB is LSDroid’s operator-data persistence layer.
Command table (FCM)
| Command | Description |
|---|---|
TAKEPICTURE | Capture photo (front/rear camera, flash control) |
CAPTUREVIDEO | Record video (camera, flash, duration) |
RECORDAUDIO | Record audio (configurable duration, max 300s) |
SCREENRECORD | Record screen |
SCREENSHOT | Capture screenshot |
START_TRACKING / STOP_TRACKING | Continuous GPS tracking |
STARTEMERGENCY / STOPEMERGENCY | Emergency mode (all sensors active) |
SMSLOG | Exfiltrate SMS messages |
CALLLOG | Exfiltrate call history |
HISTORY | Exfiltrate browser history |
GET_APP_LIST | List installed applications |
BACKUP / STOP_BACKUP | Bulk data exfiltration |
SMS | Send SMS as victim (impersonation) |
CALL | Place phone call |
WIPE / WIPESD | Factory reset / wipe SD card |
LOCK / UNLOCK | Lock/unlock device |
ALARM | Trigger alarm with message |
MESSAGE | Display message on screen |
HIDE / UNHIDE / HIDESHOW | Toggle launcher icon visibility |
FAKE_SHUTDOWN / START_SHUTDOWN | Fake power-off (screen blacks, phone stays on) |
EXEC_TERM_COMMAND | Execute arbitrary shell command (v3.8.0 only) |
STARTSHELL | Interactive WebSocket shell (v3.8.0 only) |
STARTRADAR / STOPRADAR | Proximity detection service |
ENABLEBLUETOOTH / DISABLEBLUETOOTH | Toggle Bluetooth |
ENABLEHOTSPOT / DISABLEHOTSPOT | Toggle WiFi hotspot |
LAUNCH_APP | Launch arbitrary application |
START_SERVICE | Start arbitrary Android service |
SEND_BROADCAST | Send arbitrary broadcast intent |
GETAPPCONF / SETAPPCONF | Read/write full app configuration |
REBOOT | Reboot device |
CONNECT | Force connectivity check |
Secondary channel: SMS (v3.8.0 only)
The v3.8.0 direct-download build accepts remote commands over SMS. SmsReceiver is registered with android:priority="2147483647" (Integer.MAX_VALUE) on SMS_RECEIVED, so it fires before the system inbox, and calls abortBroadcast() to suppress delivery of command messages to the inbox. The NotificationListener cancels any notification arriving within ±5 seconds of the command timestamp.
Authorization is a three-slot allowlist of phone numbers stored in SharedPreferences as number1, number2, number3. Messages from any other sender pass through untouched. The receiver’s command-parsing flow, extracted from the decompiled com.surebrec.SmsReceiver.onReceive:
- If the SMS sender doesn’t match one of the three numbers, pass the message through.
- Check the body against a magic keyword stored in SharedPref
smskeyword(defaultcerberus). - If the body contains
##, split on##. If the body contains**instead, split on\\*\\*. Two supported delimiters. - The placeholder
=CR=inside any field is substituted with an actual carriage return (SMS bodies can’t cleanly embed newlines). - Decrypt the command field with
AES/CBC/PKCS5PADDING: a 16-byte key derived from the device identifier viaString.format("%-16s", id).replace(' ', '0'), reversed, with a literal 16-byte null IV. - Match the plaintext against the command dictionary and dispatch to
SurebrecService.
The command dictionary:
SIMINFO WIPE WIPESD LOCK UNLOCK
ALARM TAKEPICTURE CAPTUREVIDEO CALL MESSAGE
SCREENSHOT REBOOT SPEAK STARTEMERGENCY STOPEMERGENCY
ENABLEWIFI DISABLEWIFI ENABLEDATA DISABLEDATA ENABLEROAMING
ENABLEBLUETOOTH DISABLEBLUETOOTH FIND
Three SMS-only commands do not appear in the FCM dispatcher:
SPEAK— Cerberus uses the victim’s phone’s text-to-speech engine to announce an operator-chosen message out loud. The operator sends the sentence via SMS.ENABLEROAMING— remotely turn on international roaming, keeping the stalkerware connected when the victim travels internationally.ENABLEDATA/DISABLEDATA— remotely toggle the victim’s mobile data connection.
Two tokens that might look like commands in the decompiled source are internal flags, not SMS keywords. findnopass is a SharedPreferences boolean key (getBoolean("findnopass", false)): when true, it lets the FIND command execute without a password check. datasms is an Intent extra key (putExtra("datasms", true)) that flags to the downstream SurebrecService handler that the command arrived via Android’s binary DATA_SMS_RECEIVED channel rather than text SMS. Neither is a keyword the abuser types.
The authentication model is the three-number allowlist plus the keyword, both of which are SharedPref values. There is no second factor, no out-of-band acknowledgement, no observable rate limit in the receiver code. An abuser with physical access during provisioning only needs to register a phone number in one of the three slots and remember the keyword. Anyone spoofing the caller ID of one of the three trusted numbers — which is trivial through commercial SMS spoofing services — can issue commands to the victim’s device without physical access.
Radar: Wi-Fi BSSID proximity tracking
FCM opcodes STARTRADAR and STOPRADAR are explained by com.surebrec.RadarService. RadarService.onStartCommand takes an Intent extra named "ssid" — a Wi-Fi network name — acquires a wake lock named "RadarService", registers a BroadcastReceiver for android.net.wifi.SCAN_RESULTS, and posts a handler message to kick off the scan loop. Each SCAN_RESULTS callback checks whether the target SSID is present in the returned scan list and reports presence or absence transitions to the operator over the C2.
Radar is Wi-Fi BSSID proximity tracking, not GPS. The operator tells Cerberus “start radar for SSID HomeWifi” via FCM, and Cerberus reports when the victim’s phone comes within Wi-Fi range of that SSID or leaves it. It works indoors, in subways, inside buildings, in underground parking garages, and in urban canyons where GPS is unreliable.
In domestic-violence contexts an abuser can configure “radar for my home Wi-Fi,” “radar for my workplace Wi-Fi,” or “radar for the SSID I saw at the shelter intake interview” and be notified every time the victim’s phone enters or leaves that specific building — including buildings where GPS doesn’t work reliably, like shelters, hospitals, courthouses, and police stations. The dedicated C2 endpoint for radar telemetry is cerberusapp.com/comm/radar.php.
The 68-opcode dispatch in direct-download SurebrecService
com.surebrec.SurebrecService.onStartCommand in the direct-download v3.8.0 build dispatches 68 contiguous integer opcodes (1 through 68). The Play Store com.ssurebrec v1.4.9 dispatcher exposes 16 case labels — 8, 9, 10, 12, 13, 14, 15, 18, 19, 20, 21, 22, 67, 68, 69, 70 — that collapse into four multi-label shared-body handlers (cases 8–10 share one body, 12–15 share another, 18–22 share another, 67–70 share another). Each shared body branches internally on the opcode value. The four functional groups are: (1) location-tracking + init + HIDE; (2) silent capture (camera, microphone, screenshot); (3) alarm + emergency suite; (4) shutdown + FORCED_UNLOCK + hide-show. Combined body size across the four groups is approximately 233 KB of decompiled Java.
v3.8.0’s 68 opcodes are LSDroid’s full C2 protocol grammar; the Play build ships a subset, with opcodes 1–7, 11, 16–17, 23–66 absent.
Not every case is reachable from FCM. Some cases are reachable only from internal paths — ShutdownAttemptReceiver, GeofenceBroadcastReceiver, AdminReceiver, SmsReceiver, and the wearable message listener — each with its own authentication model. The FCM command dispatcher exposes 44 of the 68 opcodes as named string commands; the SMS dispatcher exposes roughly 23; at least one opcode (31, “capture location as shutdown response”) is reachable only from the shutdown receiver. The C2 protocol grammar has 27 internal-path handlers in addition to the 44 string commands the FCM dispatcher exposes — geofence-crossing, wrong-admin-password, low-power-motion-sensor, and wearable-message paths, each its own capability category.
C2 endpoints
The main app references 30+ HTTPS endpoints across the /comm/ (device→server) and /api/ (operator-facing) namespaces on cerberusapp.com:
| Namespace | Endpoint | Purpose |
|---|---|---|
| device | /comm/ping2.php | Heartbeat |
| device | /comm/register.php | Device registration |
| device | /comm/sendregid.php | FCM token upload |
| device | /comm/sendpicture.php | Photo exfiltration (JPEG) |
| device | /comm/sendvideo3.php | Video exfiltration (MP4) |
| device | /comm/sendaudiofile.php | Audio exfiltration (3GP) |
| device | /comm/sendlocation.php, /comm/sendlocation2.php | Real-time GPS (v1 + v2) |
| device | /comm/sendtrack2.php | Location history (KML in ZIP) |
| device | /comm/sendmessage.php | Status messages |
| device | /comm/sendemail.php | Email notifications to operator |
| device | /comm/restoreconf2.php | Encrypted config download |
| device | /comm/radar.php | Wi-Fi BSSID radar telemetry |
| device | /comm/cs.php, /comm/s.php, /comm/us.php | Short-named upload/download channels |
| device | /comm/login2.php, /comm/verify.php | Auth flow |
| device | /comm/recoveruser.php, /comm/resetpass.php | Account recovery |
| device | /comm/getdevices.php, /comm/getgtoken.php, /comm/getlictype.php | Device-listing, GCM-token-fetch, license-tier queries |
| operator | /api/getdevices.php, /api/getdevicestatus.php | Operator device-list + status queries |
| operator | /api/login.php | Operator login |
| operator | /api/sendcommand.php | Operator → device command |
| operator | /api/setdevicename.php | Operator names a device |
| billing | /buy_license.php?username= | License purchase flow |
| shell | wss://cerberusapp.com:8443 | Interactive root-shell WebSocket |
Versioned suffixes (ping2, restoreconf2, sendlocation2, sendtrack2, sendvideo3) indicate protocol revisions kept reachable for legacy installed clients. sendvideo3 is at v3, so two video-upload protocol revisions have happened.
No certificate pinning. No network_security_config.xml. The Order header sent with every request is a 24-character random string — not a session token, not an HMAC. Zero authentication beyond the device ID.
Cloud exfiltration: Dropbox and Google Drive (v3.8.0)
The BACKUP command (call=47) accepts 6 boolean flags controlling what to exfiltrate: contacts, SMS, call log, photos, videos, and status notification. The backup data is uploaded to either Dropbox or Google Drive — operator’s choice.
- Dropbox: Full Dropbox Core SDK v2 bundled. Auth token stored in SharedPreferences as
dropboxAccessToken. Photos uploaded asimage/jpeg, videos asvideo/mpeg, with retry logic (3 attempts, 5-second backoff). Upload progress tracked per-file to avoid re-uploading. - Google Drive: Google API Client with
DRIVE_FILEOAuth2 scope. UsesGoogleAccountCredential.usingOAuth2— can leverage the victim’s own Google account or an operator-provided one.
These channels target api.dropboxapi.com and www.googleapis.com — legitimate cloud services. The victim’s surveillance data resides in the operator’s cloud storage.
The autonomous persistence layer
The 44 FCM commands are what operators request. They sit on top of a much larger autonomous layer that runs without any operator action — Cerberus reacts to dozens of system events per hour, schedules forward alarms from each reaction, and re-arms itself across reboots.
Three independent wake-up tiers in com.ssurebrec:
Tier 1 — system-event-driven receivers (11+ hooks). Each receiver launches EmptyActivity and/or the appropriate service, restoring foreground priority for downstream work that would otherwise be blocked by Android’s background-service restrictions:
| Receiver | System events | Triggers per day, typical use |
|---|---|---|
BootReceiver | BOOT_COMPLETED | once per reboot |
Lw7/p; | BATTERY_LOW, BATTERY_CHANGED, ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTED, ACTION_SHUTDOWN, internal DEVICE_STARTED | continuous (BATTERY_CHANGED fires roughly every minute) |
LockScreenReceiver | SCREEN_ON, SCREEN_OFF, USER_PRESENT, CLOSE_SYSTEM_DIALOGS, internal shutshut | every screen wake/sleep, every unlock, every power-menu open |
ConnectivityReceiver | network state changes | every Wi-Fi/cell transition |
PackageUpdateReceiver | PACKAGE_ADDED, PACKAGE_REPLACED, PACKAGE_REMOVED | every app install/update/uninstall |
GeofenceBroadcastReceiver | geofence enter/exit | per operator configuration |
ShutdownAttemptReceiver | LSP IPC com.lsdroid.shutdownattempt | every power-button press |
AdminReceiver.onPasswordFailed | wrong device-admin password | per failed unlock attempt |
PackageUpdateReceiver is the most direct example. Its entire body is five lines: build an Intent for EmptyActivity, set FLAG_ACTIVITY_NEW_TASK, call startActivity. It does not log the package event, does not detect specific apps, does not report. The only effect of receiving a PACKAGE_* broadcast is that Cerberus’s process is started: every Play Protect update, every OEM bloatware refresh, every app install or removal wakes the process.
Tier 2 — scheduled setAndAllowWhileIdle alarms (17 call sites). Every receiver/service that gets woken by Tier 1 also schedules forward alarms. setAndAllowWhileIdle is the AlarmManager method that bypasses Doze — it forces the alarm to fire even when the device is in low-power state. Cerberus has 17 distinct call sites scheduling these alarms:
AdminReceiver.onPasswordFailed, BackgroundService (lifecycle), BootReceiver, ConnectivityReceiver, GeofenceBroadcastReceiver, ShutdownAttemptReceiver, ShutdownDialogActivity.onCreate, SurebrecService.d/onStartCommand, TrackServiceFused.onStartCommand, Lw7/b1;->run, Lw7/i3;->f (preference click handler with 2-second background-launch bypass), Lw7/n;->onTrigger (low-power motion sensor trigger handler), Lw7/p;->onReceive, Lw7/x1;->b. Plus one setExactAndAllowWhileIdle in BackgroundService.
BootReceiver itself sets up two alarms: com.surebrec.DEVICE_STARTED 60 seconds after boot, com.surebrec.DAILY_PING daily. Every boot re-arms both, and ConnectivityReceiver re-arms DAILY_PING whenever it fires.
Tier 3 — offline persistence + storage-context migration. When the device is offline, Cerberus caches surveillance media to disk as pic-* (JPEG) and video-* (MP4) under getFilesDir(). When ConnectivityReceiver next fires on a CONNECTED network state, it scans the directory and dispatches one upload thread (Lw7/c1;) per cached file.
Across the locked/unlocked storage boundary (Android’s direct-boot model), ConnectivityReceiver migrates Tasker rules (AutoTaskRules.db), location coord files (coord01, coord02), and metadata (header) between credential-encrypted and device-protected storage contexts, calling android.system.Os.chmod and Os.chown to preserve the original file mode/uid/gid bits. The directBootAware declaration on LSP keeps that companion alive in the pre-first-unlock window. setAndAllowWhileIdle alarms persist across reboots through Android’s alarm subsystem — Cerberus’s wake-up schedule survives device restarts without re-registration.
A Cerberus install on a phone that’s never received a single FCM command still runs, schedules, re-arms, caches for upload, and migrates state across reboots and storage transitions.
Root capabilities (v3.8.0)
The capabilities below require root at runtime. Runtime.exec("su") succeeds, /system/bin/sh spawns under elevated UID, and app_process invocation of SuCommands runs with system-equivalent privileges — at which point the reflected binder-stub calls resolve past system_server’s permission gates.
The reflected-binder-stub call sites described below live in SuCommands.main() in v3.8.0; they run natively because v3.8.0 declares targetSdk 26, which receives Android’s hidden-API leniency for low-targetSdk apps. They are not present in com.ssurebrec v1.4.9. This is the v3.8.0-on-rooted-devices capability set.
SuCommands — reflected Android framework API abuse
SuCommands.main() is invoked via Runtime.exec("su") → app_process. It uses Java reflection to access hidden Android system service APIs:
| Command | API | Effect |
|---|---|---|
grant_permissions | IPackageManager.grantRuntimePermission() | Silently grants CAMERA, RECORD_AUDIO, READ_SMS, SEND_SMS, RECEIVE_SMS, READ_CALL_LOG, READ_CONTACTS, ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION, READ_PHONE_STATE, CALL_PHONE, PROCESS_OUTGOING_CALLS, GET_ACCOUNTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, ACCESS_COARSE_LOCATION |
block_uninstall | IPackageManager.setBlockUninstallForUser() | Prevents uninstallation — fails silently |
enable_admin | IDevicePolicyManager.setActiveAdmin() | Enables device admin without consent |
cancel_system_notifications | INotificationManager.cancelAllNotifications("android") | Clears all system notifications |
disable_status | IStatusBarService.disable(65536) + disable2(1) | Disables entire status bar for 1 hour |
shutdown_dialog | ActivityThread.systemMain() | System-level fake shutdown progress dialog |
set_owner_message | ILockSettings.setString("lock_screen_owner_info") | Sets lock screen message |
removerestrict | INetworkPolicyManager.setRestrictBackground(false) | Removes background data restriction |
allow_uninstall | IPackageManager.setBlockUninstallForUser(..., false) | Reverses block_uninstall — server-gated “legitimate uninstall” path, surfaced via LockActivity’s Button L (visibility=GONE until server config enables it) |
enable_status | IStatusBarService.disable(0) + disable2(0) | Re-enables status bar after disable_status |
disable_owner_message | ILockSettings.setBoolean("lock_screen_owner_info_enabled", false) | Clears the lock-screen owner-info text previously set by set_owner_message |
enable_powersave | IPowerManager.setPowerSaveMode(true) | Forces battery-saver on — reduces visible battery drain |
disable_powersave | IPowerManager.setPowerSaveMode(false) | Forces battery-saver off — keeps camera/mic/upload running at full rate |
restricted_permissions | IPackageManager runtime-permission-flag write | Marks granted runtime permissions as fixed by system policy; the victim cannot revoke them from Settings → Apps |
Dumping every string literal out of com.surebrec.SuCommands.main(String[]) in the decompiled v3.8.0 APK yields 93 unique literals. Eight of them are the fully-qualified names of Android internal binder stubs:
| Binder stub | What it controls |
|---|---|
android.content.pm.IPackageManager$Stub | Runtime-permission grants, package uninstall blocking |
android.permission.IPermissionManager$Stub | Runtime-permission grants on API 30+ (separate code path) |
com.android.internal.statusbar.IStatusBarService$Stub | Status bar disable, power-menu dismissal |
android.os.IPowerManager$Stub | Shutdown, reboot, wake-lock control |
com.android.internal.widget.ILockSettings$Stub | Lock-screen PIN/password manipulation |
android.app.admin.IDevicePolicyManager$Stub | Device-owner policy without being a registered admin |
android.app.INotificationManager$Stub | Programmatic notification suppression |
android.net.INetworkPolicyManager$Stub | Data-saver / background-network / battery-optimization bypass |
INetworkPolicyManager controls the data-saver toggle, the battery-optimization whitelist, and the background-network restrictions Android 9+ applies to every app. Reflection access on that binder lets Cerberus whitelist itself from every Android-level network throttle, so its C2 beacons reach cerberusapp.com regardless of background-data restrictions. There is no user-visible “allow unrestricted background data” dialog.
On a rooted device, the reflected call loop inside SuCommands.main() grants 16 dangerous runtime permissions without the Android runtime-permission dialog ever firing: READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE, GET_ACCOUNTS, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, ACCESS_BACKGROUND_LOCATION, READ_SMS, RECEIVE_SMS, SEND_SMS, READ_CALL_LOG, PROCESS_OUTGOING_CALLS, CALL_PHONE, READ_CONTACTS, READ_PHONE_STATE, CAMERA, RECORD_AUDIO.
ACCESS_BACKGROUND_LOCATION and SEND_SMS are both so policy-sensitive that Google Play requires the developer to fill out a separate compliance declaration explaining why the app needs them before Play will publish a build that requests them. By granting the permissions reflectively at runtime after install, Cerberus never has to declare them in the manifest at submission time and never has to fill out the form.
Interactive shell
ShellService opens a persistent WebSocket to wss://www.cerberusapp.com:8443. On connection, it spawns /system/bin/sh via ProcessBuilder, streams stdin/stdout over the WebSocket in JSON frames, and constructs a real shell prompt by running pwd and id. Supports su escalation within the session, CTRLCPROC (SIGINT to foreground process), and process tracking via /proc enumeration. 20-minute inactivity timeout with auto-reconnect on disconnect.
Screen recording via the system screenrecord binary
Opcode 66 (SCREENRECORD) in SurebrecService contains the literal shell-command template /system/bin/screenrecord --size <WxH> --bit-rate 1000000 --time-limit <seconds>. Cerberus invokes the Android system binary at /system/bin/screenrecord directly via Runtime.exec — the same binary Android’s developer-tools screen-capture workflow uses — produces an MP4 on-device, uploads it to /comm/sendvideo3.php, and acquires a wake lock named "Screenrecord" while the recording is in progress.
The --bit-rate 1000000 value is 1 Mbps — below the screenrecord binary’s own default of 20 Mbps on Android 11+.
System partition persistence
On rooted devices, Cerberus installs configuration to the system partition:
/system/etc/cerberus_conf.xml— full config (survives factory reset)/system/etc/cerberus_at.db— automation rules database
U1.F() reads this config on first boot, imports all settings, copies the automation database, and silently enables device admin via setActiveAdmin reflection — all without user interaction.
Reflection paths beyond SuCommands
SuCommands.main() is the most concentrated reflection chain in v3.8.0, but it is not the only one. The main utility class LU2/U1; contains its own reflection paths: setActiveAdmin reflection used at first-boot config import (described above), and AppOpsManager.setMode(int op, int uid, String packageName, int mode) reflection that allows v3.8.0 to set any AppOp on any package to MODE_IGNORED — silently denying that operation for the target app without notifying the user. On Android ≤27 the reflection succeeds directly under targetSdk 26 leniency; on Android 9+, v3.8.0 falls back to Runtime.exec("su") + appops set shell command.
IStatusBarService.disable/disable2 reflection is reachable not only from SuCommands but from broadcast receiver LL2/B;->onReceive, handler LP2/P;->handleMessage, BackgroundService lifecycle methods, and AccService.onAccessibilityEvent — so status-bar disable can fire opportunistically from accessibility events, not only after a deliberate root invocation.
The reflection chain reaches Android internal services through the canonical mechanism: Class.forName("android.os.ServiceManager").getMethod("getService", String.class).invoke(null, "<service-name>") returns an IBinder, and IFoo$Stub.asInterface(binder) wraps it as a callable IFoo. Confirmed for IPackageManager, IStatusBarService, INotificationManager, IDevicePolicyManager, IPowerManager, IPermissionManager, ILockSettings, INetworkPolicyManager.
Signature-permission probing (WRITE_SECURE_SETTINGS, READ_PRIVILEGED_PHONE_STATE)
com.ssurebrec v1.4.9 declares android.permission.WRITE_SECURE_SETTINGS in its manifest. v3.8.0 declares both WRITE_SECURE_SETTINGS and android.permission.READ_PRIVILEGED_PHONE_STATE. These are signature-protected permissions — the Play Store will not grant them on install (the user-facing permission dialog never appears for signature permissions). The permissions become active only via adb shell pm grant <package> <permission>, which an abuser with USB-debug access during the install moment can run, or via root + pm grant.
When WRITE_SECURE_SETTINGS is granted, Cerberus’s TrackServiceFused programmatically writes Settings.Secure.location_mode via Settings.Secure.putInt(...), silently overriding the user’s location-services setting (which Android’s UI presents as user-controlled and which a legitimate non-system app cannot change). When READ_PRIVILEGED_PHONE_STATE is granted on Android 10+, the IMEI access path in LU2/U1;->i() reaches TelephonyManager.getDeviceId() even though the public READ_PHONE_STATE permission would no longer suffice.
The code paths that use these permissions are present and functional when an installer has run pm grant over ADB. An installer with USB-debug access at install time can grant signature permissions that Play Store does not expose through its consent dialogs.
Stealth mechanisms
App hiding
HIDE command calls PackageManager.setComponentEnabledSetting() to disable the launcher activity. The app disappears from the home screen and app drawer. SharedPreferences stores hidden=true. On rooted devices, UNHIDE runs pm enable for com.lsdroid.cerberus and com.lsdroid.cerberuss — additional package aliases (same APK code as com.surebrec) distributed from cerberusapp.com.
Fake shutdown
ShutdownDialogActivity renders a fake Android power-off dialog using the system’s own theme (Theme.DeviceDefault.System) and power icon (ic_lock_power_off). On “power off” tap, sets screen brightness to 1/255 and button brightness to 0. The phone appears off. Surveillance continues.
LSDroid markets fake shutdown explicitly. A YouTube explainer video for the Lock Screen Protector setup — hardcoded inside the LSP APK at com.lsdroid.lsp.c line 27, opened from within LSP’s own setup UI — describes the feature as showing a fake power menu so that the person holding the phone believes it has been switched off while the device continues to track, record, and report. A similar promotional video for the main Cerberus Anti-theft app frames the same capability the same way.
Disguised build
The “disguised” APK is identical to the standard build with two manifest changes: android:label="System Framework" and android:icon="@android:drawable/sym_def_app_icon". In the victim’s app list, it looks like a system service.
Both disguised builds additionally rename their package: the standard direct-download phone and WearOS APKs declare com.surebrec (matching the direct-download main app), but the disguised phone (v3.8.0) and disguised WearOS (v3.7.6) both declare com.ssurebrec — the Play Store main app’s package name. The rename has no apparent technical purpose; on a victim’s device with the Play Store main app already installed, an inspector seeing two com.ssurebrec-prefixed processes might assume both are the legitimate Play app.
Dial-code re-entry
com.surebrec.StartReceiver is declared with intent-filter priority 2147483647 (the maximum Android accepts) on android.intent.action.NEW_OUTGOING_CALL. On every outbound call the victim dials, the receiver reads SharedPreferences("conf").getString("dialcode", "23723787") and compares it byte-for-byte against the dialed number. A match calls abortBroadcast() — the call does not go through — and launches com.surebrec.StartActivity2, the administrative UI used to reconfigure the app after the launcher icon is hidden. A non-match calls clearAbortBroadcast() and the call proceeds normally, so the victim never observes that every outbound call is being intercepted.
The default value 23723787 is the T9 keypad mapping of “CERBERUS.” Operators can overwrite dialcode per-install with any numeric string through the standard SETAPPCONF command, and the new code is delivered inside the zero-IV AES-CBC restoreconf2.php blob that the device trusts by key derivation from its own ID. The mechanism is the canonical operator re-entry point after HIDE: once the launcher icon is disabled, dialing the configured code from the victim’s own dialer is how the abuser reaches the administrative screen.
Fragmented string literals
Three string constants in com.ssurebrec v1.4.9 are fragmented across multiple literals and concatenated at runtime. com.surebrec.U2.U1 constructs admin@cerberusapp.com from "admin@cerb" + "erusapp.com", constructs support@cerberusapp.com from "support@cer" + "berusapp.com", and constructs the sideload self-update URL https://cerberusapp.com/download/version from "https://cerb" + "erusapp.com" + "/download/version" at U2.U1.java:2982. A suite-wide sweep for "X" + "Y" compile-time string concatenation surfaces exactly these three instances. Everything else — the 33-endpoint C2 URL list, the 44 FCM command-verb strings, the Firebase project identifier cerberus-lock-screen-protector, the Play Store deep links — ships as plain literals.
A grep for cerberusapp.com in the decompiled output returns a complete URL inventory on the first try.
Mixed obfuscation across the suite
Three of LSDroid’s five Cerberus Play apps — LSP, Kids, and Persona2 — ship with a post-R8 commercial obfuscator on top of standard Android R8. Main com.ssurebrec and Enterprise use plain R8 (main with heavy w7/-style repackaging, Enterprise with almost no repackaging at all). The three commercially-obfuscated APKs flatten library packages from canonical names like androidx/core/content/ down to single-uppercase-letter packages (A/, B/, C/) with single-letter class names within each — a shape consistent with DexGuard, DashO, or similar tools that run after R8.
LSP is the most heavily affected: droidsaw flags 59% of LSP’s 2869 classes as adversarially obfuscated, with a naming distribution that doesn’t match R8’s expected output at all. LSP’s decompiled classes carry // Source: SourceFile instead of the // Source: r8-map-id-<hash> that the standard-R8 builds (main and Enterprise) emit — the R8 map ID has been stripped from the LSP artifact. Across all three commercially-obfuscated apps the obfuscation scope is the same: third-party library packages get flattened; LSDroid’s own com.lsdroid.* namespaces stay canonical because the Android manifest binds framework callbacks by string and renaming them would break the apps.
What it defeats: pipeline-grade scanners that detect OkHttp, AndroidX, Firebase by package-path string-matching see the flattened classes as unrecognised code instead of well-known libraries. What it doesn’t defeat: type-signature matching against known library API shapes recovers every flattened class. The obfuscation imposes audit cost on automated tools — the same automated tools that gate Play-submission review.
Anti-analysis countermeasures: absent
Reading the binary, there is no protective layer beyond the partial obfuscation described above. There is no native code in the Play APKs beyond stock AndroidX (libdatastore_shared_counter.so and libandroidx.graphics.path.so, both Google-published, both <11 KB) — no JNI tamper-resistance, no native key derivation, no anti-debug. There is no runtime DEX loading: no LSDroid code calls DexClassLoader, PathClassLoader, InMemoryDexClassLoader, or equivalent. There are no LSDroid-attributable anti-Frida, anti-emulator, root-detection, or isDebuggerConnected checks; the only such checks in the Play binary belong to the vendored Firebase Crashlytics library and are passive (they enrich crash-report metadata, they don’t gate operation). String literals are not encrypted. The obfuscation is plain R8 — class and method names renamed, strings preserved.
The complete surveillance behavior is in the static bytecode of the shipped APKs. Reverse engineering the product requires no runtime instrumentation, no emulator-evasion countermeasures, no native-code disassembly.
Notification suppression (v3.8.0)
NotificationListener tracks all notifications with timestamps. When a command SMS arrives, any notification within ±5 seconds is cancelled via cancelNotification(). On root, it runs appops set <package> RUN_IN_BACKGROUND allow to prevent Android from killing it.
Anti-forensics (v3.8.0)
SETAPPCONF can set preventusbdebug=true remotely. This blocks USB debugging, preventing a forensic investigator from connecting via ADB to extract evidence.
OEM background-restriction awareness
Inside ShutdownAttemptReceiver.onReceive, as a literal string shipped in the compiled DEX: “Not able to execute action in background. To fix, please check www.dontkillmyapp.com”. dontkillmyapp.com is a community-maintained reference for OEM background-process restrictions on Android. Cerberus surfaces the URL when an action handler fails to run in the background, telling the operator’s dashboard which device-side workaround to apply.
OEM-specific code paths
Cerberus contains four distinct OEM-specific code paths, each targeting a different manufacturer behavior:
- Xiaomi MIUI autostart-permission probe (main, LSP, Kids — via HiddenApiBypass). Each app reflects on
android.miui.AppOpsUtils.getApplicationAutoStartto query MIUI’s autostart-permission state, gated onBuild.MANUFACTURER == "xiaomi". Detailed in What the bypass is used for. - OPPO ColorOS power-menu fingerprints (LSP
AccessibilityService). Hardcodedcom.oplus.systemui.shutdown,COUIVerticalSeekBar, andCOUISeekBarliterals. Detailed in Detecting the power menu across OEMs. - 17-OEM launcher whitelist (Kids
MyAccessibilityService). Covers Xiaomi (com.miui.home), Huawei, OPPO, OnePlus, Lenovo, LG, Motorola, Sony, TCT, ZTE, Vivo’s BBK Electronics, CyanogenMod, LineageOS, AOSP, and Pixel. Samsung’s launcher (com.sec.android.app.launcher) is not in the list, though Samsung’s setup wizard is — the launcher omission appears unintentional. Detailed in Kids: “Cerberus Child Safety”. - dontkillmyapp.com helper URL (
ShutdownAttemptReceiver.onReceive). Literal helper-text shipped in the compiled DEX, surfaced when an action handler fails to run in the background. Detailed in OEM background-restriction awareness above.
Three engineering investment cost tiers: cheap (launcher classification, list-lookup) reaches 17+ OEMs; medium (power-menu UI fingerprinting in LSP) reaches AOSP + OPPO + Samsung; expensive (Xiaomi MIUI persistence probe via HiddenApiBypass) reaches one OEM.
License-expiration self-reveal
com.surebrec.BootReceiver.onReceive reads the auth SharedPref (a reversed-timestamp encoding of license validity) on every boot and compares it against the current time. When the timestamp is in the past — the abuser’s subscription has lapsed — Cerberus exits covert mode. The boot-receiver code path that runs on expired-license boot:
- Cancels Cerberus’s own background notifications (IDs 1 and 54321).
- Builds a “Forced unlock” notification with two action buttons — one launches
StartActivity2(the admin re-entry UI), the other launchesBuy(the subscription-renewal activity). - Sets
hidden=falsein SharedPreferences and re-enables theStartActivitylauncher component viaPackageManager.setComponentEnabledSetting(..., COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP)— undoing the launcher-icon hiding fromHIDE. - Stops
BackgroundService. - Calls
DevicePolicyManager.removeActiveAdmin(...)— Cerberus relinquishes its device-admin claim, allowing the victim to uninstall normally. - Logs a Firebase Analytics event
select_content / item_id=buy_notif_shown— LSDroid’s analytics dashboard tracks each lapsed-license user who reaches this state.
This is the inverse of HIDE: the same install transitions from invisible-to-victim to visible-with-buy-prompt based on subscription state, automatically, on the next reboot after expiration. The Firebase Analytics event select_content / item_id=buy_notif_shown records the transition; the same event name is used by Play Store apps to track conversion-funnel impressions.
What the victim sees: two notification channels
SurebrecApplication.onCreate declares two notification channels with opposite configurations:
- Channel
Cerberus—IMPORTANCE_LOW(no sound, no vibration, no heads-up banner), lights off, vibration off,lockScreenVisibility = VISIBILITY_SECRET (-1)(hidden on the lock screen entirely). Used for the foreground-service notification posted during silent surveillance. - Channel
Forced unlock—IMPORTANCE_HIGH(heads-up banner pops up), lights off, vibration off, sound null,lockScreenVisibility = VISIBILITY_PUBLIC (1)(fully visible on the lock screen). Used for the FORCED_UNLOCK lure described below and the license-expiration buy prompt described above.
The foreground-service notification. Whenever Cerberus invokes the camera, microphone, screen recorder, or location service, BackgroundService calls startForeground(1, notification) with FOREGROUND_SERVICE_TYPE_CAMERA (Android 10+) and the notification:
- Body text:
"This device is protected by Cerberus"(resource string2131886633) - No content title (no
setContentTitlecall) - Channel:
Cerberus—IMPORTANCE_LOW, hidden on lock screen - Tap intent:
StartActivity2with extranotification=true— opens the operator/abuser admin UI - Notification ID:
1
This is the only persistent visible signal during silent capture. Android additionally shows the camera and microphone privacy indicators (the green/orange dots in the status bar, since Android 12) for the duration of capture. Those indicators are hardware-backed by the OS and cannot be suppressed without root. On com.ssurebrec (Play, no root) they always appear during the capture window.
The notification text reads "This device is protected by Cerberus". It does not name the camera, microphone, location, or screen-recording activity that the foreground service is performing. With lockScreenVisibility = VISIBILITY_SECRET, the notification does not appear while the device is locked.
The FORCED_UNLOCK lure. LockScreenReceiver.onReceive posts notification ID 54321 on every SCREEN_OFF broadcast when w7.k4.e() (the rules-helper “is enabled” flag) returns true. The notification:
- Channel:
Forced unlock—IMPORTANCE_HIGH, lock-screen public, heads-up banner - Title:
w7.k4.g()— operator-controlled text pulled from the rule configuration on the operator dashboard. The abuser writes whatever message they want. - Tap intent:
PendingIntent.getService(...)→ForcedUnlockService
When the victim taps the notification, ForcedUnlockService.onStartCommand is a one-line passthrough that builds an Intent for SurebrecService with call=68 and starts it. SurebrecService case 68 logs the literal string "Event FORCED_UNLOCK received, executing actions in 15 seconds." and schedules an action bundle 15 seconds out via setAndAllowWhileIdle, with action flags per the rule (location, picture, emergency, tasker).
The 15-second delay means the capture fires after the user has typically finished whatever lock-screen interaction they were performing — unlocking the phone, reading the notification, looking at the screen. The dispatched action bundle records a photo from the front camera, logs the GPS coordinates, and fires whatever Tasker macro the operator scripted.
The combination — operator-controlled lock-screen text on a high-importance public-visibility channel, tap-handler that schedules a delayed action bundle including silent camera capture — is reachable on every Cerberus install where the operator has enabled the feature on their dashboard. In an anti-theft framing the same code is the “thief sees notification, taps to interact, is photographed” flow. The mechanism is the same regardless of who installed Cerberus on whose phone; what the operator-configured text says, and to whom, depends entirely on the operator.
Crypto weaknesses
Password hashing
U1.a(): MessageDigest.getInstance("SHA-1") with ISO-8859-1 encoding. No salt. The operator’s password hash is stored in SharedPreferences (hash key), synced to device-protected storage (accessible before first unlock), and transmitted to the C2 server during config restore. Trivially crackable.
SMS command encryption
Key: SMS keyword padded to 16 bytes with '0', reversed. IV: 16 null bytes. Algorithm: AES/CBC/PKCS5PADDING. Static null IV means identical commands produce identical ciphertext. Key derived from a user-chosen word.
Config restore encryption
Same AES/CBC with device ID as key (padded/truncated to 16 bytes, reversed) and a literal 16-byte null IV. The encrypted config blob from /comm/restoreconf2.php contains the password hash, authorized SIM serials, phone numbers, and all feature flags.
The 16-byte “key” is the device identifier itself, padded with '0' to 16 characters when shorter, truncated to the first 16 characters when longer, then reversed and used as raw bytes. The decompiled body of the derivation function is unambiguous on both branches: String.format("%-16s", id).replace(' ', '0') followed by new StringBuilder(s).reverse().toString().getBytes().
The effective keyspace depends on which device-identifier source is reached. The two paths in U2.U1.i(Context, TelephonyManager) produce different shapes:
- IMEI path (when
READ_PHONE_STATEis granted): the device ID is 15 decimal digits. Padded to 16 and reversed, the key consists of 16 ASCII bytes each with 10 possible values ('0'–'9'plus the appended'0'). Effective keyspace:10¹⁶ ≈ 2⁵³. ANDROID_IDpath (otherwise): the device ID is 16 hexadecimal characters. Reversed, the key consists of 16 ASCII bytes each with 16 possible values ('0'–'9','a'–'f'). Effective keyspace:16¹⁶ = 2⁶⁴.
The label “AES-128” suggests a 128-bit key. The actual entropy is 53 to 64 bits depending on which identifier source is reached, with the remaining bits non-random — they are the constraint that the key bytes happen to spell decimal digits or hex characters.
The 53–64-bit figure describes what a network-positioned attacker — without device-side access — faces; on a 2026-vintage GPU, even the 2^64^ end of that range completes in a small number of hours. That isn’t the threat model that matters for a stalkerware deployment. Anyone with realistic motivation to decrypt the config — a forensic investigator examining a victim’s device, a co-installed app, a researcher — has device-side access and can read the device identifier directly: IMEI from *#06# or the SIM tray, ANDROID_ID from Settings or any zero-permission co-installed app or Cerberus’s own exported IdProvider ContentProvider. With the device ID in hand, decrypting Cerberus’s encrypted config is O(1): read the ID, derive the key, decrypt.
There are three independent failures in the same code path: (1) deterministic CBC — the IV is a literal sixteen zero bytes, so identical plaintexts under the same key produce identical ciphertexts and a passive observer can distinguish repeated commands by ciphertext-equality alone, without any cryptanalysis. (2) 64-bit effective keyspace as derived above. (3) No MAC — the receiver cannot detect tampering, so the same padding-oracle that recovers plaintext also produces a forgery oracle for new ciphertexts. The plaintext-equality leak from (1) is the most serious of the three because it leaks protocol state — what command is being sent — without breaking the cipher at all.
Lock passcode derivation
com.surebrec.LockActivity.onCreate renders a four-digit numeric unlock pad. On submit, the entered digits are compared against reverse(deviceID).substring(length - 4) — the first 4 characters of the device ID in reverse order. The device ID is what U2.U1.i(Context, TelephonyManager) returns; reading the decompiled body of that method shows the actual derivation: if the app holds READ_PHONE_STATE it returns TelephonyManager.getDeviceId() (the raw IMEI), otherwise it falls back to Settings.Secure.ANDROID_ID, with cosmetic stripping of -, _, ., and space characters. There is no hashing. The “device ID” used by the lock screen is the raw IMEI or the raw ANDROID_ID.
When the device ID resolves to IMEI, the first 4 digits of the IMEI are the leading digits of the TAC (Type Allocation Code), which identifies the phone’s make and model. The TAC is public information — databases like imei.info list TACs by manufacturer and model. A thief who steals the phone, reads the brand off the casing, looks up the model’s TAC, reverses the first 4 digits, and tries the result will unlock in at most a handful of attempts (one per TAC variant of the model). No installed-on-device access required, no IMEI knowledge, no operator credentials. Cerberus’s “anti-theft lock screen” is unlocked by knowing what kind of phone it is.
When the device ID resolves to Settings.Secure.ANDROID_ID, the first 4 hex characters carry roughly 16 bits of entropy in the abstract — but ANDROID_ID is exposed to any zero-permission co-installed app via Settings.Secure.getString(), and additionally exposed via Cerberus’s own com.surebrec.IdProvider (an exported ContentProvider declared with no android:permission). A zero-permission co-installed app recovers the unlock code by running content query --uri content://com.surebrec.IdProvider and taking the first four characters of the returned id column.
The same LockActivity displays the full device ID as a static line labeled “Device ID:” on the lock screen itself, so anyone holding the phone while it is locked can read the ID directly off the screen, take the first four digits, and unlock the device in one attempt. The “10,000 possible 4-digit codes” search space is theoretical; the code is recoverable in O(1) from public information about the phone, from the screen, from any other app on the device, or from Cerberus’s own unauthenticated ContentProvider.
Operator-gated uninstall escape hatch
LockActivity contains an android.widget.Button referred to in the decompiled layout binder as L, declared with android:visibility="gone". The button’s OnClickListener invokes the same Runtime.exec("su") → app_process → SuCommands allow_uninstall chain documented in the reflection-chain section, which calls IPackageManager.setBlockUninstallForUser(..., false) and then launches the system uninstall intent. The button’s visibility is toggled via a server-delivered configuration flag inside the restoreconf2.php blob. The operator controls, per-install, whether the victim is ever shown the “Allow uninstall” option from the lock screen.
Upstream request authentication
The Order header on every outbound C2 request is a 24-character lowercase alphanumeric nonce drawn from java.util.Random (not SecureRandom) — the non-cryptographic PRNG whose 48-bit internal seed is recoverable from three consecutive outputs in under a second on commodity hardware (the standard reference is Reeds 1977). The auth field on license-check requests is a reversed timestamp concatenated with random padding. Neither primitive authenticates anything: a network-positioned attacker who has observed a handful of requests can forge plausibly-ordered new ones, and the server has no HMAC or signature to reject them with. There is no upstream request authentication in the Play Store version beyond this cosmetic nonce.
Lock Screen Protector module
com.lsdroid.lsp (v3.6) is a separate Play Store app labeled “Lock Screen Protector.” Its role:
- Block power-off: Accessibility service monitors for the system power dialog, locks the device, and dismisses the dialog
- Block status bar: Prevents pulling down the notification shade (blocks airplane mode toggle)
- Screenshot on shutdown attempt: Captures screenshot and sends JPEG bytes via broadcast to the main app
- Device admin:
force-lockpolicy, prevents casual uninstallation - directBootAware: All components start before first unlock
IPC between LSP and main app:
- LSP → main: broadcast
com.lsdroid.shutdownattemptwith screenshot bytes - Main → LSP: broadcast
com.surebrec.SHUT_STARTED - Main queries LSP:
content://com.lsdroid.lsp(exported, no permission) to checkpowerblockstatus
Detecting the power menu across OEMs
LSP’s com.lsdroid.lsp.AccessibilityService.onAccessibilityEvent inspects every accessibility event and checks the current window’s class name against two literals: com.android.systemui.globalactions.GlobalActionsDialog (the AOSP power menu) and com.oplus.systemui.shutdown (the OPPO ColorOS power menu). LSDroid has OPPO-specific targeting in the code. The COUIVerticalSeekBar and COUISeekBar literals in the same method are OPPO ColorOS widget class names — LSP has manufacturer-specific fingerprints for ColorOS power-menu layouts, which implies an OPPO customer base large enough to justify the code.
Screenshot capture and the shutdown race
When LSP detects the power menu, it calls this.takeScreenshot(DEFAULT_DISPLAY, mainExecutor, new I2.c(this)) — the Android 11+ accessibility screenshot API. The callback I2.c.onSuccess(ScreenshotResult) wraps the hardware buffer as a bitmap, compresses it to JPEG quality 70, falls back to quality 50 if the result exceeds 500 KB (just under Android’s Binder transaction limit), and broadcasts the JPEG bytes via sendBroadcast() with an explicit ComponentName pointing at com.surebrec.ShutdownAttemptReceiver, action string com.lsdroid.shutdownattempt, JPEG in the "s" extra.
JPEG quality 70 preserves the legibility of text screens (passwords, 2FA codes, banking apps, messaging apps); busy images get retried at quality 50, and oversize screens above the 500 KB threshold silently drop.
Power-off interception is a timing race, not a hard block. After capturing the screenshot, LSP broadcasts android.intent.action.CLOSE_SYSTEM_DIALOGS to dismiss the power menu. The victim sees the menu flash briefly and vanish. On slow devices under CPU load the race can fail and the shutdown can succeed. On a fast phone it’s nearly always successful; on an older or loaded phone, occasional shutdowns do go through.
The IPC is bidirectional. LSP also registers a runtime BroadcastReceiver (on API < 33) for action com.surebrec.SHUT_STARTED, which the main Cerberus app fires at LSP from the other direction to request a capture out-of-band. There are eight sendBroadcast() call sites in LSP’s AccessibilityService alone — the JPEG handoff is one of eight.
ShutdownAttemptReceiver has two modes
The main-app side of the IPC is com.surebrec.ShutdownAttemptReceiver.onReceive. The receiver checks whether the Intent has an "s" extra. If yes, it writes the JPEG bytes to /data/data/com.ssurebrec/cache/scr.jpg (a fixed path with no randomization — each new screenshot overwrites the last one) and spawns an upload thread that POSTs the file to /comm/sendpicture.php.
If the Intent has no "s" extra — just the action com.lsdroid.shutdownattempt — the receiver runs a parallel action chain gated on operator-configured boolean flags: a 1.5-second debounce, then a fake shutdown dialog, then location capture (SurebrecService opcode 31 — a command not reachable via the FCM dispatcher, only through this path), then silent picture capture, then silent video capture, then a 90-second deferred emergency capture via AlarmManager.setAndAllowWhileIdle, then a Tasker macro if one is configured, then a push notification to the abuser’s paired smartwatch via com.google.android.gms.wearable.Wearable. The handler schedules the emergency capture to fire 90 seconds in the future. The recording does not start immediately on power press; it starts 90 seconds later.
Because the receiver is exported="true" without a permission guard, and because the action string and the receiver component are both publicly known, any third-party app on a victim’s phone can fire the broadcast and trigger all eight reactions, subject to the operator’s flag configuration.
Lateral movement via Tasker
Tasker is a well-known Android automation app (500k+ active installs on Play) that lets users script arbitrary device actions — launch apps, toggle settings, read sensors, send HTTP requests, manipulate files — in response to arbitrary events. It exposes a public Intent API: another app with the net.dinglisch.android.tasker.PERMISSION_RUN_TASKS permission can ask Tasker to run a named task by broadcasting an Intent.
Cerberus requests that permission in its manifest. com.surebrec.util.TaskerIntent, decompiled, contains five distinct error codes, dual package detection (net.dinglisch.android.tasker for the free version, net.dinglisch.android.taskerm for the paid Play Store version), and direct ContentProvider reads against Tasker’s internal settings at content://net.dinglisch.android.tasker/prefs to verify enabled=true and ext_access=true before dispatching — it queries Tasker’s own configuration through Tasker’s exported provider to check that the broadcast will be received before sending it.
Cross-referencing the three “Task not executed: …” log strings (which are only emitted in the Tasker error path) against every class in the decompiled source yields seven distinct event classes that dispatch Tasker macros:
| # | Class / method | Triggering event |
|---|---|---|
| 1 | com.surebrec.ShutdownAttemptReceiver.onReceive | Power button press |
| 2 | com.surebrec.AdminReceiver.onPasswordFailed(Context, Intent) | Wrong device-admin password typed |
| 3 | com.surebrec.GeofenceBroadcastReceiver.onReceive | Entering or leaving a geofenced area |
| 4 | w7.n.onTrigger(TriggerEvent) | Android low-power sensor trigger (significant-motion) |
| 5 | com.surebrec.BackgroundService.onStartCommand | Background service lifecycle |
| 6 | com.surebrec.SurebrecService.onStartCommand | Main service lifecycle |
| 7 | w7.p.onReceive(Context, Intent) | An additional minified broadcast receiver |
Three of the seven trigger categories go beyond power-press / boot / service-lifecycle events. A wrong device-admin password triggers a Tasker macro: the abuser can configure “when the victim fails the lock-screen password, run task X.” A geofence crossing triggers a Tasker macro: the abuser can draw a polygon on the dashboard and schedule arbitrary automation on entry or exit. An Android significant-motion sensor event triggers a Tasker macro: “when the victim picks up the phone, run task X.” The dispatch code is in the binary; the operator configures the task name and the trigger via the dashboard.
A rule-matching helper class, Lw7/k4;, exposes seven distinct event-dispatch methods (l, m, n, o, p, q, r), each with a different typed-parameter shape that corresponds to a different event category:
l(Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean)— 4 boolean state flags + name + 2 booleansm(Boolean, Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean)— 5 boolean state flags + name + 2 booleansn(Boolean, Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean, Boolean)— 5 booleans + name + 3 booleanso(Boolean, String, Boolean, String)— simpler 4-parameter eventp(String, Double, Double, Float, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, String)—(name, latitude, longitude, accuracy, ...flags..., extra)— the location eventq(Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean)— 7-parameter eventr(Integer, Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean)— integer payload (battery level) + 4 booleans + name + 2 booleans
The seven methods correspond to the seven event triggers in the table above — power-button (ShutdownAttemptReceiver), wrong-admin-password (AdminReceiver), geofence-cross (GeofenceBroadcastReceiver), motion-sensor (w7.n.onTrigger), location update with lat/lon/accuracy (p), background-service lifecycle, and the Lw7/p; multi-event handler. Lw7/p; itself fans out across six system events (BATTERY_LOW, BATTERY_CHANGED, ACTION_POWER_CONNECTED, ACTION_POWER_DISCONNECTED, ACTION_SHUTDOWN, and Cerberus’s internal DEVICE_STARTED), each routable to a Tasker macro through one of the dispatch methods; the class alone contains 42 TaskerIntent broadcasts.
The typed parameters are used internally for rule matching — geofence-distance computation, battery-level threshold checks, foreground-package matching — not for parametrizing the Tasker macro. A bytecode-level scan across all 12,168 LSDroid methods in com.ssurebrec finds zero Intent.putExtra("%var_name", ...) calls with Tasker-style %-prefixed keys. The TaskerIntent constructor sets only task_name, version_number, and a random id extra; Cerberus fires the macro by name and passes no parameters. The operator’s Tasker macros must read device state through Tasker’s built-in state-query actions (e.g., %BATT, %LOC, %CONN); Cerberus’s contribution is “trigger this macro now,” not “trigger this macro with these values.” The seven typed-parameter shapes describe the events Cerberus can hook, not data it ships into Tasker.
Tasker is not sandboxed. Its permissions on a power user’s phone are typically broader than Cerberus’s own. A Tasker task can send HTTP requests to arbitrary URLs, read files, send SMS, place calls, control media, manipulate notifications, and run shell commands. Every capability Tasker has on the victim’s device is reachable through Cerberus via seven different event triggers. If the victim has Tasker installed, Cerberus’s reachable capability set extends to whatever the user’s Tasker macros are configured to do — without an APK update and without new permissions.
The C2 surface is 44 FCM commands plus a root shell plus everything the victim’s own Tasker install is configured to do.
What a victim can do
For someone who suspects Cerberus is on their device:
If you are in an abusive situation: Contact the National Domestic Violence Hotline (US: 1-800-799-7233) or the Coalition Against Stalkerware before doing anything on the device — even checking can alert the abuser, and the Kids app notifies the operator of permission changes in real time. Removal can also destroy forensic evidence. Consider contacting from a different device or a trusted person’s device; the compromised phone is itself a risk surface. Cornell Tech’s Clinic to End Tech Abuse (CETA), the NNEDV Safety Net Project, Operation Safe Escape, and WESNET (Australia) are DV-aware technologists who can plan a removal with the survivor. Outside the US: regional helplines. Safety planning first.
Detection
Check for these package names in Settings → Apps (show system apps): com.ssurebrec, com.surebrec, com.lsdroid.lsp, com.lsdroid.cerberus.kids, com.lsdroid.cerberus.persona2, com.lsdroid.cerberus.enterprise. The disguised version appears as “System Framework” with a generic Android icon. The LSP module appears as “Lock Screen Protector.” The Kids app appears as “Kids.”
Check accessibility services: Settings → Accessibility. Look for “System Framework,” “Lock Screen Protector,” or any service from the packages above with BIND_ACCESSIBILITY_SERVICE permission.
Check device admin: Settings → Security → Device admin apps. Cerberus registers as device admin to prevent uninstallation and enable lockNow().
Query the exported content providers from any app or ADB:
adb shell content query --uri content://com.ssurebrec
adb shell content query --uri content://com.surebrec
adb shell content query --uri content://com.lsdroid.lsp
If any returns data, the corresponding app is installed. The first two return the Cerberus device ID — the same ID used across the entire ecosystem.
Removal
- Revoke device admin: Settings → Security → Device admin apps → disable for Cerberus
- Disable accessibility services: Settings → Accessibility → disable “System Framework” / “Lock Screen Protector” / any Cerberus Kids service
- Uninstall any Cerberus apps found in Detection: Settings → Apps → Uninstall
com.ssurebrec,com.surebrec,com.lsdroid.lsp,com.lsdroid.cerberus.kids,com.lsdroid.cerberus.persona2,com.lsdroid.cerberus.enterprise. - Each app must be uninstalled separately — removing the main
com.ssurebrecdoes not removecom.lsdroid.cerberus.kidsorcom.lsdroid.cerberus.enterprise. The Kids app’s notification listener and accessibility service persist independently; Enterprise is a parallel product that doesn’t depend on main.
On rooted devices where block_uninstall was used, removal requires root access or factory reset. Check /system/etc/cerberus_conf.xml — if present, the config survives factory reset.
Detection artifacts
Package names
com.ssurebrec # Play Store main app
com.surebrec # Direct download main + WearOS
com.lsdroid.lsp # Lock Screen Protector
com.lsdroid.cerberus # Package alias (same code as com.surebrec)
com.lsdroid.cerberuss # Package alias (same code as com.surebrec)
com.lsdroid.cerberus.enterprise # Enterprise (marketed as MDM)
com.lsdroid.cerberus.kids # Kids / "Child Safety"
com.lsdroid.cerberus.persona2 # Persona / "Personal security — Women safe"
Network indicators
www.cerberusapp.com # Primary C2 (all apps except Enterprise)
enterprise.cerberusapp.com # Enterprise C2
pippo.cerberusapp.com # Outbound mail relay
wss://www.cerberusapp.com:8443 # WebSocket shell (v3.8.0)
User-Agent: Cerberus <product> <version> - Android ... # all C2 traffic
User-Agent: nokeepalive Cerberus ... # keepalive-disabled requests
Filesystem artifacts
/system/etc/cerberus_conf.xml
/system/etc/cerberus_at.db
{data}/shared_prefs/conf.xml
{data}/databases/AutoTaskRules.db
SharedPreferences indicators
Key "hidden" = true → app is concealed from launcher
Key "smskeyword" → SMS C2 trigger word
Key "emergencymode" = true → continuous surveillance active
Key "preventusbdebug" = true → anti-forensics active
Key "protectadmin" = true → device admin removal blocked
Broadcast actions
com.lsdroid.shutdownattempt
com.surebrec.SHUT_STARTED
com.surebrec.DAILY_PING
com.surebrec.CONN_DELAY
shutshut # bare-name internal fake-shutdown signal (no reverse-domain namespace — distinctive)
The companion apps
Three more LSDroid apps ship from the same Play developer account alongside main Cerberus and LSP. The architectural picture is not uniform — the three companions split cleanly into two categories:
Add-ons (Kids, Persona2). Both layer additional surveillance capabilities on top of main, both carry their own Firebase project and (for Kids) their own FCM command channel, and both hard-depend on main being installed: each reads main’s device ID at launch via the unauthenticated IdProvider ContentProvider, and each refuses to function if main isn’t there. Their data joins main’s data stream on LSDroid’s backend under one device record. They are capability extensions on top of the main stalkerware, not standalone products.
Parallel product (Enterprise). Architecturally distinct from the add-ons: does not read main’s ContentProvider, does not share main’s device ID, does not depend on main being installed. Enterprise uses its own backend subdomain (enterprise.cerberusapp.com), its own server-assigned identity, and its own Firebase project. Enterprise is a separate product on LSDroid’s infrastructure that shares a developer account.
The companions are not orthogonal capability modules. They overlap heavily across surveillance categories — four of the five apps independently track GPS, three implement geofencing, three track app-inventory changes, two run their own accessibility services, two run their own NotificationListeners. Each app reimplements capability categories the others already cover:
| Capability | Main | LSP | Kids | Persona2 | Enterprise |
|---|---|---|---|---|---|
| GPS tracking | yes | — | yes | yes | yes |
| Geofencing | yes | — | yes | — | yes |
| App-inventory tracking | yes | — | yes | — | yes |
NotificationListener | yes | — | yes | — | — |
| Accessibility service | (uses LSP) | yes | yes (own) | — | — |
| FCM command channel | yes (44) | — | yes (17) | — | yes (5) |
What’s distinctive per app — the capabilities no other app in the suite covers:
- LSP: power-button intercept, status-bar overlay, screen-scrape via accessibility.
- Kids: continuous app-usage telemetry with operator alerts on installs, plus a
NotificationListenerconfigured for arbitrary-app suppression. The combined main + Kids + LSP deployment on a child’s phone runs three accessibility services, three FCM channels, three Firebase projects, and twoNotificationListeners simultaneously. - Persona2: lock-screen-active widget + per-victim shareable tracking URL that surfaces location data without requiring operator-login authentication. Persona2 is the only Cerberus app the victim can be persuaded to install themselves under its personal-safety-for-women positioning, which then forces installing main as a prerequisite.
- Enterprise: single-APK standalone deployment with no main-app dependency, plus marketing-as-MDM branding.
- Main: camera/microphone capture, screen recording, root shell (v3.8.0), SMS C2 channel (v3.8.0), and the full operator command surface (44 FCM commands).
The redundancy is consistent with each product being independently built — different teams or different timelines, different Gradle workspaces, different obfuscator pipelines, different Firebase projects — with no shared LSDroid platform to consolidate the duplication. The suite is parallel implementations of overlapping surveillance capabilities with narrow distinctive add-ons per app, not clean modular extensions of a single core.
The bridge: IdProvider
com.surebrec.IdProvider is a ContentProvider exported with android:exported="true" and no android:permission attribute. Its query() method returns a single-column, single-row cursor containing the Cerberus device ID — the same identifier used in every upstream message to cerberusapp.com.
query() is seven lines:
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
String[] cols = { "id" };
MatrixCursor cursor = new MatrixCursor(cols, 1);
// R8-renamed U2.U1.i — IMEI if READ_PHONE_STATE, else ANDROID_ID
cursor.newRow().add(w7.l5.d(getContext()));
return cursor;
}
The other ContentProvider methods (delete, insert, update, getType, onCreate) are stubbed; IdProvider is read-only by design. The query() method ignores all five of its parameters — whatever a caller asks, the response is one column named id with one row containing the canonical device ID. There is no checkCallingPermission, no calling-UID check, no URI authority validation. The class as written has nowhere to put an authentication path.
Any app on the device can query content://com.ssurebrec (Play Store) or content://com.surebrec (direct) and receive this ID without authentication. Two of the three companion apps do exactly this on launch. The device ID is the key that lets the server correlate data from all apps into a single victim profile.
Kids: “Cerberus Child Safety” (com.lsdroid.cerberus.kids v1.2.9)
Installed on: the child’s device (CHILD role) and the parent’s device (PARENT role). Same APK, role chosen at setup.
The Kids app declares isMonitoringTool=child_monitoring in its manifest — Google’s required metadata for approved monitoring apps. It requires the main Cerberus app to be installed first. On launch, StartActivity queries the ContentProvider. If neither content://com.ssurebrec nor content://com.surebrec responds, the app shows: “Cerberus Anti-theft must be installed and configured on the phone, for this app to work” and redirects to the Play Store.
The Kids app is not a standalone product. LSDroid markets it as an add-on to the main Cerberus subscription — and every piece of evidence in the APK itself is consistent with that: the purchase URL routes through cerberusapp.com/buy.php?app=kids, the Kids app queries the main app’s com.surebrec.IdProvider for its device ID, both apps POST to the same cerberusapp.com/comm/* upstream endpoint family, and the Kids app refuses to run if the main app isn’t installed. A single-subscription model is the only architecture those dependencies fit.
The child’s phone runs two FCM command channels simultaneously. Kids has its own Firebase project (cerberus-kids) and its own FCM service (com.lsdroid.cerberus.kids.MyFirebaseMessagingService) that dispatches 17 short bidirectional command tokens (rsu, rau, rlu, rsr, asr, apn, opn, gt, i, ers, rn, t, s, gd, nan, lpn, lsn, asn). Some are upstream (CHILD-role: send stats, send location, send installed apps); some are downstream (PARENT-role: receive geofence-crossing notifications, receive new-app-installed alerts). Same APK, role chosen at install via ChildRoleDisclaimerActivity and stored in SharedPreferences.
Because Kids requires the main com.ssurebrec to be installed on the same device, every working Kids deployment is a co-deployment: the child’s phone runs Kids’ 17-token FCM channel on cerberus-kids AND the main app’s 44-command FCM surveillance surface on api-project-999803017449. Two Firebase projects, two FCM channels, one device. Every Kids install on a child’s phone is also a main-app install on the same device.
What the CHILD role collects:
- Complete app inventory every 30 minutes (
InstalledAppsWorker), including app icons scraped from Google Play Store pages - Per-app screen time at four granularities: hourly, daily, weekly, monthly (
StatisticsWorker,MyAccessibilityService) - GPS location every 30 minutes with on-demand high-accuracy fixes
- Geofence enter/exit events with exact crossing coordinates, speed, bearing, and accuracy
- Notification metadata — the
NotificationListenercan cancel or snooze notifications from restricted apps - Permission status changes — if the child disables the accessibility service, location access, or notification listener, the operator receives an immediate alert
All data sent to www.cerberusapp.com/comm/send_upstream_message.php — the same server infrastructure that handles camera captures, root shell sessions, and SMS exfiltration from the main stalkerware.
What the accessibility service declares vs. what it does:
The accessibility service configuration requests canRetrieveWindowContent="true" — the system grants permission to read every text element on screen. The user-facing description (displayed when enabling the service) states: “No data is collected or sent.”
The InstalledAppsWorker sends app inventories to cerberusapp.com every 30 minutes. The StatisticsWorker sends usage statistics every 4 hours. Permission changes trigger immediate upstream messages. The description is false.
App-block enforcement and OEM coverage. Kids’ MyAccessibilityService enforces parental “block this app” rules by intercepting app-launch events. To avoid blocking the operating system itself, it consults a 48-entry static whitelist (Lcom/lsdroid/cerberus/kids/x;) of packages that are exempt from enforcement: AOSP system services, Google Play Services, package installer, settings, and 17 OEM launcher packages. The launcher-package list — com.miui.home (Xiaomi), com.huawei.android.launcher (Huawei), com.oppo.launcher (OPPO), net.oneplus.launcher (OnePlus), com.lenovo.launcher (Lenovo), com.lge.launcher2 and com.lge.launcher3 (LG), com.motorola.launcher3 (Motorola), com.sonyericsson.home (Sony), com.tct.tablet.launcher (TCT), com.zte.mifavor.launcher (ZTE), com.bbk.launcher2 (Vivo’s BBK Electronics), com.cyanogenmod.trebuchet (deprecated), org.lineageos.trebuchet, plus AOSP and Pixel — covers the OEMs Kids recognizes.
The Samsung launcher (com.sec.android.app.launcher) is not in the list. Samsung’s setup wizard (com.sec.android.app.SecSetupWizard) is, suggesting the launcher omission is unintentional. The consequence on Samsung phones: every interaction with the home screen is logged by StatisticsWorker as “child used app Launcher” rather than filtered out, and MyAccessibilityService posts an enforcement-decision log line and notification each time the child returns to the launcher under a “block all” policy. The phone is not bricked (the UsageLimitActivity overlay’s Home button uses an Android system intent that resolves to whatever the default launcher is), but the parent dashboard receives redundant launcher-as-app usage entries and enforcement notifications. Samsung is the largest Android OEM globally.
What protections exist for the child’s data:
None specific to children. No certificate pinning. No network_security_config.xml. No local database encryption — data.db (Room) is unencrypted SQLite. No age verification. No verifiable parental consent — a checkbox saying “I’m a parent or legal guardian” with no identity verification. No privacy policy link in the app. Ad tracking enabled via com.google.android.gms.permission.AD_ID with allowAllToAccess="true" in the ad services config and no tagForChildDirectedTreatment flag — Google’s ad network profiles the child without child-directed treatment protections.
The StartActivity contains a hardcoded blocklist of 22+ device identifiers (emulator IMEIs, DEFACE, NULL, known analysis rig serials) that prevent the app from running. The same blocklist appears in the main v3.8.0 app but not in the Play Store main app.
Who legally owns the signing chain. The Kids APK is signed with a 2009 self-issued RSA-1024, SHA-1 certificate in the name of Luca Sagaria, with all identity fields other than the common name literally set to Unknown (CN=Luca Sagaria, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown). The cert was minted on 2009-03-13 — four years before the 2013 Verge profile first named Cerberus, years before LSDroid SRL was incorporated — and expires 2036-07-29. Everything else in the Cerberus Play ecosystem is either signed by Google under Play App Signing (LSP, Enterprise, Persona2) or by an LSDroid SRL corporate cert generated in June 2023 (main, disguised). Only the child-monitoring APK still ships under the founder’s personal developer identity with no corporate linkage in its signing chain. The entity that the Play listing, the billing relationship, and every GDPR data-controller analysis names as responsible for children’s data — LSDroid SRL — is not the entity whose name appears in the signing certificate of the software actually running on the child’s phone.
Persona2: “Personal security — Women safe” (com.lsdroid.cerberus.persona2 v1.8)
Installed on: the victim’s device. The victim installs it voluntarily — it is marketed as a personal-safety app for women.
LSDroid’s Google Play listing title for com.lsdroid.cerberus.persona2 is literally “Personal security — Women safe”. The internal resource strings use “Cerberus Personal Safety” and “Cerberus Persona”; the customer-facing brand on the store page targets women specifically. The same developer account sells this “women safe” product and the covert-stalkerware main app (com.ssurebrec) — same C2 domain, shared device ID via the main app’s ContentProvider, and Persona2 refuses to register with the C2 if the main app isn’t installed.
Persona2 does not declare isMonitoringTool in its manifest. It frames itself as the user’s own safety tool: “Persona can help in dangerous situations.” It sends emergency SMS with a cerberusapp.com/persona-{token} tracking URL to the victim’s contacts.
On launch, it queries the same ContentProvider. If neither authority responds, the method returns null. At current version (versionCode 21), there is no fallback — verified at the smali bytecode level. Persona2 cannot register with the C2 server without the main Cerberus app installed. It is a hard dependency, not an optional companion.
The hard dependency appears to be a retrofit. Decompiling Persona2’s identity-resolution path (I5/O0;->p(Context)) shows a versionCode <= 10 branch that falls back to Settings.Secure.ANDROID_ID when the main app isn’t installed. That fallback is dead code at the current versionCode 21 — the gating check forces every fresh install to require main. Early Persona2 versions could run standalone with their own device-identity namespace; current Persona2 is bound to main. Somewhere between vc10 and vc21, LSDroid removed the standalone deployment path in favor of unified identity with the main stalkerware. The change is consistent with a product-strategy move toward consolidating customer identity across the suite.
The app actively directs users to install the main Cerberus app: "This app requires Cerberus Anti-theft." with a link to market://details?id=com.ssurebrec.
The WidgetActivity declares setShowWhenLocked(true) and setTurnScreenOn(true) — the widget can activate from the lock screen without unlocking.
GPS telemetry (latitude, longitude, accuracy, speed, bearing, battery level) is sent to cerberusapp.com/comm/persona_sendlocation.php. No certificate pinning.
Enterprise: “Cerberus Enterprise” (com.lsdroid.cerberus.enterprise v1.7)
Installed on: the employee’s device. Despite the “Enterprise MDM” product positioning, the APK has zero mobile-device-management capability. Architecturally, Enterprise is not a companion to the main Cerberus stalkerware — it doesn’t read main’s ContentProvider, doesn’t share main’s device ID, doesn’t depend on main being installed. It uses its own backend subdomain (enterprise.cerberusapp.com), its own Firebase project (cerberus-enterprise), and a server-assigned device identity. Enterprise is a parallel product on LSDroid’s infrastructure, not an add-on.
com.lsdroid.cerberus.enterprise v1.7 declares 13 permissions — all of them location, FCM, foreground-service, boot-completed, and notification. It does not declare BIND_DEVICE_ADMIN, it has no DeviceAdminReceiver in its manifest, and its AndroidManifest contains no Work Profile provisioning metadata. Every DevicePolicyManager reference in the decompiled binary resolves to a vendored Google library — Firebase Crashlytics’ installer-trust helper (n8.j(Context, String) calling isDeviceOwnerApp / isProfileOwnerApp), and Google’s Android Management API client (com.google.android.gms.internal.amapi.*). LSDroid’s own 12 classes contain no device-policy code at all. A surface-level scanner that greps for DevicePolicyManager sees those library strings and assumes MDM capability; the APK has none.
The actual shape of com.lsdroid.cerberus.enterprise is twelve LSDroid classes — MainActivity, FCMService and its worker, RealTimeLocationService, LocationUpdatesBroadcastReceiver and worker, GeofenceSyncWorker, GeofenceTransitionBroadcastReceiver, BootBroadcastReceiver, PackageUpdateBroadcastReceiver, UpstreamResendWorker, a top-level utility class, and a Room database. The FCM command set is 5 commands — rt_location_start_request, rt_location_stop_request, geofence_sync_required (with policy_template_id, target_version, reason params), device_data (stores device_id to SharedPreferences), geolocation_enabled — versus the consumer app’s 44. The C2 surface is a new subdomain, enterprise.cerberusapp.com, with two endpoints: /api/send_upstream_message.php (upstream JSON POST, FCM token as the only device identifier) and /api/Geofences_DeviceSync.php (geofence-definition pull). Same authentication gap as the consumer side: no HMAC, no Authorization header, no certificate pinning, no custom network-security config.
The transparency of the foreground-service notification is real — “Your enterprise is tracking this device’s location in real-time.” What is not transparent is the product category. An IT admin who purchases “Cerberus Enterprise” under an MDM assumption is not buying managed-device software. They are buying an employee-location tracker with geofencing and an installed-app inventory, labelled Enterprise.
The distinction is not cosmetic. Under EU labour law, MDM software and employee-monitoring software sit in different compliance regimes. MDM has a well-trodden legitimate-interest + DPIA + works-council-notification framework. Employee-monitoring software without MDM capability is stricter: under Italy’s Statuto dei Lavoratori Article 4 — LSDroid’s home jurisdiction — remote monitoring of workers requires either a union agreement or prior authorization from the Ispettorato Nazionale del Lavoro. Germany’s Betriebsverfassungsgesetz requires works-council co-determination. The marketing label does not change the regulatory category. An Italian employer deploying Cerberus Enterprise to staff phones on the belief that MDM precedent applies is in the wrong regime.
One subscription, one customer
The Cerberus website routes all product purchases through a single cerberusapp.com/buy.php?app=<name> checkout. The main app, the Kids app, and Persona2 all query the same shared device ID via com.surebrec.IdProvider / com.ssurebrec ContentProviders, all talk to the same cerberusapp.com C2, and each companion refuses to function without the main app installed. The architecture is consistent with a single-subscription model — one account, one backend, capability unlocked by product selection at install time.
There is no observed account tier that provides parental controls without surveillance capabilities. There is no data segregation between the anti-theft C2 and the child monitoring backend. The server receives GPS coordinates from the main app’s sendlocation.php and from the Kids app’s send_upstream_message.php with the same device ID. The child’s data and the stalkerware victim’s data sit in the same infrastructure.
What the “anti-theft” defense doesn’t cover
Cerberus markets itself as an anti-theft tool. Some of its features — GPS tracking, remote ring, remote lock, remote wipe — have plausible anti-theft use. But every one of those capabilities has been a free, built-in Android platform feature since August 2013, when Google launched Android Device Manager (now Find Hub). Every Android phone already ships with real-time device location, remote ring, remote lock, remote wipe, and — since April 2024 — offline Bluetooth-beacon tracking, all at no cost and with no third-party app installed. Apple’s equivalent predates Google’s by several years. The legitimate anti-theft customer is already served by a platform feature they didn’t have to install.
What Cerberus adds beyond Find Hub’s feature set is the Cerberus product. The SMS exfiltration, call-log harvesting, contact exfiltration, microphone-only recording, interactive root shell, keylogging declaration, notification suppression, launcher-icon concealment, dial-code re-entry, Wi-Fi BSSID proximity tracking of specific networks, fake shutdown, and silent Intent-extras login serve no anti-theft purpose. Legitimate anti-theft apps (Google Find Hub, Samsung SmartThings Find, Apple Find My) do not bundle these capabilities, because the use cases for them are not anti-theft use cases.
The Kids app sits inside the same subscription as the main app, requires the main app as a dependency, and reads its device ID via an unauthenticated ContentProvider. None of the platform anti-theft tools (Find Hub, Apple Find My, Samsung SmartThings Find) have child-monitoring companions with these dependencies. The Kids ↔ main coupling is a single-vendor surveillance-suite architecture, not an anti-theft one.
The Kids app’s accessibility service description — “No data is collected or sent” — is a false statement in a Google-reviewed metadata field, while the app sends data to cerberusapp.com every 30 minutes. The app collects children’s precise GPS, complete app inventories, per-app screen time, and behavioral data with no certificate pinning, no local encryption, no age verification, no GDPR-compliant consent mechanism, and ad tracking enabled without child-directed treatment flags. This data flows through infrastructure shared with a rootkit that provides interactive shell access to victim devices.
LSDroid is an Italian company. GDPR Article 8 requires consent from a parent or guardian for processing a child’s personal data, with “reasonable efforts to verify.” A checkbox is not verification. The Italian Garante has fined companies for less. Google’s Play Families Policy requires apps targeting children to comply with applicable children’s privacy laws, not serve uncertified ads, and provide a privacy policy. The Kids app provides no privacy policy link.
The public record
Cerberus’s path through Google’s infrastructure is documented across nine years of public artifacts.
November 2017. Google emailed LSDroid citing a Malicious Behavior Policy violation. LSDroid posted the email publicly.
May 2018. Chatterjee et al. published “The Spyware Used in Intimate Partner Violence” at IEEE S&P, naming Cerberus as IPV stalkerware and reporting the apps to Google.
Late 2018 or early 2019. Google removed com.lsdroid.cerberus from Play, citing — by LSDroid’s own archived statement — the off-store-distribution policy. LSDroid continued distributing the same app from cerberusapp.com.
September 16, 2020 / effective October 1, 2020. Google published a dedicated Stalkerware Policy — a category of policy that did not exist when Cerberus was removed in 2018. The policy defines stalkerware as “code that collects and/or transmits personal or sensitive user data from a device without adequate notice or consent and doesn’t display a persistent notification that this is happening,” names a single carve-out for parental and enterprise monitoring apps, and explicitly excludes apps used to track “anyone else (a spouse, for example) even with their knowledge and permission.”
October 2023. Google approved com.ssurebrec v1.0 for return to Play Store — three years and four days after the Stalkerware Policy went into effect. The launch capability set: 44 FCM commands including silent camera and microphone capture, fake shutdown, continuous location tracking, app-hiding, plus the org.lsposed.hiddenapibypass library.
2024–2026. LSDroid expands the product line: Persona2 enrolls in Play App Signing on 2024-02-29. Five Cerberus apps distribute on Play. Google AdMob is the named buyer of ad impressions served inside the stalkerware (publisher pub-9848961826628138, listed in Google’s own sellers.json as LSDroid SRL). Google Firebase hosts the FCM command channels and the operator-state Realtime Database described in this writeup.
Policy violations on the current Play build
Each citation is pinned to specific code in com.ssurebrec v1.4.9, verifiable from the live Play Store APK without LSDroid’s cooperation:
Non-SDK interface access via HiddenApiBypass. Three Cerberus Play APKs ship org.lsposed.hiddenapibypass: com.ssurebrec v1.4.9, com.lsdroid.lsp v3.6, and com.lsdroid.cerberus.kids v1.2.9. Verification: unzip any one of the APKs, grep for org/lsposed/hiddenapibypass/. The library’s name is “Hidden API Bypass.” Google Play’s Malicious Behavior policy bans non-SDK interface access; the 2017 Google email to LSDroid cited that same policy area. Each of the three APKs is an independent ship and an independent policy citation.
Silent runtime-permission grant of 16 dangerous permissions. com.surebrec.SuCommands.main(String[]) contains the full 16-permission grant loop as string literals plus the reflected call to IPackageManager.grantRuntimePermission.
Uninstall blocking via reflected IPackageManager. Same method: setBlockUninstallForUser string literal plus the reflected call via IPackageManager$Stub.
Launcher-icon and recents-screen concealment. FCM command HIDE (opcode 9) in com.surebrec.FCMMessagingService.c(RemoteMessage). com.surebrec.Login.onCreate calls setTaskDescription(...) with null label and icon to remove the app from Android recents.
Power-off interception and fake shutdown. com.lsdroid.lsp.AccessibilityService.onAccessibilityEvent plus the I2.c.onSuccess callback plus com.surebrec.ShutdownAttemptReceiver.onReceive.
Ad impressions served inside stalkerware via AdMob. Publisher ID pub-9848961826628138. Attribution verifiable via curl https://realtimebidding.google.com/sellers.json | jq '.sellers[] | select(.seller_id == "pub-9848961826628138")' — returns LSDroid SRL.
The Stalkerware Policy that applies
Google’s Stalkerware Policy took effect October 1, 2020 — three years before com.ssurebrec was admitted to the Play Store. It opens with the definition of the prohibited category:
Code that collects and/or transmits personal or sensitive user data from a device without adequate notice or consent and doesn’t display a persistent notification that this is happening.
It then names the only carve-out:
Apps exclusively designed and marketed for parents to track their children or enterprise management, provided they fully comply with the requirements described below are the only acceptable surveillance apps. These apps cannot be used to track anyone else (a spouse, for example) even with their knowledge and permission, regardless if persistent notification is displayed.
For monitoring apps that qualify for the parental/enterprise carve-out, the policy lists four compliance requirements:
Apps must not present themselves as a spying or secret surveillance solution. Apps must not hide or cloak tracking behavior or attempt to mislead users about such functionality. Apps must present users with a persistent notification at all times when the app is running and a unique icon that clearly identifies the app. Apps and app listings on Google Play must not provide any means to activate or access functionality that violate these terms, such as linking to a non-compliant APK hosted outside Google Play.
Each clause maps to a specific feature in the live com.ssurebrec v1.4.9 build. The mappings are pinned to source classes and methods so that a Play enforcement engineer can verify each one against the live APK without LSDroid cooperation:
| Policy clause | Cerberus implementation |
|---|---|
| “Persistent notification at all times when the app is running” | The foreground-service notification body reads "This device is protected by Cerberus" on a channel configured IMPORTANCE_LOW with lockScreenVisibility = VISIBILITY_SECRET — invisible on the locked screen, where the phone spends most of the day. The notification does not name the camera, microphone, location, or screen-recording activity occurring while it is posted. Source: SurebrecApplication.onCreate; BackgroundService.startForeground. |
| “Unique icon that clearly identifies the app” | The “disguised” build’s manifest declares android:label="System Framework" and android:icon="@android:drawable/sym_def_app_icon" — the generic stock Android icon. The HIDE command (FCM opcode 9) calls PackageManager.setComponentEnabledSetting() to disable the launcher activity entirely; setTaskDescription(null, null, color) removes the app from Android recents. Source: disguised build manifest; com.surebrec.FCMMessagingService.c; com.surebrec.Login.onCreate. |
| “Apps must not present themselves as a spying or secret surveillance solution” | LSDroid’s own promotional video, hardcoded inside the LSP APK at com.lsdroid.lsp.c line 27 and opened from within the LSP setup UI, describes the fake-shutdown feature as showing a fake power menu so that the person holding the phone believes it has been switched off while the device continues to track, record, and report. The shipped implementation matches the marketing description. Source: com.lsdroid.lsp.c:27; ShutdownDialogActivity. |
| “Apps must not hide or cloak tracking behavior or attempt to mislead users about such functionality” | HIDE command (launcher-icon concealment); dial-code re-entry (23723787 = T9 of CERBERUS, configurable per-install); NotificationListener cancels notifications within ±5 seconds of incoming command-SMS; fragmented string concatenation hides the C2 domain from static dependency scans. Source: opcode 9; StartReceiver.onReceive; NotificationListener; U2.U1. |
| “Apps and app listings on Google Play must not provide any means to activate or access functionality that violate these terms, such as linking to a non-compliant APK hosted outside Google Play” | Cerberus constructs the URL https://cerberusapp.com/download/version from runtime string concatenation ("https://cerb" + "erusapp.com" + "/download/version") and uses it as a sideload self-update path. The string is reconstructed at runtime to evade the static-link prohibition. Source: U2.U1.java:2982. |
| “Apps exclusively designed and marketed for parents to track their children” (the parental carve-out) | Kids reads its device identity from the main stalkerware via the unauthenticated IdProvider ContentProvider (com.surebrec.IdProvider); Persona2 does the same. Both refuse to register with the C2 if the main app isn’t installed. Both feed LSDroid’s backend with the same device ID as the main stalkerware. Even if Kids alone qualified for the parental carve-out, its operational dependency on com.ssurebrec defeats the “exclusive” qualifier the policy requires. Source: IdProvider; Persona2 I5/O0;->p(Context); Kids identity-resolution path. |
| “These apps cannot be used to track anyone else (a spouse, for example) even with their knowledge and permission” | The main com.ssurebrec is not designed for parental or enterprise use — operator dashboard, fake shutdown, dial-code re-entry, and Wi-Fi BSSID radar are advertised IPV-relevant features. Persona2 is marketed on its Google Play store page as “Personal security — Women safe” — explicitly outside the parental/enterprise carve-out. Source: Play Store listing for com.lsdroid.cerberus.persona2; LSDroid’s own marketing materials for the main app. |
Every clause of the policy maps to a specific implementation in the bytecode of the apps Google admitted in October 2023 and continues to host. The policy was published in 2020 to block exactly this category of app.
Available enforcement surfaces
Three Google product lines independently distribute, monetise, or operate parts of the Cerberus stack. Each is independently actionable on public evidence, without a court order:
- Google Play can remove
com.ssurebrecand the four other LSDroid-distributed Cerberus apps under the Stalkerware Policy (every clause violated; see the clause-by-clause table above) and the Malicious Behavior Policy (HiddenApiBypass non-SDK-interface access in three of the five Play apps). A developer-account-level action against LSDroid SRL’s Play account clears all five Play listings simultaneously. - Google AdMob can suspend publisher
pub-9848961826628138. The AdMob account is independently actionable from the Play Store listing. - Google Firebase / GCP can suspend the five Firebase projects (
api-project-999803017449,cerberus-lock-screen-protector,cerberus-enterprise,cerberus-kids,cerberus-persona) that route the surveillance commands and persist operator-state data. If the five projects share a single GCP customer record, one account-level action takes them all offline; if not, per-project suspensions remain individually actionable.
Each action reaches a different layer of Cerberus’s deployment.
Cerberus in commercial-stalkerware history
Cerberus sits within a documented industry timeline:
| Vendor | Event | Outcome |
|---|---|---|
| mSpy | 2015 + 2018 + 2024 customer-data breaches | Continues to operate |
| FlexiSpy | 2017 customer-data leak | Continues |
| Retina-X Studios (MobileSpy / PhoneSheriff / TeenShield) | 2019 FTC settlement — first stalkerware-vendor enforcement | Required to demonstrate purchaser-consent verification before resuming sales; voluntarily shut down after 2018 data breach |
| SpyFone (Support King) | September 2021 FTC settlement under Section 5 of the FTC Act | Banned from the surveillance industry; required to delete illegally harvested data; first FTC ban of a stalkerware vendor; followed 2018 data breach |
| TheTruthSpy network (incl. Cocospy / Spyic / Spyzie) | 2022 EFF letter to FTC urging investigation; 2025 data breach + shutdown reporting | Apps offline May 2025 |
| Spyhide | 2023 takedown after researcher disclosure | Backend offline |
Of the FTC actions, only SpyFone faced a regulator-imposed industry ban; Retina-X faced a consent-verification requirement and chose to shut down rather than comply. Cerberus has been on Google Play continuously since October 4, 2023 despite meeting both FTC settlements’ fact patterns: covert installation, monitoring without victim consent, marketing aimed at people who would surveil intimate partners, and design choices (the silent Intent-extras login, the hidden launcher icon, fake shutdown) that defeat any meaningful consent-of-the-monitored party. Despite EFF urging the FTC to investigate similar networks like TheTruthSpy in 2022, no public FTC action followed; the network went offline in May 2025 only after a data breach forced it.
Disclosure
The Cerberus apps documented here were reported to Google ahead of publication through Google Play’s reporting channel and Firebase abuse reporting.
Google AdMob does not surface a public abuse-reporting channel for stalkerware monetization that this researcher could locate, despite academic research (Gibson et al., PoPETs 2022) finding that 99% of ad-monetized stalkerware uses AdMob and MIT Technology Review’s 2022 reporting documenting Google’s failure to enforce its existing ban on stalkerware ads. The publisher attribution (pub-9848961826628138, named as LSDroid SRL in Google’s own sellers.json) was therefore not separately reported and remains active at publication.
LSDroid SRL was not given advance notice. Standard practice for commercial-stalkerware research is platform-side notification only: vendor advance-notice would create the opportunity to scrub indicators from infrastructure, migrate the command-and-control channel, or push a Play-Store update that disables the license-expiration self-reveal documented above — outcomes adverse to victim safety. The Coalition Against Stalkerware’s research conventions reflect the same reasoning.
The indicators of compromise from this research were submitted upstream to the stalkerware-indicators IOC database in commit fa248462 on 2026-04-10, ahead of publication. Every consumer of the Echap IOC feed (MVT, Quad9, AdGuard, TinyCheck, MISP) has the package names and the disguised-build display names available for end-user protection regardless of platform-side action.
If you are in an abusive situation: Contact the National Domestic Violence Hotline (US: 1-800-799-7233) or the Coalition Against Stalkerware before doing anything on the device — even checking can alert the abuser, and the Kids app notifies the operator of permission changes in real time. Removal can also destroy forensic evidence. Consider contacting from a different device or a trusted person’s device; the compromised phone is itself a risk surface. Cornell Tech’s Clinic to End Tech Abuse (CETA), the NNEDV Safety Net Project, Operation Safe Escape, and WESNET (Australia) are DV-aware technologists who can plan a removal with the survivor. Outside the US: regional helplines. Safety planning first.