How to detect non-jailbroken pirates

Please Note: The following is an approach I have put together to help other developers stop this kind of highly immoral piracy. I have not tested this myself as I am not currently maintaining any paid apps. Please report back with your results if you do try this.

Background

This section is rather long-winded, so if you’re impatient and/or an iOS ninja/expert, skip down to the “Rough Solution” section.

Most iOS developers know that jailbroken iPhone/iPad users can quite easily download and install cracked versions of their apps. This egregious violation of copyright has understandably angered devs which have poured their hearts and souls (not to mention countless hours and Benjamins) into these apps. Through this anger, methods have been developed to detect and track these illegitimate users.

But what some developers don’t realise is, there is another method for installing cracked apps on your iPhone without jailbreaking. It is not quite as easy as the jailbroken route, but I found a couple of programs which simplify the process. Google turned up a couple of results, Crappstore (a Xcode project), iReSign (automated tool) and IPA God. Part of the complexity lies in the fact that you need access to a iOS developer account as the method requires a valid Development provisioning profile. This should be a impassable barrier for most users, but there are services selling provisioning profiles to users which include their UDIDs.

The programs mentioned above are all based on the same method, which has been around for awhile now, but a dev on Twitter proposed a simple question which caught my attention. At runtime, can I detect whether an app has been installed illegally using this method?

So first, lets take a look at how this method works. It takes advantage of the fact that Apple has given developers a fair amount of leniency with regards to what code they can execute on their devices. The process is outlined below.

  1. Obtain the App Store IPA or download decrypted IPA (skip to Step 3)

  2. Decrypt using an appropriate tool usually on a jailbroken device

  3. Re-code sign using one of the tools mentioned above with a valid Development provisioning profile (you can obtain these for a fee for a few different sources)

  4. Install provisioning profile and freshly codesigned IPA onto your device

  5. Enjoy your cracked app (or more correctly, try to enjoy your app while the guilt eats away at your soul)

So how can we tell at runtime whether this app is legitimate or not? Well, the core of this method is resigning the decrypted app using a Development provisioning profile. So what we really need to do is determine the code signing identity at runtime.

The embedded.mobileprovision file holds information related to how the app was signed and the devices it is allowed to be installed on. My first thought was this would be our silver bullet, but after some research, I realised this was not the case.

Stack Overflow offered me this piece of information.

A provisioning profile is DER-encoded “pkcs7-signedData”, consisting of an XML blob (the data), a certificate chain (Apple Root Certificate Authority → Apple iPhone Certification Authority → Apple iPhone OS Provisioning Profile Signing), and the signature

So this tells me that the structure of it isn’t human readable without a ASN.1 parser, but all the pertinent information we need should be here. Luckily, most of what we need is in the “XML blob” which is actually a plist. This part is human readable in a text editor so I took a look around and I found these main differences between Development and Distribution embedded.mobileprovision files.

Development Distribution
Contains UDIDs Yes No
get-task-allow true false
Certification Authority Apple Certification Authority Apple Worldwide Developer Relations Certification Authority

However, it seems Apple strips the embedded.mobileprovision file out of your IPA at some point in the black box that is the App Store submission process. Moreover, the embedded.mobileprovison file doesn’t even need to be bundled with the app (IPAs created by iReSign didn’t contain any) as long as it exists on the device. codesign embeds the code signature (which refers to the appropriate provisioning profile) into the binary (to take a look, “codesign -dvvv AppBinary” in Terminal). Seeing as there is no app without a binary, this seems like a decent place to check the signature.

I opened the binary of an app in a text editor and found a similar structure embedded at the end of the file as we saw in the embedded.mobileprovision files. However, UDIDs are not present in both cases and get-task-allow by itself is not enough information to be determine the legitimacy of the app, so we are left with the Certification Authority.

Rough Solution

The solution I present here is the result of a couple of hours of digging and experimentation. I haven’t extensively tested it with the various method I mentioned earlier and I haven’t tested it in the wild as I don’t maintain any applicable paid apps. Please use at your own risk and only if you understand the underlying logic and possible implications. With more time and motivation, I’m sure a more elegant method would be discovered, but I believe this method is robust enough to at least use to collect some data on the use of these techniques. If someone implements this, get in contact because I’d be happy to help test this if you would like.

My proposed technique is brutally simple. Look for the “Apple Worldwide Developer Relations Certification Authority” string in the executable.

NSString *executablePath = [[NSBundle mainBundle] executablePath];
NSString *executableAsString = [NSString stringWithContentsOfFile:executablePath
                                                         encoding:NSISOLatin1StringEncoding
                                                            error:NULL];
BOOL isDevelopmentSigned = [executableAsString rangeOfString:@"Apple " @"Worldwide Developer Relations Certification Authority"
                                                     options:NSCaseInsensitiveSearch].length;

This could be done much more efficiently (using NSFileHandle), but this version is more readable and explains the technique well. If anyone is interested in seeing the extended version, I might be persuaded to do a follow-up post on that.

I would recommend you put this between some #ifdefs and define a preprocessor macro in your Distribution Build configuration. This will keep this snippet from interfering with your development testing. If this means nothing to you, you probably shouldn’t be doing this anyway.

Discussion

There are a couple of possible pitfalls that I can think of off the top of my head.

  • I tested this method using a project I am working on, which compiles to a 6.3mb binary and it takes roughly 0.9s to complete this check. There are, however, several simple optimisations I can think of that should speed this up and reduce the memory footprint

  • There is potential for various false positives here that I haven’t thought of

  • This method is vulnerable to changes in the way that Apple signs apps

  • There could possibly be issues when Apple reviews the app depending on how they test it

Update: Thanks to Steve Troughton-Smith for pointing out a silly mistake I made in the code snippet. By including an exact copy of the Certification Authority string in my code, I was (recursively?) validating the test which would produce false positives in almost all cases. For the sake of simplicity and readability, I have fixed this simply by splitting the string and concatenating it. There are definitely more elegant ways of hiding this string, but I will save this for a future post.

Comments