Exploiting X11 to monitor keystrokes

The problem

In the Internet I've found the xkey.c source code, which select inputs from all the windows tree and in this way it is able to listen to key pressed events happening in the X server (indeed it can listen to every events of the windows of the display). This is seriously a danger. You don't need to be root to listen to window events.

Just execute xkey and you can store key hits of every kind, password included!

To test this exploit I have modified the xkey.c code, so that it updates the windows it is listening to (otherwise windows opened after the execution of xkey are not listened). Then I tried to modify system date from the KDE clock applet; a request root password dialog pops up. Inserting the password, I can see it echoed on the shell where xkey is running! Wow!

I tried also opening a new shell in Konsole. It can hear. But if you change the virtual terminal (e.g. CTRL+ALT+F1), you are safe, I suppose because you are not attacched to the X display where xkey is listening. I tried also to listen to other virtual X terminal (as :1), but this, luckly, failed. So you can listen only to your terminal.

We can conclude that installing third parties software (precompiled or not) can hide this great threat. Less than 150 lines of code put inside a long source can be enough to spy to our keystrokes! They get compiled even if are you the one that launchs the ./configure make sequence —having source don't save from this exploit!

Solution 1: when you have the source

Things can be better if we are able to check the source. But still we need a little bit of knowledge about the language we are dealing with if we want be sure of our checks and then remove the malicious piece of code.

In order to listen to keystrokes from all windows of our display of the X server (say, :0), we need to query the X tree that stores the opened windows starting from the first one, the root. We must then iterate (since it is a tree, we must sure we can reach all the leaves walking all the braches).

The dangerous code would do something like this (check the xkey.c source for details) [pseudocode]

function ListenTo(rootwindow) is
    XQueryTree(display, rootwindow, rootwindow, returnparent,
               returnchildren, num_of_children)
    if num_of_children is not 0 then
        XSelectInput(display, rootwindow, KeyPressMask)
        for each element in the returnchildren array do
           XSelectInput(display, element, KeyPressMask)
           ListenTo(element)
        end for
        XFree(returnchildren)
     end if
end function ListenTo.

The XQueryTree is per display (so if we go to a safe display, we can be happier); it explores the tree starting from a specified window (the first one will be the root window); it returns the root of the starting window (as rootwindow itself), the parent (returnparent), an array of children (returnchildren) and the number of elements in this array (num_of_children).

As Dominic Giampaolo (xkey's author) states, instead of simply selecting the input, we could act on the window in different ways, e.g. destroying it or whatelse! [I didn't test it but I hope there exist security settings that don't let you kill root window, forcing de facto a log out]

The whole point of this exercise being that I shouldn't be allowed to manipulate resources which do not belong to me. We cannot disagree with this statement of D. Giampaolo, but it is not correct since all windows opened on the display I see are mine. What he wanted to say is really true when belong to me refers to the process/thread asking to manipulate the resources.

When you see such a recursive function in a code, you must become sospiciuos. When a code does a XQueryTree starting from the root window, you must check it carefully! The use of safe toolkit instead of directly program the Xlib, is a good point, but we then should be sure that the toolkit does not provide access to the same functionality given by XQueryTree with a different name!

The following piece of shell commands can help swiftly checking of a source code tree. We are looking for XQueryTree calls. When a program does not call it, we can be more relaxed...

$ cd /path/where/the/sources/are
$ find . -iregex ".*\.[ch]$" -exec egrep -H XQueryTree {} \;

This checks files ending in .c or .h, change the suffixes according to your need; e.g. .*\.\([ch]\|cpp\) should match C++ source too. We check the .h because of possible sospicious #define

When you have one or more matches, you should check if it is really dangerous. Check if the second argument of the XQueryTree is the root window. This alas means understanding a little bit the code. Check if there's an assignment like variable=DefaultRootWindow(display), or if it is used directly, i.e. XQueryTree(display, DefaultRootWindow(display) ...).

Consider positive matches as warnings, but there are good program that needs those functions...

The next check is: what the program does with the returned children? If it use XSelectInput on them, then we are in front of a probable spy! Find a trusted channel where to ask if you are not able to check how harmful is the code. Complex parsers could check this for you, but... do they exist?

Once the program has selected the input of every queried window in the tree, it must wait for events from the X server. This is done via the XNextEvent, XPeekEvent XCheckMaskEvent and maybe similar function. Since the evil program selected the input from every window, it receives all the events. Check for these functions! If they are present altogether with a XSelectInput done on every children got by exploring the full tree starting from root, then it's likely your program is evil.

Again find a trusted channel where to ask for a check.

By the way, it must be noted that we are interested in spying... but if the program would be a sort of killer of windows, running it will simply kill windows... (Imagine such a code to kill windows that have a specific name...). Since we are interested in spying, 1) we do not check for other sospicious behaviour and 2) we should check how the gathered information are sent out the program: are they put inside a file readable by anyone? Are they sent through a socket? Etc.

