Google in app purchase and cracking - android

i developed an app that's become very popular and someone cracked it. I would like to know if someone know, first of all: how?, if someone know any workaround to avoid this. The app is using the in-app purchase as per google example to unlock some premium features in this way:
private IabHelper mHelper;
if (!isPro(getActivity())) {
mHelper = new IabHelper(getActivity(), KKK);
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
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.
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;
// Is it a failure?
if (result.isFailure()) {
return;
}
Purchase pro = inventory.getPurchase(PRO_STRING);
SettingsProvider.putSecBoolean(getActivity(), "pro", pro != null && verifyDeveloperPayload(pro));
}
};
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
if (mHelper == null) return;
if (result.isFailure()) {
return;
}
if (purchase.getSku().equals(PRO_STRING)) {
SettingsProvider.putSecBoolean(getActivity(), "pro", true);
}
}
};
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
#Override
public void onDestroy() {
super.onDestroy();
if (mHelper != null) {
mHelper.dispose();
mHelper = null;
}
}
and for the purchase process:
mPro.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
RandomString randomString = new RandomString(36);
String payload = randomString.nextString();
if (mHelper != null) mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(getActivity(), PRO_STRING,
IabHelper.ITEM_TYPE_INAPP, RC_REQUEST,
mPurchaseFinishedListener, payload);
}
});
Ok, someone in someway cracked it. This means the content available in the pro version are free without paid. Maybe someone can share his experience and suggest some way to avoid this?
And also, does anyone know how can that be done? Thanks

Brief explanation
Android applications are quite easy to crack. First of all, if you are not using any obfuscation on your code (ProGuard, DexGuard, ..), the code can be easily read and understood by using some tools like JD-GUI. In some cases, the smali code is pretty easy to understand as well.
The obfuscation itself won't save you from cracking. There are de-obfuscators available on the market, but someone with a high pitched reverse engineering skill will still be able to figure out how to bypass your (or Google's) protection.
Finally, there is LuckyPatcher. This is perhaps the most famous tool for cracking Android apps' protection. It targets specifically certain types of protections (Google's LVL, IAPs, advertising networks, ..) and tries to remove them on a statistical basis. In fact, it is not guaranteed 100%, but in the vast majority of cases it will work just fine.
How to be secure, then?
You simply can't. There is no perfect 100% security, especially in a mobile environment. What you can do, however, is trying to make a cracker's work as difficult as possible.
A few ideas:
Always obfuscate. It won't do any damage to your app (if configured correctly), and it will be yet another layer of protection to your code.
Use DexGuard's tampering detection function. It will definitely decrease the chances that your app gets cracked two days after an update is released.
There are a few more, I will add them as soon as I recall them all.

Your code is really simple to crack, you just need to replace
SettingsProvider.putSecBoolean(getActivity(), "pro", pro != null && verifyDeveloperPayload(pro));
with:
SettingsProvider.putSecBoolean(getActivity(), "pro", true);
It can be done in several ways as antilvl does for example. You need to increase your protection a lot maybe with cross-check of your own server.

Related

Android In-App Billing, missing purchases

I am using Google's In-App Billing for my Android app.
I used the IabHelper class from Google's how to, as their billing seems extremely complicated.
My issue is I want to know if the purchase is successful or not. I think I'm following the process correctly, but in my logs I see a lot of users that get the upgrade, but whose purchase never shows up in my Google Play payments account. (i.e. they get the upgrade for free).
I'm logging the GP order ids, sometimes its a number like,
GPA.1234-5678-9123-1234
But sometimes its like,
1234567891234.1234567891234
Normally I think its the non GPA orders that don't get charged.
Also I think you can put an order through, then cancel it, and still get the upgrade?
How do you ensure the user really paid?
Code:
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, final Purchase purchase) {
if (result.isFailure()) {
showMessage("Google Billing Purchase Error");
return;
} else if (purchase.getSku().equals(sku)) {
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
if (result.isFailure()) {
showMessage("Google Billing Error");
return;
} else {
if (inventory.hasPurchase(sku)) {
showMessage("Thank you for upgrading");
grantUpgrade();
// ** This line gets call, but no payment occurs.
}
}
}
};
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
}
};
mHelper.launchPurchaseFlow(this, sku, 10001, mPurchaseFinishedListener, "");
*** updated to check "inventory.hasPurchase(sku)" but still see users who get the upgrade but don't pay.
** maybe the users are using Freedom hack? Anyway to prevent this?
if (result.isFailure()) {
//If the user aborts or any other problems it will jump here
}
else {
//The user purchased some item, check out which it is
mIsPremium = inventory.hasPurchase(SKU_ANY_ITEM);
}
So concerning your question, this code already verify whether the user really purchased the item !
Purchase premiumPurchase = inventory.getPurchase(SKU);
boolean mIsPremium = (premiumPurchase != null
&& verifyDeveloperPayload(premiumPurchase));
if(mIsPremium){
...
}
The Google Play Store keeps track of purchases for you, so you shouldn't assume that just because a purchase was successful, the item will stay purchased. It's possible for a user to get a refund for a purchase. For this reason, you need to query the user's inventory every time you launch and adjust your grants appropriately. You would need to do this check anyways in order to support users that expect to have the grant when they switch to a new device or uninstall/reinstall the app.

Android in app purchase hanging the application

I have an Android app that launches the purchase flow from a dialog within a Fragment.
The purchase flow is launched like:
config.getIabHelper().launchPurchaseFlow(
faActivity,
sku,
Constantes.SOLICITUD_COMPRA,
faActivity.mPurchaseFinishedListener,
purchaseIdentifier);
Where:
config.getIabHelper() returns an instance of the IabHelper class as implemented in the google documentation (the same used in the trivialgame example)
faActivity is the parent activity of the fragment
Constantes.SOLICITUD_COMPRA is a positive integer
mPurchaseFinishedListener is the listener, which is implemented in the parent activity.
So the current flow should work like:
Make a purchase.
Acknowledge that the purchase was bought.
Update the UI so that it enables the user to use the purchased item rather than to buy it.
However it works like:
Make a purchase.
The application freezes and stops.
When the application is relaunched, the inventory is queried and the UI gets updated.
It seems like mPurchasedListener is never called.
To make it more weird, everything seems to work fine with test responses. I am testing the app in alpha with real responses and that is where the trouble appears.
Any ideas?
i've had this issue before my self and there are multiple things that could be causing this issue.
Firstly, please make sure that you've done the following for aplha stage testing and that you've added all the required permissions and aidl files.
1) Are you using a version of the app you've put on your device through android studio? If so, this could be your issue. Usually you'll receive a message notifying you that the version of the app you're using isn't compatible with in-app purchases. Alpha stage apps are actually downloaded from the google play store. You should have set up a google group that has access to a specific link that will allow you to open the google play store and download your alpha stage app.
2) Are you connected to the internet? Obviously launching a purchase flow with a real SKU will require an internet connection to the google play servers.
3) Did you set up this in app product up as a managed product? For simplicity, i highly recommend doing so.
if you've correctly set up everything as I mentioned above and are still having a problem, then likely it's an issue with how you setup and attempt to use your purchase flow.
Here's the steps i took to launching a purchase flow from within a fragment
Instead of setting up the IAB Helper in the fragment, set it up in the faActivity Class. We will then call the purchase flow method within the faActivity class from within the fragment in which you're viewing via Dialog Box.
This is how i set up in app purchases :
faActivity.java
IabHelper mHelper;
In the oncreate method :
String base64EncodedPublicKey = "your in app purchase key";
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new
IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result)
{}});
Add these methods as well :
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data)
{
if (!mHelper.handleActivityResult(requestCode,
resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
consumeItem();
}
}
};
public void consumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
} else {
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
/*The purchase was successfully consumed, now update the
//ui/award the user with their purchase (I suggest storing the fact that
they're premium or whatever within the shared prefrences of your app)*/
}};
String ITEM_SKU = "";
//Launching the purchase Flow
public void makeThePurchase() {
//Assign the SKU Name of your managed product
ITEM_SKU="premiumMembership";
mHelper.launchPurchaseFlow(this, ITEM_SKU, 10000,
mPurchaseFinishedListener, "mypurchasetoken");
}
Now in your fragment, use this code to show a dialogBox asking the user if they wish to purchase the product and launch the purchase flow if they wish to do so
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
builder.setView(inflater.inflate(R.layout.dialog_signin, null))
.setPositiveButton("Purchase", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
((faActivity)getActivity()).makeThePurchase();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.setTitle("Purchase");
builder.setMessage("Purchase premium membership?");
builder.create().show();
And that is how i managed to launch a purchase flow from within a fragment. I'm sure there is a better way to do so, but when setting up the purchase flow from within the fragment, i always recieved issues as well when trying to launch a purchase.
Hopefully this solved your issue!
Good Luck!!! :)
NOTE: The code i've listed above is setup to consume the purchase (meaning that the fact that they own it won't show up when querying the inventory) Like i said above although, if you're not concerned about the users having to repurchase their items/premium purchases in the occurance of them getting a new device or uninstalling your app, then just keep track of what they've purchased and consumed within the shared preferences by calling this code below from within the onConsumePurchasedFinished listener :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("hasBoughtPremium",true);
editor.apply();
Then when reloading the app, check to see if they own the premium item :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
Boolean isPremium = pref.getBoolean("hasBoughtPremium",false);

