{"abstract":null,"author":"Mark Esler","content":"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.\nBackground Cerberus Anti-theft by LSDroid — not the unrelated banking trojan on Wikipedia\u0026rsquo;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 \u0026ldquo;violate Play Store policy by hiding their app icon and showing no notifications, making them as covert as off-store spyware\u0026rdquo; in their IEEE S\u0026amp;P paper The Spyware Used in Intimate Partner Violence.\nEchap\u0026rsquo;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.\nCerberus 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.\nGoogle 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\u0026amp;P paper documented that Cerberus \u0026ldquo;provides all [anti-theft] functionality, along with a remote Android shell in its web portal\u0026rdquo; and reported the apps to Google. Google did not act on the stalkerware finding. Some months later — LSDroid\u0026rsquo;s own archived statement places the start in November 2018 — Google removed com.lsdroid.cerberus from the Play Store, citing a different policy: \u0026ldquo;Apps that cause users to download or install applications from unknown sources outside of Google Play are prohibited.\u0026rdquo; Google enforced against the off-store-distribution business model, not against the surveillance capability set the paper had named.\nLSDroid\u0026rsquo;s response, on their own website (archived snapshot): they would \u0026ldquo;not bother to appeal\u0026rdquo; because \u0026ldquo;the full-featured app\u0026rdquo; is \u0026ldquo;always available on our website.\u0026rdquo;\nIn 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 \u0026ldquo;a slightly smaller set of commands.\u0026rdquo; The direct-download version remains free on cerberusapp.com. The removal-to-return gap was 5 years 5 months.\nThe 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.\nWhat \u0026ldquo;a slightly smaller set of commands\u0026rdquo; actually means, in the bytecode of the live Play Store APK, is what this writeup documents.\nLSDroid SRL on Google\u0026rsquo;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 — \u0026ldquo;Cerberus • LSDroid srl\u0026rdquo;, 100K+ downloads, 4.2 stars, 2.28K reviews. The company is named on the listing itself, not behind a shell-name front. The \u0026ldquo;LS\u0026rdquo; in LSDroid almost certainly stands for the founder\u0026rsquo;s initials — Luca Sagaria — paired with \u0026ldquo;Droid\u0026rdquo; for Android; the company name itself encodes the personal identity behind it.\nThe same legal entity is the named beneficiary in Google\u0026rsquo;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\u0026rsquo;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:\n{ \u0026#34;seller_id\u0026#34;: \u0026#34;pub-9848961826628138\u0026#34;, \u0026#34;seller_type\u0026#34;: \u0026#34;PUBLISHER\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;LSDroid SRL\u0026#34;, \u0026#34;domain\u0026#34;: \u0026#34;cerberusapp.com\u0026#34; } LSDroid SRL is, in Google\u0026rsquo;s own public compliance data, the entity being paid for ad impressions served inside Cerberus. In Italian corporate law \u0026ldquo;SRL\u0026rdquo; stands for Società a Responsabilità Limitata or a limited liability company. Google Payments has LSDroid SRL\u0026rsquo;s legal registration, bank details, and billing address on file because Google sends them money.\nThe 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.\nTwo apps, one product Cerberus (LSDroid srl) ships two versions.\nThe 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\u0026rsquo;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\u0026rsquo; phones.\nThe 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(\u0026quot;su\u0026quot;) 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\u0026rsquo;s inbox, keylogging via a built-in accessibility service labeled \u0026ldquo;System Framework,\u0026rdquo; 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.\nThreat 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\u0026rsquo;t trigger Play Protect, and doesn\u0026rsquo;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.\nSilent 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\u0026rsquo;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.\nOn 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\u0026rsquo;s verified boot prevents the write; on older or unlocked devices, a factory reset does not remove Cerberus.\nFor 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\u0026rsquo;s the common denominator across all deployment scenarios.\nThe HiddenApiBypass policy violation v3.8.0\u0026rsquo;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).\nSince Android 9 (API 28), reflection on these classes is blocked by default. v3.8.0 targets targetSdk 26, which receives Android\u0026rsquo;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.\nThe 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.\nA note on naming. The \u0026ldquo;LSP\u0026rdquo; acronym appears twice in this writeup. LSPosed is the open-source Android-modding project above. Lock Screen Protector (com.lsdroid.lsp) is LSDroid\u0026rsquo;s accessibility-service companion app.\nHow the library works, and what it silences The library obtains sun.misc.Unsafe via Unsafe.class.getDeclaredMethod(\u0026quot;getUnsafe\u0026quot;).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(\u0026quot;\u0026quot;) with an empty-string prefix that matches every class.\nThe javadoc on setHiddenApiExemptions:\nAll matching APIs are treated as if they were on the whitelist: access permitted, and no logging.\nThe \u0026ldquo;no logging\u0026rdquo; half matters separately. The bypass also silences Android\u0026rsquo;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.\nWhat 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\u0026rsquo;t ship it either — targetSdk 26 receives Android\u0026rsquo;s hidden-API leniency, and v3.8.0\u0026rsquo;s SuCommands reflection chain runs natively on that leniency.\nA 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:\nif (!Build.MANUFACTURER.equalsIgnoreCase(\u0026#34;xiaomi\u0026#34;)) { throw new Exception(\u0026#34;Not a Xiaomi device\u0026#34;); } if (Build.VERSION.SDK_INT \u0026gt;= 28) { org.lsposed.hiddenapibypass.\u0026lt;X\u0026gt;.b(new String[]{ \u0026#34;\u0026#34; }); } activated = true; The trigger differs per app:\ncom.ssurebrec: Lw0/e;-\u0026gt;\u0026lt;init\u0026gt;(FakeShutSetupActivity), called from FakeShutSetupActivity.onResume() during initial fake-shutdown configuration. com.lsdroid.lsp: LI2/e;-\u0026gt;\u0026lt;init\u0026gt;(SettingsActivity), called from LSP\u0026rsquo;s settings screen. com.lsdroid.cerberus.kids: Lcom/lsdroid/cerberus/kids/f;-\u0026gt;\u0026lt;init\u0026gt;(Context), called from the Kids app\u0026rsquo;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\u0026rsquo;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.\nThe demonstrated use is narrow — a single Xiaomi-OEM-specific permission probe per app, gated on MANUFACTURER == \u0026quot;xiaomi\u0026quot;, behind a static one-shot flag. The library\u0026rsquo;s broader capability is gated open (the empty-prefix setHiddenApiExemptions(\u0026quot;\u0026quot;) 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\u0026rsquo;t externally verifiable from static analysis.\nThree Play Store apps each ship org.lsposed.hiddenapibypass; each ship requires the README\u0026rsquo;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.\nThe library\u0026rsquo;s own README warns against Play deployment The HiddenApiBypass README.md documents:\nGoogle Play doesn\u0026rsquo;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.\nThe README then publishes the exact build.gradle evasion snippet:\nandroid { dependenciesInfo { includeInApk = false includeInBundle = false } } com.ssurebrec v1.4.9 is live on Play, its APK ships the library, and Play\u0026rsquo;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\u0026rsquo;s own README.\nTimeline: 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\u0026amp;P paper named it as intimate-partner-violence spyware in May 2018. It returned in October 2023. The five-year gap reflects LSDroid\u0026rsquo;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\u0026rsquo;s actual rebuild window is anchored on certificate-issuance dates, not on library availability:\nDate Event May 2018 Chatterjee et al. IEEE S\u0026amp;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\u0026rsquo;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.\nA grep is the enforcement test Three statements: (1) Google Play\u0026rsquo;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\u0026rsquo;s name is literally \u0026ldquo;Hidden API Bypass\u0026rdquo; 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.\nRuntime gating: which reflected calls actually resolve Whether the reflected calls succeed at runtime depends on each method\u0026rsquo;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(\u0026quot;su\u0026quot;) + 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.\nIn 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\u0026rsquo;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\u0026rsquo;t externally verifiable.\nAnalysis 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 / \u0026ldquo;Personal security — Women safe\u0026rdquo; 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).\nLegal 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.\nFindings # 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\u0026rsquo;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\u0026rsquo;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\u0026rsquo;s cloud storage v3.8.0 19 Dial-code re-entry — dialing 23723787 (T9 mapping of CERBERUS, configurable per-install) on the victim\u0026rsquo;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\u0026rsquo;s phone enters or leaves its range; works indoors where GPS doesn\u0026rsquo;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\u0026rsquo;s name with every identity field Unknown), three held by Google via Play App Signing Suite 24 \u0026ldquo;Cerberus Enterprise\u0026rdquo; 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\u0026rsquo;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.\nWhat 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\u0026rsquo;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 \u0026ldquo;Cerberus does X,\u0026rdquo; read it as \u0026ldquo;the bytecode encodes X to happen at runtime under documented Android behavior.\u0026rdquo;\ndroidsaw 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.\nArchitecture Cerberus ships as multiple packages across two distribution channels.\nDistribution 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\u0026rsquo;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 \u0026ldquo;Enterprise MDM\u0026rdquo; — shipped APK is a location tracker with geofencing; no device-admin capability com.lsdroid.cerberus.kids Kids / \u0026ldquo;Child Safety\u0026rdquo; — app monitoring, screen time, location, notification suppression com.lsdroid.cerberus.persona2 Persona / \u0026ldquo;Personal security — Women safe\u0026rdquo; — 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\u0026rsquo;s phone. A row marked \u0026ldquo;Via LSP\u0026rdquo; means the capability is supplied by the companion app\u0026rsquo;s accessibility service, not by the main app\u0026rsquo;s own bytecode.\nCapability 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 (\u0026ldquo;System Framework\u0026rdquo;) 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\u0026rsquo;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.\nThe FCM command handler declares EXEC_TERM_COMMAND and STARTSHELL — the dispatch code exists, but the execution handlers are absent from the bytecode.\nWhat the direct version adds The direct version targets SDK 26 (Android 8) and is distributed from cerberusapp.com — neither subject to Play Store\u0026rsquo;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\u0026rsquo;t request.\nSigning 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:\nSHA-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 Google Play App Signing CN=Android, O=Google Inc. 2023-01-07 LSP Google 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 (\u0026ldquo;Personal security — Women safe\u0026rdquo;) Google 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\u0026rsquo;s corporate cert, not a Play App Signing key held by Google — is what signs com.lsdroid.cerberus.kids on children\u0026rsquo;s phones, valid through 2036-07-29. LSDroid SRL\u0026rsquo;s name does not appear in the signing chain of its own child-monitoring product.\nThe cert is self-signed, so the standard PKI revocation path doesn\u0026rsquo;t apply — there\u0026rsquo;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\u0026rsquo;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.\nThe 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.\nC2 protocol Primary channel: Google\u0026rsquo;s Firebase Cloud Messaging Every command that reaches a victim\u0026rsquo;s device transits Google\u0026rsquo;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.\nThe 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\u0026rsquo;s google-services.json. The five projects\u0026rsquo; billing-account ownership on Google\u0026rsquo;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.\nMain\u0026rsquo;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\u0026rsquo;s PHP backend writes operator-state into it; the operator\u0026rsquo;s web dashboard reads from it. Google\u0026rsquo;s Firebase RTDB is LSDroid\u0026rsquo;s operator-data persistence layer.\nCommand 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=\u0026quot;2147483647\u0026quot; (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.\nAuthorization 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\u0026rsquo;s command-parsing flow, extracted from the decompiled com.surebrec.SmsReceiver.onReceive:\nIf the SMS sender doesn\u0026rsquo;t match one of the three numbers, pass the message through. Check the body against a magic keyword stored in SharedPref smskeyword (default cerberus). 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\u0026rsquo;t cleanly embed newlines). Decrypt the command field with AES/CBC/PKCS5PADDING: a 16-byte key derived from the device identifier via String.format(\u0026quot;%-16s\u0026quot;, 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:\nSIMINFO 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:\nSPEAK — Cerberus uses the victim\u0026rsquo;s phone\u0026rsquo;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\u0026rsquo;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(\u0026quot;findnopass\u0026quot;, false)): when true, it lets the FIND command execute without a password check. datasms is an Intent extra key (putExtra(\u0026quot;datasms\u0026quot;, true)) that flags to the downstream SurebrecService handler that the command arrived via Android\u0026rsquo;s binary DATA_SMS_RECEIVED channel rather than text SMS. Neither is a keyword the abuser types.\nThe 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\u0026rsquo;s device without physical access.\nRadar: Wi-Fi BSSID proximity tracking FCM opcodes STARTRADAR and STOPRADAR are explained by com.surebrec.RadarService. RadarService.onStartCommand takes an Intent extra named \u0026quot;ssid\u0026quot; — a Wi-Fi network name — acquires a wake lock named \u0026quot;RadarService\u0026quot;, 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.\nRadar is Wi-Fi BSSID proximity tracking, not GPS. The operator tells Cerberus \u0026ldquo;start radar for SSID HomeWifi\u0026rdquo; via FCM, and Cerberus reports when the victim\u0026rsquo;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.\nIn domestic-violence contexts an abuser can configure \u0026ldquo;radar for my home Wi-Fi,\u0026rdquo; \u0026ldquo;radar for my workplace Wi-Fi,\u0026rdquo; or \u0026ldquo;radar for the SSID I saw at the shelter intake interview\u0026rdquo; and be notified every time the victim\u0026rsquo;s phone enters or leaves that specific building — including buildings where GPS doesn\u0026rsquo;t work reliably, like shelters, hospitals, courthouses, and police stations. The dedicated C2 endpoint for radar telemetry is cerberusapp.com/comm/radar.php.\nThe 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.\nv3.8.0\u0026rsquo;s 68 opcodes are LSDroid\u0026rsquo;s full C2 protocol grammar; the Play build ships a subset, with opcodes 1–7, 11, 16–17, 23–66 absent.\nNot 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, \u0026ldquo;capture location as shutdown response\u0026rdquo;) 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.\nC2 endpoints The main app references 30+ HTTPS endpoints across the /comm/ (device→server) and /api/ (operator-facing) namespaces on cerberusapp.com:\nNamespace 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.\nNo 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.\nCloud 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\u0026rsquo;s choice.\nDropbox: Full Dropbox Core SDK v2 bundled. Auth token stored in SharedPreferences as dropboxAccessToken. Photos uploaded as image/jpeg, videos as video/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_FILE OAuth2 scope. Uses GoogleAccountCredential.usingOAuth2 — can leverage the victim\u0026rsquo;s own Google account or an operator-provided one. These channels target api.dropboxapi.com and www.googleapis.com — legitimate cloud services. The victim\u0026rsquo;s surveillance data resides in the operator\u0026rsquo;s cloud storage.\nThe 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.\nThree independent wake-up tiers in com.ssurebrec:\nTier 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\u0026rsquo;s background-service restrictions:\nReceiver 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\u0026rsquo;s process is started: every Play Protect update, every OEM bloatware refresh, every app install or removal wakes the process.\nTier 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:\nAdminReceiver.onPasswordFailed, BackgroundService (lifecycle), BootReceiver, ConnectivityReceiver, GeofenceBroadcastReceiver, ShutdownAttemptReceiver, ShutdownDialogActivity.onCreate, SurebrecService.d/onStartCommand, TrackServiceFused.onStartCommand, Lw7/b1;-\u0026gt;run, Lw7/i3;-\u0026gt;f (preference click handler with 2-second background-launch bypass), Lw7/n;-\u0026gt;onTrigger (low-power motion sensor trigger handler), Lw7/p;-\u0026gt;onReceive, Lw7/x1;-\u0026gt;b. Plus one setExactAndAllowWhileIdle in BackgroundService.\nBootReceiver 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.\nTier 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.\nAcross the locked/unlocked storage boundary (Android\u0026rsquo;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\u0026rsquo;s alarm subsystem — Cerberus\u0026rsquo;s wake-up schedule survives device restarts without re-registration.\nA Cerberus install on a phone that\u0026rsquo;s never received a single FCM command still runs, schedules, re-arms, caches for upload, and migrates state across reboots and storage transitions.\nRoot capabilities (v3.8.0) The capabilities below require root at runtime. Runtime.exec(\u0026quot;su\u0026quot;) 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\u0026rsquo;s permission gates.\nThe 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\u0026rsquo;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.\nSuCommands — reflected Android framework API abuse SuCommands.main() is invoked via Runtime.exec(\u0026quot;su\u0026quot;) → app_process. It uses Java reflection to access hidden Android system service APIs:\nCommand 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(\u0026quot;android\u0026quot;) 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(\u0026quot;lock_screen_owner_info\u0026quot;) Sets lock screen message removerestrict INetworkPolicyManager.setRestrictBackground(false) Removes background data restriction allow_uninstall IPackageManager.setBlockUninstallForUser(..., false) Reverses block_uninstall — server-gated \u0026ldquo;legitimate uninstall\u0026rdquo; path, surfaced via LockActivity\u0026rsquo;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(\u0026quot;lock_screen_owner_info_enabled\u0026quot;, 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:\nBinder 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 \u0026ldquo;allow unrestricted background data\u0026rdquo; dialog.\nOn 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.\nACCESS_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.\nInteractive 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.\nScreen recording via the system screenrecord binary Opcode 66 (SCREENRECORD) in SurebrecService contains the literal shell-command template /system/bin/screenrecord --size \u0026lt;WxH\u0026gt; --bit-rate 1000000 --time-limit \u0026lt;seconds\u0026gt;. Cerberus invokes the Android system binary at /system/bin/screenrecord directly via Runtime.exec — the same binary Android\u0026rsquo;s developer-tools screen-capture workflow uses — produces an MP4 on-device, uploads it to /comm/sendvideo3.php, and acquires a wake lock named \u0026quot;Screenrecord\u0026quot; while the recording is in progress.\nThe --bit-rate 1000000 value is 1 Mbps — below the screenrecord binary\u0026rsquo;s own default of 20 Mbps on Android 11+.\nSystem partition persistence On rooted devices, Cerberus installs configuration to the system partition:\n/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.\nReflection 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(\u0026quot;su\u0026quot;) + appops set shell command.\nIStatusBarService.disable/disable2 reflection is reachable not only from SuCommands but from broadcast receiver LL2/B;-\u0026gt;onReceive, handler LP2/P;-\u0026gt;handleMessage, BackgroundService lifecycle methods, and AccService.onAccessibilityEvent — so status-bar disable can fire opportunistically from accessibility events, not only after a deliberate root invocation.\nThe reflection chain reaches Android internal services through the canonical mechanism: Class.forName(\u0026quot;android.os.ServiceManager\u0026quot;).getMethod(\u0026quot;getService\u0026quot;, String.class).invoke(null, \u0026quot;\u0026lt;service-name\u0026gt;\u0026quot;) returns an IBinder, and IFoo$Stub.asInterface(binder) wraps it as a callable IFoo. Confirmed for IPackageManager, IStatusBarService, INotificationManager, IDevicePolicyManager, IPowerManager, IPermissionManager, ILockSettings, INetworkPolicyManager.\nSignature-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 \u0026lt;package\u0026gt; \u0026lt;permission\u0026gt;, which an abuser with USB-debug access during the install moment can run, or via root + pm grant.\nWhen WRITE_SECURE_SETTINGS is granted, Cerberus\u0026rsquo;s TrackServiceFused programmatically writes Settings.Secure.location_mode via Settings.Secure.putInt(...), silently overriding the user\u0026rsquo;s location-services setting (which Android\u0026rsquo;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;-\u0026gt;i() reaches TelephonyManager.getDeviceId() even though the public READ_PHONE_STATE permission would no longer suffice.\nThe 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.\nStealth 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.\nFake shutdown ShutdownDialogActivity renders a fake Android power-off dialog using the system\u0026rsquo;s own theme (Theme.DeviceDefault.System) and power icon (ic_lock_power_off). On \u0026ldquo;power off\u0026rdquo; tap, sets screen brightness to 1/255 and button brightness to 0. The phone appears off. Surveillance continues.\nLSDroid 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\u0026rsquo;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.\nDisguised build The \u0026ldquo;disguised\u0026rdquo; APK is identical to the standard build with two manifest changes: android:label=\u0026quot;System Framework\u0026quot; and android:icon=\u0026quot;@android:drawable/sym_def_app_icon\u0026quot;. In the victim\u0026rsquo;s app list, it looks like a system service.\nBoth 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\u0026rsquo;s package name. The rename has no apparent technical purpose; on a victim\u0026rsquo;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.\nDial-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(\u0026quot;conf\u0026quot;).getString(\u0026quot;dialcode\u0026quot;, \u0026quot;23723787\u0026quot;) 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.\nThe default value 23723787 is the T9 keypad mapping of \u0026ldquo;CERBERUS.\u0026rdquo; 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\u0026rsquo;s own dialer is how the abuser reaches the administrative screen.\nFragmented 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 \u0026quot;admin@cerb\u0026quot; + \u0026quot;erusapp.com\u0026quot;, constructs support@cerberusapp.com from \u0026quot;support@cer\u0026quot; + \u0026quot;berusapp.com\u0026quot;, and constructs the sideload self-update URL https://cerberusapp.com/download/version from \u0026quot;https://cerb\u0026quot; + \u0026quot;erusapp.com\u0026quot; + \u0026quot;/download/version\u0026quot; at U2.U1.java:2982. A suite-wide sweep for \u0026quot;X\u0026quot; + \u0026quot;Y\u0026quot; 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.\nA grep for cerberusapp.com in the decompiled output returns a complete URL inventory on the first try.\nMixed obfuscation across the suite Three of LSDroid\u0026rsquo;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.\nLSP is the most heavily affected: droidsaw flags 59% of LSP\u0026rsquo;s 2869 classes as adversarially obfuscated, with a naming distribution that doesn\u0026rsquo;t match R8\u0026rsquo;s expected output at all. LSP\u0026rsquo;s decompiled classes carry // Source: SourceFile instead of the // Source: r8-map-id-\u0026lt;hash\u0026gt; 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\u0026rsquo;s own com.lsdroid.* namespaces stay canonical because the Android manifest binds framework callbacks by string and renaming them would break the apps.\nWhat 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\u0026rsquo;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.\nAnti-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 \u0026lt;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\u0026rsquo;t gate operation). String literals are not encrypted. The obfuscation is plain R8 — class and method names renamed, strings preserved.\nThe 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.\nNotification 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 \u0026lt;package\u0026gt; RUN_IN_BACKGROUND allow to prevent Android from killing it.\nAnti-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.\nOEM background-restriction awareness Inside ShutdownAttemptReceiver.onReceive, as a literal string shipped in the compiled DEX: \u0026ldquo;Not able to execute action in background. To fix, please check www.dontkillmyapp.com\u0026rdquo;. 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\u0026rsquo;s dashboard which device-side workaround to apply.\nOEM-specific code paths Cerberus contains four distinct OEM-specific code paths, each targeting a different manufacturer behavior:\nXiaomi MIUI autostart-permission probe (main, LSP, Kids — via HiddenApiBypass). Each app reflects on android.miui.AppOpsUtils.getApplicationAutoStart to query MIUI\u0026rsquo;s autostart-permission state, gated on Build.MANUFACTURER == \u0026quot;xiaomi\u0026quot;. Detailed in What the bypass is used for. OPPO ColorOS power-menu fingerprints (LSP AccessibilityService). Hardcoded com.oplus.systemui.shutdown, COUIVerticalSeekBar, and COUISeekBar literals. 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\u0026rsquo;s BBK Electronics, CyanogenMod, LineageOS, AOSP, and Pixel. Samsung\u0026rsquo;s launcher (com.sec.android.app.launcher) is not in the list, though Samsung\u0026rsquo;s setup wizard is — the launcher omission appears unintentional. Detailed in Kids: \u0026ldquo;Cerberus Child Safety\u0026rdquo;. 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.\nLicense-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\u0026rsquo;s subscription has lapsed — Cerberus exits covert mode. The boot-receiver code path that runs on expired-license boot:\nCancels Cerberus\u0026rsquo;s own background notifications (IDs 1 and 54321). Builds a \u0026ldquo;Forced unlock\u0026rdquo; notification with two action buttons — one launches StartActivity2 (the admin re-entry UI), the other launches Buy (the subscription-renewal activity). Sets hidden=false in SharedPreferences and re-enables the StartActivity launcher component via PackageManager.setComponentEnabledSetting(..., COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP) — undoing the launcher-icon hiding from HIDE. 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\u0026rsquo;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.\nWhat the victim sees: two notification channels SurebrecApplication.onCreate declares two notification channels with opposite configurations:\nChannel 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:\nBody text: \u0026quot;This device is protected by Cerberus\u0026quot; (resource string 2131886633) No content title (no setContentTitle call) Channel: Cerberus — IMPORTANCE_LOW, hidden on lock screen Tap intent: StartActivity2 with extra notification=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.\nThe notification text reads \u0026quot;This device is protected by Cerberus\u0026quot;. 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.\nThe FORCED_UNLOCK lure. LockScreenReceiver.onReceive posts notification ID 54321 on every SCREEN_OFF broadcast when w7.k4.e() (the rules-helper \u0026ldquo;is enabled\u0026rdquo; flag) returns true. The notification:\nChannel: 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 \u0026quot;Event FORCED_UNLOCK received, executing actions in 15 seconds.\u0026quot; and schedules an action bundle 15 seconds out via setAndAllowWhileIdle, with action flags per the rule (location, picture, emergency, tasker).\nThe 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.\nThe 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 \u0026ldquo;thief sees notification, taps to interact, is photographed\u0026rdquo; 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.\nCrypto weaknesses Password hashing U1.a(): MessageDigest.getInstance(\u0026quot;SHA-1\u0026quot;) with ISO-8859-1 encoding. No salt. The operator\u0026rsquo;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.\nSMS 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.\nConfig 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.\nThe 16-byte \u0026ldquo;key\u0026rdquo; 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(\u0026quot;%-16s\u0026quot;, id).replace(' ', '0') followed by new StringBuilder(s).reverse().toString().getBytes().\nThe effective keyspace depends on which device-identifier source is reached. The two paths in U2.U1.i(Context, TelephonyManager) produce different shapes:\nIMEI path (when READ_PHONE_STATE is 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_ID path (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 \u0026ldquo;AES-128\u0026rdquo; 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.\nThe 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\u0026rsquo;t the threat model that matters for a stalkerware deployment. Anyone with realistic motivation to decrypt the config — a forensic investigator examining a victim\u0026rsquo;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\u0026rsquo;s own exported IdProvider ContentProvider. With the device ID in hand, decrypting Cerberus\u0026rsquo;s encrypted config is O(1): read the ID, derive the key, decrypt.\nThere 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.\nLock 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 \u0026ldquo;device ID\u0026rdquo; used by the lock screen is the raw IMEI or the raw ANDROID_ID.\nWhen 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\u0026rsquo;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\u0026rsquo;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\u0026rsquo;s \u0026ldquo;anti-theft lock screen\u0026rdquo; is unlocked by knowing what kind of phone it is.\nWhen 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\u0026rsquo;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.\nThe same LockActivity displays the full device ID as a static line labeled \u0026ldquo;Device ID:\u0026rdquo; 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 \u0026ldquo;10,000 possible 4-digit codes\u0026rdquo; 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\u0026rsquo;s own unauthenticated ContentProvider.\nOperator-gated uninstall escape hatch LockActivity contains an android.widget.Button referred to in the decompiled layout binder as L, declared with android:visibility=\u0026quot;gone\u0026quot;. The button\u0026rsquo;s OnClickListener invokes the same Runtime.exec(\u0026quot;su\u0026quot;) → 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\u0026rsquo;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 \u0026ldquo;Allow uninstall\u0026rdquo; option from the lock screen.\nUpstream 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.\nLock Screen Protector module com.lsdroid.lsp (v3.6) is a separate Play Store app labeled \u0026ldquo;Lock Screen Protector.\u0026rdquo; Its role:\nBlock 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-lock policy, prevents casual uninstallation directBootAware: All components start before first unlock IPC between LSP and main app:\nLSP → main: broadcast com.lsdroid.shutdownattempt with screenshot bytes Main → LSP: broadcast com.surebrec.SHUT_STARTED Main queries LSP: content://com.lsdroid.lsp (exported, no permission) to check powerblock status Detecting the power menu across OEMs LSP\u0026rsquo;s com.lsdroid.lsp.AccessibilityService.onAccessibilityEvent inspects every accessibility event and checks the current window\u0026rsquo;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.\nScreenshot 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\u0026rsquo;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 \u0026quot;s\u0026quot; extra.\nJPEG 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.\nPower-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\u0026rsquo;s nearly always successful; on an older or loaded phone, occasional shutdowns do go through.\nThe IPC is bidirectional. LSP also registers a runtime BroadcastReceiver (on API \u0026lt; 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\u0026rsquo;s AccessibilityService alone — the JPEG handoff is one of eight.\nShutdownAttemptReceiver has two modes The main-app side of the IPC is com.surebrec.ShutdownAttemptReceiver.onReceive. The receiver checks whether the Intent has an \u0026quot;s\u0026quot; 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.\nIf the Intent has no \u0026quot;s\u0026quot; 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\u0026rsquo;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.\nBecause the receiver is exported=\u0026quot;true\u0026quot; without a permission guard, and because the action string and the receiver component are both publicly known, any third-party app on a victim\u0026rsquo;s phone can fire the broadcast and trigger all eight reactions, subject to the operator\u0026rsquo;s flag configuration.\nLateral 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.\nCerberus 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\u0026rsquo;s internal settings at content://net.dinglisch.android.tasker/prefs to verify enabled=true and ext_access=true before dispatching — it queries Tasker\u0026rsquo;s own configuration through Tasker\u0026rsquo;s exported provider to check that the broadcast will be received before sending it.\nCross-referencing the three \u0026ldquo;Task not executed: …\u0026rdquo; 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:\n# 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 \u0026ldquo;when the victim fails the lock-screen password, run task X.\u0026rdquo; 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: \u0026ldquo;when the victim picks up the phone, run task X.\u0026rdquo; The dispatch code is in the binary; the operator configures the task name and the trigger via the dashboard.\nA 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:\nl(Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean) — 4 boolean state flags + name + 2 booleans m(Boolean, Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean) — 5 boolean state flags + name + 2 booleans n(Boolean, Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean, Boolean) — 5 booleans + name + 3 booleans o(Boolean, String, Boolean, String) — simpler 4-parameter event p(String, Double, Double, Float, Boolean, Boolean, Boolean, Boolean, Boolean, Boolean, String) — (name, latitude, longitude, accuracy, ...flags..., extra) — the location event q(Boolean, Boolean, Boolean, Boolean, String, Boolean, Boolean) — 7-parameter event r(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\u0026rsquo;s internal DEVICE_STARTED), each routable to a Tasker macro through one of the dispatch methods; the class alone contains 42 TaskerIntent broadcasts.\nThe 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(\u0026quot;%var_name\u0026quot;, ...) 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\u0026rsquo;s Tasker macros must read device state through Tasker\u0026rsquo;s built-in state-query actions (e.g., %BATT, %LOC, %CONN); Cerberus\u0026rsquo;s contribution is \u0026ldquo;trigger this macro now,\u0026rdquo; not \u0026ldquo;trigger this macro with these values.\u0026rdquo; The seven typed-parameter shapes describe the events Cerberus can hook, not data it ships into Tasker.\nTasker is not sandboxed. Its permissions on a power user\u0026rsquo;s phone are typically broader than Cerberus\u0026rsquo;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\u0026rsquo;s device is reachable through Cerberus via seven different event triggers. If the victim has Tasker installed, Cerberus\u0026rsquo;s reachable capability set extends to whatever the user\u0026rsquo;s Tasker macros are configured to do — without an APK update and without new permissions.\nThe C2 surface is 44 FCM commands plus a root shell plus everything the victim\u0026rsquo;s own Tasker install is configured to do.\nWhat a victim can do For someone who suspects Cerberus is on their device:\nIf 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\u0026rsquo;s device; the compromised phone is itself a risk surface. Cornell Tech\u0026rsquo;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.\nDetection 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 \u0026ldquo;System Framework\u0026rdquo; with a generic Android icon. The LSP module appears as \u0026ldquo;Lock Screen Protector.\u0026rdquo; The Kids app appears as \u0026ldquo;Kids.\u0026rdquo;\nCheck accessibility services: Settings → Accessibility. Look for \u0026ldquo;System Framework,\u0026rdquo; \u0026ldquo;Lock Screen Protector,\u0026rdquo; or any service from the packages above with BIND_ACCESSIBILITY_SERVICE permission.\nCheck device admin: Settings → Security → Device admin apps. Cerberus registers as device admin to prevent uninstallation and enable lockNow().\nQuery the exported content providers from any app or ADB:\nadb 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.\nRemoval Revoke device admin: Settings → Security → Device admin apps → disable for Cerberus Disable accessibility services: Settings → Accessibility → disable \u0026ldquo;System Framework\u0026rdquo; / \u0026ldquo;Lock Screen Protector\u0026rdquo; / 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.ssurebrec does not remove com.lsdroid.cerberus.kids or com.lsdroid.cerberus.enterprise. The Kids app\u0026rsquo;s notification listener and accessibility service persist independently; Enterprise is a parallel product that doesn\u0026rsquo;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.\nDetection 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 / \u0026#34;Child Safety\u0026#34; com.lsdroid.cerberus.persona2 # Persona / \u0026#34;Personal security — Women safe\u0026#34; 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 \u0026lt;product\u0026gt; \u0026lt;version\u0026gt; - 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 \u0026#34;hidden\u0026#34; = true → app is concealed from launcher Key \u0026#34;smskeyword\u0026#34; → SMS C2 trigger word Key \u0026#34;emergencymode\u0026#34; = true → continuous surveillance active Key \u0026#34;preventusbdebug\u0026#34; = true → anti-forensics active Key \u0026#34;protectadmin\u0026#34; = 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:\nAdd-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\u0026rsquo;s device ID at launch via the unauthenticated IdProvider ContentProvider, and each refuses to function if main isn\u0026rsquo;t there. Their data joins main\u0026rsquo;s data stream on LSDroid\u0026rsquo;s backend under one device record. They are capability extensions on top of the main stalkerware, not standalone products.\nParallel product (Enterprise). Architecturally distinct from the add-ons: does not read main\u0026rsquo;s ContentProvider, does not share main\u0026rsquo;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\u0026rsquo;s infrastructure that shares a developer account.\nThe 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:\nCapability 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\u0026rsquo;s distinctive per app — the capabilities no other app in the suite covers:\nLSP: power-button intercept, status-bar overlay, screen-scrape via accessibility. Kids: continuous app-usage telemetry with operator alerts on installs, plus a NotificationListener configured for arbitrary-app suppression. The combined main + Kids + LSP deployment on a child\u0026rsquo;s phone runs three accessibility services, three FCM channels, three Firebase projects, and two NotificationListeners 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.\nThe bridge: IdProvider com.surebrec.IdProvider is a ContentProvider exported with android:exported=\u0026quot;true\u0026quot; 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.\nquery() is seven lines:\npublic Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String[] cols = { \u0026#34;id\u0026#34; }; 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.\nAny 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.\nKids: \u0026ldquo;Cerberus Child Safety\u0026rdquo; (com.lsdroid.cerberus.kids v1.2.9) Installed on: the child\u0026rsquo;s device (CHILD role) and the parent\u0026rsquo;s device (PARENT role). Same APK, role chosen at setup.\nThe Kids app declares isMonitoringTool=child_monitoring in its manifest — Google\u0026rsquo;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: \u0026ldquo;Cerberus Anti-theft must be installed and configured on the phone, for this app to work\u0026rdquo; and redirects to the Play Store.\nThe 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\u0026rsquo;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\u0026rsquo;t installed. A single-subscription model is the only architecture those dependencies fit.\nThe child\u0026rsquo;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.\nBecause Kids requires the main com.ssurebrec to be installed on the same device, every working Kids deployment is a co-deployment: the child\u0026rsquo;s phone runs Kids\u0026rsquo; 17-token FCM channel on cerberus-kids AND the main app\u0026rsquo;s 44-command FCM surveillance surface on api-project-999803017449. Two Firebase projects, two FCM channels, one device. Every Kids install on a child\u0026rsquo;s phone is also a main-app install on the same device.\nWhat the CHILD role collects:\nComplete 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 NotificationListener can 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.\nWhat the accessibility service declares vs. what it does:\nThe accessibility service configuration requests canRetrieveWindowContent=\u0026quot;true\u0026quot; — the system grants permission to read every text element on screen. The user-facing description (displayed when enabling the service) states: \u0026ldquo;No data is collected or sent.\u0026rdquo;\nThe 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.\nApp-block enforcement and OEM coverage. Kids\u0026rsquo; MyAccessibilityService enforces parental \u0026ldquo;block this app\u0026rdquo; 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\u0026rsquo;s BBK Electronics), com.cyanogenmod.trebuchet (deprecated), org.lineageos.trebuchet, plus AOSP and Pixel — covers the OEMs Kids recognizes.\nThe Samsung launcher (com.sec.android.app.launcher) is not in the list. Samsung\u0026rsquo;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 \u0026ldquo;child used app Launcher\u0026rdquo; rather than filtered out, and MyAccessibilityService posts an enforcement-decision log line and notification each time the child returns to the launcher under a \u0026ldquo;block all\u0026rdquo; policy. The phone is not bricked (the UsageLimitActivity overlay\u0026rsquo;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.\nWhat protections exist for the child\u0026rsquo;s data:\nNone 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 \u0026ldquo;I\u0026rsquo;m a parent or legal guardian\u0026rdquo; with no identity verification. No privacy policy link in the app. Ad tracking enabled via com.google.android.gms.permission.AD_ID with allowAllToAccess=\u0026quot;true\u0026quot; in the ad services config and no tagForChildDirectedTreatment flag — Google\u0026rsquo;s ad network profiles the child without child-directed treatment protections.\nThe 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.\nWho 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\u0026rsquo;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\u0026rsquo;s data — LSDroid SRL — is not the entity whose name appears in the signing certificate of the software actually running on the child\u0026rsquo;s phone.\nPersona2: \u0026ldquo;Personal security — Women safe\u0026rdquo; (com.lsdroid.cerberus.persona2 v1.8) Installed on: the victim\u0026rsquo;s device. The victim installs it voluntarily — it is marketed as a personal-safety app for women.\nLSDroid\u0026rsquo;s Google Play listing title for com.lsdroid.cerberus.persona2 is literally \u0026ldquo;Personal security — Women safe\u0026rdquo;. The internal resource strings use \u0026ldquo;Cerberus Personal Safety\u0026rdquo; and \u0026ldquo;Cerberus Persona\u0026rdquo;; the customer-facing brand on the store page targets women specifically. The same developer account sells this \u0026ldquo;women safe\u0026rdquo; product and the covert-stalkerware main app (com.ssurebrec) — same C2 domain, shared device ID via the main app\u0026rsquo;s ContentProvider, and Persona2 refuses to register with the C2 if the main app isn\u0026rsquo;t installed.\nPersona2 does not declare isMonitoringTool in its manifest. It frames itself as the user\u0026rsquo;s own safety tool: \u0026ldquo;Persona can help in dangerous situations.\u0026rdquo; It sends emergency SMS with a cerberusapp.com/persona-{token} tracking URL to the victim\u0026rsquo;s contacts.\nOn 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.\nThe hard dependency appears to be a retrofit. Decompiling Persona2\u0026rsquo;s identity-resolution path (I5/O0;-\u0026gt;p(Context)) shows a versionCode \u0026lt;= 10 branch that falls back to Settings.Secure.ANDROID_ID when the main app isn\u0026rsquo;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.\nThe app actively directs users to install the main Cerberus app: \u0026quot;This app requires Cerberus Anti-theft.\u0026quot; with a link to market://details?id=com.ssurebrec.\nThe WidgetActivity declares setShowWhenLocked(true) and setTurnScreenOn(true) — the widget can activate from the lock screen without unlocking.\nGPS telemetry (latitude, longitude, accuracy, speed, bearing, battery level) is sent to cerberusapp.com/comm/persona_sendlocation.php. No certificate pinning.\nEnterprise: \u0026ldquo;Cerberus Enterprise\u0026rdquo; (com.lsdroid.cerberus.enterprise v1.7) Installed on: the employee\u0026rsquo;s device. Despite the \u0026ldquo;Enterprise MDM\u0026rdquo; product positioning, the APK has zero mobile-device-management capability. Architecturally, Enterprise is not a companion to the main Cerberus stalkerware — it doesn\u0026rsquo;t read main\u0026rsquo;s ContentProvider, doesn\u0026rsquo;t share main\u0026rsquo;s device ID, doesn\u0026rsquo;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\u0026rsquo;s infrastructure, not an add-on.\ncom.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\u0026rsquo; installer-trust helper (n8.j(Context, String) calling isDeviceOwnerApp / isProfileOwnerApp), and Google\u0026rsquo;s Android Management API client (com.google.android.gms.internal.amapi.*). LSDroid\u0026rsquo;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.\nThe 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\u0026rsquo;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.\nThe transparency of the foreground-service notification is real — \u0026ldquo;Your enterprise is tracking this device\u0026rsquo;s location in real-time.\u0026rdquo; What is not transparent is the product category. An IT admin who purchases \u0026ldquo;Cerberus Enterprise\u0026rdquo; 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.\nThe 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\u0026rsquo;s Statuto dei Lavoratori Article 4 — LSDroid\u0026rsquo;s home jurisdiction — remote monitoring of workers requires either a union agreement or prior authorization from the Ispettorato Nazionale del Lavoro. Germany\u0026rsquo;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.\nOne subscription, one customer The Cerberus website routes all product purchases through a single cerberusapp.com/buy.php?app=\u0026lt;name\u0026gt; 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.\nThere 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\u0026rsquo;s sendlocation.php and from the Kids app\u0026rsquo;s send_upstream_message.php with the same device ID. The child\u0026rsquo;s data and the stalkerware victim\u0026rsquo;s data sit in the same infrastructure.\nWhat the \u0026ldquo;anti-theft\u0026rdquo; defense doesn\u0026rsquo;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\u0026rsquo;s equivalent predates Google\u0026rsquo;s by several years. The legitimate anti-theft customer is already served by a platform feature they didn\u0026rsquo;t have to install.\nWhat Cerberus adds beyond Find Hub\u0026rsquo;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.\nThe 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.\nThe Kids app\u0026rsquo;s accessibility service description — \u0026ldquo;No data is collected or sent\u0026rdquo; — 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\u0026rsquo;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.\nLSDroid is an Italian company. GDPR Article 8 requires consent from a parent or guardian for processing a child\u0026rsquo;s personal data, with \u0026ldquo;reasonable efforts to verify.\u0026rdquo; A checkbox is not verification. The Italian Garante has fined companies for less. Google\u0026rsquo;s Play Families Policy requires apps targeting children to comply with applicable children\u0026rsquo;s privacy laws, not serve uncertified ads, and provide a privacy policy. The Kids app provides no privacy policy link.\nThe public record Cerberus\u0026rsquo;s path through Google\u0026rsquo;s infrastructure is documented across nine years of public artifacts.\nNovember 2017. Google emailed LSDroid citing a Malicious Behavior Policy violation. LSDroid posted the email publicly.\nMay 2018. Chatterjee et al. published \u0026ldquo;The Spyware Used in Intimate Partner Violence\u0026rdquo; at IEEE S\u0026amp;P, naming Cerberus as IPV stalkerware and reporting the apps to Google.\nLate 2018 or early 2019. Google removed com.lsdroid.cerberus from Play, citing — by LSDroid\u0026rsquo;s own archived statement — the off-store-distribution policy. LSDroid continued distributing the same app from cerberusapp.com.\nSeptember 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 \u0026ldquo;code that collects and/or transmits personal or sensitive user data from a device without adequate notice or consent and doesn\u0026rsquo;t display a persistent notification that this is happening,\u0026rdquo; names a single carve-out for parental and enterprise monitoring apps, and explicitly excludes apps used to track \u0026ldquo;anyone else (a spouse, for example) even with their knowledge and permission.\u0026rdquo;\nOctober 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.\n2024–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\u0026rsquo;s own sellers.json as LSDroid SRL). Google Firebase hosts the FCM command channels and the operator-state Realtime Database described in this writeup.\nPolicy 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\u0026rsquo;s cooperation:\nNon-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\u0026rsquo;s name is \u0026ldquo;Hidden API Bypass.\u0026rdquo; Google Play\u0026rsquo;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.\nSilent 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.\nUninstall blocking via reflected IPackageManager. Same method: setBlockUninstallForUser string literal plus the reflected call via IPackageManager$Stub.\nLauncher-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.\nPower-off interception and fake shutdown. com.lsdroid.lsp.AccessibilityService.onAccessibilityEvent plus the I2.c.onSuccess callback plus com.surebrec.ShutdownAttemptReceiver.onReceive.\nAd 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 == \u0026quot;pub-9848961826628138\u0026quot;)' — returns LSDroid SRL.\nThe Stalkerware Policy that applies Google\u0026rsquo;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:\nCode that collects and/or transmits personal or sensitive user data from a device without adequate notice or consent and doesn\u0026rsquo;t display a persistent notification that this is happening.\nIt then names the only carve-out:\nApps 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.\nFor monitoring apps that qualify for the parental/enterprise carve-out, the policy lists four compliance requirements:\nApps 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.\nEach 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:\nPolicy clause Cerberus implementation \u0026ldquo;Persistent notification at all times when the app is running\u0026rdquo; The foreground-service notification body reads \u0026quot;This device is protected by Cerberus\u0026quot; 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. \u0026ldquo;Unique icon that clearly identifies the app\u0026rdquo; The \u0026ldquo;disguised\u0026rdquo; build\u0026rsquo;s manifest declares android:label=\u0026quot;System Framework\u0026quot; and android:icon=\u0026quot;@android:drawable/sym_def_app_icon\u0026quot; — 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. \u0026ldquo;Apps must not present themselves as a spying or secret surveillance solution\u0026rdquo; LSDroid\u0026rsquo;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. \u0026ldquo;Apps must not hide or cloak tracking behavior or attempt to mislead users about such functionality\u0026rdquo; 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. \u0026ldquo;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\u0026rdquo; Cerberus constructs the URL https://cerberusapp.com/download/version from runtime string concatenation (\u0026quot;https://cerb\u0026quot; + \u0026quot;erusapp.com\u0026quot; + \u0026quot;/download/version\u0026quot;) 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. \u0026ldquo;Apps exclusively designed and marketed for parents to track their children\u0026rdquo; (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\u0026rsquo;t installed. Both feed LSDroid\u0026rsquo;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 \u0026ldquo;exclusive\u0026rdquo; qualifier the policy requires. Source: IdProvider; Persona2 I5/O0;-\u0026gt;p(Context); Kids identity-resolution path. \u0026ldquo;These apps cannot be used to track anyone else (a spouse, for example) even with their knowledge and permission\u0026rdquo; 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 \u0026ldquo;Personal security — Women safe\u0026rdquo; — explicitly outside the parental/enterprise carve-out. Source: Play Store listing for com.lsdroid.cerberus.persona2; LSDroid\u0026rsquo;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.\nAvailable 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:\nGoogle Play can remove com.ssurebrec and 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\u0026rsquo;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\u0026rsquo;s deployment.\nCerberus in commercial-stalkerware history Cerberus sits within a documented industry timeline:\nVendor 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\u0026rsquo; 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.\nDisclosure The Cerberus apps documented here were reported to Google ahead of publication through Google Play\u0026rsquo;s reporting channel and Firebase abuse reporting.\nGoogle 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\u0026rsquo;s 2022 reporting documenting Google\u0026rsquo;s failure to enforce its existing ban on stalkerware ads. The publisher attribution (pub-9848961826628138, named as LSDroid SRL in Google\u0026rsquo;s own sellers.json) was therefore not separately reported and remains active at publication.\nLSDroid 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\u0026rsquo;s research conventions reflect the same reasoning.\nThe 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.\nIf 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\u0026rsquo;s device; the compromised phone is itself a risk surface. Cornell Tech\u0026rsquo;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.\n","date":"2026-04-30","description":"Decompiling Cerberus Stalkerware (LSDroid SRL): a static reverse engineering of 9 APKs across 8 packages, two distribution channels, three companion apps on Google Play. Documents the 44 FCM commands, the operator-state Realtime Database, the HiddenApiBypass dependency that gated the 2023 Play return, and the v3.8.0 reflected-binder-stub root toolkit. Includes attribution data, a chronology, and per-class indicators of compromise.","lastmod":"2026-04-30","readingTime":84,"section":"datagrams","title":"Cerberus Anti-theft is stalkerware: a reverse engineering","url":"https://hexproof.dev/datagrams/cerberus-stalkerware-re/","wordCount":17750}