daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

play-integrity-attestation-bypass.md (14733B)


      1 ---
      2 title: "Play Integrity Attestation Bypass (SafetyNet Replacement)"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/play-integrity-attestation-bypass.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/play-integrity-attestation-bypass.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # Play Integrity Attestation Bypass (SafetyNet Replacement)
     14 
     15 ## What Play Integrity Does
     16 
     17 **Play Integrity** is Google’s SafetyNet successor for **app attestation**. The app requests an integrity token and forwards the encrypted token to its backend. The backend sends it to `playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken` (or uses the supported Google API client library), then validates the returned verdict and binds it to the protected request. When Google manages response encryption, the application backend should not attempt to decrypt the token locally.<sup>[[10]](#references)[[12]](#references)</sup>
     18 
     19 - **`appIntegrity`**: APK build/signature match (no repack/tamper).
     20 - **`deviceIntegrity`**: genuine & certified device, locked bootloader, no root/system tamper.
     21 - **`accountDetails`**: installation via Google Play.
     22 - **`environmentDetails`** *(optional)*: recent integrations can also request signals about risky apps in the environment (overlay, capture, control) and Play Protect state.<sup>[[10]](#references)</sup>
     23 - **Other optional signals**: some backends also enable **recent device activity** or **device recall** to catch hyperactive devices and previously flagged hardware even when a verdict otherwise looks valid.<sup>[[10]](#references)</sup>
     24 
     25 Key verdict flags commonly enforced:<sup>[[1]](#references)</sup>
     26 - `MEETS_BASIC_INTEGRITY`: token generated by genuine Play Services (not emulator/tampered transport).
     27 - `MEETS_DEVICE_INTEGRITY`: genuine/certified device, bootloader locked, no root/system tamper.
     28 - `MEETS_STRONG_INTEGRITY`: on **Android 13+** it requires `DEVICE` plus **recent security patches on all partitions (OS + vendor)**. On **Android 12 and lower**, it mainly means hardware-backed boot integrity, so it is a weaker signal than many testers assume.<sup>[[10]](#references)</sup>
     29 
     30 ## Bypass Model
     31 
     32 Instead of forging Google’s JWT, **spoof the signals Google evaluates** so they correspond to a different, legitimate device profile. The attack chain:<sup>[[1]](#references)</sup>
     33 1) Hide root so local checks and Play Services probes don’t see Magisk/su.
     34 2) Replace the **key attestation certificate chain** (`keybox.xml`) with one from a genuine device so Play Integrity sees a certified/locked device.
     35 3) Spoof the **security patch level** to satisfy `MEETS_STRONG_INTEGRITY`.
     36 
     37 Google mitigates by **revoking abused keyboxes**; rotation is required when a keybox is blocked.<sup>[[1]](#references)</sup>
     38 
     39 ## Prerequisites & Tooling
     40 
     41 - **Root hiding:** [ReZygisk](https://github.com/PerformanC/ReZygisk) (or ZygiskNext). Disable Zygisk, enable Magisk Hide, install module, reboot. If the target also detects Zygote injection or Frida side effects, cross-check [Android anti-instrumentation and SSL pinning bypass](/hacktricks/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass).<sup>[[2]](#references)</sup>
     42 - **Key attestation spoofing:** [TrickyStore](https://github.com/5ec1cff/TrickyStore) + [Tricky Addon](https://github.com/KOWX712/Tricky-Addon-Update-Target-List) (Magisk modules).<sup>[[3]](#references)[[4]](#references)</sup>
     43 - **Property / fingerprint spoofing:** [PlayIntegrityFork](https://github.com/osm0sis/PlayIntegrityFork). It mainly spoofs props only for the Google Play Services **DroidGuard** path. This mainly helps with **Android `<13`** or apps that still gate on legacy/softer checks; on **Android 13+** a passing `DEVICE` verdict still depends on locked-bootloader, hardware-backed signals.<sup>[[5]](#references)</sup>
     44 - **UI helper:** [KSU Web UI](https://github.com/adivenxnataly/KsuWebUI) to drive TrickyStore.<sup>[[6]](#references)</sup>
     45 - **Validation:** [Play Integrity API Checker](https://play.google.com/store/apps/details?id=gr.nikolasspyr.integritycheck) and [Key Attestation](https://github.com/vvb2060/KeyAttestation) APKs.<sup>[[7]](#references)[[8]](#references)</sup>
     46 - Optional background on attestation key material: <https://tryigit.dev/android-keybox-attestation-analysis><sup>[[9]](#references)</sup>
     47 
     48 ## Achieve `MEETS_BASIC_INTEGRITY` + `MEETS_DEVICE_INTEGRITY`
     49 
     50 1. **Install modules & reboot:** Flash *TrickyStore* and *Tricky Addon* in Magisk, reboot.<sup>[[1]](#references)</sup>
     51 2. **Configure TrickyStore (via KSU Web UI):** Select `TrickyStore` → `Select All` → `Deselect Unnecessary` → **Save**.
     52 3. **Inject a valid keybox:** In `Keybox`, choose **Valid** to download/apply a new `keybox.xml` (vendor attestation credentials). This file underpins hardware key attestation and is now spoofed from a certified/locked device.
     53 4. **Verify:** Run *Play Integrity API Checker* → `MEETS_BASIC_INTEGRITY` and `MEETS_DEVICE_INTEGRITY` should pass. In *Key Attestation* the bootloader appears **locked** because the attestation chain is replaced.
     54 
     55 ## Achieve `MEETS_STRONG_INTEGRITY` (Patch-Level Spoof)
     56 
     57 `STRONG` fails on outdated patch levels. TrickyStore can spoof a modern security patch date for all partitions:<sup>[[1]](#references)</sup>
     58 
     59 1. In TrickyStore, pick **Set Security Patch** → **Get Security Patch Date** → **Save**.
     60 2. Re-run *Play Integrity API Checker*; `MEETS_STRONG_INTEGRITY` should now pass.
     61 
     62 ## Practical Tester Angles Against Weak Integrations
     63 
     64 Even when you cannot permanently recover `DEVICE`/`STRONG`, many app backends still misuse the API. During testing, look for:<sup>[[10]](#references)</sup>
     65 
     66 - **Missing action binding:** `standard` requests should bind the protected action to `requestHash`; `classic` requests should bind it to a high-entropy `nonce`. `standard` requests have Google-managed replay mitigation, but the backend still needs to validate `requestHash` (and the request timestamp) against the protected action. `classic` integrations are especially replay-prone if the server accepts any valid token without matching the original `nonce`.
     67 - **Weak freshness checks:** verify whether the backend enforces `timestampMillis` and rejects old tokens. Long replay windows are common in rushed integrations.
     68 - **Over-trusting package metadata:** Google documents that `requestPackageName` can be spoofed in the middle of the request, so it should not be the only app-identity check.
     69 - **Legacy policy assumptions:** some apps still treat `MEETS_STRONG_INTEGRITY` as equivalent across Android versions. On pre-13 devices that can lead to weaker trust decisions than the product team expects.
     70 
     71 ## Emerging Technique: Remote Key Attestation (RKA)
     72 
     73 A newer evolution is to **relay the attestation request to another Android device** instead of copying `keybox.xml` onto every client device. The usual flow is:<sup>[[11]](#references)</sup>
     74 
     75 1. Intercept the local Key Attestation / Play Integrity request before it reaches the TEE/KeyStore path.
     76 2. Forward the nonce / app identity / request details to a remote rooted host.
     77 3. Let that host generate the attestation response using either an unrevoked legacy keybox or a genuinely vulnerable device whose boot chain still reports a locked state.
     78 4. Return the attestation blob to the client app/backend.
     79 
     80 Why it matters for testers:<sup>[[11]](#references)</sup>
     81 
     82 - it avoids burning a public `keybox.xml` on every device used during an assessment;
     83 - it makes simple certificate-serial revocation less effective against the operator;
     84 - it shifts the problem from **keybox distribution** to **relay infrastructure + host-device compromise**.
     85 
     86 This is where the ecosystem is moving: **Remote Key Provisioning (RKP)** reduces the long-term value of leaked static keyboxes, but it does **not** fully remove relay-style attacks when the attacker controls a privileged or exploited host device.<sup>[[11]](#references)</sup>
     87 
     88 ## Hardware Attestation Clean-Device Relay
     89 
     90 This variant targets applications that treat a valid **Android Keystore X.509 attestation chain** as a Boolean device-integrity result; it is distinct from forging a Play Integrity token. OID `1.3.6.1.4.1.11129.2.1.17` proves that *some* acceptable TEE/StrongBox generated the leaf key for `attestationChallenge`, but it does not bind that hardware to the process or network session presenting the chain. Consequently, even signature validation, Google-root pinning, freshness, revocation, security-level, `deviceLocked=true`, and `verifiedBootState=Verified` checks can all pass on relayed evidence because those claims are genuine for the oracle phone.<sup>[[11]](#references)[[13]](#references)[[15]](#references)</sup>
     91 
     92 A practical clean-device relay works as follows:<sup>[[13]](#references)[[14]](#references)</sup>
     93 
     94 1. Instrument the rooted target process and capture the backend's raw challenge before local key generation.
     95 2. Re-encode it as unpadded Base64URL (`android.util.Base64`: `NO_PADDING | NO_WRAP | URL_SAFE`, or `1 | 2 | 8`) and send `POST /attest` with `{"nonce":"..."}` to a stock, locked phone.
     96 3. On that phone, generate an ephemeral `secp256r1` AndroidKeyStore signing key with SHA-256 and `.setAttestationChallenge(challenge)`; request StrongBox if policy requires it, then export `KeyStore.getCertificateChain(alias)` as Base64 DER.
     97 4. Inject that chain into the target using the exact application-specific Java/Kotlin return type. Do **not** call the original local method, or the rooted phone will generate its own failing `RootOfTrust`.
     98 
     99 The highest-level method that accepts `ByteArray` and returns an attestation wrapper is usually the safest seam. The following shortened Frida 17-style pattern synchronously relays the challenge and reconstructs the expected object; the controller must always post a `response` (even an empty array after an HTTP error) or `op.wait()` deadlocks the application thread.<sup>[[13]](#references)[[14]](#references)</sup>
    100 
    101 ```javascript
    102 import Java from 'frida-java-bridge';
    103 Java.perform(() => {
    104   const K = Java.use('com.target.KeystoreAttestation');
    105   const Result = Java.use('com.target.AttestedKey');
    106   const B64 = Java.use('android.util.Base64');
    107   const ArrayList = Java.use('java.util.ArrayList');
    108   K.generateAttestedKey.overload('[B').implementation = challenge => {
    109     send({event: 'attestation_request', nonce: B64.encodeToString(challenge, 1 | 2 | 8)});
    110     let chain = []; const op = recv('response', m => { chain = m.payload ?? []; });
    111     op.wait();
    112     const remote = ArrayList.$new(); chain.forEach(cert => remote.add(cert));
    113     return Result.$new(remote, 'relayed', null);
    114   };
    115 });
    116 ```
    117 
    118 When the application has no convenient wrapper, hook `KeyGenParameterSpec.Builder.setAttestationChallenge(byte[])` on input and `java.security.KeyStore.getCertificateChain(String)` on output. Track the key alias and per-request context so concurrent generations cannot pair one challenge with another chain.<sup>[[13]](#references)</sup>
    119 
    120 ### Boundary and backend checks
    121 
    122 A chain-only relay does not copy the oracle's private key. Requiring a signature over fresh **session-, challenge-, and transaction-specific data** proves possession of the attested leaf key and forces an attacker to proxy every signing operation to the oracle, rather than reuse the initial certificate result.<sup>[[13]](#references)</sup>
    123 
    124 Also parse `attestationApplicationId` (authorization tag `[709]`) and compare the package name and SHA-256 signing-certificate digest with independently configured expected values. This blocks the simple cross-app oracle when it runs a different helper package; pair it with a hardware-enforced locked/Verified boot policy because application identity is platform-supplied rather than an independent physical-device binding.<sup>[[13]](#references)[[15]](#references)</sup>
    125 
    126 ## Operational Notes
    127 
    128 - **Revocation risk:** Hitting the API repeatedly with the same `keybox.xml` can flag and block it. If blocked, replace with a fresh valid keybox.<sup>[[1]](#references)</sup>
    129 - **Arms race:** Publicly shared keyboxes burn fast; keep private copies and track community module updates (XDA/Telegram/GitHub) for new working chains. Newer RKA-style setups reduce direct keybox exposure but increase operational complexity.<sup>[[1]](#references)[[11]](#references)</sup>
    130 - **Optional environment / abuse signals:** Some newer apps also evaluate `environmentDetails` (for example risky overlay/capture/control apps), `recentDeviceActivity`, or `deviceRecall`. In those cases, passing `DEVICE`/`STRONG` alone is not enough if your testing stack leaves visible overlay, accessibility, screen-capture artifacts, or generates obviously abnormal token volume.<sup>[[10]](#references)</sup>
    131 - **Scope:** This bypass only spoofs attestation inputs; backend signature verification by Google still succeeds because the JWT itself is genuine.<sup>[[1]](#references)</sup>
    132 
    133 ## References
    134 
    135 - [1] [Play Integrity API: How It Works & How to Bypass It](https://m4kr0.vercel.app/posts/play-integrity-api-how-it-works--how-to-bypass-it/)
    136 - [2] [ReZygisk](https://github.com/PerformanC/ReZygisk)
    137 - [3] [TrickyStore](https://github.com/5ec1cff/TrickyStore)
    138 - [4] [Tricky Addon](https://github.com/KOWX712/Tricky-Addon-Update-Target-List)
    139 - [5] [PlayIntegrityFork](https://github.com/osm0sis/PlayIntegrityFork)
    140 - [6] [KSU Web UI](https://github.com/adivenxnataly/KsuWebUI)
    141 - [7] [Play Integrity API Checker](https://play.google.com/store/apps/details?id=gr.nikolasspyr.integritycheck)
    142 - [8] [Key Attestation](https://github.com/vvb2060/KeyAttestation)
    143 - [9] [Android keybox attestation analysis](https://tryigit.dev/android-keybox-attestation-analysis)
    144 - [10] [Integrity verdicts | Android Developers](https://developer.android.com/google/play/integrity/verdicts)
    145 - [11] [Bypassing the Key Attestation API with Remote Devices](https://www.guardsquare.com/blog/bypassing-key-attestation-api)
    146 - [12] [Android Developers - Make a standard Play Integrity API request](https://developer.android.com/google/play/integrity/standard)
    147 - [13] [Bypassing Android Hardware Attestation with a Clean-Device Relay](https://blog.quarkslab.com/bypassing-android-hardware-attestation.html)
    148 - [14] [Quarkslab Android hardware attestation relay demo](https://github.com/quarkslab/android-hardware-attestation-demo)
    149 - [15] [Android Open Source Project - Key and ID attestation](https://source.android.com/docs/security/features/keystore/attestation)