272 lines
13 KiB
Markdown
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.*
|