Skip to content

Releases: iamjosephmj/DeviceIntelligence

3.0.1 — backend verifiers publishable everywhere

Choose a tag to compare

@iamjosephmj iamjosephmj released this 27 Sep 18:35

First release where every backend verifier is publish-ready:

  • verifier-kotlin ships to Maven Central in the release workflow (parallel JDK-only job) — tech.thessemaj:verifier-kotlin.
  • New Publish verifier ports workflow for PyPI / npm / RubyGems / crates.io, token-gated.
  • Go module resolvable from verifier-go/v1.0.0 tags; PHP via Packagist.
  • New pip tools: deviceintelligence-licence (RVN2 server.key generator, di-licence) and deviceintelligence-lab (test-chain/token mint, di-lab).
  • Rust port: full 32-test suite incl. real-device parity; P-384 intermediate support.
  • Packaging metadata completed across the Rust crate, npm package, Ruby gemspec and Python package.

Patch bump from 3.0.0 (which shipped the rebranded SDK + verifier ports).

3.0.0 — the revamp

Choose a tag to compare

@iamjosephmj iamjosephmj released this 25 Sep 19:22

ollvm-era toolchain rework, kotlin 2.2, signal registry v2 (all INTEL codes reshuffled), squishy+flinger sample ui. Maven Central artifacts for 3.0.0 publish from a build machine with the obfuscating toolchain.

2.2.0 — Maven Central + tech.thessemaj

Choose a tag to compare

@iamjosephmj iamjosephmj released this 11 Jul 14:26

DeviceIntelligence is now on Maven Central under the tech.thessemaj namespace, and the package has been renamed from io.ssemaj.* to tech.thessemaj.* throughout (Kotlin, native JNI symbols, plugin id).

// settings.gradle.kts — standard repos, no resolutionStrategy needed
pluginManagement { repositories { google(); mavenCentral(); gradlePluginPortal() } }

// app/build.gradle.kts
plugins { id("tech.thessemaj.deviceintelligence") version "2.2.0" }

Breaking: the runtime package moved to tech.thessemaj.deviceintelligence — update imports (import tech.thessemaj.deviceintelligence.DeviceIntelligence). Verified end-to-end: native library builds for all ABIs and the instrumented smoke suite passes on a Pixel 6 Pro. Legacy com.github.iamjosephmj.DeviceIntelligence tags (≤ 2.1.0) remain on JitPack.

DeviceIntelligence 2.1.0 — App Bundle (AAB) integrity

Choose a tag to compare

@iamjosephmj iamjosephmj released this 29 Jun 09:29

DeviceIntelligence 2.1.0

App Bundle (AAB) bundle-mode integrity

