9

There used to be a method to check if an application was purchased from the App Store, to protect against cracking:

NSBundle *bundle = [NSBundle mainBundle]; 
NSDictionary *info = [bundle infoDictionary]; 
if ([info objectForKey: @"SignerIdentity"] != nil) 
{ /* do something */  }

but this method no longer works because the crackers have found ways around altering the Info.plist. I'm aware of this older question, but the answers presented there rely on the above technique, which is no longer valid.

How can you detect whether your application was cracked or purchased legitimately from the App Store without reading the SignerIdentity from the Info.plist?

Community
  • 1
  • 1
Dmitry
  • 14,306
  • 23
  • 105
  • 189
  • Just to clarify... You want to know if your app was installed on a device outside of the App Store? (E.g. Either via developer channels or illegally?) – scunliffe Oct 08 '12 at 01:28
  • 4
    +1 for wanting to see an up to date answer. The duplicate's answer AND the edited answer are both from 2009! – Carl Veazey Oct 08 '12 at 01:40
  • 8
    Honestly, don't bother. First of all, the crackers have a ton of new technology to bypass these things, so they'll always be one step ahead, and on top of that it's probably not worth it in the first place. Some of my non-developer friends pirate stuff, and they'll either get it for free or just not get it. If they install an app and it says "This app was pirated, buy it from the app store", they'll just delete it. – Greg Oct 08 '12 at 02:39
  • In the comments on the top-voted answer to that earlier question, as well as the update to that answer, another technique was mentioned as being the preferred one in place of the Info.plist check: http://landonf.bikemonkey.org/code/iphone/iPhone_Preventing_Piracy.20090213.html . Is this approach also no longer valid? – Brad Larson Oct 08 '12 at 03:10
  • @Altaveron - Could you explain why that method is not valid, as well? It relies on a completely different means of checking the encryption status directly. You've only stated that the Info.plist methods can be worked around, and Landon's approach is completely different. Simply stating that something is not valid because it is old is not helpful in finding something that does work. – Brad Larson Oct 08 '12 at 14:20
  • Brad, did you check this method? Please add an answer if it works. Thanks. – Dmitry Oct 08 '12 at 22:16
  • I have tested a code from the link - and it everytime returns NO (illegal) for application on a device from xCode. Is it obsolete? – Dmitry Oct 11 '12 at 03:46
  • 2
    I once read an article by Rovio's devs, saying that you'll lose more money spending time trying to fight against the hackers than not bothering about it. Even more, pirates may bring some new users to your app. They download it for free, use it, and if they like it, they may recommend it to non pirates who will later buy your app. What @Greg says is also true. I had a jailbroken device, and **bought** Skype. When I launched it, I had a great UIAlertView saying this app doesn't run on jailbroken devices, and then killing the app. Loved it. – rdurand Oct 12 '12 at 08:39
  • Rovio would say that, but then again their business model relies mostly on (near-uncrackable) add-on purchases, not on the comparatively tiny app price. Not every dev out there want or can rely on quasi-freemium models. On the other hand: totally agree that even a small false positive rate in crack detection code is unacceptable. – Dave Oct 24 '12 at 01:59

4 Answers4

12

I like Mick's answer personally as it's short and simple.

Greg's response isn't valid -- Mick's code only checks whether the App can open that URL so there should be no chance of crashing.

I've implemented the following in one of my apps before which does a more strict check of whether the app is encrypted or not, if it's not it's most likely a cracked app:

From analytics, this method has prevented thousands of pirated users for me and took maybe 5 minutes to implement so the cost of doing it was almost nothing -- for me, I didn't care if it increased sales (which I was sure it wasn't going to anyway--it's more that I don't want people freeloading off of my hard work). In addition, a good amount of my app's content feeds information after figuring out whether the app is pirated or not and returns junk data if it is.

In main.m

#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <TargetConditionals.h>

#if TARGET_IPHONE_SIMULATOR && !defined(LC_ENCRYPTION_INFO)
#define LC_ENCRYPTION_INFO 0x21
struct encryption_info_command {
    uint32_t cmd;
    uint32_t cmdsize;
    uint32_t cryptoff;
    uint32_t cryptsize;
    uint32_t cryptid;
};
#endif

static BOOL isEncrypted();

