App with jailbreak detection rejected by Apple - ios

the App we are working on was rejected because the Device in the Review Process was detected as jailbroken ^^
To detect a jailbroken Device, several Tests were performed:
NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
// scan for itunes metadata
BOOL isDirectory = NO;
NSString* directoryPath = [bundlePath stringByAppendingPathComponent:#"SC_Info/"];
BOOL directoryIsAvailable = [[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:&isDirectory];
BOOL contentSeemsValid = ([[[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:NULL] count] == 2);
if (directoryIsAvailable && contentSeemsValid) {
return YES;
}
contentSeemsValid = [[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/iTunesMetadata.​plist", bundlePath]];
if (contentSeemsValid) {
return YES;
}
// scan for cydia app
NSURL* testURL = [NSURL URLWithString:#"cydia://"];
if ([[UIApplication sharedApplication] canOpenURL:testURL]) {
return YES;
}
// scan for paths available
NSArray* paths = #[#"/Applications/Cydia.app", #"/Applications/RockApp.app", #"/Applications/Icy.app", #"/usr/sbin/sshd", #"/usr/bin/sshd", #"/private/var/lib/apt", #"/private/var/lib/cydia", #"/private/var/stash", #"/usr/libexec/sftp-server"];
for (NSString* string in paths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:string]) {
return YES;
}
}
// scan for forking
int forkValue = fork();
if (forkValue >= 0) {
return YES;
}
// try to write in private space
NSString* testString = #"test";
NSError* error = nil;
[testString writeToFile:#"/private/test.txt" atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error == nil) {
return YES;
}
// seems not jailbroken
return NO;
One (or more) of these Tests return YES on the Devices Apple use for Review, but none of our DevDevices. Which one could it be? Does anybody know more Details about the Devices Apple use for the Review? Any Hints or other Guesses? (The Context from the App is HealthCare in Hospitals, so we need to be sure that the Patient Data were save)
Best Regards,
Zeek

From https://www.theiphonewiki.com/wiki/Bypassing_Jailbreak_Detection
While there are countless ways apps can implement checks for jailbroken devices, they typically boil down to the following:
Existence of directories - Check your file system for paths like /Applications/Cydia.app/ and /private/var/stash, amongst a handful of others. Most often, these are checked using the -(BOOL)fileExistsAtPath:(NSString*)path method in NSFileManager, but more sneaky apps like to use lower-level C functions like fopen(), stat(), or access().
Directory permissions - Check the Unix file permissions of specific files and directories using NSFileManager methods as well as C functions like statfs(). Far more directories have write access on a jailbroken device than on one still in jail.
Process forking - sandboxd does not deny App Store applications the ability to use fork(), popen(), or any other C functions to create child processes on non-jailbroken devices. sandboxd explicitly denies process forking on devices in jail. if you check the returned pid on fork(), your app can tell if it has successfully forked or not, at which point it can determine a device's jailbreak status.
SSH loopback connections* - Due to the large portion of jailbroken devices that have OpenSSH installed, some apps will attempt to connect to 127.0.0.1 on port 22. If the connection succeeds, it means OpenSSH is installed and running on the device, therefore it is jailbroken.
system() - Calling the system() function with a NULL argument on a device in jail will return 0; doing the same on a jailbroken device will return 1. This is since the function will check whether /bin/sh exists, and this is only the case on jailbroken devices.[1]
dyld functions - By far the hardest to get around. Calling functions like _dyld_image_count() and _dyld_get_image_name() to see which dylibs are currently loaded. Very difficult to patch, as patches are themselves part of dylibs.
*Only a very small number of applications implement this (as it is not nearly as effective as the others)
These methods seem like they would be less likely to be rejected by apple and are very simple to use.
The above passage has been edited for brevity

Related

Creating Custom URL Scheme in iOS using Objective C

I am working on custom URL scheme basically i am new with us functionality.
What i done before?
I implemented this successfully,i create simple url "myApp" now when i hit this url on safari like (myApp://) its launch my application.
My code
i used:
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
What i want?
Basically i want to implement two things that is:
when an user hit a link on safari if an application is installed on device,through that link application should launch.That point i already done successfully.
The second point which i can't done yet,after trying a lot but i couldn't found any solution yet,that is:
If an application is not installed on device that link should redirect to app store where user can installed application.
If it is possible,then please tell me how to do that,please refer me any link,example or your answer also help me.Thanks in advance
What you are describing is called Deep Linking. It's a very common app feature to implement — most apps have it — and conceptually, it seems like an easy thing to build. However, it's complicated to get right, and there are a lot of edge cases.
You basically need to accomplish two things:
If the app is installed: open the app and route users to the correct content inside it.
If the app is NOT installed: forward users to the App Store so they can download it. Ideally, also route users to the correct content inside the app after downloading (this is known as 'deferred deep linking').
While not required, you'll also probably want to track all of this activity so you can see what is working.
If the app is installed
Your existing custom URI scheme fits into this category. However, Apple has decided that custom URI schemes are not a good technology, and deprecated them with iOS 9 in favor of Universal Links.
Apple is right about this. Custom URI schemes have a number of problems, but these are the biggest:
There is no fallback if the app isn't installed. In fact, you get an error.
They often aren't recognized as links the user can click.
To work around these, it used to be possible to use a regular http:// link, and then insert a redirect on the destination page to forward the user to your custom URI scheme, thereby opening the app. If that redirect failed, you could then redirect users to the App Store instead, seamlessly. This is the part Apple broke in iOS 9 to drive adoption of Universal Links.
Universal Links are a better user experience, because they are http:// links by default and avoid nasty errors. However, they are hard to set up and still don't work everywhere.
To ensure your users end up inside the app when they have it installed, you need to support both Universal Links and a custom URI scheme, and even then there are a lot of edge cases like Facebook and Twitter which require special handling.
If the app is NOT installed
In this case, the user will end up on your http:// fallback URL. At this point, you have two options:
Immediately forward the user directly to the App Store.
Send the user to your mobile website (and then use something like a smart banner to give them the option of going to the App Store).
Most large brands prefer the second option. Smaller apps often go with the first approach, especially if they don't have a website.
To forward the user to the App Store, you can use a Javascript redirect like this:
<script type="text/javascript">
window.onload = function() {
window.location = "https://itunes.apple.com/app/id1121012049";
};
</script>
Until recently, it was possible to use a HTTP redirect for better speed, but Apple changed some behavior in Safari with iOS 10.3, so this no longer works as well.
Deferred deep linking
Unfortunately there's no native way to accomplish this last piece on either iOS or Android. To make this work, you need a remote server to close the loop. You can build this yourself, but you really shouldn't for a lot of reasons, not the least of which being you have more important things to do.
Bottom line
Deep linking is very complicated. Most apps today don't attempt to set it up by building an in-house system. Free hosted deep link services like Branch.io (full disclosure: they're so awesome I work with them) and Firebase Dynamic Links can handle all of this for you, and ensure you are always up to date with the latest standards and edge cases.
See here for a video overview I made of everything you need to know about this.
In AppDelegate.m call this:
-(BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;{
NSLog(#"url recieved: %#", url);
NSLog(#"query string: %#", [url query]);
NSLog(#"host: %#", [url host]);
NSLog(#"url path: %#", [url path]);
NSDictionary *dict = [self parseQueryString:[url query]];
NSLog(#"query dict: %#", dict);
if ([[url host] isEqualToString:#"login"]) {
[self setupHomeScreen:NO type:#"" entityId:#""];
[self _endSession];
return YES;
} else if ([[url host] isEqualToString:#"smartoffice"]) {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
NSRange needle = [url.absoluteString rangeOfString:#"?" options:NSCaseInsensitiveSearch];
NSString *data = nil;
if(needle.location != NSNotFound) {
NSUInteger start = needle.location + 1;
NSUInteger end = [url.absoluteString length] - start;
data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
}
for (NSString *param in [data componentsSeparatedByString:#"&"]) {
NSArray *keyvalue = [param componentsSeparatedByString:#"="];
if([keyvalue count] == 2){
[result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
}
}
NSString *entityID = ([result objectForKey:#"secondparameter"]);
NSString *type = [result objectForKey:#"parameter"];
[self setupHomeScreen:YES type:type entityId:entityID];
//[self setupHomeScreen:YES type:#"TASK" entityId:#"160315"];
return result;
} else {
NSLog(#"myApp is not installed");
[self setupHomeScreen:NO type:#"0" entityId:#"0"];
}
return NO;
}
Open Xcode, got to Project Settings -> Info, and add inside ‘The URL Types” section a new URL scheme.
That is the scheme ://resource. So we go ahead and type com.myApp://
Fo me its backofficeapp://
Now use this link on Safari
backofficeapp://smartoffice/1?parameter=TASK&secondparameter=157536
Where
parameter = “TASK”
secondparameter = “taskID” // #iOS Mobile 2020 = 157536
This parameter is for landing on a specific screen.
Remember this URL won’t work if the app is not installed.

Large size image uploading issue with latest iOS dropbox sdk

I am trying to upload 4 to 5 mb size image on dropbox using latest iOS dropbox sdk. But every time I get Error code =-1001 The operation couldn't be completed.
My internet connection speed is fair enough (2 mbps).
I have also tried by reducing image size to 512kb and it gets uploaded. But if I try to upload 5 or 6 images in sequence than again i get same error after successful upload of 2 or 3 images.
I have tried using below methods.
[self.restClient uploadFile:filename toPath:destDir withParentRev:nil fromPath:localPath]
[self.restClient uploadFileChunk:nil offset:0 fromPath:localPath]
According to the documentation a -1001 error means NSURLErrorTimedOut, meaning that the connection took too long. There are a number of factors that can contribute to this, including the device, the device's network stack and Internet connection, the particular route between your ISP and the destination servers, etc.
When it comes to your Internet connection, you only upload as fast as your Internet service provider (ISP) allows. Consumer ISP's normally provide less upload speed compared to download speed. In addition to this, your speeds may be slower than what your ISP rates it. Sometimes resetting or retrying your connection gets you a different route and better speeds, but that is outside of our control. Some ISPs also throttle sustained connections so if you see an initial high connection speed followed by lower speeds, that could be the reason.
When using uploadFile, you're attempting to upload the entirety of the file in one HTTPS request. If the file is big enough, along with the above considerations, this may make the request take too long and fail. For that reason, chunked uploading can be better.
When using uploadFileChunk, you're only attempting to upload a portion of the file per HTTPS request, meaning each request doesn't need to take as long and is less likely to fail.
In either case, one more thing to be aware of is how many simultaneous requests you're attempting to make. If you have too many running at the same time, they may all slow down, causing these failures. That being the case, you'll want to limit how many you run concurrently. Some developers choose a limit of 4 simultaneous connections, but the right number for your app will depend on the specifics of the scenario, so you may want to try a few different numbers.
Edit, here's some basic sample code for using chunked uploading:
- (void) doTest {
if ([[DBSession sharedSession] isLinked]) {
NSLog(#"Running test...");
// Write a file to the local documents directory
NSString *text = #"Hello world. Usually something much longer here.";
NSString *filename = #"working-draft.txt";
NSString *localDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
self.localPath = [localDir stringByAppendingPathComponent:filename];
[text writeToFile:self.localPath atomically:YES encoding:NSUTF8StringEncoding error:nil];
self.dbClient = [[DBRestClient alloc] initWithSession:[DBSession sharedSession]];
self.dbClient.delegate = self;
// Start the upload. No upload ID known yet
[self.dbClient uploadFileChunk:nil offset:0 fromPath:self.localPath];
}
}
- (void)restClient:(DBRestClient *)client uploadedFileChunk:(NSString *)uploadId newOffset:(unsigned long long)offset
fromFile:(NSString *)localPath expires:(NSDate *)expiresDate {
NSLog(#"uploadedFileChunk: %#, newOffset: %llu, fromFile: %#, expires: %#", uploadId, offset, localPath, expiresDate);
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attrs = [fileManager attributesOfItemAtPath:self.localPath error: NULL];
// in real use, there's probably a dangerous race condition here in checking the size each time, but this is just for demonstration purposes
if (offset >= [attrs fileSize]) {
// all data has been uploaded
NSLog(#"Finishing upload...");
[self.dbClient uploadFile:#"chunked_uploaded_file.txt" toPath:#"/" withParentRev:nil fromUploadId:uploadId];
} else {
// more data to upload
NSLog(#"Continuing upload...");
[self.dbClient uploadFileChunk:uploadId offset:offset fromPath:localPath];
}
}
- (void)restClient:(DBRestClient *)client uploadFileChunkFailedWithError:(NSError *)error {
NSLog(#"uploadFileChunkFailedWithError: %#", error);
// in real use, the app should probably implement some retrying logic here
}
- (void)restClient:(DBRestClient *)client uploadFileChunkProgress:(CGFloat)progress {
NSLog(#"uploadFileChunkProgress: %f", progress);
}

iOS MDM: Can we get to know whether device is rooted or jailbroken?

I am working on MDM implementation for iOS. I want to know whether there any command using which we can get to know whether iOS device is rooted or jailbroken?
I had seen the MDM protocol reference and I haven't found any field in DeviceInformation command to know this.
How server can know this status from device?
The Apple MDM protocol does not have a way to check if a device is jailbroken. MDM vendors will usually come up with their own solution for this.
You can look for Cydia (or similar apps) with NSFileManager. And you should check if you have access to the bash on the phone. You can try something like this:
- (BOOL) isJailbroken
{
//If the app is running on the simulator
#if TARGET_IPHONE_SIMULATOR
return NO;
//If its running on an actual device
#else
BOOL isJailbroken = NO;
//This line checks for the existence of Cydia
BOOL cydiaInstalled = [[NSFileManager defaultManager] fileExistsAtPath:#"/Applications/Cydia.app"];
FILE *f = fopen("/bin/bash", "r");
if (!(errno == ENOENT) || cydiaInstalled) {
//Device is jailbroken
isJailbroken = YES;
}
fclose(f);
return isJailbroken;
#endif
}
This code is not really tested.. let me know if it worked.

Why does my iOS app only detect the current language properly on first run?

I am localizing my iOS app, and in the Simulator it runs correctly in my chosen language every time.
When testing on my iPhone 5, it only detects the language properly the first time the app runs. Every other time I recompile and run my app on the device, it detects "en" as the language, even though I am testing with Español ("es") selected.
I detect the language using:
[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]
I've also used:
[[NSLocale preferredLanguages] objectAtIndex:0]
Same result.
If I kill the app after the first run, and restart it on the device, it continues to detect the language properly.
But if I kill the app and then recompile/restart via Xcode after the initial run, it will load with "en" (English) detected instead.
After that, killing and re-starting the app continuously detects as English unless I delete the app completely, and recompile/reinstall/run the app via Xcode. The cycle then repeats... subsequent rebuild/restart without first deleting the app from the device results in misdetection.
All other apps on my device display with Spanish language the entire time. The entire UI shows in Spanish.
UPDATE: I've now tested on my iPad (3rd gen) also running iOS 6, and am experiencing the same behavior.
UPDATE 2:
In didFinishLaunchingWithOptions, I have this code to detect language: (language is an NSString*):
language = [[NSLocale preferredLanguages] objectAtIndex:0];
Followed by this debugging statement, to compare the value I'm getting, as well as a slightly different way of detecting it, just for debugging:
NSLog(#"Detected language: %# / %#", language, [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]);
The output shows as "Detected language: es / es" when the app works properly in Spanish mode, and then shows as "Detected language: en / en" when it doesn't. Still no idea why it decides to load as English sometimes...
UPDATE 4: I appreciate everybody's answers, and I've tried the various suggestions. Unfortunately I was unable to award the +100 bounty as none of the suggestions seemed to fix the issue. If someone does ultimate find a solution that works for me, I will award another +50 bounty to them at that time.
UPDATE 5: I have updated from Xcode 4.5 to 4.5.2, and experiencing this same issue.
UPDATE 6: I have now created a new test project from scratch, and it works perfectly fine! Obviously something must be wrong in the way my project is laid out, or perhaps in one of the data files. I guess my next journey will be to re-create the project from scratch, copying file data over one by one...
UPDATE 7 (MONTHS LATER): Sadly, I am again facing this issue after temporarily resolving it (seemingly) by painstakingly recreating my project. On first load, the language is correctly rendered, but on subsequent loads, it reverts back to English.
SOLVED See my final solution below. Thanks for the help everyone. I may dole out some of the bounty since it will go to waste anyway.
I have FINALLY solved this problem after many months! Thanks to all for the help (I also had some good back and forth with an Apple developer via the dev channels).
TL;DR: I was accidentally syncing language preferences (among many other unexpected things) between devices, using my app's iCloud key value store (via MKiCloudSync)! Read on...
I am using a third-party class called MKiCloudSync, which helps with syncing [NSUserDefaults standardUserDefaults] to my app's iCloud key value store. My intention when I began using it was to let it handle some user favorites syncing in the background.
However, not understanding how standardUserDefaults works, what I didn't realize is that there are a lot of other things being written into standardUserDefaults other than just my own custom app settings!
So what was happening was this:
Start the app up for the first time. Fresh standardUserDefaults in place, and the internal "AppleLanguages" key that stores the ordered list of language preferences is correct based on the current device choices.
App displays properly in the designated language.
In the background, MKiCloudSync syncs ALL standardUserDefaults to iCloud. Conversely, if you had run this app elsewhere, say with an English set device, that device would have also synced it's language settings up to iCloud. So now this current running app is actually having it's language preferences overwritten.
BOOM ... next time the app is run, no matter what you have selected on the device, it's whatever was pulled down from iCloud that will be used as the default language!
What I plan to do to solve the issue with my next app update:
Use a forked version of MKiCloudSync that allows for syncing only whitelisted key names.
Add code that will do a one-time cleanup, first cleaning out the iCloud keystore for my app, then (based on this SO answer), calling this code to reset the user defaults:
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
In my testing so far, this sort of solves the issue... unfortunately, the user would have to restart the app for the language fix to kick in. However, my guess is most users are not experiencing this issue, as they are unlikely to be using multiple devices with different default languages.
In settings->general->international, there is an option to set the location language etc. if you set that to the language you are trying to test it will work if your code is correct.
I tested your steps on my iPhone 5 without issues. This leads me to think there's something else at play here: most probably there's something interferring with the way in which you're reading the locale value.
The steps I'd recommend you take to help you debug this issue are:
Post the complete code of the method in which you're obtaining the preferred language value. Make sure the method is executed each time the app is run.
Make sure the code you post includes the location of the NSLog directive you're using to test for the language setting.
Are you storing the preferred language somewhere else after the first run?
Try with following code:
LocalizationSystem.h===
#import <Foundation/Foundation.h>
#define AMLocalizedString(key, comment) \
[[LocalizationSystem sharedLocalSystem] localizedStringForKey:(key) value:(comment)]
#define LocalizationSetLanguage(language) \
[[LocalizationSystem sharedLocalSystem] setLanguage:(language)]
#define LocalizationGetLanguage \
[[LocalizationSystem sharedLocalSystem] getLanguage]
#define LocalizationReset \
[[LocalizationSystem sharedLocalSystem] resetLocalization]
#interface LocalizationSystem : NSObject {
NSString *language;
}
+ (LocalizationSystem *)sharedLocalSystem;
//gets the string localized
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)comment;
//sets the language
- (void) setLanguage:(NSString*) language;
//gets the current language
- (NSString*) getLanguage;
//resets this system.
- (void) resetLocalization;
#end
LocalizationSystem.m===
#import "LocalizationSystem.h"
#implementation LocalizationSystem
//Singleton instance
static LocalizationSystem *_sharedLocalSystem = nil;
//Current application bundle to get the languages.
static NSBundle *bundle = nil;
+ (LocalizationSystem *)sharedLocalSystem{
#synchronized([LocalizationSystem class])
{
if (!_sharedLocalSystem){
[[self alloc] init];
}
return _sharedLocalSystem;
}
// to avoid compiler warning
return nil;
}
+(id)alloc{
#synchronized([LocalizationSystem class])
{
NSAssert(_sharedLocalSystem == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedLocalSystem = [super alloc];
return _sharedLocalSystem;
}
// to avoid compiler warning
return nil;
}
- (id)init{
if ((self = [super init]))
{
//empty.
bundle = [NSBundle mainBundle];
}
return self;
}
// Gets the current localized string as in NSLocalizedString.
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)comment{
return [bundle localizedStringForKey:key value:comment table:nil];
}
// If this function is not called it will use the default OS language.
// If the language does not exists y returns the default OS language.
- (void) setLanguage:(NSString*) l{
NSLog(#"preferredLang: %#", l);
NSString *path = [[ NSBundle mainBundle ] pathForResource:l ofType:#"lproj" ];
if (path == nil)
//in case the language does not exists
[self resetLocalization];
else
bundle = [[NSBundle bundleWithPath:path] retain];
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:l, nil] forKey:#"AppleLanguages"];
}
// Just gets the current setted up language.
// returns "es","fr",...
//
// example call:
// NSString * currentL = LocalizationGetLanguage;
- (NSString*) getLanguage{
NSArray* languages = [[NSUserDefaults standardUserDefaults] objectForKey:#"AppleLanguages"];
NSString *preferredLang = [languages objectAtIndex:0];
return preferredLang;
}
// Resets the localization system, so it uses the OS default language.
//
// example call:
// LocalizationReset;
- (void) resetLocalization{
bundle = [NSBundle mainBundle];
}
#end
This code works perfectly as you mentioned.
It worked for me and that game is live now app store, if you want to check(HueShapes).
Do you by chance use NSUserDefaults to save something language related?
Look into your Simulator App directory -> Library -> Preferences -> <YourAppBundleName>.plist
See: How to force NSLocalizedString to use a specific language for description of the NSUserDefaults method of setting a language.
Perhaps you just save your language and thus detection just returns the saved value.

IOS: Using stringWithContentsOfURL when network is unavailable

In this code poll from within my app for a reachable network
("http://soxxx9.cafe24.com/event.php")
NSString * szURL =[NSString stringWithFormat:#"http://soxxx9.cafe24.com/event.php"];
NSURL *url = [NSURL URLWithString:[szURL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding ]];
NSString *strData;
while(1)
{
NSError *error = nil;
strData = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
if(!error)
break;
//String data is not owned by me, no need to release
}
If you have a better way, please teach me.
This code seems to be heavily power consuming when network is out : you'll try million times to download something that is unreachable...
Have a look at the Reachability class, provided by Apple (http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/Intro.html). You'll find ARCified versions on gitHub (https://github.com/tonymillion/Reachability for example).
The idea is to register for notifications about the network reachability.
So, in your code :
Check network resource availability before retrieving the string you want.
If this is available, use your code WITHOUT the while(TRUE)
Check your string for any error while retrieving it client side = in your code
If the network is not available, you'll have to inform the user that network is unreachable, and register for reachability notifications to retrieve your string as soon as it is reachable again for example.
You should a class to handle the connection for you. This way you have more control of what's going on with it. MKNetworkKit is a solution, you can check it here.

Resources