How Moatgator works
Moatgator is a mobile RASP (runtime application self-protection) built for apps that move money. It watches the device from inside your app, reports what it sees to a backend you control, and lets you decide — per detection — whether to only watch, warn the user, block the sensitive action, or end the session.
What runs on the device
The runtime SDK (Kotlin + C/JNI on Android, Swift + C on iOS) adds several independent layers. None of them changes your app's behaviour on its own — detection is separate from the response you choose.
- Runtime detections. Dozens of signals grouped into detections, each with a confidence score. Weak signals on their own stay suspicious; a strong one, or several weak ones together, become compromised.
- Native checks. The highest-value checks (root, hidden-root mounts, Frida threads) run in C through direct syscalls, so swapping a Java or libc function at runtime does not hide the result.
- Anti-analysis / obfuscation. A Gradle plugin (
com.moatgator.protect) hardens the release build sostrings,grepand decompilers do not hand your detection logic to an attacker (see below). - Anti-dump / anti-debug.
prctl(PR_SET_DUMPABLE, 0)and Yama stop another process from reading the app's memory or attaching a debugger — the same hardening banking apps use to protect keys and tokens in the heap. - Certificate pinning (SPKI SHA-256) on the telemetry channel, Attest (Play Integrity + App Attest folded into one signed verdict), and Pix Shield for payment screens.
What Moatgator detects
A detection is a signal you choose a response for. The five core detections are in every plan; the payment-screen, anti-fraud and instrumentation detections start at the Scale plan.
| Category | Detections | What it catches |
|---|---|---|
| Device integrity | root / jailbreak, emulator, tamper | Rooted/jailbroken devices (incl. Magisk + hidden-root mounts via the native layer), emulators and fraud farms, repackaged or re-signed apps. |
| Dynamic analysis | debugger, hooking, instrumentation | Attached debuggers, hooking frameworks (Xposed/LSPosed/Substrate), and dynamic instrumentation (Frida) — detected by maps, open ports and thread names, so a hidden .so still shows. |
| Payment screen (Pix Shield) | overlay, accessibility_abuse, screen_capture, remote_access | The Brazilian banking-trojan playbook: an overlay stealing taps, an abused accessibility service, screen recording/mirroring, and remote-control tools during a payment. |
| Anti-fraud | mock_location, memory_editor, app_clone, camera_injection, vpn_proxy | Fake GPS, memory editors (GameGuardian), cloned/dual-space apps, virtual/injected cameras that beat liveness and KYC, and VPN/proxy masking the connection origin. |
That is 15 detections today, each documented with its typical false positives and how to roll it out in the detections reference. A detection is a signal, not proof of fraud — use it alongside your own fraud engine, never instead of it.
How the app is hardened against reverse engineering
The com.moatgator.protect Gradle plugin runs after javac/kotlinc and before R8, and layers several transforms so an attacker cannot simply read the detection logic out of the .dex or the .so:
- String encryption — URLs, feature flags and detection keywords are decrypted at runtime, not shipped in clear.
- Numeric-constant obfuscation — magic numbers become runtime calls that survive R8's constant folding.
- Control-flow obfuscation — opaque predicates the optimizer cannot fold away.
- API hiding — calls to tell-tale framework APIs (PackageManager, Debug, AppOps) are routed through reflection wrappers, and their method names are themselves encrypted.
- Aggressive renaming — a confusing R8 dictionary plus package flattening.
- Native string obfuscation — the C layer's
/procpaths, su paths and signal names are XOR-obfuscated sostrings libmoatgator.soreveals nothing. - Encrypted secrets vault — values from
moatgator-secrets.propertiesare encrypted into an asset and read at runtime withMoatgator.secret(), keeping them out of the APK's clear text.
None of this is a cryptographic secret — the keys travel in the binary, as with all obfuscation. The goal is to raise the cost for an attacker, not to promise perfect protection.
What leaves the device
When a detection fires, the SDK sends a compact event.v1 message — the detection type, a confidence score, short signal names (no personal data), the app version and an anonymous, rotating device hash — over pinned TLS. With Attest on, the signals are folded into a single signed verdict your backend can verify offline.
The policy travels the other way: you edit it in the console or as a moatgator.yml file, it is signed, and the SDK picks up the new responses the next time it syncs — no app release needed. A kill switch can disable a misbehaving policy immediately.
You decide the response
Every detection maps to one response: monitor (record only), warn (tell the user), block (stop the sensitive action), or terminate (end the session). Everything you do not list stays on monitor, so adopting a newer version of Moatgator never starts blocking users on its own. The recommended rollout is monitor → warn → block, one detection at a time.
From your CI
The CLI, GitHub Action and GitHub App register and statically scan every release build — in GitHub Actions via OIDC, so there is no long-lived key in your secrets. The static Scan grades the package (manifest, permissions, network config, secrets, protections) independently of the runtime SDK.
The protection engine is in preview and some pieces are not deployed publicly yet. Before you plan around Moatgator, read limits and guarantees.