27

I've throughtoutly searched this site as well as others for answers and found no actual one.

My question is what exactly does the Freedom Hack (which allows users to get in-app purchases without paying) do. That is, what part of the process is altered. I've found this list of applications for which the hack works, and some of the entries there are dated to this month, meaning that it hasn't been completely fixed yet. The responses I've seen were "verify the application in your server", but if the hack, for example, alters the Java.Security's signature verification function, so it always returns true, then adding my own signature in the server wouldn't help much.

Asaf
  • 616
  • 2
  • 6
  • 17
  • Check if the package name of Freedom Hack is present, if yes, ask the user to remove the application. – Mdlc Feb 23 '14 at 09:29
  • But if Freedom's package name is changed, the check wouldn't work anymore. Also, for all I know, it masks itself so it wouldn't be discovered (I'm sure it can do it with root) – Asaf Feb 23 '14 at 09:38
  • You would need to detect the Freedoms service, and (for as I know), freedom always displays a notification when active, perhaps you can detect that. – Mdlc Feb 23 '14 at 09:44
  • 1
    This is not a solution either, for pretty much the same reasons as I placed above. I'm looking for a solution for the functionality of Freedom, not to the application itself, which can easily hide any trace for its existence. – Asaf Feb 23 '14 at 11:02

3 Answers3

14

I don't know if the author still follow this topic or not. But I spent sometime to find out (googling) the way how freedom work and how to prevent it (until they update the way freedom work) in my project and it works. My implementation is really simple and you don't need to verify by sending request to server (which affect the performance and take more effort to implement it).

The current implementation of freedom is that it will replace (redirect) all the method calls of java.security.Signature.verify(byte[]) to a freedom's jni method which in turn just simply always return true (or 1).

Take a look at java.security.Signature.verify(byte[]):

 public final boolean verify(byte[] signature) throws SignatureException {
        if (state != VERIFY) {
            throw new SignatureException("Signature object is not initialized properly");
        }
        return engineVerify(signature);
    }

Here the engineVerify method is an abstract protected method which is first defined in java.security.SignatureSpi(Signature extends SignatureSpi). OK, that enough, because I can't believe java.security.Signature.verify(byte[]) method anymore, I would use engineVerify method directly. To do that, we need to use reflection. Modify the verify method of IABUtil/Security from:

