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:
- Write
./executable
ininit.rc
.- On reboot
init.rc
reset to its original contents. I figured out that Magisk done this.
- On reboot
- 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