11 KiB
| title | description | status | category | tags | draft | created | updated | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Reverse engineering — methods & drift playbook | How the GoPay/GoBiz merchant APIs were reverse engineered — browser HAR captures and byte-for-byte replay, APK static analysis (apktool/androguard/strings), code archaeology, version-drift tracking, and the ethics/hygiene rules the researchers follow. | concluded | technical |
|
true | 2026-10-15 | 2026-10-15 |
Reverse engineering — methods & drift playbook
1. The three evidence channels
The API knowledge behind these gateways came from three complementary methods — plus one public spec that needed no reverse engineering at all:
| Channel | Object | Output | Fidelity |
|---|---|---|---|
| A. Browser HAR captures | portal.gofoodmerchant.co.id sessions (merchant's own login) |
Exact request/response shapes, headers, token formats | Highest — live traffic, byte-for-byte |
| B. APK static analysis | GoPay Merchant 2.3.0 (APKM), GoFood Merchant 5.49.0 (XAPK) | ~250 endpoint paths, hosts, anti-tamper map, sister-app evidence | Static strings only — mostly runtime-unverified |
| C. Code archaeology | Earlier open-source repos (kavionn, warungerik, namtxs, alhifnywahid) | Hypotheses, working patterns, prior art | Variable — often unverified, to be capture-checked |
| EMVCo QRIS spec | Public standard | TLV structure, tag 54, CRC16 rules | N/A (public) |
All methods operate on the researchers' own merchant accounts and publicly distributed artifacts; no backend probing, no third-party data. [S9 §5][S11]
2. Channel A — HAR captures & byte-for-byte replay
Documented captures (all on the merchant's own account) [S6 §9, S7, S13]:
| Capture | Client | Size / entries | What it verified |
|---|---|---|---|
gobizverbrowser.har |
iPhone WebView (mark.via.gp) → portal |
~80 MB, 1063 entries, 77 API calls | OTP flow, merchants/search, users/me, analytics, journals (+aggregation variant), payouts — plus 5 endpoints no repo had |
GOTO.har (19 Sep 2026) |
Firefox desktop | ~51 MB, 589 entries (2-min session) | OTP login drift: X-AppVersion bump, bare phone format, split headers confirmed |
emialku.har.json (19 Sep 2026) |
Reqable | 169 entries (19 s) | Email+password flow verified end-to-end |
| Browser HAR (3 Oct 2026) | Browser → portal | (size not stated) | New build v3.125.0; auth calls moved to the portal host [S13] |
Method, as documented in the repo's reproduction guide and reports [S6][S7][S9]:
- Capture the portal session with a normal browser/devtools export (Chrome/Firefox/WebView/Reqable) — i.e. record the merchant's own login and dashboard usage.
- Map every host/path, isolate the merchant API calls (e.g. 30 calls to
api.gobiz.co.idout of 589 entries), and pick the flows of interest. - Diff captured requests against client code header-by-header and field-by-field. Every mismatch is either a bug or drift; every match upgrades the client claim from [reported] to [verified].
- Cross-check the data endpoints (
merchants/search,journals/search, payouts, analytics) against the SDK's assumptions. - Fix minimally, mirror the browser byte-for-byte — the repo's stated principle ("tiru browser, byte-per-byte").
Credential hygiene is explicit — captures contain live access/refresh tokens, phone numbers, email, otp_token [S7 preamble]: HAR files are .gitignored and kept outside the repo; every sensitive value is masked in reports ("format only, e.g. 11-digit bare national digits"); APKs are deleted after analysis and only hashes retained. [S9 §3.9, S11]
3. The drift playbook (what breaks, and how it was found)
The portal is a moving target; the documented fixes read like a clinician's notes [S6 §9, S7, S13]:
X-AppVersionbumps (the client build string sent on/goid/*):platform-v3.107.0(reference repos, Jun–Jul 2026) →v3.119.0(mid-Sep capture) →v3.122.0(19 Sep) →v3.125.0(3 Oct). Evidence that the server gates on it: an OTP flow that "doesn't work" was fixed by bumping nothing but this string + phone format. [S7 §3.1/§3.3/§4]- Phone number normalisation: the portal always sends bare national digits (
851…); passing0851…or+62851…through produced server-side failures. Fixed with a normaliser that strips+,62, one leading0. [S7] login_typeis vestigial: reference repos sendlogin_type:"otp"on the OTP request; the browser sends no such field; server ignores it either way. QMID removed it to match the browser exactly. [S6 §9.2]- Auth host moved: by Oct 2026 the browser sends
/goid/login/request+/goid/tokentohttps://portal.gofoodmerchant.co.idwhile data APIs stay onhttps://api.gobiz.co.id. Clients updated to route per-call. [S13][S10] - Request-body drift: the portal started adding
_source:[…]tomerchants/search; the older bare{from,size}shape was kept after being verified non-broken. [S7 §3.4] - Split-header discipline: device headers (
X-AppVersion,X-PhoneModel, …) appear only on/goid/*; sending them everywhere or nowhere is a fingerprint mismatch. The SDK mirrors the split. [S7 §3.3]
[inference] The pattern across all six: GoBiz tolerates a plausible browser, not an arbitrary client. The RE work is less "break the gate" and more "stay indistinguishable from a current browser session" — an ongoing treadmill that explains why these repos need continuous maintenance and why version bumps break communities of users at once.
4. Channel B — APK static analysis
The QrisMerchantID project ran a full static-analysis pipeline on both merchant apps and published both the results and the reproduction guide [S8][S9]. Pipeline (from the guide) [verified — commands in S9]:
- Download the container from APKMirror/APKPure (APKM for GoPay Merchant 2.3.0 —
com.gojek.gopaymerchant; XAPK for GoFood Merchant 5.49.0). - Hash everything (
md5sum/sha1sum/sha256sum), verify signature/certificates with androguard (CN=Gojekcert 2014–2039 + Google Play App Signing; v2-signed). apktool ddecode → smali;stringson the Flutter AOT blob (libapp.so, 22 MB) for the Dart-half of the app.- Grep for URL/host patterns in smali + libapp strings → endpoint inventory; scan for secrets (with mandatory masking), anti-tamper indicators (frida/xposed/root/ptrace/Play Integrity counts), crypto usage.
- Write an honest report: findings graded (0 critical), everything unproven marked
BELUM TERVERIFIKASI+ TODO, dynamic analysis explicitly skipped (no device/emulator available), tools recorded for reproducibility.
Outputs relevant here [S8][S12]:
- ~150 GoPay-Merchant-app endpoints under groups: histories/reconciliation (
/api/v1/unified-histories,/api/v2/histories), finance (/api/v1/finance/balance/*), transactions & QRIS (/api/transactions/charge/qris-link,/api/v1/qris/shared), payouts, auth (/api/auth/token,/api/v1/login/complete,/api/v1/totp), PIN endpoints, configs. - Realtime WebSocket channel:
wss://gopay-merchant-raccoon.gojekapi.com/api/v1/events(protobufodpf.raccoon.v1beta1) — the app's push path, an eventual replacement for polling if someone traffic-verifies it. - Hosts incl.
gopaymerchant.midtrans.com(Midtrans gateway),customer.gopayapi.com,app.gwk.gopayapi.com, SSOaccounts.goto-products.com. - Anti-tamper is heavy (frida/xposed/root/emulator/Play Integrity checks in the app), so dynamic hooking of the app is hard; the web portal path (Channel A) avoided all of that, which is [inference] the strategic reason the tooling standardized on portal replay instead of app interception.
- Sister-app evidence: GoPay Merchant app and GoFood Merchant app share the same
CN=Gojeksigning certificate, 6 identical PIN SDK endpoint paths, and a shared module set — and GoPay Merchant's T&C links point at GoBiz. The maintainer ofgobiz-paymenthad answered a user question (issue #3) that GoPay Merchant transactions are visible on the GoBiz web for the same account; the APK work independently confirmed "same account/system, different API gateways". [S8 §5.4][S4]
Honesty caveat, verbatim in spirit: the endpoint CSV marks all ~249 entries as static strings, runtime use unverified (file column values are all BELUM TERVERIFIKASI). Only Channel-A endpoints are capture-verified. [S12]
5. Channel C — code archaeology & cross-verification
The flagship research doc [S6] shows the working method: clone and read the prior repos (kavionn's gobiz.js read fully; warungerik's user-tested gateway; merchantid for ShopeePay; namtxs/gopay-api as the customer-side comparator), then verify every inherited claim against captures before shipping code. Examples of corrections this produced: login_type (dropped), X-AppVersion (bumped), phone format (normalized), analytics/journal shapes (confirmed 1:1). Unverified claims kept labelled TODO. Prior art is credited (README credits list) as a hard project rule. [S1][S6]
6. What the reverse engineering produced — and what it couldn't
Produced [verified]: a complete, capture-backed map of the merchant web-portal API subset (auth, merchants, analytics, journals, payouts), a static endpoint map of the mobile apps, and a working pattern (dynamic QRIS + watcher) replicated across at least 8 independent repos within ~4 months.
Couldn't / didn't [unverified, per the sources' own TODOs] [S15]:
- the app-side live flow (needs device + frida/mitmproxy; app has anti-tamper);
- the realtime WebSocket event protocol (identified, never hooked);
- request signing specifics — no static signing secret exists in the client and none was observed on the web path, so "how the server authenticates beyond the Bearer token" is simply not answered;
- impact of the app's embedded Google API key / staging hosts (static indicators, never tested).
7. Counterpoint: reverse engineering vs. readable results
The knowledge was gained by replaying the merchant's own browser session, but the flagship gateway repo (gopay-api-gateaway) then closed the loop by obfuscating its own auth code (login.js, sessionManager.js built with javascript-obfuscator; no plain strings leak). So the community can reuse the public API knowledge, but cannot audit what the most popular implementation does with the credentials it collects — a distribution choice, not an RE constraint. kavionn/gobiz-payment and QrisMerchantID went the other way: fully readable code + published methodology. [S3][S2][S1]
8. Assessment
[inference] This is a textbook example of gray-box research on one's own entitlements: using (a) your own account, (b) your own traffic, (c) publicly distributed apps and code — while staying away from probing, credential testing, and third-party data. The research outputs are unusually well-documented for this niche (reproduction guide, conflict register, responsible-disclosure doc, masking rules). The remaining ethical question isn't the method — it's the use: the same knowledge automates your own merchant account, and nothing in the pipeline prevents someone from pointing it at an account that isn't theirs (stolen session → payouts/balance visibility). Nothing in the audited repos encourages that; all three carry disclaimers, and the gateways self-host credentials rather than relaying them to a SaaS. [S1][S11]