TTY replay daemon
Main page | Installing | Running | Detailed Tech Data | ttyrpld 0.83 |
The source is small, and so I think it can be understood in a clear way. However, here is some background story and infos. | ||
First stage - Kernel Patch > |
Different keyloggers take different ways. Linspy (see Phrack 50, File 5) modifies the syscall table to provide its own functions, vlogger (Phrack 59, File 0x0E) chooses to intercept the receive_buf() function. ttyrpld's technique arose out of 2 lines of UML (User Mode Linux) code which I found in 2.4.21-SuSE. UML only had tty logging within, well... UML, and it did not look that pretty to me. On top, UML had it easy, because the host system accounted for writing it to disk, but what's the parent of a real Kernel? None! The first step in capturing the data off a tty is directly in the kernel, within drivers/char/tty_io.c. It is easier than it sounds, actually, I was pretty much driven by the two lines of UML code, as stated above. I did not take any further action within that file, but merely duplicated what UML already had in, yet more flexible. Five variable function pointers -- rpl_qopen, rpl_qread, rpl_qwrite, rpl_qclose and rpl_qioctl -- are exported from tty_io.c for module hook-up. That means that a module has to redirect them to its own functions, when it shall go into action. That way, there is no overhead (read: 2 CPU instructions) when RPL logging is not active. Excerpt from drivers/char/tty_io.c
#ifdef CONFIG_TTY_RPL
#ifdef CONFIG_TTY_RPL tty_write(), etc. is the spot to take (as UML did), because this is directly below before the tty buffers spread into their disciplines. That way, any tty (teletype) is logged, vc (virtual console, aka Ctrl+Alt+F1), pts (Unix98 pty, used i.e. with SSH), ttyp (BSD ptys, rarely used anymore), ttyS (serial), and if someone's gonna try it, even weirdo stuff like ttyI (isdn). |
|
Second stage - Kernel Module > |
The 2nd stage consists of the functions behind the rpl_* variables, the functions as present in the module. They copy the data "captured" (let's say it was passed voluntarily) by the tty driver to a buffer, so that rpl_qopen() return as soon as possible to not block the tty driver. This can get a little problematic if you have a lot of simultaenous users entering or producing a lot of text. I am not out to the disk's speed and/or the log size, but the module buffer size where the tty data is temporarily copied to before it is passed on. There is only one global ring buffer for all ttys, and I do not see the use of one buffer per tty as useful, for this would raise the memory and CPU overhead. Overall, the whole RPL structure speaks a compact binary protocol to minize overhead because of data passing. I have not tested how much the buffer fills up under heavy load or collaborative shell developmental work. If in doubt, raise the buffer size. Thanks to rpldev being a module, you can remove it and re-insmod with the corresponding parameter GI_bufsize. Should the buffer ever overrun, a message will be printed to klogd/syslogd. Excerpt from kernel-2.6/rpldev.c
static int krn_read(const char *buf, size_t count, struct tty_struct *tty) { There is said binary protocol. When the user-space daemon (rpld) gets a scheduling slice, it can do the clean-ups rather than the Kernel. |
|
Third stage - Userspace Gateway > |
The module provides a character-based device node, /dev/rpl (or /dev/misc/rpl) to read from. Device and userspace logging daemon must use the same protocol for the data passed over the channel. The device node is automatically created by misc_register() in drivers/char/misc.c by using devfs -- or what is left of it in 2.6. The device is read-only even though the initial mknod by devfs (or misc) issues sets it to read-write. That should not hurt as it is prohibited to open the character device with O_RDWR or O_WRONLY and trying so will fail with EPERM. Furthermore, the device can only be opened once, because it would be unspecified which tty packet would go to which rpld instance. As far as ttyrpld is concerned, its Kernel module rpldev allocates the buffer and hooks up on the rpl_* function pointers when the device is successfully opened, not when the module is loaded. By design, krn_write() will always enqueue something into the buffer. (That is, I did not want to have a global variable indicating the device is opened and having an if() within krn_write().) Similar applies when closing the device, the memory is released and the function hooks are set back. This saves memory and also CPU time, because an early if() within tty_io.c will skip everything when no one is reading /dev/rpl. Excerpt from kernel-2.6/rpldev.c
static ssize_t uif_read(struct file *filp, char *buf, size_t count, loff_t *ppos) { |
|
Fourth stage - Userspace Logging Daemon > |
A userspace daemon reads, evaluates and stores the data retrieved via the device. The reasons why I think this design is good are that:
|
|
"Fifth stage" - Replaying logs > |
ttyreplay is that thing to analyze the logs stored by rpld. Thanks to the time-stamping of rpld and the 1:1 passing of arbitrary data allows ttyreplay to show what has happened on the chosen terminal in real-time, and exactly as the original user saw it. (Objection: You need to use the same terminal..., because also the ANSI codes are reprinted as-is.) Being able to play it back in real-time also requires a fine timer for the delays between two keystrokes / data packs. I found out that delays have a maximum precision (in the SCHED_OTHER policy domain). This is especially notable in Linux 2.4 (2.6 not anymore), so I invented some kind of algorithm to bypass this visible effect for the user. It's about the "delay overhead correction algorithm". The maximum delay precision for user-space applications within SCHED_OTHER is 1/HZ seconds. (See linux/include/asm/param.h). So when wanting a 5000 microsecond delay, the real time we are waiting is between 10000 to 15000 µs (Linux 2.4 with HZ=100). To get around this, the algorithm checks the time it has actually spent for a particular delay. For more details, please see the usleep_ovcorr() function in user/replay.c which has been commented thoroughly. |
|