Solution 2: when you have the binary

Here, unless we are able to disassemble and understand the code, the only thing we can do is to check for the symbols XQueryTree and XSelectInput, even though we can't say how they are used. They could be used finely rather than evilshly.

$ objdump -Tt executable |egrep "XQueryTree|XSelectInput"

When these appear and the piece of software you are looking at is not logically interested in looking at the tree, then you should start to be sospicious...

This check over kdm gives as only result the XQueryTree function. Can this make us happier? Not too much... instead of selecting input, we could pick up event from windows (collected by looking at the tree) through a loop. It is not the case of kdm, which I searched with

$ objdump -Tt `which kdm` |egrep "XQueryTree|XSelectInput|X[A-Za-z]*Event"

Interesting, isn't it? Window managers do not need to call XSelectInput, nor to explore events with any X*Event function... it seems so.

Here follows a list of binaries that call XQueryTree... Of course I did not check all the X binaries of the world...

ProgramXQueryTreeXSelectInputComments
skype yes yes Skype connects to the Internet
xine yes yes multiwindow X interface
bumpmaptest no yes Imlib2 tool
color_spaces no yes no comment
gforce no yes graphics effect
gifview no yes no comment
imlib2 no yes no comment
imlib2_test no yes no comment
imlib2_view no yes no comment
irxevent yes no infrared X-event sender
playdv no yes no comment
polytest no yes no comment
povray no yes no comment
timidity no yes no comment
wmf2x no yes no comment
xboard yes no no comment
xscreensaver yes yes to log user idle time
xscreensaver-command yes yes no comment
xscreensaver-demo yes yes no comment
xscreensaver-getimage yes yes no comment
BackGround yes no no comment
fvwm yes yes window manager
kinput2 yes yes input method
smproxy yes yes no comment
twm yes yes session manager proxy
unclutter yes yes to log idle cursor
vncevent no yes no comment
vncviewer yes yes X viewer client for VNC
xbanner no yes no comment
xdm yes no no comment
Xdmx no yes no comment
xev no yes no comment
xf4vncviewer yes yes no comment
xkill yes no must search the tree to identify the window to kill
xlsclients yes no no comment
xlsfonts yes no no comment
xprop yes yes property displayer
xrandr no yes no comment
xterm yes yes no comment
xwd yes no no comment
xwininfo yes no no comment
xwinswitch yes no no comment
gimp-remote-2.2 yes no no comment
xchat yes no no comment
zenity yes yes no comment
dcop no yes no comment
dcopquit no yes no comment
kdesktop_lock yes yes no comment
kdm yes no no comment
kdm_greet yes no no comment
kfax no yes no comment
kompmgr yes yes compositing manager
krandom.kss yes no no comment
krandrtray yes yes no comment
ksnapshot yes no no comment
ksplashsimple no yes no comment
ksplashx no yes no comment
ksystraycmd yes no no comment
kxsconfig no yes no comment
nspluginviewer yes no no comment

1803 executables checked!

Added 2008-05-07: 907 + 2288 shared objects (dynamic linking libraries) checking follows:

