4

NOTE ---- The answer below uses iOS6- methods, Apple has since then removed developer's access to MAC addresses. If you are developing for iOS7+ disregaurd first answer and just encrypt your IAP unlock data based on other variables that will be unique to each device (like the date the app was first launched)

I have features that need tone unlocked so I store them in my plist files.... a feature like a new avatar in a chat room could have the id "13891" and if it is unlocked I might assign it some key like "93" and if it's locked it might have any other key "37" for example.... So the plist will say: "13891" = "93" My question is can jailbroken phones edit the plist files easily and unlock features for themselves? What's a better way of storing this data? I don't want to have to check Apple's servers every time, it takes too long with low internet connection.

Edit: Current Answer:

4 measures to take:

1) Store it in the keychain. (Or plist I guess now that I've added measure #4)

2) Check Apple's servers every time but if you are worried about the lag that follows just check it in the background and in the meantime let the user use the app if it says they can.

3) Store your variables as encrypted keys in the keychain... don't store "FishingRod = unlocked" store "3dhk34D@HT% = d3tD@#".

4) Encrypt each key with the devices MAC address (these MAC addresses do NOT change and are available with or without WiFi connection... code below). That way if a user downloads a plist off of the internet and tries to use it, it won't work because when you decrypt it using their device ID you will can't random nonsense instead of the unlock key (in my examples case that would be "d3tD@#".)!!! -- Mac Address can no longer be accessed as of iOS7+, instead encrypt with other device unique things, such as the date the app was first launched

MAC address code (Just stick it in the view controllers ViewDidAppear... in the .H import )

#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>

-(void)viewDidAppear:(BOOL)animated {

    int                 mgmtInfoBase[6];
    char                *msgBuffer = NULL;
    NSString            *errorFlag = NULL;
    size_t              length;

    // Setup the management Information Base (mib)
    mgmtInfoBase[0] = CTL_NET;        // Request network subsystem
    mgmtInfoBase[1] = AF_ROUTE;       // Routing table info
    mgmtInfoBase[2] = 0;
    mgmtInfoBase[3] = AF_LINK;        // Request link layer information
    mgmtInfoBase[4] = NET_RT_IFLIST;  // Request all configured interfaces

    // With all configured interfaces requested, get handle index
    if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0)
        errorFlag = @"if_nametoindex failure";
    // Get the size of the data available (store in len)
    else if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0)
        errorFlag = @"sysctl mgmtInfoBase failure";
    // Alloc memory based on above call
    else if ((msgBuffer = malloc(length)) == NULL)
        errorFlag = @"buffer allocation failure";
    // Get system information, store in buffer
    else if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0)
    {
        free(msgBuffer);
        errorFlag = @"sysctl msgBuffer failure";
    }
    else
    {
        // Map msgbuffer to interface message structure
        struct if_msghdr *interfaceMsgStruct = (struct if_msghdr *) msgBuffer;

        // Map to link-level socket structure
        struct sockaddr_dl *socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);

        // Copy link layer address data in socket structure to an array
        unsigned char macAddress[6];
        memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6);

        // Read from char array into a string object, into traditional Mac address format
        NSString *macAddressString = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
                                      macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]];
        NSLog(@"Mac Address: %@", macAddressString);

        // Release the buffer memory
        free(msgBuffer);

        //return macAddressString;
        NSLog(@"MAC: %@", macAddressString);
        //F0:DC:E2:1D:EB:50
    }

    // Error...
    NSLog(@"Error: %@", errorFlag);
}

Note: I got the MAC address code from a friend... I'm not claiming that I wrote the code... I don't know if he wrote it or got it from someone else as well.

Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195

3 Answers3

4

4 measures to take:

1) Store it in the keychain. (Or plist I guess now that I've added measure #4)

2) Check Apple's servers every time but if you are worried about the lag that follows just check it in the background and in the meantime let the user use the app if it says they can.

3) Store your variables as encrypted keys in the keychain... don't store "FishingRod = unlocked" store "3dhk34D@HT% = d3tD@#".

4) Encrypt each key with the devices MAC address (these MAC addresses do NOT change and are available with or without WiFi connection... code below). That way if a user downloads a plist off of the internet and tries to use it, it won't work because when you decrypt it using their device ID you will can't random nonsense instead of the unlock key (in my examples case that would be "d3tD@#".)!!! -- Mac Address can no longer be accessed as of iOS7+, instead encrypt with other device unique things, such as the date the app was first launched

MAC address code (Just stick it in the view controllers ViewDidAppear... in the .H import )

#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>

