Files
apk-reverse/references/membership-and-limits.md
T
inliver233 c66658b6b3 feat: apk-reverse Agent Skill
Progressive-disclosure skill for Android APK reverse engineering:
recon, ad removal, membership limits, dex surgical patching, repack/sign,
runtime data, Frida, environment setup, and a hard-won failure catalogue.
2026-09-19 21:25:35 +08:00

5.3 KiB

Membership, paywalls, and the honest limits of client-side patching

Read this before investing hours. Most "unlock VIP / remove the paywall" requests cannot be satisfied by patching the client, and knowing that early is the most valuable output you can produce.

First question: who decides?

Signal Owner Client patch can...
A local boolean/int/string field in an app model, computed locally Client Fully determine the outcome
A field that comes from an API response and is merely read by the UI Server Change display only — not access
Download/stream/play URLs issued by an API Server Nothing. You cannot mint a valid URL.
Entitlement checked on every protected action with a server round-trip Server Nothing
A local cache of a server value Server (cached) Temporarily fool the UI until it refreshes

Decision rule: if the thing you want (a media URL, a decrypted payload, premium data) is delivered by the server, the client is not the gate. The gate is server-side and is not patchable.

How to prove it in minutes

Do not guess. Test the API directly (references/server-api.md).

1. Call the protected endpoint with no credentials.       -> 401/403 means auth-gated
2. Call it with an obviously forged token.                -> another 401 confirms it is validated
3. Call the metadata endpoint.                            -> often 200, giving you the shape,
                                                             but deliberately omitting the asset URL
4. Compare: is the valuable field simply absent?           -> then no client change can invent it

A concrete, real result pattern worth remembering:

  • GET /videos/{id} → 200, full metadata, cast list, source list — but no playback URL at all
  • GET /v2/sections/{id}/play-url → 401 with no token
  • same, with Authorization: Bearer <forged> → 401
  • GET /user/me with forged token → 401

That combination is conclusive: metadata is public, the asset URL is server-minted, and the token is verified. Client-side patching cannot unlock it.

What is legitimately achievable client-side

Don't overclaim, and don't underclaim either. Genuinely client-side wins:

  • Remove ads / SDKs. Ads are a client behavior (references/ad-removal.md).
  • Remove a UI gate where the underlying data is already delivered and only a local check hides it (a "locked" tab whose content is already fetched, a disabled button).
  • Skip promotional interstitials and self-promo popups driven by local flags.
  • Unlock client-only features: debug menus, extra player settings, higher local bitrate selection, gesture options.
  • Grant a client-side reward only if the reward is neither validated nor authoritative server-side (check first — a reward that later fails server validation is worse than no reward).
  • Bypass a client-side trial timer whose enforcement is purely local (but expect the server to re-assert on next sync).

What is NOT achievable, and how to say so

  • Paid content whose URL comes from the server. State it plainly and show the 401 evidence.
  • Server-validated rewards / purchases / credits.
  • Anything the server re-checks on the next request. The patch may look like it works for one screen and then silently revert.

Wording that is both honest and useful:

The client cannot unlock this. Metadata is public, but the asset URL is issued by the server, and the endpoint returns 401 for both anonymous and forged credentials — so no local change can produce a valid URL. What is removable client-side is X, Y, Z. To actually get access you need a real account with the required entitlement.

The fake-VIP trap

Forcing a local isVip()-style method to return true is the classic mistake. Two failure modes:

  1. Cosmetic only — UI shows VIP styling, but protected actions still fail server-side.
  2. Actively harmful — the client now believes it holds entitlements it does not, so it enters code paths that expect server data it never receives. Observed result: blank screen (process alive, nothing rendered, only a swallowed uncaught exception in the crash buffer).

If you patch such a method, patch it in a conservatively correct way, and verify all screens, not just the one you cared about:

  • Do not fabricate entitlements that gate data fetching.
  • Prefer patching presentation consumers of the flag over the flag's computation, if the flag is also used for access decisions.

Local state vs code — the deliverable question

A value can be "fixed" in two places:

Where Survives fresh install? Fits an APK deliverable?
Code (patched dex) Yes Yes
App data (datastore/prefs/db) No No — unless you also patch the read path

If a fix works on your device but a user reports it gone after reinstall, this is why (references/pitfalls.md P11). Re-implement the fix by patching the read path so it always yields the desired value.

Reporting template

Goal:            <what the user asked for>
Gate owner:      client | server | mixed        (evidence: ...)
Achievable:      <list, with evidence each>
Not achievable:  <list, with the 401/absent-field evidence>
Residual:        <what remains and the exact coupling that prevents removing it>
Verified on:     <device, build hash, what was exercised>