LibraryXQueryTreeXSelectInputComments
libawt.so yes yes no comment
libqt-mt.so.3.3.4 yes yes no comment
libgforce.so no yes no comment
libtitlefader.so no yes no comment
libtk8.4.so yes yes no comment
libaa.so.1.0.4 no yes no comment
X11.so no yes no comment
libpy2qt.so.2.0.0 yes yes no comment
vidsite.so yes yes no comment
libSDL-1.2.so.0.7.1 no yes no comment
libgdraw.so.1.0.8 yes yes no comment
libflashplayer.so yes no no comment
interface_i.so no yes no comment
libMagick.so.6.2.3 yes yes no comment
xineplug_vo_out_xshm.so yes yes no comment
xineplug_vo_out_xxmc.so yes yes no comment
xineplug_vo_out_vidix.so yes yes no comment
xineplug_vo_out_xv.so yes yes no comment
xineplug_vo_out_opengl.so yes yes no comment
libnullplugin.so no yes no comment
ximcp.so.2 yes yes no comment
libXmu.so.6.2 yes no no comment
libXmuu.so.1.0 yes no no comment
libXt.so.6.0 no yes no comment
nppdf.so yes yes no comment
libXm.so.3.0.2 yes yes no comment
libX11.so.6.2 yes yes no comment
libI810XvMC.so.1.0 yes no no comment
libxrx.so.6.8 no yes no comment

LibraryXQueryTreeXSelectInputComments
libxmmskde.so yes no no comment
kded_klaptopdaemon.so yes yes no comment
kcm_khotkeys.so yes no no comment
kwin3_b2.so no yes no comment
dockbar_panelextension.so no yes no comment
kcm_screensaver.so no yes no comment
libkdeinit_kpowersave.so yes no no comment
libkdeinit_kdesktop.so yes yes no comment
libkdeinit_kwin.so yes yes no comment
libkdeinit_ksmserver.so no yes no comment
libksplashthemes.so.0.0.0 no yes no comment
libkdecore.so.4.2.0 yes yes no comment
libkdefx.so.4.2.0 no yes no comment
libkdeui.so.4.2.0 yes yes no comment
libxvim.so.0.0.0 no yes no comment
libkscreensaver.so.4.2.0 yes no no comment
libgdk-1.2.so.0.9.1 yes yes no comment
libgtk-1.2.so.0.9.1 no yes no comment
libgdk-x11-2.0.so.0.800.3 yes yes no comment
libgail.so no yes no comment
libgtk-x11-2.0.so.0.800.3 no yes no comment
libgdk_pixbuf_xlib-2.0.so.0.800.3 yes no no comment

A command line (or script) you can use to do it yourself is:

for el in /exec/paths/* ;
do
   if [[ -h "$el" ]]; then
      continue;
   fi
   if [[ $(file "$el" |egrep -c ELF) -eq 0 ]];
      then continue;
   fi
   echo "+ $el"
   objdump -Tt "$el" |egrep "XQueryTree|XSelectInput"
done >OUTPUT

(I used a script to generate the HTML table with all the information in it, and then edited to add some comments)

We notice that multiwindow interfaces that access directly the Xlib use both XQueryTree and XSelectInput. We can check the code for xine (I compiled it by myself:) but what about Skype? It can connect to the Internet and send out our keystrokes! Anyway it should be easy to check outbound connections and be sure that no clear data are sent... clear not, but crypted some way?!

I guess that any complex widget can use XQueryTree and XSelectInput. Here the point is: XQueryTree is called recursively starting from the root? We cannot know it with this simple analysis.

Solution 3: patching X11

Here we use imagination since I still won't recompile Xorg... (My first try was unsuccessful because of lacking of something, can't remember, was eons ago)

The basic idea is to patch XQueryTree so that it returns only windows whose owner is the process/thread calling it. This breaks a lot of useful programs. To avoid this we can create a group that is allowed to explore the full tree. Safe and known softwares that need to access the full tree will have the group set to the proper one. Administrator will be responsible to set it accordingly the needs without compromising the security of each user.

I suppose this road can be taken.

Here just remember that we can refuse undesired connections; e.g. on my system (as default) I cannot connect to another one's session to spy his/her keystrokes. Generally this is a good behaviour, unless we need to accept outer connection to the display. When this happens we must be sure that the computer allowed to connect to our display is trustworthy, clean and secure. But we already know that we should always be sure about who can connect to us!

Solution 4: grabbing

Even though not all our keystrokes will be safe, we can grab the keyboard for ourselves when we must input things like password. This should prevent others to listen to that events. I will test this one day.


Home page