Skip to content

stealth: m0.5 borrow system app store session#1

Merged
Evil0ctal merged 7 commits into
mainfrom
feat/m0.5-stealth-poc
May 5, 2026
Merged

stealth: m0.5 borrow system app store session#1
Evil0ctal merged 7 commits into
mainfrom
feat/m0.5-stealth-poc

Conversation

@Evil0ctal
Copy link
Copy Markdown
Owner

what

stealth path proof-of-concept (m0.5). adds 4 system-session importer
strategies and a debug-only diagnostics view that runs all of them and
exports a redacted markdown report.

why

so the user does not have to log in a second time inside EvilStore. the
real apple-id session is borrowed off-device, removing the new-device
risk-control path that asspp users hit.

changes

  • new SystemSessionImporter protocol + CompositeImporter chain
  • path A — accountsd (ACAccountStore via objc kvc bridge)
  • path B — filesystem (/var/mobile/Library/com.apple.itunesstored/
    • Apple Binary Cookies v0x100 reader, fixture-driven test)
  • path C — keychain access group com.apple.itunesstored
  • path D — xpc storeaccountd: skipped, marker only
  • StealthDiagnosticsView under #if DEBUG; release tipa drops it
  • Util/Logger redaction helpers + Account.debugDescription tail-mask
  • entitlements gain path A + path C ents (will be trimmed once the
    diagnostics matrix shows what is actually reachable per ios version)
  • CI fixed: macos-15 runner, default xcode 17, swiftformat 0.55+ syntax

test

local: xcodebuild archive Debug + Release both green; final tipa carries
all 10 entitlements; lint clean.

real-device verification (m0.5 task 6) lands as follow-up commits adding
docs/m05_diagnostics/_.md reports.

Evil0ctal added 7 commits May 5, 2026 11:20
add the empty shells for system-session borrowing:
* SystemSessionImporter protocol + SystemSessionError enum
* CompositeImporter chain (first success wins, all-fail aggregates)
* Account / HTTPCookieBox domain models with redacting debugDescription
* Util/Unimplemented placeholder for the importer impls landing next

no business logic here; project.yml extended so xcodegen picks up
EvilStore/{Core,Domain,Util}.
path B reads /var/mobile/Library/com.apple.itunesstored/ off-disk:
* BinaryCookiesParser: pure-byte v0x100 reader (apple format)
* FileSystemImporter: accountInfo plist + accountTokens (best effort) +
  Cookies/com.apple.itunesstored.binarycookies via the parser
* multi-key fallback for accountInfo schema drift across ios versions
* storefront head extraction "143441-19,29" -> "143441" matching the
  authenticator response shape

needs no entitlements beyond the m0 minimal set.

unit test exercises the parser against a hand-built 236-byte fixture with
two cookies. tests run on real device under m0.5 task 6 — simulator path
is intentionally not trusted (see 2-doc §8).
path A walks ACAccountStore for both com.apple.account.AppleAccount and
com.apple.account.iTunesStore. private property names (DSID, AltDSID,
storefront, oauthToken) are reached via valueForKey: with @Try blocks
because the keys drift across ios majors. on success the importer maps
to Account; on failure the objc bridge stashes a human-readable reason
that AccountsdImporter classifies into notLoggedIn vs entitlementDenied.

bridging header lands at EvilStore/EvilStore-Bridging-Header.h and is
wired through SWIFT_OBJC_BRIDGING_HEADER in project.yml.

entitlements gain com.apple.accounts.appleaccount.fullaccess and
com.apple.private.accounts.bundleidspoofing for the PoC. these will be
trimmed to a verified-minimum once the m0.5 diagnostics matrix shows
which ones are actually needed per ios version.
path C: SecItemCopyMatching against access group com.apple.itunesstored
for DSID, passwordToken, Storefront, GUID. multi-key fallback handles
the same property-name drift seen on paths A and B.

isAvailable() probes with kSecMatchLimitOne so it cheaply distinguishes
"no entitlement" (errSecMissingEntitlement -34018, common on ios 16+) from
"entitlement granted, just empty".

entitlements gain:
* keychain-access-groups = ["*"]
* com.apple.private.keychain.allowed-application-groups = [com.apple.itunesstored]
* com.apple.private.keychain.unrestricted

if real-device m0.5 testing shows path C is denied across the board,
docs/4 §3.5 says we may drop the entitlements again rather than carry
ones we can't use.
debug-only Settings entry runs all SystemSessionImporter strategies in
order, renders a redacted markdown report with tail-4 secret masking,
and hands the markdown to UIActivityViewController for AirDrop.

* SettingsView placeholder takes over the 4th tab
* StealthDiagnosticsView under #if DEBUG; release builds drop it entirely
* Util/Logger.swift adds redactTail + Log.info field-scrubbing helpers
  (single source of truth — Account.debugDescription now uses Util's redactTail)

verified Debug + Release archive both pass and the final tipa carries
every m0.5 entitlement (path A + path C ents) plus the m0 baseline.

next step: airdrop the .tipa to ios 14/15/16/17 devices, run the
diagnostics, write the redacted reports into docs/m05_diagnostics/.
three CI failures observed:

1. swiftformat 0.55+ rejects `--lint EvilStore EvilStoreTests` because
   --lint is now a pure boolean flag. moved the flag after the paths.

2. xcodegen 2.45 emits objectVersion=77 (xcode 17 format). the runners
   were pinned to Xcode_15.4.app which can't open it. switch both
   workflows to macos-15 + default xcode (ships 17.x).

3. swiftformat picked up real issues across the m0.5 sources (hoistTry,
   spaceAroundOperators, trailingCommas, etc). applied auto-format to
   bring 17 files to spec.

also added `--ifdef no-indent` to .swiftformat so `#if DEBUG` blocks at
file scope stay un-indented and greppable.

verified locally: lint ok, debug+release archive both green.
design and planning docs (architecture, build layout, ui wireframes,
m0.5 stealth poc plan, m0 getting-started) live only on the maintainer's
machine. they leak too much intent — full private entitlement table,
ios-version session-borrowing tactics, risk-control analysis — and have
no operational value to a downstream user.

* gitignore now hides docs/
* git rm --cached drops the 5 markdown files; local copies stay on disk
* README rewritten as a public-only quickstart (no doc cross-links)
* code comments referencing docs/N §X stripped to keep the public source
  self-explanatory

what stays public: source, build scripts, ci, project.yml, xcconfig,
LICENSE, README, entitlements (declarative, not secret).
@Evil0ctal Evil0ctal merged commit 2220066 into main May 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant