Run a daemon (as root) on iOS

Hello people!

A lot of you have been asking about the writing of a daemon on iOS recently, and Chris Alvares’ tutorial 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 wikipedia, 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 page, 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:

FunMaker-MBP:Code snakeninny$ cd ./iOSREd
FunMaker-MBP:iOSREd snakeninny$ touch com.naken.iosred.plist
FunMaker-MBP:iOSREd snakeninny$ chmod 644 com.naken.iosred.plist

and fill it with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>KeepAlive</key>
        <true/>
        <key>Label</key>
        <string>com.naken.iosred</string>
        <key>Program</key>
        <string>/usr/bin/iOSREd</string>
        <key>RunAtLoad</key>
        <true/>
</dict>
</plist>

Among those keys, “Label”

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:

<key>ProgramArguments</key>
    <array>
        <string>arg1</string>
        <string>arg2</string>
        <string>more args...</string>
    </array>

After that we should put this config file under /Library/LaunchDaemons/:

FunMaker-MBP:iOSREd snakeninny$ mkdir -p ./Layout/Library/LaunchDaemons/
FunMaker-MBP:iOSREd snakeninny$ mv com.naken.iosred.plist ./Layout/Library/LaunchDaemons/

The project looks like this now:

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)
FunMaker-MBP:iOSREd snakeninny$ dpkg-deb -c ./packages/com.naken.iosred_1.0-2+debug_iphoneos-arm.deb
drwxr-xr-x snakeninny/staff  0 2016-09-26 14:58 ./
drwxr-xr-x snakeninny/staff  0 2016-09-26 14:52 ./Library/
drwxr-xr-x snakeninny/staff  0 2016-09-26 14:53 ./Library/LaunchDaemons/
-rw-r--r-- snakeninny/staff 413 2016-09-26 14:43 ./Library/LaunchDaemons/com.naken.iosred.plist
drwxr-xr-x snakeninny/staff   0 2016-09-26 14:58 ./usr/
drwxr-xr-x snakeninny/staff   0 2016-09-26 14:58 ./usr/bin/
-rwxr-xr-x snakeninny/staff 132640 2016-09-26 14:58 ./usr/bin/iOSREd

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 fauxsu by DHowett.

Download a compiled version from here, extract fauxsu and libfauxsu.dylib to $THEOS/bin/ and do some magic:

