Basilisk is a loaded kernel module rootkit (LKM rootkit) that started as a rootkit for TryHackMe's King of The Hill game.
It also contains the code for my article in tmp.out.
While this project is centered around king of the hill, it includes a variety of interesting techniques.
- Self-hiding from procfs and sysfs
- Make the module unremovable (even when visible) by tampering with the reference count
- Advanced king protection (see here)
- Stealthy communication via ProcFS hooking
To install Basilisk, clone the repository and build the module as follows:
$ git clone https://github.com/lil-skelly/basilisk
$ cd basilisk/src && make
Basilisk employs a new technique that hooks the operations of trusted procfs entries (e.g., /proc/kallsyms). To interact with the rootkit, compile and use client.c:
$ gcc -o client client.c
$ ./client
Usage: ./client <cmd> [pid]
hide
: Toggles the visibility of the module in procfs and sysfs.protect
: Toggles protection to prevent the removal of the module (even if visible)god
: combines the functionality of hide and protectroot
: Grants root privileges to a specified process (by [pid]).
If no PID is provided, the rootkit elevates the privileges of the client's parent process (typically the shell from which the client was executed).
You can customize the LKM by modifying the following:
/* in include/king.h */
#define KING_FILENAME "/root/king.txt" // Path to king file
#define KING "SKELLY\n" // King
Command signals can also be adjusted:
/* in include/main.h */
enum {
SIG_GOD = 0xFF,
SIG_HIDE = 0xFA,
SIG_PROTECT = 0xFB,
SIG_ROOT = 0xBA,
};
Important
Ensure that any modifications to signals in client.c match those in basilisk.c.
At first glance, the goal of a KoTH game is to root the machine and place your username inside the king file (/root/king.txt
).
The real challenge is to keep your name in there.
To do that, basilisk utilizes a new technique focusing on manipulating the file operations structure of the king file (/root/king.txt
).
First, basilisk hooks the openat
syscall and resolves the path from the given file descriptor.
If the path is that of our king file, it opens a new file descriptor by calling the original openat
syscall, poisons the files file_operations
structure and returns the file descriptor to our now poisoned file.
We poisoned the file operations structure by making the read
field point to the address of our own implementation of the read syscall.
Now every time somebody reads from the king file, it will always read our name! Despite what its actual contents are.
In the KoTH scene, hooking the read syscall is not new. But we are not just hooking any read syscall. We are only hooking the read syscall which will be used to read from that very specific file.
Furthermore, the function that is resolving the final path also follows any symbolic links/mount points.