TEESimulator: software KeyMint inside the real keystore daemon
Software simulation for Android hardware-backed key pairs with key attestation
At a glance
- What is it?
- TEESimulator answers Android hardware key attestation with a software KeyMint that runs inside the real keystore daemon, signing certificates with a keybox you supply. Here is how the interception works, how to install the Magisk module, and where it stops being the right tool.
- Who is it for?
- Adopt TEESimulator if you run Android 10 or newer on a rooted 64-bit device (arm64-v8a or x86_64) and you already hold a keybox you are entitled to use; the module ships no keybox, so without one the interceptor is a no-op. Do not adopt it on a 32-bit device, on Android 9 or older, or if you expect it to fabricate a verified-boot state, since the root of trust is harvested and frozen rather than configurable.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What TEESimulator actually intercepts
Android's hardware key attestation lets an app ask the keystore for a certificate chain that proves the key lives in a TEE and describes the device's verified-boot state. TEESimulator does not rewrite that chain after the fact. It embeds AOSP's own reference KeyMint trusted application, kmr-ta, and signs attestations with a keybox the user provides, so the certificates are produced the way a real TEE produces them. The README's claim is narrow and testable: the chain is internally consistent by construction, because one implementation generated every field.
The audience is correspondingly narrow. You need root, a 64-bit device, Android 10 or newer, and a keybox with private keys. This is a tool for people who already understand why an app is checking attestation and who have a legitimate keybox in hand. It is not a general privacy switch, and the module deliberately does not try to be one.
The daemon and the injected library split
Two components do different jobs. A privileged control daemon owns the configuration and the real device identity. The injected native library is a pure crypto-and-routing engine with no configuration of its own. The daemon starts at boot, harvests the device's real attestation parameters (verified-boot state, patch levels, OS version) from a throwaway hardware key, resolves the configured profiles against the live device, injects the interceptor into the keystore daemon, and pushes the resolved configuration over a local socket. Editing the configuration re-pushes it without a reboot.
The interception point depends on the Android generation, because the daemon guarding keys changed. On Android 12 and newer, keystore2 reaches the KeyMint HAL over Binder; the module injects a native library into keystore2 and PLT-hooks AIBinder_transact. A KeyMint transaction for a targeted app, or for a key the module itself created, is redirected to a local in-process IKeyMintDevice wrapping the TA. Everything else is forwarded to real hardware. On Android 10 and 11, the legacy keystore daemon is reached over IKeystoreService, so the module injects into keystore and hooks ioctl on libbinder, redirecting that service to a local stub handled in-process. For a targeted app it generates the key itself and hands back the public key; at attestation time it imports that key into the TA with the app's challenge, and the TA issues a keybox-signed chain over it.
The filter is what keeps this from being a blunt instrument. Keys the simulator creates are tagged on Android 12+ or tracked by caller on Android 10 and 11, so only those are served and real hardware keys are never intercepted. If no keybox is loaded, the interceptor stays a no-op. A misconfigured module is inert rather than a hazard, which is the right default for something that runs inside the keystore daemon.
Install the module and serve a first app
The README gives four steps. Flash the module with your root manager (Magisk, KernelSU or APatch) and reboot. Then place a hardware-backed keybox.xml at /data/adb/teesim/keybox.xml. List the apps to simulate under a profile in /data/adb/teesim/config.json, or edit the profile in the WebUI, and save; the daemon watches the configuration and applies changes live.
The shipped config.json already targets Google Play services and the Play Store, so the file you edit is usually this one:
{
"version": 1,
"profiles": {
"default": {
"keybox": "keybox.xml",
"patchLevel": { "system": "today", "vendor": "YYYY-MM-05", "boot": "YYYY-MM-05" },
"osVersion": "",
"apps": ["com.google.android.gms", "com.android.vending"]
}
}
}The keybox path is relative to /data/adb/teesim, and the daemon validates the file: it must parse, contain both an rsa and an ecdsa key, and each key needs a CertificateChain of at least two certificates. The ECDSA key is expected on NIST P-256. Until that file exists, nothing is intercepted.
If something goes wrong, the recovery path is to kill the keystore daemon. A fresh one starts clean, with no interception, until the module re-injects it:
su -c 'kill $(pidof keystore2)'On Android 10 and 11 the process name is keystore rather than keystore2. Note that the keybox is private and is never shipped with the module; the repository cannot supply one, and neither can this article.
Profiles, patch levels and the mini-language
Configuration is organised into profiles: a named bundle of a keybox, an operation mode, patch and OS levels, and optional device-identity values such as brand, model and IMEI, assigned to a set of apps. Each targeted app belongs to exactly one profile, and its attestations are signed and shaped by that profile. That constraint is stated plainly, and it means you cannot layer two profiles over the same package.
Patch and OS levels accept a small mini-language resolved against the device. harvested reuses the value captured from the real TEE at harvest time. system_property reads the matching build property from getprop and nothing else. Both report nothing when their source has no value, and the tag is omitted rather than sent as a made-up default, which is the more honest behaviour and also the one that will surprise anyone expecting a fallback. today means the current month; YYYY-MM-DD or YYYY-MM is an explicit date; no suppresses the level. The tokens YYYY, MM and DD resolve to today, so the shipped YYYY-MM-05 for vendor and boot patch levels means the 5th of the current month and tracks the calendar.
Device-identity fields fall back to values captured from the real TEE at harvest, so an app attesting the device's real ids gets a matching answer. A non-empty field overrides that, and both are omitted only when neither is set. The root of trust is not listed in the config at all: the verified-boot key, verified-boot state and device-locked flag are harvested once and frozen. The README gives two reasons. They make the attested root of trust authentic, and they seed KeyMint's key-encryption-key derivation, so freezing them yields a stable per-device key that keeps stored keys decryptable across reboots. Anyone hoping to flip the verified-boot state through this config will not find a key for it.
Where TEESimulator is the wrong tool
The requirement list is the first limit. Android 10 or newer, a 64-bit device (arm64-v8a or x86_64, because the keystore daemon is 64-bit), and root via Magisk, KernelSU or APatch. On a 32-bit device or Android 9 there is no supported path, and the reason is structural rather than a missing feature.
The second limit is the keybox. The module ships none, the keybox carries private keys, and the daemon rejects a file that does not parse or lacks the required key types and chain depth. Until a valid one is placed, the interceptor does nothing. If your goal is to pass an attestation check on a device where you have no keybox, TEESimulator does not help you, and the no-op default means it will also not break anything while you look for one.
The third limit is scope. Only keys the simulator created, tagged on Android 12+ or tracked by caller on Android 10 and 11, are served. Real hardware keys are never intercepted. That is a deliberate boundary, but it also means the module cannot retroactively re-sign keys an app already generated on real hardware. And because the root of trust is frozen, the attested boot state reflects the device rather than the profile, so a profile cannot present a different verified-boot story. The README does not document rollback of the module beyond the keystore-kill recovery path, and it does not describe what happens to keys created by the simulator if the module is later removed.
TEESimulator vs Tricky Store
Tricky Store is the comparison the search data keeps returning, and the topic list on the repository includes trickystore alongside teesimulator. The difference in approach is the interesting part. Tricky Store is built around intercepting and substituting certificate chains for selected packages. TEESimulator takes the other route: it runs AOSP's reference KeyMint TA inside the keystore daemon and signs with a keybox, so the chain is generated rather than patched. The README frames this as the reason its certificates are internally consistent by construction.
That choice has consequences. Generating attestations through a real KeyMint implementation means the module has to carry that implementation, inject into the keystore daemon per Android generation, and hook at the Binder or ioctl layer depending on version. A certificate-substitution approach has a smaller surface. In exchange, TEESimulator's output comes from the same code path a TEE would use, and the simulator only answers for keys it created. Which trade-off matters depends on what is checking the chain and how deeply it inspects it; the README does not offer a compatibility matrix, so that is something you establish on your own device.
Licence, releases and upgrade cost
The project is GPL-3.0, with a NOTICE file at the repository root and a third_party directory that holds the bundled components, including the reference KeyMint TA. GPL-3.0 matters if you plan to redistribute a modified module or link it into something you ship; this is a statement about the licence text, not legal advice, and the NOTICE file is where the bundled components' terms are recorded.
Upgrades arrive as canary builds. The release list shows canary-67, canary-66 and canary-63, with canary-67 published on 2026-09-05, the same timestamp as the last push to the dev branch. Canary naming is a signal about stability expectations: these are not tagged stable releases. The cost of upgrading is the usual one for an injected keystore module, plus the specific risk that a canary changes how the interceptor resolves profiles against the live device. The daemon re-pushes configuration over a local socket without a reboot, so config edits are cheap; replacing the module binary is not, and the recovery path is to kill the keystore daemon and let it start clean.
Editorial conclusion
Adopt TEESimulator if you run Android 10 or newer on a rooted 64-bit device (arm64-v8a or x86_64) and you already hold a keybox you are entitled to use; the module ships no keybox, so without one the interceptor is a no-op. Do not adopt it on a 32-bit device, on Android 9 or older, or if you expect it to fabricate a verified-boot state, since the root of trust is harvested and frozen rather than configurable. Verify first that your root manager is Magisk, KernelSU or APatch, that /data/adb/teesim/ exists after the first reboot, and that your keybox parses with both an rsa and an ecdsa key whose chains have at least two certificates, because the daemon validates the file before any app is served.
Frequently asked questions
What is TEESimulator?
It is a rooted Android module that simulates hardware-backed key attestation by serving selected apps from a software KeyMint running inside the real keystore daemon, signing attestations with a user-provided keybox. Keys the simulator creates are served by it; real hardware keys are never intercepted.
How do I use the TEE simulator on Android?
Flash the module with Magisk, KernelSU or APatch and reboot, place a keybox.xml at /data/adb/teesim/keybox.xml, then list the apps to simulate under a profile in /data/adb/teesim/config.json or edit the profile in the WebUI and save. The daemon watches the configuration and applies changes without a reboot.
How does TEESimulator compare with Tricky Store?
Tricky Store works by substituting certificate chains, while TEESimulator embeds AOSP's reference KeyMint TA and generates the attestation with a keybox, so the chain is internally consistent by construction. The README does not publish a compatibility comparison between the two.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jingmatrix-teesimulator)