Tuesday, 22 January 2013

iOS - Run application with root privileges

Please view this page in desktop version, or visit it from a PC. It contains syntax-highlighted code snippets which are badly displayed on mobile devices.

As you may already know, regular iOS apps run as Unix user "mobile", with limited privileges (most notably, limited filesystem access). If you wish to gain full control over the whole directory tree, your app has to run as user "root".

In order to accomplish this, your app has to satisfy the following conditions:

  1. Its bundle must be located in "/Applications".
  2. Its executable must belong to the root user.
  3. Its executable must be able to set user and group IDs.
  4. It has to explicitly set user and group ID at runtime.
  5. It must not be run directly. Rather, something else should launch it (keep reading to learn how to do this).


How do I do all of that? Well, that's actually pretty simple:

  1. Put your .app bundle in the ./Applications folder of your debian package.
  2. Set ownership of your app binary to "root:wheel":
    chown root:wheel ./Applications/my.app/myApp
  3. Give your app binary the "set user ID" and "set group ID" flags.
    chmod 6755 ./Applications/my.app/myApp
  4. Explicitly call "setuid(0)" and "setgid(0)" very early in your code (specifically in "main", somewhere before calling "UIApplicationMain"). Here's a quick example:
    #import <UIKit/UIKit.h>
    #import "MyAppDelegate.h"
    int main(int argc, char *argv[]){
        @autoreleasepool
        {
            // Set uid and gid
            if (!(setuid(0) == 0 && setgid(0) == 0))
            {
                NSLog(@"Failed to gain root privileges, aborting...");
                exit(EXIT_FAILURE);
            }
    
            // Launch app
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
        }
    }
Well that was easy, wasn't it? But what about that "don't run directly" madness?

Essentially, you need to add a "launch script" to the app bundle, which will be called when you tap on the SpringBoard icon, and will actually launch the app.

Not doing this will result in your app being terminated as soon as it attempts to setuid.

Assuming the executable of your app is named "myApp", all you have to do is rename it to "myApp_", and add a script called "myApp" to the .app bundle. The script should be structured like this:

#!/bin/bash
myAppPath=$(dirname "$0")
exec "$myAppPath"/myApp_ "$@"

Finally, make sure to set the ownership of the script to "root:wheel" and its permissions to "755".

Whew! After all of this, your app will finally be able to run as root.

Notes:
  • This doesn't apply to regular AppStore apps. Running as root is only possible on jailbroken devices.
  • The fact that your app will be running with root privileges does not mean you can skip the code signature step. Your app has to be properly pseudo-signed or self-signed in order to run. See this article from Saurik about bypassing code signature.
  • You should only give root privileges to your app if it really needs them. Misusing root access may potentially have catastrophic consequences if you don't know what you're doing.

Friday, 4 January 2013

Programmatically terminate an iOS application (with animation)

Please view this page in desktop version, or visit it from a PC. It contains syntax-highlighted code snippets which are badly displayed on mobile devices.

Before reading, please note that proper AppStore apps should not programmatically terminate themselves, as it's against Apple's recommendations. Also, I'll be using private API function calls in this code, which will automatically have your app rejected. This approach is only meant for open application development and/or Cydia submission.

iOS applications may be programmatically terminated by calling the private method:
[[UIApplication sharedApplication] terminateWithSuccess];

Or the C function:
exit(EXIT_SUCCESS);

These, however, will instantly terminate the app, without showing any animation. Basically, it will look like the app has crashed.

The only method that will show an animation while sending the app in background is the private:
[[UIApplication sharedApplication] suspend];

This method will act differently depending on the device and iOS version:
  • On iOS 4.0 and later, and if the device supports multitasking, it will suspend the application and send it to background.
  • On previous iOS versions, or if the device does not support multitasking, it will actually close the application.

To have the app terminated even in the first case, you might want to add the "UIApplicationExitsOnSuspend" key to the app's Info.plist. This works fine if you wish to terminate the app every time you call the "suspend" method, or the user presses the home button.

However, you might want to selectively suspend or close the app depending on various circumstances, but still retain the zooming animation (which is lost by calling "exit" or "terminateWithSuccess"). I came up with this approach: enable background execution, call the suspend method, then kill the app after a certain delay.

The most coherent solution would be using a category to extend the UIApplication class with a "close" method. Here's the code I have been using in my apps:

// Needed to directly call the private methods
@interface UIApplication (existing)
- (void)suspend;
- (void)terminateWithSuccess;
@end

@interface UIApplication (close)
- (void)close;
@end

@implementation UIApplication (close)

- (void)close
{
    // Checks if the current device supports background execution
    BOOL multitaskingSupported = NO;
    // iOS < 4.0 compatibility check
    if ([[UIDevice currentDevice] respondsToSelector:@selector(isMultitaskingSupported)])
        multitaskingSupported = [UIDevice currentDevice].multitaskingSupported;
    // Checks if application responds to selector "suspend" (good practice, we're using a private method)
    if ([self respondsToSelector:@selector(suspend)])
    {
        if (multitaskingSupported)
        {
            [self beginBackgroundTaskWithExpirationHandler:^{}];
            // Change the delay to your liking. I think 0.4 seconds feels just right (the "close" animation lasts 0.3 seconds).
            [self performSelector:@selector(exit) withObject:nil afterDelay:0.4];
        }
        [self suspend];
    }
    else
        [self exit];
}

- (void)exit
{
    // Again, good practice
    if ([self respondsToSelector:@selector(terminateWithSuccess)])
        [self terminateWithSuccess];
    else
        exit(EXIT_SUCCESS);
}

@end
This will allow you to call:
[[UIApplication sharedApplication] close];
And have your application gracefully animate and close itself.