Play Integrity Attestation Bypass (SafetyNet Replacement)
What Play Integrity Does
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.[10][12]
appIntegrity: APK build/signature match (no repack/tamper).deviceIntegrity: genuine & certified device, locked bootloader, no root/system tamper.accountDetails: installation via Google Play.environmentDetails(optional): recent integrations can also request signals about risky apps in the environment (overlay, capture, control) and Play Protect state.[10]- 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.[10]
Key verdict flags commonly enforced:[1]
MEETS_BASIC_INTEGRITY: token generated by genuine Play Services (not emulator/tampered transport).MEETS_DEVICE_INTEGRITY: genuine/certified device, bootloader locked, no root/system tamper.MEETS_STRONG_INTEGRITY: on Android 13+ it requiresDEVICEplus 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.[10]
Bypass Model
Instead of forging Google’s JWT, spoof the signals Google evaluates so they correspond to a different, legitimate device profile. The attack chain:[1]
- Hide root so local checks and Play Services probes don’t see Magisk/su.
- Replace the key attestation certificate chain (
keybox.xml) with one from a genuine device so Play Integrity sees a certified/locked device. - Spoof the security patch level to satisfy
MEETS_STRONG_INTEGRITY.
Google mitigates by revoking abused keyboxes; rotation is required when a keybox is blocked.[1]
Prerequisites & Tooling
- Root hiding: 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.[2]
- Key attestation spoofing: TrickyStore + Tricky Addon (Magisk modules).[3][4]
- Property / fingerprint spoofing: PlayIntegrityFork. It mainly spoofs props only for the Google Play Services DroidGuard path. This mainly helps with Android
<13or apps that still gate on legacy/softer checks; on Android 13+ a passingDEVICEverdict still depends on locked-bootloader, hardware-backed signals.[5] - UI helper: KSU Web UI to drive TrickyStore.[6]
- Validation: Play Integrity API Checker and Key Attestation APKs.[7][8]
- Optional background on attestation key material: https://tryigit.dev/android-keybox-attestation-analysis[9]
Achieve MEETS_BASIC_INTEGRITY + MEETS_DEVICE_INTEGRITY
- Install modules & reboot: Flash TrickyStore and Tricky Addon in Magisk, reboot.[1]
- Configure TrickyStore (via KSU Web UI): Select
TrickyStore→Select All→Deselect Unnecessary→ Save. - Inject a valid keybox: In
Keybox, choose Valid to download/apply a newkeybox.xml(vendor attestation credentials). This file underpins hardware key attestation and is now spoofed from a certified/locked device. - Verify: Run Play Integrity API Checker →
MEETS_BASIC_INTEGRITYandMEETS_DEVICE_INTEGRITYshould pass. In Key Attestation the bootloader appears locked because the attestation chain is replaced.
Achieve MEETS_STRONG_INTEGRITY (Patch-Level Spoof)
STRONG fails on outdated patch levels. TrickyStore can spoof a modern security patch date for all partitions:[1]
- In TrickyStore, pick Set Security Patch → Get Security Patch Date → Save.
- Re-run Play Integrity API Checker;
MEETS_STRONG_INTEGRITYshould now pass.
Practical Tester Angles Against Weak Integrations
Even when you cannot permanently recover DEVICE/STRONG, many app backends still misuse the API. During testing, look for:[10]
- Missing action binding:
standardrequests should bind the protected action torequestHash;classicrequests should bind it to a high-entropynonce.standardrequests have Google-managed replay mitigation, but the backend still needs to validaterequestHash(and the request timestamp) against the protected action.classicintegrations are especially replay-prone if the server accepts any valid token without matching the originalnonce. - Weak freshness checks: verify whether the backend enforces
timestampMillisand rejects old tokens. Long replay windows are common in rushed integrations. - Over-trusting package metadata: Google documents that
requestPackageNamecan be spoofed in the middle of the request, so it should not be the only app-identity check. - Legacy policy assumptions: some apps still treat
MEETS_STRONG_INTEGRITYas equivalent across Android versions. On pre-13 devices that can lead to weaker trust decisions than the product team expects.
Emerging Technique: Remote Key Attestation (RKA)
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:[11]
- Intercept the local Key Attestation / Play Integrity request before it reaches the TEE/KeyStore path.
- Forward the nonce / app identity / request details to a remote rooted host.
- 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.
- Return the attestation blob to the client app/backend.
Why it matters for testers:[11]
- it avoids burning a public
keybox.xmlon every device used during an assessment; - it makes simple certificate-serial revocation less effective against the operator;
- it shifts the problem from keybox distribution to relay infrastructure + host-device compromise.
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.[11]
Hardware Attestation Clean-Device Relay
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.[11][13][15]
A practical clean-device relay works as follows:[13][14]
- Instrument the rooted target process and capture the backend’s raw challenge before local key generation.
- Re-encode it as unpadded Base64URL (
android.util.Base64:NO_PADDING | NO_WRAP | URL_SAFE, or1 | 2 | 8) and sendPOST /attestwith{"nonce":"..."}to a stock, locked phone. - On that phone, generate an ephemeral
secp256r1AndroidKeyStore signing key with SHA-256 and.setAttestationChallenge(challenge); request StrongBox if policy requires it, then exportKeyStore.getCertificateChain(alias)as Base64 DER. - 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.
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.[13][14]
import Java from 'frida-java-bridge';
Java.perform(() => {
const K = Java.use('com.target.KeystoreAttestation');
const Result = Java.use('com.target.AttestedKey');
const B64 = Java.use('android.util.Base64');
const ArrayList = Java.use('java.util.ArrayList');
K.generateAttestedKey.overload('[B').implementation = challenge => {
send({event: 'attestation_request', nonce: B64.encodeToString(challenge, 1 | 2 | 8)});
let chain = []; const op = recv('response', m => { chain = m.payload ?? []; });
op.wait();
const remote = ArrayList.$new(); chain.forEach(cert => remote.add(cert));
return Result.$new(remote, 'relayed', null);
};
});
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.[13]
Boundary and backend checks
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.[13]
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.[13][15]
Operational Notes
- Revocation risk: Hitting the API repeatedly with the same
keybox.xmlcan flag and block it. If blocked, replace with a fresh valid keybox.[1] - 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.[1][11]
- Optional environment / abuse signals: Some newer apps also evaluate
environmentDetails(for example risky overlay/capture/control apps),recentDeviceActivity, ordeviceRecall. In those cases, passingDEVICE/STRONGalone is not enough if your testing stack leaves visible overlay, accessibility, screen-capture artifacts, or generates obviously abnormal token volume.[10] - Scope: This bypass only spoofs attestation inputs; backend signature verification by Google still succeeds because the JWT itself is genuine.[1]
References
- [1] Play Integrity API: How It Works & How to Bypass It
- [2] ReZygisk
- [3] TrickyStore
- [4] Tricky Addon
- [5] PlayIntegrityFork
- [6] KSU Web UI
- [7] Play Integrity API Checker
- [8] Key Attestation
- [9] Android keybox attestation analysis
- [10] Integrity verdicts | Android Developers
- [11] Bypassing the Key Attestation API with Remote Devices
- [12] Android Developers - Make a standard Play Integrity API request
- [13] Bypassing Android Hardware Attestation with a Clean-Device Relay
- [14] Quarkslab Android hardware attestation relay demo
- [15] Android Open Source Project - Key and ID attestation