Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS - saving in app purchase data in plist files?

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.

like image 548
Albert Renshaw Avatar asked Feb 18 '23 00:02

Albert Renshaw


2 Answers

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);
}
like image 83
Albert Renshaw Avatar answered Mar 04 '23 19:03

Albert Renshaw


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.

like image 39
Kendall Helmstetter Gelner Avatar answered Mar 04 '23 20:03

Kendall Helmstetter Gelner