The build-baked tamper detection now works when an app ships as an Android App Bundle through Google Play (previously APK-only — an AAB build silently lost / false-positived the integrity core under Play's re-split / re-encode / re-sign).

  • Opt-in DSL: deviceIntelligence { appBundle { enabled = true; playSigningCertSha256("AB:CD:…") } } — default off; the APK path is byte-for-byte unchanged.
  • Pins a signer allow-set (upload cert ∪ Play app-signing cert) checked by membership, and decompressed SHA-256 of classes*.dex + lib/<abi>/*.so (stable across Play's re-encode), verified across the base APK + all installed config splits.
  • BundleIntegrityTask bakes the fingerprint into the .aab and re-signs it (JDK JarSigner; no bundletool dependency); native NDK-zlib streaming inflate for decompressed hashing.
  • Fingerprint schema bumped to v3 (additive; v1/v2 still decode).
  • Validated on-device via real bundletool split delivery: clean install → no false positives; tampered split → apk_entry_modified CRITICAL.

License

Relicensed to Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0) (was Apache-2.0). Commercial use and verbatim redistribution permitted with attribution; redistributing modified copies is not.

Distributed via JitPack (built from this tag).

DeviceIntelligence 2.0.1

Choose a tag to compare

@iamjosephmj iamjosephmj released this 14 Jun 06:19

Changed

  • attestation.key verdict: a non-Verified boot is now CRITICAL (was HIGH). deriveIntegrityVerdict no longer treats SelfSigned (yellow) as a genuine OS — only a Google-Verified (green) boot qualifies for MEETS_DEVICE_INTEGRITY. SelfSigned (yellow), Unverified (orange), and Failed (red) all mean the boot chain was modified (custom AVB key / unlocked / failed verification), so the tee_integrity_verdict finding is now emitted at CRITICAL severity for all three. A yellow/relocked-with-own-key boot (custom kernel / KernelSU / custom ROM) no longer reaches MEETS_DEVICE_INTEGRITY and no longer downgrades to HIGH.

Added

  • Hardware-attested "OS modified" proof on tee_integrity_verdict. When the boot state is non-Verified, the finding's message now states why the OS is considered modified, and details gain two fields: verified_boot_key_sha256 (the attested boot-signing key hash — Google's on stock, a custom value on a re-signed boot) and os_modified_proof (a human-readable, hardware-attested explanation). These come from the secure element's RootOfTrust and cannot be forged on-device.

Wire-format impact

schema_version stays at 2 — the new details keys are additive and the severity field already existed. Backends that branch on tee_integrity_verdict.severity will now see critical where they previously saw high for non-Verified boots; treat that as a stronger signal, not a new shape.

DeviceIntelligence 2.0.0

Choose a tag to compare

@iamjosephmj iamjosephmj released this 28 May 16:24

Stable major release. The SDK no longer performs any network calls — the entire telemetry pipeline (collection → JSON → upload) now lives entirely in the consumer's process and on the consumer's backend.

Removed

  • Native analytics layer (cpp/dicore/analytics.{cpp,h}, 594 LOC). The JNI-bridged HttpURLConnection POST drain that shipped from 1.0.0 onwards — client_id derivation, ring-buffer queue, detached drain thread, fire-and-forget POST to the Cloud Functions ingest endpoint — is gone in its entirety. libdicore.so now contains zero networking code.
  • NativeBridge.queueTelemetryReport / nativeQueueTelemetryReport and the matching Java_..._nativeQueueTelemetryReport JNI export.
  • disableAnalytics Gradle DSL property. Redundant; removing it surfaces the new "no analytics, period" contract at build time rather than silently no-op'ing the opt-out.
  • Manifest opt-out <meta-data android:name="io.ssemaj.di.analytics" /> generation in GenerateOptionalManifestTask.
  • android.permission.INTERNET declaration in the library AAR. The permission was only there to satisfy the analytics drain; the library itself never needed it.

Breaking changes (Gradle DSL)

Consumers currently writing

deviceintelligence {
    disableAnalytics.set(true)
}

will get an Unknown property 'disableAnalytics' for object of type DeviceIntelligenceExtension configuration-time error on 2.0.0.

Migration: delete the line. There is nothing to opt out of any more.

No other public Kotlin / Gradle / JNI surface changed.

Wire-format impact

None. schema_version stays at 2. TelemetryReport, Finding.kind, every detector ID, IntegritySignal, and the JSON encoder are byte-identical to 1.1.0 for the same input. Backends do not need to re-version anything.

Privacy posture

The SDK now makes zero network calls under any configuration. All telemetry stays in the consumer's process until the consumer's own code chooses to upload DeviceIntelligence.collectJson(context) to the consumer's own backend. The README's "Backend-agnostic" / "no SDK tries to talk to a vendor cloud" promise is now true unconditionally (it was previously gated on the optional disableAnalytics.set(true) flag).

Validation

  • ./gradlew :deviceintelligence:assembleRelease :deviceintelligence-gradle:assemble → BUILD SUCCESSFUL (Kotlin + Gradle plugin + native libdicore.so for arm64-v8a, armeabi-v7a, x86_64).
  • ./gradlew :deviceintelligence:testDebugUnitTest → BUILD SUCCESSFUL.
  • Tree-wide grep for analytics.h / analytics:: / disableAnalytics / queueTelemetryReport / nativeQueueTelemetryReport / io.ssemaj.di.analytics / cloudfunctions returns zero hits.

Full diff: 1.1.0...2.0.0

DeviceIntelligence 1.1.0

Choose a tag to compare

@iamjosephmj iamjosephmj released this 13 May 16:28

DeviceIntelligence 1.1.0

First minor since 1.0. Four additive Finding kinds across three detectors, plus internal infrastructure for the next round. Wire-format additive only — schema_version stays at 2.

What's new

runtime.root — Magisk hide-module bypass + TLS-trust-store MITM

Three new finding kinds close the most common bypass paths for the cheap-channel root checks. Shamiko — the LSPosed module that targeted-app-hides Magisk — operates by unsharing the per-process mount namespace of the target app. It cannot patch init (PID 1)'s namespace, and it cannot un-bind the daemon's actively-held abstract Unix socket. Hence:

  • magisk_in_init_mountinfo (HIGH) — /proc/1/mountinfo scan. A hit here while /proc/self/mountinfo looks clean is a strong "Magisk is actively hiding from us" signal.
  • magisk_daemon_socket_present (HIGH) — @magisk_daemon abstract Unix socket via /proc/self/net/unix. Bound in init's network namespace; visible to every process in the namespace, survives filesystem-artifact hides.
  • tls_trust_store_tampered (CRITICAL) — tmpfs bind-mount over /apex/com.android.conscrypt. This is the MagiskTrustUserCerts-family technique that swaps Android's system TLS trust store with one accepting user-installed roots. Once mounted, any HTTPS the app makes is MITM-interceptable by whoever installed the cert (Burp / Charles / mitmproxy / etc.). The signal is active TLS interception enablement, not just "root tool present" — backends should treat as a hard block for sensitive flows.

All three Shamiko-bypass channels read procfs paths through File.readText(); on a denied EACCES (e.g. hidepid=2 locked-down ROMs that hide PID 1's namespace from non-root processes) the read fails and the channel silently degrades to "no signal" — read failures never escalate into findings.

runtime.environment — Frida 16+ Gum memfd-JIT attribution

  • frida_memfd_jit_present (HIGH) — /proc/self/maps shows a /memfd:jit-cache mapping with rwxp perms AND region size > 8 MB. ART legitimately maps the same memfd path but only with r-xp / r--p perms — the rwxp combination is a Frida-only signature on Android. Fires in addition to the generic rwx_memory_mapping so backends that want a Frida-attribution signal can pivot on this kind directly without inspecting rwx_memory_mapping's details.

attestation.key — EAT/CBOR format detection (KeyMint 200+)

  • attestation_eat_format_detected (LOW) — the leaf cert's KeyDescription extension (OID 1.3.6.1.4.1.11129.2.1.17) carries CBOR-EAT bytes instead of the legacy ASN.1 KeyDescription SEQUENCE. KeyMint 200+ on Android 14+ devices using RKP-provisioned keys can emit attestation in this format. Library-side parsed fields are null on those leaves; backends must re-parse the raw chain bytes (app.attestation.chain_b64) server-side for full field-level data. Heuristic detection only — full on-device CBOR-EAT decoding is tracked for a follow-up minor.

Plus an internal Knox extension-presence helper carried on AttestationResult.Success.knoxExtensionPresent for the follow-up minor that adds Samsung Knox warranty-bit byte parsing.

Wire-format impact

Additive only. Four new Finding.kind strings; no detector ID renames, no IntegritySignal enum changes. schema_version stays at 2. Backends with strict-mode kind allowlists need to add:

  • magisk_in_init_mountinfo
  • magisk_daemon_socket_present
  • tls_trust_store_tampered
  • frida_memfd_jit_present
  • attestation_eat_format_detected

Everything else continues working unchanged.

IntegritySignal mapping

New kind Maps to
magisk_in_init_mountinfo, magisk_daemon_socket_present, tls_trust_store_tampered ROOT_INDICATORS_PRESENT
frida_memfd_jit_present HOOKING_FRAMEWORK_DETECTED
attestation_eat_format_detected TEE_ATTESTATION_DEGRADED

Backends already gating on the existing signals will pick up the new channels automatically without code changes.

Validation

  • JVM unit tests for every new finding kind (+19 tests across MapsParser, RootIndicators, KeyDescriptionParser, IntegritySignalMapper).
  • Pixel 6 Pro on-device validation tracked alongside the existing CTF roadmap rigs.
  • Sample :samples:minimal release APK assembled and instrumented through the Gradle plugin's fingerprint baking pipeline — all three ABIs (arm64-v8a, x86_64, armeabi-v7a) hashed, asset injected, APK re-signed v1+v2+v3.

Install

Same as 1.0.0 — just bump the version:

plugins {
    id("io.ssemaj.deviceintelligence") version "1.1.0"
}

Technique credits

All four runtime-detection channels are clean-room reimplementations from public Android security knowledge; no source borrowed:

  • Shamiko-bypass channels, MagiskTrustUserCerts MITM detection, Frida memfd-JIT signature: inspired by techniques in the snitchtt project (MIT + Commons Clause; license incompatible with our Apache 2.0 distribution, so no code reuse).
  • EAT format heuristic, Knox extension OID prefix: referenced from vvb2060/KeyAttestation (Apache 2.0; cryptographic OIDs are non-copyrightable identifiers).

Full per-detector reference: docs/DETECTORS.md. Detection backlog: tools/red-team/CTF_ROADMAP.md.

DeviceIntelligence 1.0.0

Choose a tag to compare

@iamjosephmj iamjosephmj released this 03 May 19:45

DeviceIntelligence 1.0.0

First stable release. The wire format and public Kotlin API are now committed-to: additive changes only without a major version bump; breaking changes will require both a schema_version increment and a major version bump.

What's in 1.0

Eight detectors with stable IDs and stable finding kinds:

  • integrity.apk — APK bytes vs. the build-time fingerprint baked by the Gradle plugin
  • integrity.bootloader — TEE-spoofing / cached-chain detection
  • integrity.art — In-process ART tampering across 5 vectors (Frida, Xposed, LSPosed, Pine, …)
  • attestation.key — TEE / StrongBox attestation: Verified Boot, bootloader lock, OS patch level
  • runtime.environment — Debugger / ptrace / native integrity stack (.text hash, GOT, injected libs) + runtime DEX-injection (InMemoryDexClassLoader / DexClassLoader payloads)
  • runtime.root — su binary, Magisk artifacts, test-keys, root-manager apps
  • runtime.emulator — CPU-instruction-level signals (arm64 MRS / x86_64 CPUID hypervisor bit)
  • runtime.cloner — Foreign APK mappings, mount-namespace inconsistencies, UID mismatches

Eight-layer native anti-hook stack (G0–G7): module liveness, .text SHA-256 vs build-time baseline, library inventory, GOT integrity, StackGuard call-stack inspection, StackWatchdog sampled inspection, JNI return-address verification.

Twelve hook framework signatures: Frida, Cydia Substrate, Xposed, LSPosed, Riru, Zygisk, Taichi, Dobby, Whale, YAHFA, FastHook, zygisk-il2cpp-dumper.

Runtime DEX injection detection (CTF Flag 1):

  • Channel (a) — multi-root BaseDexClassLoader chain walk
  • Channel (b) — /proc/self/maps named-anon scan (Android 14+ [anon:dalvik-DEX data] and ≤ Android 13 [anon:dalvik-classes.dex extracted in memory from <source>] formats)
  • Pre-baseline timing-gap handling via unattributable_dex_at_baseline (consolidated to one summary finding per session, not per region)

Three native ABIs: arm64-v8a, x86_64, armeabi-v7a. All runtime-validated on the Sauce Labs Android 11+ device farm spanning Samsung One UI, Xiaomi HyperOS / MIUI, Vivo OriginOS, Honor MagicOS, OPPO ColorOS, OnePlus OxygenOS, Motorola, plus AOSP-equivalent Pixels.

Cumulative session API (observeSession(), SessionFindings, SessionFindingsAggregator) for UIs / backends that don't want to lose sight of a transient signal the moment it stops appearing. Per-finding firstSeenAtEpochMs / lastSeenAtEpochMs / observationCount / stillActive flags. Canonical wire format via SessionFindings.toJson().

Hardware-attestation × runtime correlation (CTF Flag 5 — captured 1.0.0). Derived hardware_attested_but_userspace_tampered finding emitted at CRITICAL severity when both:

  1. attestation.key.verifiedBootState == "Verified" (TEE asserts a clean, locked-bootloader, signed-OS boot)
  2. Any IntegritySignal.HOOKING_FRAMEWORK_DETECTED-mapped finding fires in the same report

The strongest single tamper signal the SDK can produce. Either the TEE is compromised (rare but exists) or this is post-attestation injection (Magisk + Shamiko, etc.). Backends should treat this as a hard-block-grade signal.

Privacy-first analytics: SHA-256 of ro.build.fingerprint for client_id, no PII, no package names, no memory addresses on the wire. Disable via deviceintelligence { disableAnalytics.set(true) }.

Open-source end to end (Kotlin + native C++); zero Google Play Services dependency; works on AOSP, GrapheneOS, CalyxOS, OpenGApps, etc. Hardware key attestation still works on those builds because it's a Keymaster API call, not a Google API call.

Validation

  • 209 unit tests across 17 test classes
  • Pixel 6 Pro red-team (KernelSU + LSPosed + Frida): every Frida vector A–F + Frida-Java + DEX injection (in-memory and disk) + LSPosed late and pre-baseline scenarios + newer-frameworks signature trip
  • Sauce Labs cross-OEM (Android 11+ device farm) — Samsung, Xiaomi, Vivo, Honor, OnePlus, OPPO, Motorola, Pixel
  • Cross-ABI runtime stability: all three ABIs verified loading the native library and running every detector to completion

What "1.0 wire-format commitment" means concretely

For consumers building backends against the JSON contract:

  • The schema_version (currently 2) is the source of truth for wire-format breakage. It does NOT bump for additive changes.
  • Detector IDs (integrity.apk, integrity.art, etc.) are stable.
  • Finding.kind identifiers (hook_framework_present, art_method_entry_drifted, dex_in_anonymous_mapping, etc.) are stable.
  • Finding.severity, Finding.subject, Finding.message shapes are stable; specific message text may evolve (don't string-match on it server-side).
  • Finding.details is opaque diagnostic data — useful for forensics, but its keys may change between releases without a schema_version bump.
  • Adding new detectors, new finding kinds, or new IntegritySignal enum values is additive. Existing kind strings are never repurposed.

What's intentionally NOT in 1.0

Some detection capabilities are tracked for future minor releases, not deferred for any quality reason:

  • CTF Flag 3 (Shamiko / Magisk-hide property interception) — most-deployed real-world evasion, planned next.
  • Real Zygisk module for Flag 1 production-realism validation — the LSPosed-early simulation already proves the pre-baseline timing logic.
  • 32-bit ART struct characterisation — integrity.art reports INCONCLUSIVE on armeabi-v7a because the ArtMethod field-offset table is currently 64-bit-only. Per-API characterisation is a research task for a future minor.
  • ShadowHook / SandHook / Pine signature additions — these are commonly embedded in legitimate apps (Bytedance ships ShadowHook in TikTok / Douyin / CapCut / Lemon8 for in-app patching), so name-only detection would FP every install. The fix is the embedded-vs-injected distinction; tracked as a separate roadmap item.

See tools/red-team/CTF_ROADMAP.md for the full backlog.

Consumer (JitPack)

plugins {
    id("io.ssemaj.deviceintelligence") version "1.0.0"
}

com.github.iamjosephmj.DeviceIntelligence · tag 1.0.0

Documentation

  • README.md — install + quick start + IntegritySignal vocabulary + JSON contract
  • CHANGELOG.md — full version history from 0.5.2 → 1.0.0
  • SECURITY.md — vulnerability disclosure process, response SLOs
  • docs/DETECTORS.md — per-detector reference (threat model, finding kinds, sample tripped JSON)
  • NATIVE_INTEGRITY_DESIGN.md — design of the 8-layer (G0–G7) anti-hooking stack
  • tools/red-team/ — Frida + LSPosed offensive verification harnesses

Sample APK

minimal-release.apk is attached to this release — install on a rooted device + LSPosed manager to exercise every detector against real attacker tooling. Signed with the local debug keystore (matches every prior release).

DeviceIntelligence 0.9.0

Choose a tag to compare

@iamjosephmj iamjosephmj released this 03 May 18:26

Highlights

CTF Flag 2 captured: detection coverage for five additional hook frameworks. MapsParser.HOOK_FRAMEWORK_SIGNATURES now matches the canonical library names of frameworks the previous releases didn't know about:

Framework Substrings matched Notes
Dobby libdobby, dobby_bridge General-purpose ARM inline-hook lib (github.com/jmpews/Dobby).
Whale libwhale Java method-hook framework (github.com/asLody/whale).
YAHFA libyahfa "Yet Another Hook Framework for ART" — used as the ART-side backend by EdXposed and some LSPosed configurations.
FastHook libfasthook Niche, mostly seen in game-modding kits.
il2cpp-dumper libil2cppdumper, zygisk-il2cpp Zygisk module that dumps Unity IL2CPP game logic. Common on cheating frameworks targeting Unity-based games.

All five emit through the existing hook_framework_present finding kind under runtime.environment, with the canonical name in details.framework. Same IntegritySignal.HOOKING_FRAMEWORK_DETECTED mapping. Backends already speaking the runtime.environment wire format keep working unchanged — the only delta is that details.framework can now contain the new names on attacked devices.

Three frameworks intentionally NOT shipped

ShadowHook (libshadowhook), SandHook (libsandhook), and Pine (libpine) are deferred. Detecting them on library name alone would false-positive on every legitimate consumer app that embeds them (Bytedance ships ShadowHook in TikTok / Douyin / CapCut / Lemon8 for in-app instrumentation; SandHook + Pine are used by some legitimate game-cheat-prevention frameworks).

The proper fix is the embedded-vs-injected distinction — cross-reference each loaded library's path against the consumer app's own build-time native-lib inventory (already captured in Fingerprint.expectedSoList). Tracked under tools/red-team/CTF_ROADMAP.md for a future minor release.

Validation

  • 27 unit tests pass, including 8 new ones — one per shipped framework + a negative-control test confirming the deferred-FP-risk signatures do NOT trip when their library names appear in synthesised maps lines.
  • Pixel 6 Pro (KernelSU + Frida) end-to-end:
    • Clean baseline at prewarm (Frida not yet attached): zero hook_framework_present findings — no false positives from the new signature additions.
    • Post-tamper (harness names 5 anon pages via prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ...) + Frida self-attaches): runtime.environment.findings contains 6 hook_framework_present entries — all 5 new frameworks plus Frida itself.

New red-team harness

tools/red-team/maps-newer-frameworks.js — Frida script that uses Memory.alloc + prctl(PR_SET_VMA_ANON_NAME) to label anonymous pages with each candidate framework's library name, runs a fresh collectBlocking() (after RuntimeEnvironmentDetector.resetForTest() to drop the cached scan), and asserts every new signature trips. FLAG CAPTURED lands when all five fire.

Wire format

Additive only — no schema bump, no new finding kinds, no new IntegritySignal values, no public API changes. Just new strings the existing detector knows how to recognise.

Consumer (JitPack)

plugins {
    id("io.ssemaj.deviceintelligence") version "0.9.0"
}

com.github.iamjosephmj.DeviceIntelligence · tag 0.9.0

Sample APK

minimal-release.apk attached. APK size unchanged from 0.8.1 — the addition is a few hundred bytes of constant strings in MapsParser.kt.

DeviceIntelligence 0.8.1

Choose a tag to compare

@iamjosephmj iamjosephmj released this 03 May 17:11

Highlights

Doc-only patch release. The 0.8.0 tag was cut before the README confirmation that armeabi-v7a is runtime-validated on Sauce Labs landed on main. 0.8.1 moves the latest tag forward to include that doc fix.

Identical binary behaviour to 0.8.0. Same wire format, same detector coverage, same .so contents modulo the version string. Upgrading from 0.8.0 is a no-op for any consumer that's already pinned to 0.8.0.

What's new in the README

The "Validated against" section now contains a "Cross-ABI stability" subsection that records the runtime-stability outcome of the Sauce Labs sweep on 32-bit ARM hardware:

ABI Status
arm64-v8a full coverage — every detector works
x86_64 full coverage — every detector works
armeabi-v7a runtime-stable since 0.8.0 (validated on Sauce Labs 32-bit ARM devices). Every detector works EXCEPT integrity.art, which reports INCONCLUSIVE because the underlying ArtMethod field-offset table is 64-bit-only.

Plus install snippet and JSON example bumped to 0.8.1.

Consumer (JitPack)

plugins {
    id("io.ssemaj.deviceintelligence") version "0.8.1"
}

com.github.iamjosephmj.DeviceIntelligence · tag 0.8.1

For everything else 0.8.0 added (third ABI shipped, per-detector coverage on armeabi-v7a, implementation notes), see the 0.8.0 release notes — they apply unchanged to 0.8.1.

Sample APK

minimal-release.apk attached. versionName=0.8.1. APK contents identical to 0.8.0 modulo the version string.