How to make iOS application tamper-evident? - ios

I am working on a project (mobile app) where I need to monitor adversary actions. So, my question is how can I make iOS app tamper-evident?
e.g.
Whenever any adversary try to tamper code then system should alert admin for these actions
and block that adversary
If user tries to install app on rooted device then system can detect that.
System should able to monitor adversary actions.
I found solution for android like ProGuard, SafetyNet but did not found anything for iOS.

I've used this JailBreak detection in one of my project.
With this, you can prevent the possibility.
if ([DTTJailbreakDetection isJailbroken]) {
// your custom activity and business logic here
}
Also, In precise you can use the below snippet:
BOOL isJailbroken()
{
#if !(TARGET_IPHONE_SIMULATOR)
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Applications/Cydia.app"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/MobileSubstrate.dylib"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/bin/bash"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/usr/sbin/sshd"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/etc/apt"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/private/var/lib/apt/"] ||
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"cydia://package/com.example.package"]]) {
return YES;
}
FILE *f = NULL ;
if ((f = fopen("/bin/bash", "r")) ||
(f = fopen("/Applications/Cydia.app", "r")) ||
(f = fopen("/Library/MobileSubstrate/MobileSubstrate.dylib", "r")) ||
(f = fopen("/usr/sbin/sshd", "r")) ||
(f = fopen("/etc/apt", "r"))) {
fclose(f);
return YES;
}
fclose(f);
NSError *error;
NSString *stringToBeWritten = #"This is a test.";
[stringToBeWritten writeToFile:#"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
[[NSFileManager defaultManager] removeItemAtPath:#"/private/jailbreak.txt" error:nil];
if(error == nil)
{
return YES;
}
#endif
return NO;
}
Also , Obfuscation in iOS - objective C you can use this open source-library and for Methods & Classes.

Apart from detecting jailbroken device, and obfuscating code (as #itechnician mentioned), you can:
Detect if debugger is attached: https://developer.apple.com/library/content/qa/qa1361/_index.html
Check the load commands in Mach-O header to check if there's anything injected
Check code integrity
Anyway, all of these can be easily bypassed when on jailbroken device (even the check if it's jailbroken). The best way is to use multiple techniques including obfuscation, to make tampering as hard as possible (so it's not worth it). But I'm not sure if you could make fully tamper-proof app.
You might find these links useful:
https://www.coredump.gr/articles/ios-anti-debugging-protections-part-1/
https://www.raywenderlich.com/45645/ios-app-security-analysis-part-1
http://resources.infosecinstitute.com/ios-application-security-part-31-problem-using-third-party-libraries-securing-apps/
This book is a bit old, but still useful: http://shop.oreilly.com/product/0636920023234.do
Here are opensource ObjC obfuscators/string encryptors:
https://github.com/Polidea/ios-class-guard
https://github.com/FutureWorkshops/Objc-Obfuscator
https://github.com/pjebs/Obfuscator-iOS

I think you're looking something like ixguard

Related

How Can I Get A List of Available System Sounds in iOS?

I found this question, but it's a bit aged. It looks like that directory access is no longer available for the example app they recommend.
The Apple documentation doesn't seem to have what I need, either.
What I need is to be able to list the built-in sounds (not provide my own), and allow a user of my app to choose one to play.
Sounds simple enough, eh?
UPDATE:
Here is the relevant code in the example app mentioned below:
NSURL *directoryURL = [NSURL URLWithString:#"/System/Library/Audio/UISounds"];
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
[audioFileList addObject:url];
}
}
The problem is that the enumerator is always empty. I suspect this may be a security/sandbox issue.
Reading up on this, you can find code to get a list of system sound files - but only on a "jail break" device.
Based on this note on Apple's docs (link), it sounds like (sorry for the pun) we won't have much luck in trying to access the "internal" sound clips:
Note System-supplied alert sounds and system-supplied user-interface sound effects are not available to your iOS application. For example, using the kSystemSoundID_UserPreferredAlert constant as a parameter to the AudioServicesPlayAlertSound function will not play anything.

Jailbreak check method, apple will approve this?

In my app i need to check sometimes if the running device is a jailbreak device or not. This is the most complete method i have found:
BOOL Jail=NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:#"/Applications/Cydia.app"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/Library/MobileSubstrate/MobileSubstrate.dylib"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/bin/bash"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/usr/sbin/sshd"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/etc/apt"] ||
[[NSFileManager defaultManager] fileExistsAtPath:#"/private/var/lib/apt/"] ||
[[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:#"cydia://package/com.example.package"]]) {
Jail= YES;
}
FILE *f = NULL ;
if ((f = fopen("/bin/bash", "r")) ||
(f = fopen("/Applications/Cydia.app", "r")) ||
(f = fopen("/Library/MobileSubstrate/MobileSubstrate.dylib", "r")) ||
(f = fopen("/usr/sbin/sshd", "r")) ||
(f = fopen("/etc/apt", "r"))) {
fclose(f);
Jail= YES;
}
fclose(f);
NSError *error;
NSString *stringToBeWritten = #"This is a test.";
[stringToBeWritten writeToFile:#"/private/jailbreak.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
[[NSFileManager defaultManager] removeItemAtPath:#"/private/jailbreak.txt" error:nil];
if(error == nil)
{
Jail= YES;
}
What i haven't found, it's a clear answer to the question: "Apple will approve this code?" , in guidelines i can read:
"Apps that read or write data outside its designated container area will be rejected".
However it's clear that i'm trying to write or read only to check if the device is jailbroken... So there's someone that have successfully submitted this code to apple?
This is the code of a submitted app
BOOL bash = NO;
FILE *f = fopen("/bin/bash", "r");
if (f != NULL)
{
bash = YES;
}
fclose(f);
return bash;
So, I believe your code will be accepted too
Maybe.
Many developer's have been rejected for including jailbreak checking, and many have not. If you submit with a jailbreak check, you should NOT expect to be approved (although you may get through the review process this time).
App with jailbreak detection rejected by Apple
https://github.com/sat2eesh/ios-jailBroken/issues/4

Finding out if the device is locked, from a Notification Widget

I'd like to know if the device is locked when I'm loading my Notification/Today widget, so I can show the widget appropriately. (it's financial, and we don't want to show balances on a locked phone)
On devices with TouchID, I can just try to access the Keychain, and if I get
errSecInteractionNotAllowed
back, it's locked. All good. This doesn't work on devices without touchID (but with a PIN). I've found a few things, which recommend using
[[UIApplication sharedApplication] protectedDataAvailable]
However I don't have [UIApplication sharedApplication] in a widget.
Any ideas where and how to do this? I just need a yes/no: is the device locked.
Thanks
[UPDATE: here's the code I have]
Getting the filename:
+ (NSString *)lockedDeviceFilename {
NSURL *directoryUrl = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:USER_DEFAULTS_GROUP_NAME];
return [directoryUrl.path stringByAppendingPathComponent:#"security.dummy"];
}
Writing / creating the file (in the app, not the extension:
NSError *error = nil;
NSString *documentPath = [FOOStorageGatekeeper lockedDeviceFilename];
[[NSFileManager defaultManager] removeItemAtPath:documentPath error:&error];
BOOL created = [[NSFileManager defaultManager] createFileAtPath:documentPath
contents:[#"super secret file contents. we only care about the permissions" dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey : NSFileProtectionComplete}];
Reading:
BOOL isReadable = [[NSFileManager defaultManager] fileExistsAtPath:[FOOStorageGatekeeper lockedDeviceFilename]];
NSLog(#"isReadable? %#", isReadable ? #"YES" : #"NO");
It's always able to read the file, even on a TouchID device with the screen locked. If I look at the attributes, it shows the NSFileProtectionKey is set to NSFileProtectionComplete... but I can STILL READ IT :(
Update: found it. Marking Ian's answer as correct
Create a file with NSFileProtectionComplete while your app is running and then attempt to access it from your extension. If you can't access it, the screen is locked.
[[NSFileManager defaultManager] createFileAtPath:someFilePath
contents:[#"Lock screen test." dataUsingEncoding:NSUTF8StringEncoding]
attributes:#{NSFileProtectionKey: NSFileProtectionComplete}];
EDIT: Final steps included to complete solution and consolidate answers. (Remaining work provided by Nic Wise.)
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)
Finally, after 3-4 days of looking, found the answer. It was more in how I was reading the result back. Ian is right: I need to create the file using createFileAtPath, but then read it back using
NSData *data = [NSData dataWithContentsOfURL:[FOOStorageGatekeeper lockedDeviceUrl] options: NSDataReadingMappedIfSafe error:&error];
if (error != nil && error.code == 257) {
NSLog(#"**** the keychain appears to be locked, using the file method");
return YES;
}
The other method, using errSecInteractionNotAllowed also works, but only for TouchID devices.
I found the answer (indirectly) here (rego with the iOS dev program most likely needed)
I tried that and my file was always readable (in lock screen or not).
I found this document :
https://www.apple.com/business/docs/iOS_Security_Guide.pdf
It appeared that the files are locked 10 seconds after the device is locked.
Knowing that, you can create the files from the extensions, and it seems to work.

NSURLIsExcludedFromBackupKey - Apps must follow the iOS Data Storage Guidelines or they will be rejected

My app was rejected cause it seems that 7 mb are stored in documents folder and they are automatically send to icloud. So i have looped all files that will be written to documents folder throught this method :
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL {
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
if (&NSURLIsExcludedFromBackupKey == nil) {
// iOS 5.0.1 and lower
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
else {
// First try and remove the extended attribute if it is present
int result = getxattr(filePath, attrName, NULL, sizeof(u_int8_t), 0, 0);
if (result != -1) {
// The attribute exists, we need to remove it
int removeResult = removexattr(filePath, attrName, 0);
if (removeResult == 0) {
NSLog(#"Removed extended attribute on file %#", URL);
}
}
// Set the new key
NSError *error = nil;
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
return error == nil;
}
The version 1.1 of my app was approved after this code implementation. Last week i tried to send the version 1.2 of the same app (nothing has changed in file management, all files that are stored in documents folder are looped through the addSkipBackupAttributeToItemAtURL method). My app was rejected again for the same reason.  I can't move my file to temp or cache folder because my app can't completely restore the file (one of this file is a db, restoring db means loose any user inserted data), so this one can't be the solution. Anyway i have found an issue in the code, this is how i call the method :
[self addSkipBackupAttributeToItemAtURL:[NSURL fileURLWithPath:fullPath]];
using [NSURL fileURLWithPath:fullPath] device with ios 5.1 return an error and it seems impossible to create the attribute. If i change the initialization of the nsurl with [NSURL URLWithString:defaultStorePath], device with 5.1 seems to add correctly the attribute.
With ios 5.0.1 all is inverted , [NSURL URLWithString:defaultStorePath] return an error while [NSURL fileURLWithPath:fullPath] works.
Maybe i can check ios version and set an appropriate nsurl initialization, but  it  still remains a problem. In rejection explanation i read :
In particular, we found that on launch and/or content download, your app stores 7mb. To check how much data your app is storing:
Install and launch your app
Go to Settings > iCloud > Storage & Backup > Manage Storage
If necessary, tap "Show all apps"
Check your app's storage
If i try to check this value i see 7 mb also with the correct nsurl initialization (when all the attributes are set  correctly) . What is the correct behaviour? Anyone with this problem?  Do I have to do something specific before the app storage check suggested by apple to make it significant?
I think the trick is to add the NSURLIsExcludedFromBackupKey AND make sure the directory is outside the documents directory. I did this by moving my documents to the Library/Application Support folder (since it didn't make sense in the /tmp or /Caches folders):
// store in /Library/Application Support/BUNDLE_IDENTIFIER/Reference
// make sure Application Support folder exists
NSURL *applicationSupportDirectory = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:&error];
if (error) {
NSLog(#"KCDM: Could not create application support directory. %#", error);
return nil;
}
NSURL *referenceFolder = [applicationSupportDirectory URLByAppendingPathComponent:#"Reference" isDirectory:YES];
if (![[NSFileManager defaultManager] createDirectoryAtPath:[referenceFolder path]
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
NSLog(#"KCDM: Error creating Reference folder to store model %#: %#", modelName, error);
return nil;
}
BOOL success = [referenceFolder setResourceValue:#YES forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"KCDM: Error excluding %# from backup %#", referenceFolder, error);
}
I had the same problem as you until I deleted my app from my device, and re-installed. I also had to delete the existing cached data from the iCloud backup by going to Settings->Storage&Backup -> Manage Storage
That seemed to do the trick.
Also, my code to add the skip attribute is a bit different:
Code lifted from this post
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
if (&NSURLIsExcludedFromBackupKey == nil) { // iOS <= 5.0.1
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
else { // iOS >= 5.1
NSError *error = nil;
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
return error == nil;
}
}

sqlite not working on iPhone simulator

I have a weird problem. My app works fine on my device but the sqlite database does not work on the simulator. The file "database.sqlite" exists in the same folder as my apps, it has the same name and the columns names are also correct.
So I assume there is something wrong with the configuration but I don't know what. Can someone please help me out.
Thanks
Here are some posts that seem to address the problem : http://forums.macrumors.com/showthread.php?t=484899
One reason might be because you should ensure the database is copied from your Supporting Files application directory (read only) to library or documents before you use it. Here's an ensurePrepared function from a sample of mine that uses sqlite that does just that. In this case, it's called contacts.db
- (BOOL)ensureDatabasePrepared: (NSError **)error
{
// already prepared
if ((_dbPath != nil) &&
([[NSFileManager defaultManager] fileExistsAtPath:_dbPath]))
{
return YES;
}
// db in main bundle - cant edit. copy to library if !exist
NSString *dbTemplatePath = [[NSBundle mainBundle] pathForResource:#"contacts" ofType:#"db"];
NSLog(#"%#", dbTemplatePath);
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
_dbPath = [libraryPath stringByAppendingPathComponent:#"contacts.db"];
NSLog(#"dbPath: %#", _dbPath);
// copy db from template to library
if (![[NSFileManager defaultManager] fileExistsAtPath:_dbPath])
{
NSLog(#"db not exists");
NSError *error = nil;
if (![[NSFileManager defaultManager] copyItemAtPath:dbTemplatePath toPath:_dbPath error:&error])
{
return NO;
}
NSLog(#"copied");
}
return YES;
}

Resources