Android In-App-Billing refund/cancel takes long

)
I'm trying to implement in-app-billing within my app. In the Google Play Developer Console I declared a managed item. Buying this item works really fine. But now, when I refund or cancel the purchase in the google wallet merchant center, my app takes very long (more days) to recognize that the item is not longer owned.
I've already read lots of other articles about this problem and think one logical explanation is that the purchase is saved in the cache of the Google Play Store. Although I know that this question has asked often before, I ask here again:
Is it possible to clear the cache or does anyone know how to tell my app, when the purchase is not longer owned?
I'm thankful for any hint, that helps me to solve this problem :D
In addition, my code where I ask, if the item is purchased. I'm using in-app-billing v3.
public boolean hasUserBoughtItem() {
try {
Bundle ownedItems = mService.getPurchases(mUsedAPI, mContext.getPackageName(),
mPurchaseType, null);
int response = ownedItems.getInt("RESPONSE_CODE");
if(response == 0) {
ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
if(!ownedSkus.isEmpty()) {
for(String sku : ownedSkus) {
if(sku.equals(Constants.ITEM_ID_ALL_RECIPES)) {
return true;
}
}
}
return false;
}
} catch(Exception e) {
e.printStackTrace();
}
return false;
}
The IAP purchase inventory isn't cached by the Play Store at all and should be queried regularly in your activities. It should only take approximately 15-30mins for order cancellations to propagate.
Are you using the IABHelper as per the sample app to connect to Google Play?
IabHelper.QueryInventoryFinishedListener mGotInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// handle error here
}
else {
// does the user have the premium upgrade?
mIsPremium = inventory.hasPurchase(SKU_PREMIUM);
// update UI accordingly
}
}
};

Not being able to establish connection for In app billing in android

I am trying to integrate In app billing v3 in my app. I call the following function to initialize.
public static void SetupInappBilling()
{
mHelper = new IabHelper(context, base64EncodedPublicKey);
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()
{
public void onIabSetupFinished(IabResult result)
{
if (result.isSuccess())
{
Log.e("tag", "connected");
ConnectionEstablished = true;
}
else
{
Log.e("tag", "not connected");
ConnectionEstablished = false;
}
}
});
}
but i dont get call back in OnIabSetupFinishedListener
Can anyone tell me how to fix this issue
As discussed, the example IabHelper implementation ignores the return value of the bindService() call.
bindService() returns false if it cannot bind to the Service. In this case, no callbacks will ever get called. In this respect, the return value of bindService() is essential for the program logic and should never be ignored; the example application is not exactly perfect here.
If I recall this correctly, conditions where binding to the IAB V3 will not work are:
Only IAB V2 is available (it uses a different class name)
Google Play is not fully set up

In-App Billing test: android.test.purchased already owned

