[Update: 8/24/10 @ 7:45 PM EST by Aaron] Tim Bray responded to Justin's article, but seems to have misunderstood the goal. Thus, Justin has written a follow-up article here.
This article was not written to teach people how to pirate or ridicule Google's Android License Verification Library (LVL) that handles communication with Google's Android Market Licensing Service.
I am very much against piracy, and very much pro-Google. I have spent more time researching copy protection for my applications than development of the applications themselves.
I would like to thank:
- the author of Star Hunt for allowing me to use his application in my demo video
- the author of Tasker for allowing me to use his application, which has the best implementation of LVL I found, in this article
Both of these applications are available in the market - I highly suggest you give them a try. Support your developers, and pay for your apps.
Since I started doing Android development, I have looked at many licensing options, from simple forward locking (/data/app-private), which was a complete failure, to KeyesLab's AAL which I really liked, to a custom one I was privy to, written by my friend Colin O'Dell. I even went as far as writing two different ones myself.
By far the best looking, and nicest option I have seen is Google's own Android Licensing Service. However, even Goliath can fall to a pebble.
A few days ago, after asking the AndroidPolice editors to write an article on piracy, I found a problem with Google's Android License Verification Library. A minor patch to an application employing this official, Google-recommended protection system will render it completely worthless.
Our findings show that most (any?) apps can be easily patched and stripped of licensing protection, making them an easy target for off-Market, pirated distribution. By corollary, this means that sites dedicated to pirating apps can continue to do so, using a few automated scripts mixed with some smarts.
Watch this demonstration video of my patch method tricking both the protected version of the game StarHunt and the Google LVL demo app into thinking they have been purchased legitimately:
I am providing an unpatched app based on Google's sample, compiled with my public key, and a patched version. The unpatched version will fail validation, because you have no license for it. The patched version will always pass validation, no matter what the case
No demos of other patched apps will be provided publicly out of respect for their developers.
Breaking The Library (aka The Technical Mumbo Jumbo)
A little back-story on Java, which most Android applications are written in. Java applications are compiled into bytecode, that runs on top of a Virtual Machine, generally independent of platform. Due to the need for cross compatibility, the bytecode is fairly readable. Many software suites exist to decompile/disassemble it, making it an easy target for reverse engineering.
For Android, the main disassembly suite is smali/baksmali. The bytecode output from baksmali can be edited in any text editor, and reassembled using smali.
Because the License Verification Library is not part of the Android OS (i.e. it doesn't ship with phones - it's an optional SDK download), an app developer needs to package it with the app that uses it, making it an easier patch target, without requiring root access.
The first step in reproducing this is to dissemble the apk using baksmali and find the LicenseValidator class. In custom implementations and pro-guarded apps like Tasker, this filename will differ, and so will the code, but not enough to stop a pirate from patching it.
When disassembling the basic implementation of the licensing service, you will find this file out/com/android/vending/licensing/LicenseValidator.smali. This class responds back to the application, telling it the results of the verification attempt. Opening this file in a text editor will show you the bytecode, and at the beginning you will see these constants:
.field private static final ERROR_CONTACTING_SERVER:I = 0x101 .field private static final ERROR_INVALID_PACKAGE_NAME:I = 0x102 .field private static final ERROR_NON_MATCHING_UID:I = 0x103 .field private static final ERROR_NOT_MARKET_MANAGED:I = 0x3 .field private static final ERROR_OVER_QUOTA:I = 0x5 .field private static final ERROR_SERVER_FAILURE:I = 0x4 .field private static final LICENSED:I = 0x0 .field private static final LICENSED_OLD_KEY:I = 0x2 .field private static final NOT_LICENSED:I = 0x1
This code itself is unimportant, and may not show up in custom implementations, but it will help you understand the next step.
Scrolling to the bottom of LicenseValidator.smali, you will see this block of code in the “verify” method:
.sparse-switch 0x0 -> :sswitch_d3 0x1 -> :sswitch_de 0x2 -> :sswitch_d3 0x3 -> :sswitch_11d 0x4 -> :sswitch_f3 0x5 -> :sswitch_101 0x101 -> :sswitch_e5 0x102 -> :sswitch_10f 0x103 -> :sswitch_116 .end sparse-switch
This is a switch block, which essentially tells the licensing library what to do next, depending on the results of the verification query. Each possible result is “mapped” to a different function.
Notice how the values on the left correspond to the constants at the beginning of the file. 0X0 and 0x3 are both positive results, which will tell the application that your device has a valid license. The others are various forms of negative results, and depending on how the application is coded, will result in different things.
The important one here is 0x1, or NOT_LICENSED. By changing “0x1 -> :sswitch_de ” to “0x1 -> :sswitch_d3” we basically point it to a positive outcome instead, so the library tells your app the license is actually valid.
The final step is to reassemble with smali, placing the new dex file in the apk, and re-sign it with any valid key (even test-keys).
Even though the library knows the status is NOT_LICENSED, the described tweak ensures the application will receive a LICENSED result instead and believe that it is, in fact, licensed. This method is so simple, even a novice programmer could write a script to automatically patch most apps.
The current situation with piracy in our community is out of control, and only set to get worse as the platform grows. Sites like the recently taken down AndroidPlayground are profiting from the hard work of our developers, and stifling future development.
For now, Google's Licensing Service is still, in my opinion, the best option for copy protection; however, we really need to see a better solution, such as checking the apk for alterations or ways to confirm an application was installed through official means.
I will continue to investigate copy protection methods on Android, and will hopefully have an update soon. I will possibly be releasing an add-on class for LVL soon, to help protect against out of market installs and unauthorized modifications of apps.
It goes without saying but comments from Google and app developers are very welcome.