Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent Android's in-app purchase items being cracked

I have a free to download Android app, which comes with in-app purchase item (Non-consumable Items)

Recently, I found several black markets carry my Android app, with all the in-app purchase item can be accessible freely.

I was wondering, how does the crackers do so? This is what I had done on my side.

  • Have proguard to obfuscate my Android code.
  • Have the following in-app purchase code from Google official example. my onCreate will trigger updateIsPremium.

The code is as follow

private void updateIsPremium() {
    if (mHelper != null) {
        return;
    }

    /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
     * (that you got from the Google Play developer console). This is not your
     * developer public key, it's the *app-specific* public key.
     *
     * Instead of just storing the entire literal string here embedded in the
     * program,  construct the key at runtime from pieces or
     * use bit manipulation (for example, XOR with some other string) to hide
     * the actual key.  The key itself is not secret information, but we don't
     * want to make it easy for an attacker to replace the public key with one
     * of their own and then fake messages from the server.
     */
    String base64EncodedPublicKey = "...";

    base64EncodedPublicKey = decrypt(base64EncodedPublicKey);

    mHelper = new IabHelper(MyApplication.instance(), base64EncodedPublicKey);

    // enable debug logging (for a production application, you should set this to false).
    mHelper.enableDebugLogging(false);

    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
        public void onIabSetupFinished(IabResult result) {
            Log.d(TAG, "Setup finished.");

            if (!result.isSuccess()) {
                // Oh no, there was a problem.
                // (Do not use alert dialog. It is difficult to handle it
                // correctly due to bug in v4)
                //Utils.showLongToast(getString(R.string.problem_setting_up_in_app_billing_template, result.toString()));
                return;
            }

            // Have we been disposed of in the meantime? If so, quit.
            if (mHelper == null) return;

            // IAB is fully set up. Now, let's get an inventory of stuff we own.
            Log.d(TAG, "Setup successful. Querying inventory.");
            //mHelper.queryInventoryAsync(mGotInventoryListener);

            // http://stackoverflow.com/questions/15471131/in-app-billing-v3-unable-to-query-items-without-network-connection-or-in-airplan/15471951#15471951
            // Not sure this is going to help to solve the problem :
            // In-app billing v3 unable to query items without network 
            // connection or in airplane/flight mode
            List<String> skulist = new ArrayList<String>();
            for (Shop shop : Shop.values()) {
                skulist.add(shop.sku);
            }
            try {
                mHelper.queryInventoryAsync(true, skulist, mGotInventoryListener);
            } catch (java.lang.IllegalStateException exp) {
                // Ugly fix on mystery crash :
                // java.lang.IllegalStateException: Can't start async operation (refresh inventory) because another async operation(launchPurchaseFlow) is in progress.
                Utils.showLongToast(getString(R.string.failed_to_query_inventory_template, exp.getMessage()));
            }
        }
    });
}

// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
        Log.d(TAG, "Query inventory finished.");

        // Have we been disposed of in the meantime? If so, quit.
        if (mHelper == null) return;

        // Is it a failure?
        if (result.isFailure()) {
            return;
        }

        Log.d(TAG, "Query inventory was successful.");

        final MyOptions myOptions = MyApplication.instance().getMyOptions();
        myOptions.setInventory(inventory);

        /*
         * Check for items we own. Notice that for each purchase, we check
         * the developer payload to see if it's correct! See
         * verifyDeveloperPayload().
         */

        // Update shop information.

        boolean atLeastBoughtOne = false;
        for (Shop shop : Shop.values()) {
            final Purchase purchase0 = inventory.getPurchase(shop.sku);

            final boolean mIsPurchase0 = (purchase0 != null && org.gui.billing.Utils.verifyDeveloperPayload(purchase0));                

            if (mIsPurchase0) {
                atLeastBoughtOne = true;
                myOptions.bought(shop);
            } else {
                myOptions.cancel(shop);
            }
        }

        myOptions.turnOnShopChecked();
    }
};

// We're being destroyed. It's important to dispose of the helper here!
@Override
public void onDestroy() {
    super.onDestroy();

    if (this.isFinishing()) {
        // very important:
        Log.d(TAG, "Destroying helper.");
        if (mHelper != null) {
            try {
                mHelper.dispose();
            } catch (java.lang.IllegalArgumentException ex) {
                Log.e(TAG, "", ex);
            }
            mHelper = null;
        }
    }
}

// The helper object
public static IabHelper mHelper;

May I know, how does cracker able to reverse engineer my obfuscated code?

I was wondering, is there any more pre-caution steps I can take, to avoid my in-app purchase item being cracked? Will App Licensing help? My understanding on App Licensing is that, it is only useful for "paid app", not "free app with in-app purchase items"

like image 718
Cheok Yan Cheng Avatar asked Jan 28 '15 08:01

Cheok Yan Cheng


1 Answers

Reverse-engineering obfuscated code is actually not that difficult. I've done it for legitimate reasons, such as figuring out how to use a very poorly-documented library that some suit had committed us to using without consulting the development team. You just decompile the app or library and load it up in your IDE. The names of public methods can't be obfuscated, so you start from there, figure out what's what, and use your IDE's refactoring tool to start giving everything meaningful names. You can completely reverse-engineer an app in about a day.

For all practical purposes, you are powerless to prevent this kind of hacking. If your app can be run, it can be decompiled. If your media can be viewed, it can be copied. The only way to stop this would be to encrypt your software so it can only be run on a proprietary device with an embedded key, tamper sensors, and a self-destruct mechanism. That's why the software and media industries have been seeking legal rather than technical solutions -- or hybrid solutions where they come up with some totally worthless technical solution and then issue legal challenges to anyone who circumvents it.

like image 186
Kevin Krumwiede Avatar answered Sep 25 '22 16:09

Kevin Krumwiede