A lot of you have been asking about the writing of a daemon on iOS recently, and Chris Alvares' tutorial80 seems to be a little bit outdated. So today I'm gonna present you a simple guide of writing a daemon (and run it as root) on iOS, and this post will not only cover the process of composing a daemon, but the very basic theory contained in it. Our target today is a working daemon that reboots iOS when it receives a specified notification, "com.naken.iosred.reboot". Have fun!
Part I Basic theory
1. Daemon
What's a daemon? According to wikipedia43, a daemon
is a computer program that runs as a background process, rather than being under the direct control of an interactive user. Traditionally daemon names end with the letter d: for example, syslogd is the daemon that implements the system logging facility and sshd is a daemon that services incoming SSH connections.
You can name a few other daemons on iOS, say backboardd, mediaserverd, apsd, etc.
Daemons are started by the first process on iOS, launchd, which is also a daemon, on boot time. What can a daemon do? It
serves the function of responding to network requests, hardware activity, or other programs by performing some task.
Note, daemons (running as root) can be so powerful while staying low that even powerusers may not know the existence of a daemon, so some malware are born as daemons. This post is for educational purposes only, you take the charge if you're doing something risky.
2. Daemon ownership
Daemons are launched by launchd, via "launchctl" command plus their configuration files. On its man page30, we should pay special attention to this sentence
Note that per-user configuration files (LaunchAgents) must be owned by the user loading them. All system-wide daemons (LaunchDaemons) must be owned by root. Configuration files must not be group- or world-writable. These restrictions are in place for security reasons, as allowing writability to a launchd configuration file allows one to specify which executable will be launched.
Because daemons are loaded by launchd, which is owned by root:wheel,
FunMaker-5:~ root# ls -l /sbin/launchd
-r-xr-xr-x 1 root wheel 154736 Nov 8 2013 /sbin/launchd
so both a daemon and its config file must be owned by root:wheel too, it borns and runs as root. Take it in mind and we'll get back to this later.
Part II Composing
As we have already stated in iOS App Reverse Engineering, daemons consists of 2 parts, an executable binary and a configuration plist file. So let's make an executable binary with Theos now:
FunMaker-MBP:Code snakeninny$ nic.pl
NIC 2.0 - New Instance Creator
------------------------------
[1.] iphone/activator_event
[2.] iphone/application_modern
[3.] iphone/cydget
[4.] iphone/flipswitch_switch
[5.] iphone/framework
[6.] iphone/ios7_notification_center_widget
[7.] iphone/library
[8.] iphone/notification_center_widget
[9.] iphone/preference_bundle_modern
[10.] iphone/tool
[11.] iphone/tweak
[12.] iphone/xpc_service
Choose a Template (required): 10
Project Name (required): iOSREd
Package Name [com.yourcompany.iosred]: com.naken.iosred
Author/Maintainer Name [snakeninny]: snakeninny
Instantiating iphone/tool in iosred/...
Done.
And modify the content of main.mm
static void Reboot(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo)
{
NSLog(@"iOSRE: reboot");
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
system("reboot");
#pragma GCC diagnostic pop
}
int main(int argc, char **argv, char **envp)
{
NSLog(@"iOSRE: iOSREd is launched!");
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), NULL, Reboot, CFSTR("com.naken.iosred.reboot"), NULL, CFNotificationSuspensionBehaviorCoalesce);
CFRunLoopRun(); // keep it running in background
return 0;
}
That's it. Now let's turn to the config file, create a file with the name "com.naken.iosred.plist" and permission 644:
contains a unique string that identifies your daemon to launchd,
while "Program" contains the path of the executable; both of them are required. If you have arguments for the daemon, just add another key/value pair to the file like this:
Modify Makefile and control to make it compilable. Then run "make package" and check the owner of the deb:
FunMaker-MBP:iOSREd snakeninny$ make package
> Making all for tool iOSREd…
make[2]: Nothing to be done for `internal-tool-compile'.
> Making stage for tool iOSREd…
warning, `/Users/snakeninny/Code/iOSREd/.theos/_/DEBIAN/control' contains user-defined field `Name'
warning, `/Users/snakeninny/Code/iOSREd/.theos/_/DEBIAN/control' contains user-defined field `Author'
dpkg-deb: building package `com.naken.iosred' in `./packages/com.naken.iosred_1.0-2+debug_iphoneos-arm.deb'.
dpkg-deb: ignoring 2 warnings about the control file(s)
All files inside deb are owned by snakeninny:staff. Remember in the "Daemon ownership" part, daemons must be owned by root:wheel? So this daemon has wrong owner, which will lead to a load failure (you can try it out on your iOS).
You may wonde why? That's because this deb is made on OSX, and the maker is snakeninny. To change its owner back to root:wheel, we need a tool called fauxsu84 by DHowett7.
Download a compiled version from here117, extract fauxsu and libfauxsu.dylib to $THEOS/bin/ and do some magic:
FunMaker-MBP:iOSREd snakeninny$ make package
> Making all for tool iOSREd…
make[2]: Nothing to be done for `internal-tool-compile'.
> Making stage for tool iOSREd…
warning, `/Users/snakeninny/Code/iOSREd/.theos/_/DEBIAN/control' contains user-defined field `Name'
warning, `/Users/snakeninny/Code/iOSREd/.theos/_/DEBIAN/control' contains user-defined field `Author'
dpkg-deb: building package `com.naken.iosred' in `./packages/com.naken.iosred_1.0-3+debug_iphoneos-arm.deb'.
dpkg-deb: ignoring 2 warnings about the control file(s)
iOSREd was started on boot, and it stays in the background. Finally, let's check if it works as expected:
FunMaker-SE:~ root# cycript -p SpringBoard
cy# np = @encode(unsigned int(*)(char const*))(dlsym(RTLD_DEFAULT, "notify_post"))
&(extern "C" unsigned int notify_post(char const*))
cy# np("com.naken.iosred.reboot")
Connection to localhost closed by remote host.
Connection to localhost closed.
It works like a charm.
Part IV Conclusion
Actually daemons and agents on iOS/OSX are far more complicated than this post describes, and I strongly suggest you take a look at the references below. Again, daemons are powerful tools that can do both good and bad, you'd better know what you're doing before you use them, and you should be really careful when you use them. Thanks for your time.