tangem-app-android-audited/APKANALYSIS.md

272 lines
13 KiB
Markdown

# Binary Artifact Analysis
## Tangem Android Wallet — Official Release APK vs. Build from Audited Source
---
| | |
|---|---|
| **Subject** | Comparison of Tangem's official v6.1.2 release APK against a build produced from the audited source |
| **Companion to** | [AUDIT.md](./AUDIT.md) — static source review of commit `358144ee61a` |
| **Analysis date** | 17 August 2026 |
| **Analysis type** | Static binary inspection (manifest, signing, package structure). No decompilation, no execution |
| **Objective** | Determine whether the officially distributed binary can be confirmed to originate from the audited source |
---
## 1. Executive Summary
**The official APK cannot be verified as originating from the audited source, and no available
technique would allow it.** This is a structural property of how Tangem builds and hardens its
release artifact, not a deficiency of this analysis or an indication of wrongdoing.
What *can* be established is weaker but not worthless: every externally observable property of the
official binary that could be compared against a build from the audited source **matches**. That
rules out crude substitution or repackaging. It does not, and cannot, rule out a targeted
modification concealed beneath the protection layer.
The comparison additionally surfaced **two factual errors in AUDIT.md**, both of which overstate the
security posture of the shipped application. They are recorded in §5 and have been corrected in the
audit report.
### Verdict
> **UNVERIFIABLE — CONSISTENT.** Provenance of the official binary cannot be established by any
> means available to a third party. All comparable attributes are consistent with the audited
> source. Absence of contradiction is not evidence of correspondence.
---
## 2. Artifacts Compared
| | Official release | Build from audited source |
|---|---|---|
| Filename | `app-release-protected-1819-6.1.2.apk` | `app-google-debug.apk` |
| Origin | `github.com/tangem/tangem-app-android` releases, tag `v6.1.2` | `./gradlew :app:assembleGoogleDebug` at `358144ee61a` |
| Size | 134,544,995 bytes | 176,908,283 bytes |
| SHA-256 | `6ed4ac317f2f5bdccd5bb1cec9d6733207261e6b4f823a4de883e646872166a1` | `5df4c81755bc423ed39e92499830d789b309561211c62091585fa9038adfe5cd` |
| Package | `com.tangem.wallet` | `com.tangem.wallet.debug` |
| Version | `6.1.2` (code `1819`) | `1.0.0-SNAPSHOT` (code `1`) |
| Build type | `release`, minified, DexProtector-hardened | `debug`, unminified, unprotected |
| Signer | `L=Zug, O=Tangem AG` | Committed dummy debug keystore |
| Archive entry dates | 2026-08-13 | 2026-08-17 |
The size difference is expected and runs counter to intuition: the debug build is *larger* because
it is not minified by R8, despite carrying no protection layer.
---
## 3. Why Provenance Cannot Be Established
Four independent obstacles apply. Any one alone would be sufficient to defeat verification.
### 3.1 The release is commercially hardened
The official APK ships Licel DexProtector:
```
lib/arm64-v8a/libdexprotector.so 446,117 bytes
lib/armeabi-v7a/libdexprotector.so 394,517 bytes
assets/assets.dat 229,499 bytes
assets/ict.dat 10,853 bytes
```
DexProtector encrypts and obfuscates DEX bytecode. The transformation is keyed per build and is
explicitly designed to prevent the analysis that verification would require. Comparing bytecode
between the two artifacts is not merely impractical — it is precluded by design.
### 3.2 The compared artifacts are different build types
The official build passes through R8 minification, obfuscation, and resource shrinking. The
reference build does not. Class names, method signatures, and the DEX file layout diverge for
reasons entirely unrelated to source content: 54 DEX files versus 68.
### 3.3 The source tree does not correspond to the released version
The repository carries **no version of its own**. `AppConfig.versionCode` is `1`, and
`VersionNameProvider` derives the name from the current git branch, falling back to
`1.0.0-SNAPSHOT`. Both values are injected by CI via `-PversionCode` / `-PversionName`.
Consequently nothing in the tree identifies which release a given commit produced. The evidence
indicates the audited commit is **newer** than v6.1.2: archive entries in the official APK are dated
2026-08-13, whereas the audited commits are dated 2026-08-14. The two artifacts therefore do not
represent the same source state, and could not be expected to match even in principle.
### 3.4 The build is not reproducible by a third party
Independent of the above, an outside party cannot reconstruct the official build:
- `core/ui/ds-tokens` is a submodule gitlink with **no `.gitmodules` mapping anywhere in the
repository's 16,087-commit history**, so it cannot be resolved (see AUDIT.md O-3)
- All third-party API keys in `config_prod.json` are `PLACEHOLDER`; the official build embeds real
values, compiled in at build time by `GenerateEnvironmentConfigTask`
- Release signing requires a keystore that is deliberately not published
Tangem publishes no reproducible-build attestation, and the artifact's construction is incompatible
with one.
---
## 4. Consistency Findings
The following were compared and **match**. Each is a property an attacker repackaging or
substituting the application would find difficult to preserve.
### 4.1 Permission set — identical
Both APKs declare **exactly 23 permissions**. After normalising the debug package prefix, a sorted
diff of the two sets is **empty**.
This is the most informative single result. The comparison was made against a binary built from the
audited source, so any permission present in Tangem's distributed build but not produced by that
source would be a direct indicator of divergence. There is none.
Both sets also **exclude** location and microphone permissions, confirming that the
`tools:node="remove"` stripping described in AUDIT.md §3.6 is genuine and survives into the shipped
artifact.
### 4.2 Native libraries — identical, plus protection runtime
| | Count | Detail |
|---|---|---|
| Build from audited source | 14 | see below |
| Official release | 16 | the same 14, plus `libdexprotector.so` and `libalice.so` |
```
libTrustWalletCore.so · libsecp256k1-jni.so · libTrezorCrypto.so · libargon2.so
libblst.so · libsqlcipher.so · libuniffi_yttrium.so · libtoolChecker.so
libtensorflowlite_jni.so · libbarhopper_v3.so · libjnidispatch.so
libimage_processing_util_jni.so · libandroidx.graphics.path.so · libsurface_util_jni.so
```
The audited source reproduces the official release's native library set exactly. The only additions
are the protection layer. `libalice.so` was not independently identified; it appears only in the
protected build alongside `libdexprotector.so`, which is consistent with it forming part of the
DexProtector runtime, but this was not confirmed.
### 4.3 Build configuration and identity
- `targetSdkVersion` 36 and `compileSdkVersion` 36 in both, matching the source configuration
- Package `com.tangem.wallet` (reference build carries the expected `.debug` suffix)
- Official APK signed by `L=Zug, O=Tangem AG`, certificate SHA-256
`e0fe798927cf94709b9d439c9ce728a90a2891698cdff31a4c4d63435b946748`, coherent with Tangem AG's
registration in Zug, Switzerland
**Note on the signing certificate:** this establishes only that the holder of that key signed the
artifact. It is a first-observation baseline, not an identity proof. Its value is comparative —
future releases can be checked against this digest to detect a signing-key change.
---
## 5. Discrepancies Against AUDIT.md
Both findings below arise from the same root cause: the audit examined the **source tree**, and its
conclusions were phrased as though they described the **shipped application**. Neither indicates
malicious behaviour. Both have been corrected in AUDIT.md.
### D-1 — Permission set understated (AUDIT.md §3.6)
The audit lists nine permissions as the "complete permission set across all manifests" and states
that external storage and `RECEIVE_BOOT_COMPLETED` are **absent**.
The nine are accurate for the hand-written manifests (`app/src/main/` and `app/src/huawei/`), which
were verified to declare exactly that set. However the **merged** manifest — in Tangem's release and
in the build from the audited source alike — contains 23. The fourteen not accounted for:
```
READ_EXTERNAL_STORAGE · WRITE_EXTERNAL_STORAGE · WRITE_SETTINGS
RECEIVE_BOOT_COMPLETED · FOREGROUND_SERVICE · USE_FINGERPRINT · POST_NOTIFICATIONS
BIND_GET_INSTALL_REFERRER_SERVICE · ACCESS_ADSERVICES_ATTRIBUTION
ACCESS_ADSERVICES_AD_ID · c2dm.RECEIVE · DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION
com.samsung.android.mapsagent.permission.READ_APP_INFO
com.huawei.appmarket.service.commondata.permission.GET_COMMON_DATA
```
These are introduced by manifest merging from bundled third-party SDKs — the same mechanism the
audit correctly credits Tangem for defending against with `tools:node="remove"`. The characterisation
"permissions MINIMISED" and "the set contains nothing a wallet does not require" is not supported by
the artifact as shipped. `WRITE_SETTINGS` and `RECEIVE_BOOT_COMPLETED` in particular warrant
explanation rather than omission.
### D-2 — "No native code" is true of the repository, false of the application (AUDIT.md §3.1)
The audit states "No `.so` libraries, no JNI, no NDK build inputs," and answers **No** to the
methodology question *"Does it ship native binaries or opaque blobs?"*
This is correct for the source tree — no `.so` file is committed. It is incorrect for the
application, which bundles fourteen native libraries even in the unprotected build (§4.2).
The distinction is material because several are security-critical: `libTrustWalletCore.so`,
`libsecp256k1-jni.so`, and `libTrezorCrypto.so` perform key derivation and signing; `libargon2.so`
provides key-stretching; `libsqlcipher.so` provides at-rest database encryption. These arrive
through the pre-built Maven dependencies that AUDIT.md §6.1 places outside the review's scope, so
the audit's scope statement does technically cover them. But a reader reaching "ships native
binaries: **No**" in the summary table will draw the opposite conclusion about the application they
are installing.
---
## 6. What This Analysis Does and Does Not Establish
**Does establish:**
- The official v6.1.2 APK is signed by Tangem AG and structured as the audited source predicts
- Its permission surface and native library set are exactly reproduced by building the audited
source, with no unexplained additions
- Gross tampering — repackaging, permission injection, substitution of a different application — is
ruled out
- The privacy hardening claimed in AUDIT.md §3.6 is present in the distributed artifact
**Does not establish:**
- That the official APK was built from the audited source, or from any particular source
- That the DEX bytecode is free of modification. DexProtector renders this unexaminable
- Anything about the pre-built Tangem SDKs, which perform mnemonic encryption and at-rest key
storage and remain unreviewed (AUDIT.md §6.1)
- That any *future* release will share these properties
A user installing the official APK is extending trust to Tangem's build pipeline and signing key.
Nothing available to a third party can substitute for that trust. This analysis narrows the range of
undetected substitutions; it does not eliminate it.
---
## 7. Method
All inspection used Android SDK build-tools 36.0.0.
```bash
# Manifest, version, permissions
aapt2 dump badging <apk>
# Signing certificate
apksigner verify --print-certs <apk>
# Package structure, native libraries, DEX count
unzip -l <apk>
```
Permission sets were extracted, normalised for the debug package prefix, sorted, and compared with
`diff`. Native library sets were compared with `comm` after stripping ABI directory components.
No decompilation, deobfuscation, or execution was performed. No attempt was made to defeat
DexProtector.
---
## 8. Limitations
1. **Static inspection of archive metadata only.** Application behaviour was not observed.
2. **Version mismatch.** The reference build derives from a commit dated one day after the official
artifact. Differences attributable to intervening source changes cannot be distinguished from
differences attributable to anything else.
3. **Reference build was not fully faithful.** It was produced with `verifyDesignTokens` skipped
(the `ds-tokens` submodule is unresolvable) and with `PLACEHOLDER` API keys. Neither affects the
manifest or native library comparisons on which the findings rest.
4. **Single point in time.** Applies to v6.1.2 build 1819 only. Re-verification is required for
every subsequent release, and the certificate digest in §4.3 is the recommended anchor.
---
*Binary analysis of `app-release-protected-1819-6.1.2.apk` against a reference build of commit
`358144ee61a`. Companion to AUDIT.md; findings D-1 and D-2 have been applied to that report.*