Protect iOS app from runtime hooking - ios

iOS application when running in a device gets attached to many runtime libraries. How can we protect our iOS app from any other debugger getting attacked to the application process.
Like using GDB we can hack into the application process and manipulate runtime. Is there a way we can stop in using any setting or code?
Or is there a way to check if any other runtime libraries are getting attached to the process?
Can we quite the app in such scenarios?

We can check the modification date of the Info.plist file and Appname and compare it with the package modified date. If there is a miss match found we can conclude that the app binary was modified.
//Check date of modifications in files (if different - app cracked)
NSString* path = [NSString stringWithFormat:#"%#/Info.plist", bundlePath];
NSString* path2 = [NSString stringWithFormat:#"%#/AppName", bundlePath];
NSDate* infoModifiedDate = [[manager attributesOfFileSystemForPath:path error:nil] fileModificationDate];
NSDate* infoModifiedDate2 = [[manager attributesOfFileSystemForPath:path2 error:nil] fileModificationDate];
NSDate* pkgInfoModifiedDate = [[manager attributesOfFileSystemForPath:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"PkgInfo"] error:nil] fileModificationDate];
if([infoModifiedDate timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {
return YES;
}
if([infoModifiedDate2 timeIntervalSinceReferenceDate] > [pkgInfoModifiedDate timeIntervalSinceReferenceDate]) {
return YES;
}

A bit late to the party, hope it helps somebody. In case you like pure, Swift solution, you might want to look at iOSSecuritySuite. It provides some protections against modifications you mentioned, for instance debugging detection:
let amIDebugged: Bool = IOSSecuritySuite.amIDebugged()
Which might save you some lines if you'd wanted to write it on your own.

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.

UI automation testing with multilanguage in iOS

I've written some UI tests in Xcode 7 and when I need to refer to a button I use its accessibility.identifier. This logic worked correctly for all the languages.
app.buttons["signin"].tap()
With Xcode 7.3 when I try to launch this code the test fails because the button cannot be found if the simulator language is not English. I've also tried to record the navigation to check how Xcode reads this button when language is different by English and I found out that it use the translations as key... it doesn't make any sense at all!
Those test were really useful to create screenshots... but obviously with this issue I cannot run tests (and create screens) for all the languages.
How can I point to a button in absolute way if it cannot be recognized by identifier!?
----EDIT
I found the main issue. The company that did the translation has translated the labelidentifier fields :/
I'm trying to get the element using app.buttons.elementBoundByIndex(1) but it doesn't seem to work correctly
Accessibility identifiers should not be localized strings, otherwise they will change when you are running your app in a different language.
Just hard-code the accessibility identifiers to make them persistent regardless of language.
button.accessibilityIdentifier = "myButton"
You can have actual accessibilityIdentifier assigned to button and then try accessing it. Accessing buttons with text is always a bad idea as text may change anytime.
E.g. : XCUIApplication().buttons["AppSignInIdentifier"]
I hope your project is maintaining the localization file. I faced the same issue. If so then localization doesn't work directly with UIAutomation yet.
There is a workaround for this, I am sharing the code snippet with you.
You need to find out the xcodeproj file in your bundle first. As these files are not bundled in the UIAutomation target.
- (NSString *)getLocalizedString:(NSString *)string withComments:(NSString *)comments {
NSString *userLocale = [[NSLocale currentLocale] localeIdentifier];
NSString *userLanguage = [userLocale substringToIndex:2];
NSString *path = [self getProjectPath];
path = [path stringByAppendingPathComponent:[NSString stringWithFormat:#"/YourProjectFileName/Resources/Localization/%#.lproj",userLanguage]];
NSBundle *bundle = [NSBundle bundleWithPath:path];
NSString *localizedString = NSLocalizedStringFromTableInBundle(string, #"Localizable", bundle, comments);
return localizedString;
}
- (NSString *)getProjectPath {
NSString *currentSourceFilePath = [[NSString stringWithCString:__FILE__ encoding:NSUTF8StringEncoding] stringByDeletingLastPathComponent];
NSString *currentPath = [currentSourceFilePath copy];
BOOL foundIt = NO;
do {
NSString *testPath = [currentPath stringByAppendingPathComponent:#"XYZ.xcodeproj"];
if ([[NSFileManager defaultManager] fileExistsAtPath:testPath]) {
// found it
foundIt = YES;
break;
}
if ([currentPath isEqualToString:#"/"]) {
// cannot go further up
break;
}
currentPath = [currentPath stringByDeletingLastPathComponent];
} while ([currentPath length]);
if (!foundIt) {
return nil;
}
return currentPath;
}
And to use this //
NSString *loginStr = [self getLocalizedString:#"Login" withComments:#""];
And moreover for the better use of Automation, please set the accessibility labels for your controls, so that it would be easy for the automation process to find out that, which is less overhead on processor and less prone to crashes and issues.

IOS get installed Apps [duplicate]

Is it possible to programatically find out name of all apps installed on my iOS device ?
Is there any API available for same ?
Thanks for the help
No, on iOS applications has no access to information of/about other applications due to sandboxed environment.
Yes it is possible to get list of all installed app
-(void) allInstalledApp
{
NSDictionary *cacheDict;
NSDictionary *user;
static NSString *const cacheFileName = #"com.apple.mobile.installation.plist";
NSString *relativeCachePath = [[#"Library" stringByAppendingPathComponent: #"Caches"] stringByAppendingPathComponent: cacheFileName];
NSString *path = [[NSHomeDirectory() stringByAppendingPathComponent: #"../.."] stringByAppendingPathComponent: relativeCachePath];
cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
user = [cacheDict objectForKey: #"User"];
NSDictionary *systemApp=[cacheDict objectForKey:#"System"];
}
systemApp Dictionary contains the list of all system related app
and user Dictionary contains other app information.
Not from the device. However, from the desktop you could peek into the iTunes library.
There are ways to do this without a jailbroken device and not get your app rejected.
1. get a list of currently running processes see this SO answer. You will need to translate from process name to app name.
2. Check to see if any apps have registered a unique URL scheme with UIApplicationDelegate canOpenURL. There are a few sites cataloging known url schemes, this is the best one.
If an app is not currently running and does not register a custom url scheme then it will not be detected by these methods. I am interested in hearing a method that will be allowed in the app store that works better than this.
try this, it will work even with non-jailbroken devices:
#include <objc/runtime.h>
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
SEL selector=NSSelectorFromString(#"defaultWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:selector];
SEL selectorALL = NSSelectorFromString(#"allApplications");
NSLog(#"apps: %#", [workspace performSelector:selectorALL]);//will give you all **Bundle IDS** of user's all installed apps
You can do it by checking whether an application is installed or not by using canOpenURL method or by checking the background processes and matching them with the name of the app you are interested in.
You can use runtime objective c to get the list of all installed apps. It will give you an array of LSApplicationProxy objects.
Following is a code snippet that prints Name of all applications installed in your device.
Class LSApplicationWorkspace_class = objc_getClass("LSApplicationWorkspace");
NSObject* workspace = [LSApplicationWorkspace_class performSelector:NSSelectorFromString(#"defaultWorkspace")];
NSMutableArray *array = [workspace performSelector:NSSelectorFromString(#"allApplications")];
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
for (id lsApplicationProxy in array) {
if(nil != [lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]){
[mutableArray addObject:[lsApplicationProxy performSelector:NSSelectorFromString(#"itemName")]];
}
}
NSLog(#"********* Applications List ************* : \n %#",mutableArray);
Don't forget to include <objc/runtime.h> .

How to use a prepopulated SQLite database with PhoneGap / Cordova 2.0?

I want to use a pre populated database with my web-app, so that my app works offline. How can i do this with the newest version of PhoneGap / Cordova (2.0)?
I know that this question has been asked before, but all answers seem to be out of date relative to the current version of cordova and iOS
https://github.com/atkinson/phonegap-prepopulate-db has not been updated for two years
https://github.com/davibe/Phonegap-SQLitePlugin has not been updated for 7 months and is for 1.7
I found a post here:
http://www.raymondcamden.com/index.cfm/2012/7/27/Guest-Blog-Post-Shipping-a-populated-SQLite-DB-with-PhoneGap is this the only way?
Also, i should note that i use iOS 6
I have used the following plugin for Cordova 2.7 with iOS6 and I see it was just updated a day ago. https://github.com/pgsqlite/PG-SQLitePlugin-iOS
They also have an Android version here as well.
Part of the problem is PhoneGap changes so often keeping plugins up to date can be time consuming so they lapse. Here is the code for the init method in the AppDelegate.m I use in this project http://www.binpress.com/app/conference-core-ios-for-phonegap/1483
Keep in mind that you sometimes need to wipe the iOS Simulator so it reloads the files related to the application.
- (id)init{
NSHTTPCookieStorage* cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
int cacheSizeMemory = 8 * 1024 * 1024; // 8MB
int cacheSizeDisk = 32 * 1024 * 1024; // 32MB
if __has_feature(objc_arc)
NSURLCache* sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"];
else
NSURLCache* sharedCache = [[[NSURLCache alloc] initWithMemoryCapacity:cacheSizeMemory diskCapacity:cacheSizeDisk diskPath:#"nsurlcache"] autorelease];
endif
[NSURLCache setSharedURLCache:sharedCache];
databaseName = #"SomeCoolDatabase.db";
NSLog(#"databaseName: %#", databaseName);
databasePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex: 0];
NSLog(#"databasePath: %#", databasePath);
databaseFile = [databasePath stringByAppendingPathComponent:databaseName];
NSLog(#"databaseFile: %#", databaseFile);
// Execute the "checkAndCreateDatabase" function
[self checkAndCreateDatabase];
self = [super init];
return self;
}
-(void)checkAndCreateDatabase {
// Check if the SQL database has already been saved to the users phone, if not then copy it over
BOOL success;
// Create a FileManager object, we will use this to check the status
// of the database and to copy it over if required
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check if the database has already been created in the users filesystem
success = [fileManager fileExistsAtPath:databaseFile];
// If the database already exists then return without doing anything
if(success){
NSLog(#"Database Present");
return;
} else {
NSLog(#"database not present");
}
// If not then proceed to copy the database from the application to the users filesystem
// Get the path to the database in the application package
NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
// Create the database folder structure
[fileManager createDirectoryAtPath:databasePath withIntermediateDirectories:YES attributes:nil error:NULL];
// Copy the database from the package to the users filesystem
[fileManager copyItemAtPath:databasePathFromApp toPath:databaseFile error:nil];
[fileManager release];
}
Hope this helps...
Thank you! I wouldn't have been able to do this without starting here!
That said, this wasn't working for me out of the box, so I updated it, and I'm by no means 'good' with native code for iOS, so I'm VERY open to suggestions.
I added the suggested code to the AppDelegate.m file, got a heap of errors.
So, I moved over to the AppDelegate.h file:
#interface AppDelegate : NSObject <UIApplicationDelegate>{} --Looks like this to start.
Make it look like this:
#interface AppDelegate : NSObject <UIApplicationDelegate>{
NSString* databaseName;
NSString* databasePath;
NSString* databaseFile;
}
I just had to declare the variables in there.
I also removed this line:
[fileManager release];
As, according to this answer, my project seemingly automatically releases? I hope?
Now I have to work on getting the DB file into the correct location for xcode to find it.
Hope this helps!
Though this question is old... thought it might help someone to post my solution - found in 2017. (Since PhoneGap has changed, older solutions no longer will work or be accepted on the Android and Apple app markets.) I worked on this problem for a long time - the issue is you want to import a pre-populated database - which there are not alot of easy solutions for. I found the best and easiest - and currently just about the ONLY - solution to be LiteHelpers CORDOVA-SQLITE-EXT (https://github.com/litehelpers/cordova-sqlite-ext)
I have added this in to my app and it is working well. I had to switch away from PhoneGap though - and use CORDOVA - but it is working for me.

Check on app resume for server file changes?

I have a Data plist (conveniently named Data.plist) that is updated on launch of the app:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Determile cache file path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0],#"Data.plist"];
NSString *dataURLString = #"http://link/to/Data.plist";
NSURL *dataURL = [[NSURL alloc] initWithString:dataURLString];
NSData *plistData = [NSData dataWithContentsOfURL:dataURL];
[plistData writeToFile:filePath atomically:YES];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(#"The bundle is %#", filePath);
self.data = dict;
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
return YES;
}
I'd like to be able to have some way of checking the saved plist against the server plist - I've seen some implementations that use external libraries but there has to be something in the original iOS SDK. Any ideas? I've read whatever code I do end up using needs to be implemented in viewWillAppear but I'm not sure what that code is exactly.
Two things... first, dataWithContentsOfURL: and generally any of Apple's (temptingly convenient) <anything>WithContentsOfURL: methods are extremely unsafe in the real world. It's blocking which means that no other code will execute until your request succeeds or fails. That means that if the server isn't available or your device doesn't have internet or for some other reason cannot retrieve your data, your phone will sit there until either the iOS watchdog process kills your app for freezing for too long, or it just fails. Then the rest of your app that is expecting data will freak out because suddenly you have no data when your code assumes you should. This is one of many problems with synchronous requests.
I won't go into how to implement asynchronous requests, but head over to Apple's documentation or you can use a wrapper framework like http://allseeing-i.com/ASIHTTPRequest/ that does it for you. Also have a look at http://www.cocoabyss.com/foundation/nsurlconnection-synchronous-asynchronous/
To answer your actual question, you could have a tiny text file on your server with a version number or time stamp and download that along with your plist. on subsequent launches, you can pull down the time stamp/version number and compare it against the one you've got stored, and if the version on the server is more recent, then you pull it and save the new time stamp/version number.

Resources