Tuesday, July 4, 2017

root access - How to run an executable on boot and keep it running?


I have an executable build from a ndk-build program. I want to run it on a rooted Android device. How much is the possibility that Android will kill my executable?


Using adb shell, I am able to execute my executable using following commands:


adb push executable /sdcard

adb shell
device:/ $ su
device:/ # cd /system
device:/system # mount -o rw,remount /system
device:/system # mv /sdcard/executable .
device:/system # ./executable

My requirement is to run this executable on device boot.


I have tried following:




  1. Write ./executable in init.rc.

    • On reboot init.rc reset to its original contents. I figured out that Magisk done this.



  2. Write commands in /system/etc/init/bootstat.rc

    • ./executable

    • service custom /system/executable

    • on boot ./system/dhandler/diag_revealer





None of the above thing is working.



Answer




How much is the possibility that Android will kill my executable?



Privileged native processes usually don't get killed by Android except if they can't handle an error occurred inside, such as some system resource not available or permission denied because of SELinux etc. To free up memory, Android only kills processes within its framework i.e. running under zygote. To manage resources for native processes, Android uses cgroups.


Processes are killed when they receive SIGNALS from kernel or other userspace programs (e.g. with kill command) (1, 2). Kernel is the actual operating system, not visible to us but handling everything we do with device. A developer can program his code how to react to a specific signal if received, or completely ignore it (3). Except SIGKILL. Which can't be handled by program, no warning from kernel, no grace period to exit safely, just being terminated immediately. But kernel won't mind your presence unless he gets short on hardware resources or you start misbehaving. That's why programming is important.



Programs can send each other signals (including KILL), which are forwarded by kernel, governed by UID (4). However, init the very first process in userspace started by kernel is the dear one, kernel never forwards dangerous signals to init. And if this happens for some reason, kernel gets panic and reboots (5).


Summarizing above lines, it is possible to avoid being killed (AMAP) programmatically or using some scripting tricks as @alecxs has mentioned. But if you want to make sure that your process should restart if gets killed, define an Android init service.



On reboot init.rc reset to its original contents. I figured out that Magisk done this.



No, Magisk didn't do this. Android's rootfs is a temporary filesystem (not a persistent one like on /system or /data) that gets cleared on every reboot. Contents of root directory (/) are extracted from another partition named boot which contains kernel and ramdisk (though things have changed with system-as-root). So you can't change init.rc permanently unless you extract, modify, repack and reflash boot.img.


But to define a new init service, modifying init.rc isn't necessary. Android parses all .rc files from /etc/init directories located under /system and /vendor (6). So you can create your own .rc file.




NOTE: In order to get real root privileges and to deal with SELinux, all of the options given below depend on Magisk. See this answer for details.


INIT.D SCRIPT



You can use traditional init.d-like feature of Magisk to start a process on boot. Create script /data/adb/service.d/custom.sh:


#!/system/bin/sh

# write log file if executable throws something at stdout/sterr
exec >>/data/media/0/executable.log 2>&1

# run script in background to avoid blocking boot chain
[ -n "$BG" ] || { BG=Y "$0" & exit; }

# try to ignore signals as much as possible

for i in $(seq 64); do trap '' "$i"; done

# execute script whenever exits e.g. when executable gets killed
trap "sleep 5; exec $0" EXIT

# avoid multiple instances e.g. if script killed but executable is running
pkill -9 -x /system/bin/executable

# execute the binary, should run in foreground, otherwise get in loop
echo "$(date): Starting program..."

/system/bin/executable

# program is killed, won't reach here if script is killed
echo "$(date): Re-executing script..."

* EXIT is shell's pseudo-signal.
* Android's /system/bin/pkill (from toybox) is buggy, better use busybox applet.


Place the executable under /system/bin and set permissions:


~# chown 0.0 /system/bin/executable /data/adb/service.d/custom.sh
~# chmod 0755 /system/bin/executable /data/adb/service.d/custom.sh


You can also place script under /data/adb/post-fs-data.d/ but that's executed a bit earlier. Be sure that filesystem paths (and other required resources if any) are available at that stage.


EXECUTE PROGRAM FROM INIT


Another way is to directly execute the binary from init. Create custom.rc file:


#/etc/init/custom.rc

# execute the binary when boot is completed
on property:sys.boot_completed=1
exec_background u:r:magisk:s0 -- /system/bin/executable


Set permissions:


~# chown 0.0 /etc/init/custom.rc
~# chmod 0644 /etc/init/custom.rc
~# chcon u:object_r:system_file:s0 /etc/init/custom.rc

And that's all! Restart device for changes to take effect.


However it's a one time execution, won't be restarted. Also there are some shell scripting features not available in .rc files. For instance you can't redirect stdout/stderr to a file, this has to be handled by executable program itself. So we can try to make use of both; shell script and .rc file:


INIT SERVICE


Instead of directly executing binary from .rc file, execute a shell script. Create script /system/bin/custom.sh:


#!/system/bin/sh


# write log file if executable throws something at stdout/sterr
exec >>/data/media/0/executable.log 2>&1

# execute the binary, should run in foreground, otherwise get in loop
echo "$(date): Starting program..."
exec /system/bin/executable

Create init service:


#/etc/init/custom.rc


# define service, use executable here if script not needed
service custom /system/bin/custom.sh

# don't start unless explicitly asked to
disabled

# only execute once, don't restart if exited
# don't add if you want to restart service when killed
#oneshot


# run with unrestricted SELinux context to avoid avc denials
# it's required if SELinux is enforcing and service needs access
# to some system resources not allowed by default sepolicy
seclabel u:r:magisk:s0

# start the service when boot is completed
on property:sys.boot_completed=1
start custom


Set permissions on executable, custom.sh and custom.rc as stated above and restart.


Other parameters (7) such as user, group, capabilities are required if you want to run the service as non-privileged user. Granting least required privileges is the recommended approach from security's perspective. See this answer for more details on capabilities and SELinux.


init will keep on restarting service every 5 seconds (by-default) if it gets killed. You can stop the service with setprop ctl.stop custom. Replace stop with start to start again.
To see what happens with service: dmesg | grep init: | tail.


RELATED:



No comments:

Post a Comment

samsung galaxy s 2 - Cannot restore Kies backup after firmware upgrade

I backed up my Samsung Galaxy S2 on Kies before updating to Ice Cream Sandwich. After the upgrade I tried to restore, but the restore fails ...