static BOOL isEncrypted () {
    const struct mach_header *header;
    Dl_info dlinfo;

    /* Fetch the dlinfo for main() */
    if (dladdr(main, &dlinfo) == 0 || dlinfo.dli_fbase == NULL) {
        //NSLog(@"Could not find main() symbol (very odd)");
        return NO;
    }
    header = dlinfo.dli_fbase;

    /* Compute the image size and search for a UUID */
    struct load_command *cmd = (struct load_command *) (header+1);

    for (uint32_t i = 0; cmd != NULL && i < header->ncmds; i++) {
        /* Encryption info segment */
        if (cmd->cmd == LC_ENCRYPTION_INFO) {
            struct encryption_info_command *crypt_cmd = (struct encryption_info_command *) cmd;
            /* Check if binary encryption is enabled */
            if (crypt_cmd->cryptid < 1) {
                /* Disabled, probably pirated */
                return NO;
            }

            /* Probably not pirated <-- can't say for certain, maybe theres a way around it */
            return YES;
        }

        cmd = (struct load_command *) ((uint8_t *) cmd + cmd->cmdsize);
    }

    /* Encryption info not found */
    return NO;
}
webstersx
  • 771
  • 7
  • 10
  • This returns me NO for application start from xCode. Is it normal? – Dmitry Oct 24 '12 at 15:35
  • @Altaveron It's quite normal. It will return YES only if you launch the app from appstore. When you give your ipa to Apple, it encrypts and signs the app. Debug and AdHoc builds will be always non-encrypted, while Appstore build - encrypted. – Tertium Oct 24 '12 at 20:54
  • How to test the code before application will be send to the AppStore? – Dmitry Oct 27 '12 at 20:33
  • 1
    Does apple use the un-encrypted edition of the app when reviewing it? If so, it could display the wrong content to them and get your app to be rejected.. – Matej May 08 '13 at 20:20
  • can you please share its swift version? – Asad Ali Choudhry Mar 22 '20 at 14:15
3

The official Apple's answer:

Hello Dmitry,

Thank you for contacting Apple Developer Technical Support (DTS). 

DTS does not provide code-level support for DRM issues.  

Please try posting your inquiry to Apple Development Forum:

<https://devforums.apple.com>

While you were initially charged a Technical Support Incident (TSI) for this request, we have assigned a replacement TSI back to your account.

Thank you for understanding our support policies.

Best Regards,

Apple Developer Support 
Worldwide Developer Relations
Dmitry
  • 14,306
  • 23
  • 105
  • 189
1

I'd suggest a smaller code snippet that does the same thing as @user1353482 suggested (and the same way). I'd write in comments, but code would be unreadable then. Moreover, I may be wrong but it seems that additional defines is not needed anymore even when compiling for simulator (at least this works in xcode 4.5.1, target is 5.0).

Please note that this code returns false on debug and adhoc binary, but we're talking about appstore, right? It's Apple who makes the final encryption and you're supposed to not try this at home :)

#include <execinfo.h>
#import <mach-o/ldsyms.h>

bool executableEncryption()
{
    const uint8_t *command = (const uint8_t *) (&_mh_execute_header + 1);
    for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx)
    {
        if (((const struct load_command *) command)->cmd == LC_ENCRYPTION_INFO)
        {
            struct encryption_info_command *crypt_cmd = (struct encryption_info_command *) command;    
            if (crypt_cmd->cryptid < 1)
                return false;
            return true;
        }
        else
        {
            command += ((const struct load_command *) command)->cmdsize;
        }
    }
    return false;
}
Tertium
  • 6,049
  • 3
  • 30
  • 51
-4

While not a check to see if an Application was purchased from the App Store, I use this code to check to see if my application is running on a jailbroken device:

+(BOOL)isJailbroken { 
    NSURL* url = [NSURL URLWithString:@"cydia://package/com.example.package"]; 
    return [[UIApplication sharedApplication] canOpenURL:url]; 
} 
Mick Walker
  • 3,862
  • 6
  • 47
  • 72
  • 9
    Not the best answer because not all jailbreakers are pirates. If someone pays for your app, and it crashes because they are jailbroken, it will piss them off. – Greg Oct 14 '12 at 23:33
  • But what about "Icy" and "Installer"? – Yauheni Shauchenka Dec 10 '12 at 08:44
  • Greg, my code won't crash the application, it simply checks if the URL can be opened, ie the URL scheme has been registered on the device. – Mick Walker Jan 07 '14 at 14:17