I am currently testing In-App Billing for a future app, and after I successfully "bought" the test item "android.test.purchased" the first time, I now receive the response code 7 every time I try to buy it again, which means that I already own this item.
12-15 23:02:14.149: E/IabHelper(19829): In-app billing error: Unable
to buy item, Error response: 7:Item Already Owned
From what I understand, this purchase is supposed to always be possible, right? So that the developer can test his/her app?
If not, how can I "reset" its state to not owned? I am using the util package from the Google In-App Billing Sample.
Add this code to a thread to initiate consume request.
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
Here for the purchase test, purchaseToken is
purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
And
if (response == 0)
then the consumption is successful.
also don't forget to make mService public in
IabHelper.Java
then it would be possible to access like this:
int response = mHelper.mService.consumePurchase(3, getPackageName(), purchaseToken);
No need to write any special consumption code. Just use the adb command for clearing the Google Play Store data:
adb shell pm clear com.android.vending
It turns out that the android.test.purchased item behaves like a regular ID. It means that if you want be able to buy it again, you have to consume it somewhere in your code. I think that the Google documentation is misleading on this matter, and that they should add another static ID that you can buy endlessly for test purposes.
In-app version 3:
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
.....................
if (inventory.hasPurchase(SKU_CONTENT)) {
mHelper.consumeAsync(inventory.getPurchase(SKU_CONTENT), null);
}
}
};
Version 3 - Fastest way to solve : Clearing the cache of Google Play Store will let "android.test.purchased" available again.
This is how we can consume the Item
consume.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
String purchaseToken = "inapp:" + getPackageName() + ":android.test.purchased";
try {
Log.d("","Running");
int response = mService.consumePurchase(3, getPackageName(), purchaseToken);
if(response==0)
{
Log.d("Consumed","Consumed");
}else {
Log.d("","No"+response);
}
}catch (RemoteException e)
{
Log.d("Errorr",""+e);
}
}
});
t.start();
}
});
In my opinion if your program is not designed to consume the item you do not need to tweak the code in order to clear the memory of an outside vendor. This will make your code more fragile and you will have then to spend a lot of time to add and remove code that does not belong to your software so it is a bad design to implement a solution like that.
The best solution that worked for me to clear android.test.purchased was
adb uninstall com.yourapp.name
and then
adb shell pm clear com.android.vending
I did not need to clear cash and to browse my apps setting or to change code for that. I did need to add the adb to path variables of windows system which was pretty straight forward. So yes you need to use adb which you probably need anyway so..
You just add your C:\ ...\android-sdk\platform-tools; in windows path in environment variables, and I imagine that it is pretty simple in mac and linux os as well. Hope it helps someone to spend few days less with implementing android in app billings.
Go to the Google Play Developer Console, open Order Management menu item from the left side and select the order you want to refund. Also make sure to remove the entitlement.
The main issue is you have to consume the android.test.purchased item. But this item won't be available in your query inventory, so you can't consume using the normal flow.
So, if you are using IabHelper, in IabHelper class, you can temporarily change the IInAppBillingService mService to public so that it is accessible from your IabHelper.
Then in your class, you can consume like this,
int response = mHelper.mService.consumePurchase(3, getPackageName(), "inapp:"+getPackageName()+":android.test.purchased");
If success, the response is going to be 0.
Hope this helps.
For testing purposes I also suggest you to insert a piece of code that will be clearing all the products that you've bought before calling a method that initializes gp purchase flow. That is especially comfortable, when you test just one item at the moment. E.g. like this:
PurchasesResult purchasesResult = mBillingClient.queryPurchases(BillingClient.SkuType.INAPP);
for (Purchase sourcePurchase : purchasesResult.getPurchasesList()) {
if(sourcePurchase != null){
ConsumeResponseListener listener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(String outToken, #BillingResponse int responseCode) {
System.out.println("all consumed");
}
};
mBillingClient.consumeAsync(sourcePurchase.getPurchaseToken(), listener);
}else{
System.out.println("null");
}
}
// and then initiate whole process with clear "shoping basket"
BillingFlowParams.Builder builder = new BillingFlowParams.Builder()
.setSku(itemName).setType(BillingClient.SkuType.INAPP);
If you are in test environment
1) In the case of android.test.purchased, I can reset the fake payment by restarting android device(consumed the inventory).
2) In InApp util there is a file called Security.java make it as following, for temporary. Since the testing payment(fake) always return false due to security exception.
public static boolean verifyPurchase(String base64PublicKey,
String signedData, String signature) {
return true; }
Then in your OnIabPurchaseFinishedListener call fechInvForconsumeItem()
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
// Handle error
Log.e("123","Failure");
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
Log.e("123","PURCAsed");
fechInvForconsumeItem(); // Restart device if not consume
}
}
};
The fechInvForconsumeItem() is
public void fechInvForconsumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
Log.e("11","Failure");
} else {
Log.e("11","suc");
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
Consume Listener is
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
if (result.isSuccess()) {
} else {
// handle error
Log.e("11","sucConsume");
}
}
};
IabHelper.QueryInventoryFinishedListener
mQueryFinishedListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
if (result.isFailure()) {
return;
}
try {
if(inventory.hasPurchase("product_sku_id"))
{
isItemEnable= true;
mHelper.consumeAsync(inventory.getPurchase("product_sku_id"),null);
}
else
{
isItemEnable = false;
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
In my case, it appears that Google does not record a purchase for the item. Rather, the local copy of Google Play Services caches the purchase. That way, when a second request is made on the same device, android.test.purchased already owned appears. However, using another device or resetting the device clears the cache, and allows the purchase to be repeated.
In my case, I just needed to clear the apps cache. After clearing the cache, I was able to initiate the purchase flow again.
From my device (4.4.2), I navigated to "Settings->Application manager". Next, I selected the app from the "DOWNLOADED" tab, and then "Clear cache".
This is the difference between consumable and non-consumable items; non-consumable items (what you seem to be dealing with here) have their state tracked persistently, while consumable items can be purchased multiple times. You'll have to go into your Play management console and cancel/refund the sale to test it again.

Categories

Resources