I run stock LineageOS without GApps. microG handles the Play Services replacement. Most apps work fine on this setup. Banking apps and anything that calls Google's Play Integrity API do not, at least not without a stack of community modules and one specific microG toggle that defaults to "block".

I spent a long evening getting Revolut, Erste George, McDonald's, and the rest of the Aurora-installed proprietary apps to actually work on this device. Here is what I learned, what worked, and what was a dead end.

The premise#

Phone: Samsung Galaxy S10+, unlocked bootloader (Knox tripped, Samsung's permanent warranty bit), stock LineageOS 23.2 (Android 16), rooted with APatch. No Google Play Services - microG only, installed as a system priv-app. F-Droid + Aurora Store for app installs. The phone exists because I want a usable Android without a Google account, but I also want my bank and my food orders to work.

Default state, before any integrity work: Revolut would not get past the login screen. George (Austrian Erste Bank app) crashed on launch in user 0 and showed an integrity error in the work profile. McDonald's refused to install because Aurora Store sets installerPackageName=com.aurora.store and the app checks for com.android.vending. The Play Integrity API Checker returned a generic -100 error which is microG-specific noise, not a useful diagnostic.

End state, a few hours later: all of them work. Reliably enough that I have not had to retouch the setup in a week. Here is the structure of what actually mattered.

The one toggle that does the heavy lifting#

microG 0.3.9 added a setting called Block hardware attestation under Device Attestation -> Advanced. It defaults to on. That single default is why most "I tried microG, banking apps don't work" guides exist.

When this is on, microG's DroidGuard re-implementation refuses to forward hardware-attestation requests to Google's Play Integrity API. The verdict path returns MEETS_BASIC_INTEGRITY at best - which is why every guide on the internet says microG can't pass DEVICE integrity. Banking apps that require DEVICE (most do these days) show their flavour of "this device cannot be trusted".

Turn it off, reboot, and microG happily produces DEVICE-grade integrity tokens when the rest of your stack is set up right. This is the single biggest "why is this so hard" thing I encountered. It is documented nowhere I could find in the first dozen pages of search results. I found it in a microg/GmsCore GitHub issue with 72 comments going back to mid-2025, where the actual answer is buried in a reply from the maintainer.

The "rest of your stack" is the keybox story.

The keybox economy#

Hardware-backed Play Integrity wants the device to sign an attestation challenge with a key chained to Google's Hardware Attestation Root CA. A real phone with a working TEE produces this naturally. A custom ROM with an unlocked bootloader cannot, because the bootloader unlock broke the chain.

The workaround is a leaked OEM keybox. Someone, somewhere, extracted a real per-device attestation key from a real phone (Samsung Knox, Pixel TEE, MediaTek, the OEM doesn't matter much). The private key plus its full certificate chain ends up in an XML file called keybox.xml. You put that file in /data/adb/tricky_store/keybox.xml. A module called TEESimulator (the successor to the dormant Tricky Store) intercepts the Android Keystore API calls, signs the attestation challenge with the leaked key, and returns the leaked chain. Google's verifier checks the chain, doesn't notice (or doesn't care) that the same per-device key is being used across thousands of devices, and returns DEVICE.

Google does eventually notice. Every leaked keybox eventually ends up on Google's certificate revocation list at https://android.googleapis.com/attestation/status. Verifiers check this list. The window between "leaked keybox shows up in a community feed" and "Google revokes the leaf serial" is anywhere from 6 to 31 days based on the historical data I looked at.

So the keybox economy is a cat-and-mouse subscription. Two public GitHub feeds publish fresh leaked keyboxes:

  • KOWX712/Tricky-Addon-Update-Target-List keybox branch (was primary in 2025, has slowed)
  • Yurii0307/yurikey (more frequently updated in 2026, has been my primary)

A module called Tricky Addon provides a WebUI to manage the keybox file, including a Fetch button that's supposed to pull from these feeds with CRL validation. On my setup the WebUI's network access is broken in the WebView and the button returns "Please check your internet connection" even when the device's internet is fine. So I fetch and verify the keybox on a PC, then adb push it to the phone:

# Fetch + decode
curl -sL https://raw.githubusercontent.com/KOWX712/Tricky-Addon-Update-Target-List/keybox/.extra \
  | xxd -r -p | base64 -d > keybox.xml

# Sanity check - expect 6 BEGIN CERTIFICATE blocks (3 per chain, ECDSA + RSA)
grep -c BEGIN.CERTIFICATE keybox.xml

# Verify the leaf is not on Google's CRL before relying on it
SERIAL=$(awk '/BEGIN CERT/,/END CERT/' keybox.xml | sed -n '1,/END CERT/p' \
  | openssl x509 -noout -serial 2>/dev/null | cut -d= -f2 | tr A-Z a-z)
curl -s https://android.googleapis.com/attestation/status | grep -ic "$SERIAL"
# 0 = live, install. 1+ = revoked, do not bother.

# Install + reboot
adb push keybox.xml /sdcard/Download/keybox.xml
adb shell 'su -c "cp /sdcard/Download/keybox.xml /data/adb/tricky_store/keybox.xml"'
adb reboot

Weekly maintenance. Slightly annoying. There is an actively-maintained Rust-daemon alternative (Enginex0/tricky-addon-enhanced) that automates the rotation with built-in CRL validation, but it requires a newer APatch than I currently run, so I have not switched.

One thing that catches people: the security patch date claimed by your spoofed device fingerprint (PIF auto-rotates a Pixel Canary build) must exactly match the security patch date in your Tricky Store config. If PIF rotates to a fingerprint with patch 2026-04-05 and your security_patch.txt still says 2026-03-05, Google's verifier sees an inconsistent chain and rejects it. The keybox passes CRL but the verdict comes back BASIC. "I had DEVICE last week and now I don't" is almost always this.

Per-app weirdness#

The integrity layer is one problem. Each banking-style app then layers its own checks on top.

Revolut is the easiest. Once the integrity stack works, Revolut accepts the verdict and logs in. Done.

McDonald's does two checks Revolut does not. First, getInstallerPackageName() must return com.android.vending. Aurora Store records itself as the installer (com.aurora.store) by default. There is a "Root installer" mode in Aurora Store -> Settings -> Installer that, once you grant Aurora root permission, installs apps with the Play Store as the recorded installer. Set that and McDonald's stops complaining about installation source. Second, McDonald's checks UserManager.isProfile() and refuses to run in a work profile. So McDonald's lives in user 0.

Erste George (at.erstebank.george, the Austrian-Erste-Bank app, uses commercial RASP - likely Promon Shield) is the opposite. It crashes on launch in user 0 with a SIGABRT from inside its native library. The crash is triggered by Promon's TEETESTSUPPORT keystore probe behaving differently in user 0 versus a work profile (the SELinux context for untrusted_app differs across user contexts, which leaks signal to Promon). The same installation in user 10 (a Shelter-managed work profile) does not crash and reaches the integrity check normally. Pass the integrity check and you log in.

So I have one user profile that holds apps refusing work-profile context (McDonald's, etc.), and a Shelter-managed work profile that holds George. Aurora Store lives in user 0 because its Root installer hardcodes --user 0 in source - it always installs to the primary user regardless of which profile you run it from. For George I have to install via adb shell pm install --user 10 -i com.android.vending once, then clone if needed.

If George ever breaks again, the s Identity companion app from Erste runs the 2FA signing flow with much weaker integrity checks. Combined with web George at george.at, that is a reliable fallback that does not depend on the keybox at all.

What is NOT load-bearing#

A lot of advice on the internet repeats steps that turn out to not actually matter for the verdict. Empirically (I tested each by toggling it):

  • Random su path in APatch settings. Default is /system/bin/su. Banking apps in my set do not stat this. Nice defense-in-depth, not load-bearing.
  • Zygisk Assistant module. Some guides treat it as required. With NeoZygisk + denylist_enforce=1 already active, ZA does nothing additional that I could measure. Also: Promon Shield in George detects ZA's companion socket and crashes. So this is actively harmful for some apps.
  • Hide My Applist scope on banking apps. HMA hides packages from getInstalledPackages() queries. With APatch's Umount modules toggle hiding the actual mount artifacts at zygote spawn, HMA's package-list hiding is overkill - the apps I tested don't probe for APatch's manager package directly.
  • Play Integrity API Checker (gr.nikolasspyr.integritycheck). microG returns -100 to its calls regardless of actual integrity state because of how the checker queries with a hardcoded cloudProjectNumber. Useless on microG; test with the actual app instead.
  • microG's SafetyNet self-check. Always fails with a 404 because Google killed the legacy androidcheck/v1/attestations/attest endpoint in mid-2025. Apps that matter use the newer Play Integrity API, which does work.

The biggest insight from this exercise is that the integrity-spoofing community publishes a lot of "everything you must do" lists, most of which compose into a worse setup than the minimum that works. The minimum is: NeoZygisk + Vector + TEESimulator + a live community keybox + PIF with all flags on + microG with the hardware-attestation block disabled + per-app umount in APatch. That is it. Everything else is variance.

What this costs to keep working#

About 5 minutes a week. Mostly: open Tricky Addon WebUI, look at the keybox status badge, fetch a fresh one if needed (or run the curl one-liner on my PC if the WebUI's fetcher is misbehaving), reboot.

About once a month: PIF rotates the Pixel fingerprint to a fresh Canary build, the security patch date in the fingerprint shifts. Update security_patch.txt to match the new date.

Maybe twice a year: a microG update lands in their F-Droid repo and Droidify auto-updates it. That has not broken anything for me in the year I have been running this setup.

The whole thing only stops working if Google revokes the entire leaked-OEM-keybox root certificate family (f92009e853b6b045) wholesale rather than per-leaf serial. That has not happened in four years. If it does, software-only DEVICE-grade integrity on a custom ROM is dead for everyone outside Pixel hardware overnight, and I switch to web George + s Identity full-time.

The full step-by-step procedure (modules, URLs, configuration files, per-app fixes) lives in my personal knowledge base. This post is the narrative. If anyone wants the recipe and not the story, the modules are easy to find: NeoZygisk, Vector, TEESimulator, Tricky Addon for the integrity stack; microG Installer Reborn for the microG-as-system-priv-app dance on stock LineageOS; APatch for root; Aurora Store for installs; Shelter for the work profile. The microG toggle to flip is the one mentioned above. The rest is reading.