17

When logged in to a machine, I can find out each user's pseudo-terminal device(s) from the output of w. Being a sysadmin, is it possible for me to eavesdrop on this terminal without the user being aware? In other words, I would like to see everything being done on this terminal as output on my own terminal.

Please note the following:

  • This is not for a practical use case of monitoring user activities: I'm aware there are system auditing tools for that. I'm just curious if it can be done.
  • I'm aware of this question and it doesn't seem to cover what I'm asking about as all the solutions suggested there are either invasive (the user would be aware of what I'm doing) or produce too much noise (the strace solution). The one solution that comes close is the one that suggests using gdb. But this only lets me see stdout of the other terminal.

What I have tried

I tried this from my terminal:

tee /dev/pts/user_pts </dev/pts/user_pts

This allows me to see each character the user types in the other pseudo-terminal as they type it. The problem is, every few characters, it would "skip": it would show one rogue character on one terminal device but not the other. It also prevents the execution of any commands from the user's pseudo terminal device. I'm not really sure why this is happening and whether there's a way to improve it.

What I would like to see

USER TERMINAL        |    MY TERMINAL
$ echo "Test"        |    # slick_command_here
Test                 |    echo "Test"
$                    |    Test
1

4 Answers 4

11

It's the fd to the master side of the pseudo-terminal in the terminal emulator that you want to monitor if you want to see what's displayed on it. That master fd is what simulates the wire that goes to a real terminal. What xterm writes on it is the characters generated from the key you press. What it reads from it is what it displays.

For instance, on Linux:

$ lsof -ac xterm /dev/ptmx
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
xterm   15173 chazelas    4u   CHR    5,2      0t0 2131 /dev/ptmx

And then run for instance:

stty -echo -opost
strace -e read -e read=4 -p15173 2>&1 | stdbuf -o0 sh -c '
  grep "^ |" | cut -b11-60 | tr -d " " | xxd -r -p'

Of course, it works better if you run that in a terminal of same type and size as the one you're trying to monitor. You can get the size with:

stty size < /dev/pts/that-terminal

That dumps what is read by xterm from the master side of the terminal, so what is displayed there, including the local echo of what is being typed.

The -e read=4 above is for strace to output a hexdump of what xterm reads on its fd 4. The rest of the command is to convert that to the actual characters. I tried peekfd -n -8 15173 4 but for some reason that only gave what was being written.

We're using -opost to disable any post-processing in our monitoring terminal, so that everything xxd writes to the slave side makes it unchanged to our master side, so that our monitoring xterm gets the same thing as the monitored one. -echo is so that if the application in the monitored terminal sends an escape sequence that requests an answer from the terminal (such as those that request the cursor position or the terminal type or window title), that will make its way to our monitoring xterm and our xterm will reply as well. We don't want a local echo of that.

You could also monitor what is being typed by tracing the write system calls to that same fd (replace read with write above). Note that upon pressing Enter, the terminal emulator sends a CR character, not LF. Also, since we're tracing on the master side, if the user types a<Backspace>b, we'll see all 3 keystrokes even if the terminal device is in canonical mode.

As to why yours doesn't work:

tee /dev/pts/user_pts </dev/pts/user_pts

Reading from the terminal device is reading the user input, and writing to it is to display it to the user.

You're telling tee to read from the terminal device. So what it reads (the user input) won't be read by the application(s) running in the terminal (and vis versa, tee and that application will fight for the terminal input). Writing to the terminal device, is for display there, it is not for putting it back there as input. When you do

echo test

(with echo's stdout being the terminal), it is not the same thing as if you had typed test.

There is an ioctl (TIOCSTI) to put characters back as input, but even that would not really work because you could put it back after the application as already read some more, so it would change the order the application is reading input, and any way, that would mean you would read it over and over again.

1
  • 1
    +1 For the explanation and for not using external tools. I will need to do a bit of reading to understand many parts of your answer, but I feel it's along the lines of what I want.
    – Joseph R.
    Aug 3, 2013 at 23:12
5

If your OS is supporting dtrace, that a simple script, shellsnoop, should allow you to monitor everything typed/printed on a given tty.

If you are running Linux, ttysnoop used to do a similar thing but needed an intrusive configuration as a prerequiste and is AFAIK no more supported with current kernels anyway so won't help in your case. There are more or less advanced attempts to provide dynamic tracing with Linux, systemtap, ktap and even dtrace so you might investigate them.

Edit: Beware of peekfd, its manual page states:

Bugs:

Probably lots. Don't be surprised if the process you are monitoring dies.

3

This approach involves a little of gdb and tee. Ah, and it also uses socat to emulate a pseudo-terminal. It could work without it, but the user will notice that his/her output is no longer a terminal (programs like vi will complain).

It does the following:

  1. Create an interceptor, using socat, which exposes itself as a pty.
  2. The interceptor is connected to tee, which duplicates streams in both the $sys terminal, and the $usr terminal.
  3. Gdb is used to replace the stdout/stderr file descriptors to point to the interceptor instead of the $usr terminal.

I noticed that bash seems to write what you type to stderr, I'm not sure if other programs do the same. If that's the case, stdin doesn't have to be intercepted.

Call it like this: chmod +x /path/to/script; sudo /path/to/script <usr> <sys-adm>. usr and sys-adm are the names of the terminals, for example, /dev/pts/1. So a sample call would look like this: sudo /path/to/script /dev/pts/1 /dev/pts/2. You can find out your terminal with the tty command. And the user terminal either with w or ps.

#!/bin/sh

[ "$1" ] || exit 1
[ "$2" ] || exit 1

usr=$1
sys=$2
utty=${1#/dev/}

ps -e -o tty= -o pid= -o user= | { 
    found_it=

    while read -r tty pid_sh owner; do
        if [ "$utty" = "$tty" ]; then
            found_it=y
            break;
        fi
    done

    [ "$found_it" ] || exit 1

    tmp=$(mktemp)
    tmp_gdb=$(mktemp)

    trap 'rm "$tmp" "$tmp_gdb"' EXIT

    socat PTY,link="$tmp",echo=0,raw,openpty,user="$owner",mode=0600 SYSTEM:"tee $sys > $usr"      &

    printf 'call dup2(open("%s", 1), 1)\ncall dup2(open("%s", 1), 2)
            detach\nquit\n' "$tmp" "$tmp" > "$tmp_gdb"
    gdb -p "$pid_sh" -x "$tmp_gdb" >/dev/null 2>&1 &

    wait
}
2

There is a simple C program called xkey.c to show the exploits of X11. I'll let you google it. You can capture the keystrokes on an xterm using this without the user being aware of it.

3
  • I was hoping for a terminal-emulator-agnostic solution actually.
    – Joseph R.
    Aug 4, 2013 at 17:46
  • xkey will give you keystrokes on an X display. That will be all the xterms and any other utility that needs keyboard input.
    – unxnut
    Aug 4, 2013 at 18:02
  • Right. I thought you meant xterm specifically.
    – Joseph R.
    Aug 4, 2013 at 18:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .