The VIP Flag That Didn't Matter: Server-Side Auth in a Scalping Assistant App

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


The VIP Flag That Didn’t Matter: Server-Side Auth in a Scalping Assistant App

Most “crack this app” writeups end the moment a boolean flips. This one is worth reading because the boolean didn’t matter — and the right lesson is on the developer’s side. The target is a scalping assistant: a tool for getting precise restock/drop alerts on hype sneakers, plus timed jump-and-buy helpers for limited-edition liquor and e-commerce flash sales. After registration and login, tapping the toolbox shows the familiar gate:

The “members only” gate

No free lunch. So I set out to see whether it cracks — or where its actual defenses are.

Unpacking

JADX-GUI first:

Loading the APK into JADX

Packed. Looking at the activity list:

The activity list

dumpsys activity top points at the blue box — the app’s main activity after launch. To see its code I have to unpack first, so I ran it through the FART unpacker and pulled the dumped dex files from the device.

Locating MainNewActivity in the dump

The main logic lives in 8937780_dexfile.dex. dex2jar, then JD-GUI:

JD-GUI on the converted jar

MainNewActivity wasn’t instruction-extracted, so it’s readable. Searching for the “members only” string lands directly on the gate:

The gate logic

if (MyApplication.user_vip == true) {
    // launch HyBridActivity, pass a URL
}

Simple: if user_vip is true, it launches HyBridActivity with a URL. The question is where user_vip gets set — and the functions here weren’t extracted, so I searched it. It’s read from local storage, shared_prefs/user.xml:

user_vip read from shared_prefs

Opening the file on-device:

user.xml

user_vip is false. Flip it to true, restart, tap the toolbox — no more “members only” toast, and I’m into the VIP toolbox:

Inside the VIP toolbox

“天下没有免费的午餐” indeed — but so far this looks like a textbook local-flag crack.

The module that refused to work

The guide says: paste a product’s share link into the “client BP” module and the app recognizes it, times the drop, calibrates against the server clock, and jumps to a fast checkout. So I tap it. Nothing happens.

The page is HyBridActivity, loaded through a DWebView component:

HyBridActivity

The key functions are instruction-extracted, but they look like initialization. The important detail is visible: the URL is assembled from another field in user.xml, auth. The final URL is:

https://[redacted]/skrtool.html?auth=[redacted]

Opening it in a desktop browser:

The WebView page in a browser

It renders just like the in-app page. Clicking “client BP” now navigates to a secondary page:

https://[redacted]/tool-tmallBP.html?auth=[redacted]

The secondary page

So why did the phone’s click do nothing? I pasted a product share link to see if I’d get a BP link back:

Paste a share link

A “parse failed” popup. The page’s real logic lives in its source — and it’s packed:

<script type="text/javascript">
;eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){...}}('3u(N).3v(A($){3 g=K;3 h="X";h=3w.3x("1K");4(!h||h==\'X\'){C.D(\'请前往官网Y://2g.2h更新最新版本\')}...',62,308,'|||var|if|...'.split('|'),0,{}));
</script>

A single eval blob with a packed dictionary. Untangling it is painful, but by now I had a hypothesis. On register/login, the account info is sent to the server; the server computes an auth value and writes it into shared_prefs/user.xml. Every BP page then ships that local auth back, and the backend looks it up — VIP returns a working parse page, non-VIP gets the warning. If the VIP check is server-side and I don’t know how auth is generated, I can’t forge it locally and the app is unbreakable. Time to verify with a capture.

Capture

Charles with HTTPS, starting from login (email login here):

The UserSign request

The first request is UserSign — a small JSON with timeStamp, an initial auth, the verification code, the email address, and a simple MD5 sign (recoverable from the code). The server returns a JSON containing auth, which the app writes to shared_prefs/user.xml.

That auth is then carried through every BP module. Now watch what happens when I paste a share link and submit:

The tmallBP request

The tmallBP request, continued

The request is:

https://[redacted]/aed0843961fb06dc/tmallBP?auth=[redacted]&mlink=[redacted short link]&callback=jsonp&_=1593173135285
  • auth — the local shared_prefs/user.xml value, straight from the server.
  • mlink — the short link extracted from the pasted share link.
  • callback — the JSONP callback.
  • _ — a timestamp.

So the VIP check really is server-side. Non-members get no useful result, just an alert. The local user_vip=true I set earlier did nothing but unlock the UI shell — the actual functionality is gated by a server-issued, server-validated token.

What this actually teaches

I went in expecting to crack it and came out agreeing with the developer: with the verification server-side and the auth generation unknown, this app isn’t crackable through the obvious path. The user_vip flag was a decoy.

That’s the pattern worth copying if you ship a paid feature in an app:

  1. Never make a client boolean the real gate. The local user_vip flag looked like the VIP check — and it was trivial to flip — but it controlled only the UI shell. The real enforcement was elsewhere.
  2. Issue a server-side token at auth. The auth value is produced by the server on login and validated on every privileged request. That’s the actual trust anchor, not anything on the device.
  3. Validate on every privileged request. Each BP page carried auth and the server decided. No client state, no local bypass.
  4. Obfuscation is defense-in-depth, not the defense. The packed JS slowed me down but the wall was the server-side check, not the minified code.

The one residual question — whether the auth generation itself can be reversed or replayed — is where any remaining attack surface would be. But as a baseline, this is server-side entitlement done right.


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch