spod

Security Engineer

Home About Resume Projects

projects

todo: Writeup on rootkit

As part of the UNSW Course COMP6447: System and Software Security Assessment, I worked in a team of four to write a basic rootkit for the FreeBSD OS

Basic Assignment Specification

Rootkit

You are permitted to do anything you wish to achieve the following fundamental functionality

Provide a method to escalate to uid(0) (root) by a low privileged/non-root user/binary.
Attempt to hide itself from detection. e.g. concealing itself from kldstat, and userland components from find.

Rootkit Detection

You must return status code 0 if no rootkit is detected, and return 1 if a rootkit is detected. You may print whatever output you wish during the script.

Fundamental Functionality

Key Utilities

Inline Hooking

We need a way of communicate to the kernel from userland, we do this via inline hooking of system calls.
We have a utility called do_hook, which takes in a function to overwrite and a function to overwrite it with.

void do_hook(void* overwrite, void* func) {
	char* function_to_overwrite = (char*) overwrite;
	char* function_to_call = (char*) func;

	uprintf("Overwriting %p with %p\n", function_to_overwrite, function_to_call);

	int diff = (int) ((function_to_call - function_to_overwrite) - 5);

	int* new_lol = (int*) (function_to_overwrite+1);
	function_to_overwrite[0] = 0xe9; // JMP
	new_lol[0] = diff;
}

The first instruction of the original function becomes a x86 JMP32 command. This command has the opcode 0xe9 followed by a 4 byte relative address to where you want to jump to.

We can calculate the relative address by subtracting the function we want to jump to by the address we are patching. func_to_overwrite - func_to_jump_to. To summarise, we overwrite the function with 0xe9 followed by the calculated offset. This is all done by just referencing the function as a char array, and setting the first 5 bytes.

Hidden Files

We also needed a way of creating hidden files from the userland

You’ll often see files prepended with hd. We refere to these as hidden files. This indicates the file is hidden from the user and can neither be listed or read from syscalls.

This is all done by using the hooker described above to change getdirentries, open and read.
Specifically the hooks do the following:

  • getdirentries: Hidden files are removed from the outputed entries list.
  • open: Keeps track of all file descriptors that references a Hidden file via a linked list.
  • read: Returns NULL back to the user if the file descriptor refernced is in the linked list of Hidden files.

Priv Esc

Now we needed a way to escalate to root privs from userland.
This is done by implementing a backdoor in our rootkit module. Specically, the following line of C will increase the privledges of the caller to uid=0.

read(0xdeadbeef, NULL, 0xdeadb33f)

This is done internally by editing the current processes struct ucred

void escalate_privs(struct thread* td) {
    struct ucred* creds = td->td_ucred;

    // uid
    creds->cr_uid = 0;
    creds->cr_ruid = 0;
    creds->cr_svuid = 0;

    // groups
    creds->cr_rgid = 0;
    creds->cr_svgid = 0;
}

Concealment

Hiding processes

Freebsd keeps a few lists of running processes in the kernel. Particularly the global lists p_list and p_hash contains a linked list of running processes. We can therefore simply unlink any processes we wish from these lists, making the processes hidden from other users running tests.

Bonuses

Extended Detector

TODO explain detector