Protecting In-App Purchases and Network Traffic
Since Android 7, apps don’t trust certificate authorities the user installed. On a normal phone that stops a capture app cold. On a rooted one, CheatDroid puts its certificate authority into the system store, next to the ones Android ships, and the rule no longer applies to it.
What capture does
CheatDroid sends one app’s connections to a proxy on the phone, with iptables rules for that app’s UID, so nothing else on the phone is touched. The proxy completes the TLS handshake with a certificate it makes on the spot for the host the app asked for, and opens its own connection to the real server. Every request and response lands in a list, bodies included, and any request can be edited and sent again.
The “don’t trust user certificates” default doesn’t help here, and neither does a network security config that only says <certificates src="system"/>. The certificate is in the system store now.
Pin your server’s key
With pinning, the app accepts only your server’s public key, whatever the trust store says. CheatDroid’s certificate fails the check and the handshake fails. CheatDroid then passes the connection through untouched, and all it records is the host name.
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<pin-set expiration="2027-06-01">
<pin digest="SHA-256">base64 of the current key's SPKI hash</pin>
<pin digest="SHA-256">base64 of the backup key's SPKI hash</pin>
</pin-set>
</domain-config>
</network-security-config>
Pin the key, not the certificate, so a renewal with the same key changes nothing. Always ship a backup pin for a key you hold but don’t use yet: a certificate change with no matching pin locks every installed copy out of your server until people update. OkHttp’s CertificatePinner does the same in code. In Unity, give UnityWebRequest a certificateHandler that compares the server’s public key.
Pinning hides your API, which is worth something on its own. It doesn’t make the API safe. Everything below still has to hold when someone reads your traffic with a hooking tool that switches pinning off.
Purchases: the server decides
Some games remember a purchase as a 0 or a 1 in PlayerPrefs. That’s one tap in a file editor, and I won’t write that tutorial, but someone will. Send the purchase token to your server, check it with the Google Play Developer API, store the entitlement there and let the app ask for it. CheatDroid can’t fake Google’s answer to your server, because that connection never touches the phone.
Rewards: one token, one reward
“Watched an ad, give me 50 gems” as a request from the app can be sent a hundred times from CheatDroid’s request list. Rewarded ads from AdMob and AppLovin MAX can call your server directly when an ad completes; grant the reward from that callback, once per transaction ID. For rewards of your own, have the server issue a one-time token and spend it on the first claim.
Scores
A score posted by the app is whatever number the app sent. Check it against what’s possible: the level’s maximum, the time the round took, how far the player was yesterday. Pinning only sends a cheater back to editing memory, and a plausibility check catches both.