-(void)viewDidAppear:(BOOL)animated {

    int                 mgmtInfoBase[6];
    char                *msgBuffer = NULL;
    NSString            *errorFlag = NULL;
    size_t              length;

    // Setup the management Information Base (mib)
    mgmtInfoBase[0] = CTL_NET;        // Request network subsystem
    mgmtInfoBase[1] = AF_ROUTE;       // Routing table info
    mgmtInfoBase[2] = 0;
    mgmtInfoBase[3] = AF_LINK;        // Request link layer information
    mgmtInfoBase[4] = NET_RT_IFLIST;  // Request all configured interfaces

    // With all configured interfaces requested, get handle index
    if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0)
        errorFlag = @"if_nametoindex failure";
    // Get the size of the data available (store in len)
    else if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0)
        errorFlag = @"sysctl mgmtInfoBase failure";
    // Alloc memory based on above call
    else if ((msgBuffer = malloc(length)) == NULL)
        errorFlag = @"buffer allocation failure";
    // Get system information, store in buffer
    else if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0)
    {
        free(msgBuffer);
        errorFlag = @"sysctl msgBuffer failure";
    }
    else
    {
        // Map msgbuffer to interface message structure
        struct if_msghdr *interfaceMsgStruct = (struct if_msghdr *) msgBuffer;

        // Map to link-level socket structure
        struct sockaddr_dl *socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);

        // Copy link layer address data in socket structure to an array
        unsigned char macAddress[6];
        memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6);

        // Read from char array into a string object, into traditional Mac address format
        NSString *macAddressString = [NSString stringWithFormat:@"%02X:%02X:%02X:%02X:%02X:%02X",
                                      macAddress[0], macAddress[1], macAddress[2], macAddress[3], macAddress[4], macAddress[5]];
        NSLog(@"Mac Address: %@", macAddressString);

        // Release the buffer memory
        free(msgBuffer);

        //return macAddressString;
        NSLog(@"MAC: %@", macAddressString);
        //F0:DC:E2:1D:EB:50
    }

    // Error...
    NSLog(@"Error: %@", errorFlag);
}
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
  • Note: I got the MAC address code from a friend... I'm not claiming that I wrote the code... I don't know if he wrote it or got it from someone else as well. – Albert Renshaw Dec 30 '12 at 19:56
  • Presumably you're using this code in a public app - I don't imagine there would be any reason for Apple to reject it, would there? – Luke Jan 31 '13 at 22:42
  • 1
    Our app with this method went live about a month back... since then we've had two updates that have also gotten accepted... So for my Apple didn't reject it multiple times and it is a very secure way of encrypting your app data that is unique for each device :) – Albert Renshaw Feb 01 '13 at 03:01
3

You don't even need to jailbreak. Wherever you store a writable file, an application like iPhone Explorer can let the user grab and modify that file then write it back out to the device. It would only take one person purchasing to send the unlocked plist file out to the internet at large.

What I would do is store the unlocked items in the keychain on the device (just to obscure it a tiny bit more), and trust that on launch - but then also every time try to contact the Apple servers in the background to verify that the user really should have those unlocked items. That way they may have the items unlocked for a short time even if they can forge the keychain entries, but the ability will be removed if the device is connected to the internet at all while the app runs. Having to remember to disconnect a device from internet connectivity before each run is probably too annoying to make the stolen unlock worth it for the forger.

Since the keychain persists even across application deletion, you may also want to write out a plist file on first launch, if you do not detect that initially created plist file on later launches clear out the keychain.

If anything though, the risk of someone fiddling and unlocking things in your app is probably low. Always err on the side of giving user access when the situation is murky so you do not cause problems for real users.

Kendall Helmstetter Gelner
  • 74,769
  • 26
  • 128
  • 150
  • Ah! So so far I think there are 3 measures to take: 1) store it in the keychain, 2) check Apple's servers every time but if you are worried about the lag that follows just check it in the background and in the meantime let the user use the app if it says they can, 3)store your variables as encrypted keys in the keychain... don't store "FishingRod = unlocked" store "3dhk34D@HT% =d3tD@#" – Albert Renshaw Dec 30 '12 at 18:53
  • For additional security you could incorporate the users device ID into each field that way nobody can just leak it online for anyone to download because it will only work on the persons phone with that same device ID. – Albert Renshaw Dec 30 '12 at 19:15
  • UUID access is deprecated though. You are not supposed to use that call in new apps. Plus if they figured that out they would simply edit in their own ID. – Kendall Helmstetter Gelner Dec 30 '12 at 20:36
  • My new answer uses the MAC address and it encrypts the key with the users MAC address so that no simpleton can just edit the plist file O:) – Albert Renshaw Dec 30 '12 at 20:38
  • @Albert: You have the gist of what I was suggestion, those steps sound good. I'm not sure I'd bother with encrypting the entries but you can if you like. Also to make using the keychain easier use SFHFKeychainUtils: http://stackoverflow.com/questions/7663443/sfhfkeychainutils-ios-keychain-arc-compatible – Kendall Helmstetter Gelner Dec 30 '12 at 20:41
  • There is a strong downside to using any ID unique to the device in encrypting things. What happens when the user gets a new iPhone and restores app settings from backup? Your app has to wait to verify they have access again... – Kendall Helmstetter Gelner Dec 30 '12 at 20:42
  • I don't believe that is a strong downside... Most likely if a user got a new device and restored everything I would verify their in-app-purchases anyways. – Albert Renshaw Dec 30 '12 at 20:45
  • The thing is it would show no access for a bit, until Apple verified the purchase. Then it would bring it in... but it would also render you enable to encrypt what you had encrypted in the keychain using the UDID/MAC as a key. In any case it's a worse experience for the user, although perhaps "strong" is too strong. – Kendall Helmstetter Gelner Dec 31 '12 at 03:13
1

can jailbroken phones edit the plist files easily and unlock features for themselves?

Yes, exactly.

What's a better way of storing this data?

Maybe the keychain, but that can be altered too on jailbroken devices.

I don't want to have to check Apple's servers every time, it takes too long with low internet connection.

Too bad. If you want to be secure at least to some extent, you better check Apple's server, or even better, your own server as well. (That's also not a 100% guarantee that your game won't be hacked on a jailbroken phone, since the behavior of the app can be modified as one wishes using MobileSubstrate, but at least it's a bit more secure.)