How I hacked an Android app for a premium feature

Merab Tato Kutalia
ProAndroidDev
Published in
5 min readOct 18, 2021

--

Photo by FLY:D on Unsplash

This blog is for EDUCATIONAL PURPOSES only as it exposes common vulnerabilities in Android applications. DO NOT try this on real applications.

TL;DR

I rewrote the bytecode to unlock premium features of the app on my device and now I will show you how to prevent it from happening to your apps.

Do not download APKs from random websites, as they might have injected bytecode and potentially steal the data from device.

Paid Features with Billing

Lots of Android apps offer in-app purchases to users for the premium features or some kind of tokens for games, etc. Once bought with the user's Google account it is stored forever on the Google server with its unique purchase id. This allows purchased items to be shared across the devices.

But what about Huawei where Google Play Services is not running/available? Huawei provides an almost identical In-App Purchases (IAP) API, although user can’t transfer their purchases and subscriptions between platforms.

You can read more on implementation details here: https://developer.android.com/google/play/billing/integrate

Problem

Sometimes applications try to use shortcuts and do not follow full instructions and recommendations by Google. It means, sometimes they opt-out validating previously purchased items on app start, therefore leaving an open door for any malicious actor. Doing things on-device is never a good idea.

The story below applies to a wide range of applications and can be done on different levels. This blog tries to underline obvious pain points and recommend solutions.

Let’s talk about my use case, I purchased a premium feature on Google Play but the app itself was not available on the Huawei store. So I downloaded APK and installed it manually but the premium features were not available.

Static Code Analysis

The very first step is to decompile the APK into Smali code. I will not go into details, because it can be found anywhere on the internet. During the analysis, I found out this application X was saving the boolean flag in the Android SharedPreferences after the successful transaction. Doing those kinds of operations on-devices is the red flag 🚩!

Premium feature as a flag in SharedPreferences

This boolean flag was used to check if a user had an active premium feature later in the app.

As a result, we can find a correct place to inject our bytecode to bypass the check — always override it with the flag TRUE.

Smali

I don’t want to jump directly to the result and explain what’s going on in-depth. When we compile a Java application, bytecode is generated. On the Android platform, we had Dalvik Virtual Machine and now Android Runtime (ART), instead of Java bytecode it is converted to the instruction set in .dex format Dalvik Bytecode which can be converted to human readable Smali code. Smali is very similar to Java bytecode.

Here is the snippet of the beginning of the onCreate method from MainActivity in Smali:

The following code can be translated to:

where flag 128 constant for FLAG_KEEP_SCREEN_ON from WindowManager.

The very first line is method signature, but what are the .locals, p0, p1, v0?

v0..v15 are just local variables. Methods have 16 registers.

.locals with the number describe how many free variables this method has. It is purely a debug flag put by the decompiler which helps us a lot during the static code analysis.

p0..p15 are the parameters of the method we are injecting.

For example, onCreate has 2 parameters p0 and p1 which translates to “this” and Bundle respectively -> super.onCreate(Bundle)

Rule of thumb: Reuse existing “v” variables whenever it is possible.

Dalvik OpCodes:

Code Injection

After all the steps above we know that to bypass the check for a premium feature we just need to inject code that sets boolean value before anything loads. The most preferable place is in the Application class or the main Activity depends on the application. Let’s say we are going to inject it in MainActivity#onCreate.

This code snippet is the same as

SharedPreferences.Editor editor = sharedPref.edit();
edit.putBoolean("preferencePremium", true);
edit.apply();

The easiest way to achieve this is to write the actual Kotlin/Java code, compile and then decompile it to get the Smali out of it, or directly write Smali bytecode which I prefer for simple tasks like this.

After that, the app will always start with the boolean flag true.

The same method is used by websites that distribute hacked applications.

How To Prevent?

  • Move logic to your backend!!!
  • Always verify purchased item and then give access to the app
  • Make frequent validations with Google via your backend
  • Use encryption to save data locally and preferably use a device-specific encryption key
  • Use SafetyNet to disable rooted devices and potential use of Xposed Framework

Check what purchased items are available for the current user on the app start and then proceed. Do not store anything sensitive on the device. If you are going to save unlocked content on the device, make sure to check its validity often and encrypt it with a device-specific encryption key. In case of malicious activity use a temporary ban is a good practice.

Read more recommendations from Google:
https://developer.android.com/google/play/billing/security

That’s all for now.

Thanks!

--

--