FunMaker-MBP:iOSREd snakeninny$ sudo mv /Users/snakeninny/Downloads/fauxsu/* /opt/theos/bin/
Password:
FunMaker-MBP:iOSREd snakeninny$ sudo chmod +x /opt/theos/bin/fauxsu
FunMaker-MBP:iOSREd snakeninny$ sudo chmod +x /opt/theos/bin/libfauxsu.dylib
FunMaker-MBP:iOSREd snakeninny$ sudo chown root:wheel /opt/theos/bin/fauxsu
FunMaker-MBP:iOSREd snakeninny$ sudo chown root:wheel /opt/theos/bin/libfauxsu.dylib
FunMaker-MBP:iOSREd snakeninny$ ls -l /opt/theos/bin/ | grep faux
-rwxr-xr-x@ 1 root  wheel    777 Nov 24  2010 fauxsu
-rwxr-xr-x@ 1 root  wheel  51536 Nov 24  2010 libfauxsu.dylib

Make another package and check again:

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)
FunMaker-MBP:iOSREd snakeninny$ dpkg-deb -c ./packages/com.naken.iosred_1.0-3+debug_iphoneos-arm.deb
drwxr-xr-x root/wheel        0 2016-09-26 15:08 ./
drwxr-xr-x root/wheel        0 2016-09-26 14:52 ./Library/
drwxr-xr-x root/wheel        0 2016-09-26 14:53 ./Library/LaunchDaemons/
-rw-r--r-- root/wheel      413 2016-09-26 14:43 ./Library/LaunchDaemons/com.naken.iosred.plist
drwxr-xr-x root/wheel        0 2016-09-26 15:08 ./usr/
drwxr-xr-x root/wheel        0 2016-09-26 15:08 ./usr/bin/
-rwxr-xr-x root/wheel   132640 2016-09-26 15:08 ./usr/bin/iOSREd

Now the owner is correct. Run “make install” to setup iOSREd:

FunMaker-MBP:iOSREd snakeninny$ make install
==> Installing…
Selecting previously deselected package com.naken.iosred.
(Reading database ... 1908 files and directories currently installed.)
Unpacking com.naken.iosred (from /tmp/_theos_install.deb) ...
Setting up com.naken.iosred (1.0-3+debug) ...

Part III Testing

Reboot (and rejailbreak if you’re running iOS 9.2 ~ 9.3.3) to check if it’s launched on startup :

FunMaker-SE:~ root# ps -e | grep iOSRE
  364 ??         0:00.01 /usr/bin/iOSREd
  729 ttys000    0:00.00 grep iOSRE

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.

References:

http://en.wikipedia.org/wiki/Daemon_(computing)

https://www.chrisalvares.com/blog/7/creating-an-iphone-daemon-part-1/

https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/launchctl.1.html

https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html

13 个赞

detailed and wonderful tutorial!

it works like a charm

好文章,以前看到过用 SBSetting 的 commands 来实现 reboot, 但肯定还是实现自己的 Daemon 来得方便。

请问iOSRERootDaemonTester是什么程序,怎么生成的?

是一个用来发送com.iosre.rootdaemon.reboot消息的可执行文件,“rootdaemond was started on boot, and it stays in the background. Finally, let’s check if it works as expected, with the following tester:”下面写出了源代码及编译方式

谢谢,现在弄懂呢,教程强大啊

漂亮。相当漂亮。赞个。

狗神,我编译tester的时候遇到这样的错误

Desktop  clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk -o ShakeRootDaemonTester -arch armv7 /Users/shakespeare/Desktop/main.mm 
clang: error: invalid version number in '-miphoneos-version-min=.sd'

然后google无果,不知道如何,求教。

你的SDK路径搞错了吧?默认情况下应该没有iPhoneOS.sdk这个文件

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 fauxsu by DHowett. Download a compiled version from here, extract fauxsu and libfauxsu.dylib to $THEOS/bin/ and “chmod a+x” them. Make another package and check again
请问大神这里提到的用“a tool called fauxsu”修改root权限的问题,按照教程做了在THEOS下使用 make package 生成的 deb文件 用 dpkg-deb 查看是正确的 root/wheel。但是我的开发环境是iosopendev 用这个工具(引用上面相同的theos)编译出来的packages里的deb文件还是 snakeninny/staff 这个权限呢?

fauxsu是针对Theos的tool,我不确定它是否适用于opendev。坛子里好像分享过opendev提权的办法,你搜搜看

我搜过了没有关于iosopendev 的提权介绍啊,我试了下可以用chown root:wheel ‘file’ 的方式修改文件所属用户组为root/wheel 但是还有个问题是我的项目是PreferenceBundle 项目 没有main.m 这些东西请问setuid(0);
setgid(0);这二句应该加到哪里啊?另外我试了下修改 info.plist 的ex…file 为 bash(bash为下载的rootapp里的,拿来修改成#!/bin/bash
root=$(dirname “$0”)
exec “${root}”/自己bundle项目名字) 后将插件装到自己的手机里运行’设置‘里的相应插件后提示 以下信息
iPhone Preferences[724]: Error loading /Library/PreferenceBundles/CylinderSettings.bundle/bash: dlopen(/Library/PreferenceBundles/CylinderSettings.bundle/bash, 265): no suitable image found. Did find:
/Library/PreferenceBundles/CylinderSettings.bundle/bash: file too short
Nov 22 14:56:22 iPhone Preferences[724]: Failed to load PreferenceBundle at /Library/PreferenceBundles/CylinderSettings.bundle.
这是怎么回事呢?

PreferenceBundle的提权我没做过,它是由PreferenceLoader而不是backboardd来启动的,不清楚PreferenceLoader会不会去读一个bash脚本再启动对应的可执行文件;
setuid(0)加在你需要root权限才能执行的代码之前,例如:

void Reboot(void)
{
    setuid(0);
    system("reboot");
}

Reboot函数可以在任意位置调用

首先感谢 snakeninny 管理员的耐心解答,按照你说的preferenceloader应该不会启动bash脚本来启动另一个可执行文件了,因为错误信息大概意思就是说我的 bash不是一个二进制文件吧.但我见过的 BioProtect 和 iconRenamer 这二个插件都不是.app的桌面应用也是 bundle 类型,不知道为什么它们实现了root…

你怎么知道它们拿到了root权限?如果我没记错的话,IconRenamer是开源的,你可以看看它是怎么拿到root的

我用ifile看他们的的文件都是 root/wheel 类型的。。。

那是文件所有者的身份,不是运行时的权限,你看看我的另一个帖子,讲App提权的

那程序自己如何查看自己运行时权限呢?getuid() 和 getgid()?

对,那个帖子里都有