public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        Signature sig;
        try {
            sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(signedData.getBytes());
            if (!sig.verify(Base64.decode(signature))) {
                Log.e(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (...) {
            ...
        }
        return false;
    }

To:

public static boolean verify(PublicKey publicKey, String signedData, String signature) {
        Signature sig;
        try {
            sig = Signature.getInstance(SIGNATURE_ALGORITHM);
            sig.initVerify(publicKey);
            sig.update(signedData.getBytes());
            Method verify = java.security.SignatureSpi.class.getDeclaredMethod("engineVerify", byte[].class);
            verify.setAccessible(true);
            Object returnValue = verify.invoke(sig, Base64.decode(signature));
            if (!(Boolean)returnValue) {
                Log.e(TAG, "Signature verification failed.");
                return false;
            }
            return true;
        } catch (...) {
            ...
        }
        return false;
    }

That is simple but it works with the current implementation of freedom until they update its algorithm in the future.

lemycanh
  • 827
  • 1
  • 11
  • 14
  • Great answer, I will try your solution soon. – Asaf Nov 25 '14 at 10:51
  • Sorry, not yet. We're currently busy on other aspects. I promise I'll accept once I test it. – Asaf Nov 27 '14 at 08:46
  • You can simply check if the method is implemented via native interface using java reflection. In this case return always false. – greywolf82 Jan 18 '15 at 08:25
  • how can i reflect and modify the Methods, all the example i found is changing fields – user987760 Jan 23 '15 at 09:37
  • @user987760 you can use some functions in android libdvm library, e.g.: dvmFindLoadedClass, dvmFindVirtualMethod, dvmUseJNIBridge,... Here is a good reference: http://conference.hitb.org/hitbsecconf2013kul/materials/D1T1%20-%20Collin%20Mulliner%20-%20Android%20DDI%20-%20Dynamic%20Dalvik%20Instrumentation%20of%20Android%20Applications.pdf – lemycanh Jan 24 '15 at 05:43
  • Thank you for this excellent simple solution! I replaced the verify function in my app and can confirm the above code is tested and working. Implemented server side receipt verification as well. – NULL Apr 24 '15 at 16:25
  • You can also "test" if the signature is actually verified by sending data you KNOW should return false. If it returns true someone tampered with it and abort. – Terminator_NL Mar 19 '17 at 12:16
  • No resolved with this answer. My InApp purchase app can be hacked with Lucky Patcher with root. – jazzbpn Jan 24 '18 at 06:37
9

then adding my own signature in the server wouldn't help much.

That is not correct, the signature that "Freedom" uses is invalid and the order id is also invalid.

What I did to ensure that my Application is safe is:

  1. Send isPurchaseValid(myPurchase.getSignature(), myPurchase.getOriginalJson()) to my server to verify over there and it works with real purchases but freedom fails everytime.

  2. On the server I check if the signature matches

  3. If it does match I contact "Google APIs Google Play Android Developer API > androidpublisher.inapppurchases.get" to verify that the Purchase exists and that returns my developer payload.

  4. I then use the developer payload to make sure that this purchase is for this specific user and not some other user and this user is sending me his data.

P.S. The developer payload is a String you set before the purchase is made from your android app, it should be something unique to your user.

It maybe a lot of work but It ensure that no one will buy your stuff with freedom and succeed.

The only thing that I am unable to do is not let freedom have an affect on my application, for example the folks in Path did something I don't know what which made Freedom have no effect what so ever!!!!

Shereef Marzouk
  • 3,282
  • 7
  • 41
  • 64
  • But after I verify the purchase in my server, I need to inform the application that the verification was successful. For this I sign the response with my own signature. Otherwise someone could make a fake server that mimics the response that my server gives and forward the request to there (all this without making changes to the application itself). So if Freedom alters Java's signature verification function, this measure will not help. – Asaf Mar 12 '14 at 12:57
  • Nope when I query my server for the purchases the user has I only get the purchase tokens and I query Google for the purchases and once again I get the payloads and from that I verify that the purchases are owned by this user – Shereef Marzouk Mar 12 '14 at 13:27
  • From what I understood you only described what you do in your server to verify the purchase, but not what you do in the client to verify the server is indeed your server. Correct me if I'm wrong. – Asaf Mar 12 '14 at 13:50
  • Well this answer is about verifying the purchase not securing the connection with you server, if you use https or a simple encryption you can securely communicate with your server. – Shereef Marzouk Mar 12 '14 at 23:00
  • When a purchase is done, even if it's fake i allow the user to enter and i have a background service that sends the data to the server for verification, in case it fails it keeps retrying until it succeeds but if it fails too many times within a day i send the user an email telling him that validation failed and if he feels there is an error that he should contact me and i reverse his purchase from my app, but then i get an email and i manually verify my merchant account..etc – Shereef Marzouk Apr 22 '15 at 11:54
2

I'm using something like this, I know it's not a good solution compared to a remote server check for your signature. I'm checking if Freedom app is installed, if so I'm not opening my app.

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);
    if(isHackerAppIsntalled())
        finish();
}

private boolean isHackerAppInstalled() {        
    final PackageManager pm = getApplication().getPackageManager();
    List<ApplicationInfo> packages = pm
            .getInstalledApplications(PackageManager.GET_META_DATA);
    for (ApplicationInfo packageInfo : packages) {
        String packageName = packageInfo.packageName;
        if (packageName.contains("cc.madkite.freedom")
                || packageName.contains("madkite.freedom")) {
            return true;
        }
    }
    return false;
}
nexx
  • 579
  • 1
  • 7
  • 17
  • It's a pretty bad idea. All the hacker needs to do is to change his package name (which is, of course, very simple). – Asaf Feb 21 '15 at 13:49
  • Yes I think so too, each time you need to update the package name too:( – nexx Feb 21 '15 at 13:59
  • and Freedom is not the only hacking app.. search for Lucky Patcher – GorvGoyl Jan 18 '17 at 10:48
  • 1
    it´s not the worst solution, because even the accepted answer will not work if this app changes the algorithm or another app works on another way. In fact, we can´t do something against such apps, it would be a endless cat and mouse game for developers to avoid something like this. We shouldn´t spend too much time for it. It makes me more sad that there are developers outside who creates such apps. They must be millionars because nobody can work for free, everybody has to eat and live....instead we should users make think about.... – Opiatefuchs Apr 13 '17 at 06:23