MiKey - A Linux keylogger

Nick Hoffman
MiKey - A linux keylogger

Linux malware is slowly becoming more popular.  Within the past couple years there were several major incidents that cited the use of Windows backdoors being ported to Linux.  Through our research on the Windows KLRD keylogger from the Odinaff report, we were able to discover several new keyloggers.  The focus of this blog post is MiKey, a little-known and poorly detected keylogger.

At the time of this writing, the malware wasn’t detected by a single engine on Virustotal.

miKey on VirtusTotal


The malware is a 64 bit Linux executable:

9c07ed03f5bf56495e1d365552f5c9e74bb586ec45dffced2a8368490da4c829: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=550c58e6a9bc88b8724fd8ab7fd79a6c58c12d28, not stripped

And depends on the following libraries:

       linux-vdso.so.1 (0x00007ffd25123000)
       libX11.so.6 => /usr/lib/x86_64-linux-gnu/libX11.so.6 (0x00007f7f56420000)
       libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7f5621c000)
       libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7f55e7e000)
       libxcb.so.1 => /usr/lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f7f55c56000)
       /lib64/ld-linux-x86-64.so.2 (0x00005597839c6000)
       libXau.so.6 => /usr/lib/x86_64-linux-gnu/libXau.so.6 (0x00007f7f55a52000)
       libXdmcp.so.6 => /usr/lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f7f5584a000)

Analyzing the symbol table for this binary yielded some interesting function names.  (Full output omitted for readability):

    63: 00000000004014b2    79 FUNC    GLOBAL DEFAULT   14 createProccess
    64: 0000000000400ed6   128 FUNC    GLOBAL DEFAULT   14 initPlugins
    67: 0000000000400f56   105 FUNC    GLOBAL DEFAULT   14 moduleFeed
    68: 000000000040102d  1157 FUNC    GLOBAL DEFAULT   14 keylogger
    75: 0000000000400dc6   159 FUNC    GLOBAL DEFAULT   14 handleArgs
    83: 0000000000400e65   113 FUNC    GLOBAL DEFAULT   14 moduleHandleArgs
    85: 00000000004015fc   209 FUNC    GLOBAL DEFAULT   14 addData
    87: 0000000000400cd0    42 FUNC    GLOBAL DEFAULT   14 _start
    88: 0000000000400fbf   110 FUNC    GLOBAL DEFAULT   14 addParentheses
    92: 0000000000401501   126 FUNC    GLOBAL DEFAULT   14 main
   103: 0000000000400b00     0 FUNC    GLOBAL DEFAULT   11 _init

Comments left by the compiler provide evidence it was compiled on Ubuntu 16.04.2:

9c07ed03f5bf56495e1d365552f5c9e74bb586ec45dffced2a8368490da4c829:     file format elf64-x86-64

Contents of section .comment:

 0000 4743433a 20285562 756e7475 20352e34  GCC: (Ubuntu 5.4
 0010 2e302d36 7562756e 7475317e 31362e30  .0-6ubuntu1~16.0
 0020 342e3229 20352e34 2e302032 30313630  4.2) 5.4.0 20160
 0030 36303900                             609.  

This is further evidenced by the build path in the binary:


The strace tool was used to quickly identify high-level function workflows and identify potential focus areas.  One anomaly identified was a failed file opening, “mikey-text.so.”  So we began there.

open("./tls/x86_64/mikey-text.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("./tls/mikey-text.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("./x86_64/mikey-text.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("./mikey-text.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

The malware isn’t explicitly searching these directories for mikey-text.so.  This is a side effect of dlopen.  From the man page:

“If, at the time that the program was started, the environment variable LD_LIBRARY_PATH was defined to contain a colon-separated list of directories, then these are searched. (As a security measure this variable is ignored for set-user-ID and set-group-ID programs.)”

After a little searching we located a second binary (SHA-256 bc6d25dff00dfb68b19b362c409d2cf497e5dd97d9d6e5ce2bde2ba706f2bdb3) which contained the string “mikey-text.c.”  From this, we assessed that mickey-text.so is the compiled version of this binary.

Using this assertion, we renamed the second binary to mikey-text.so and placed it in the load path identified using strace.  This caused the successful execution of the malware.  The output file (out.log) contained the logged keystrokes with associated timestamps.

The output file contained the logged keystrokes with associated timestamps

Through static analysis, we were able to identify that when the keylogger starts up, it loaded plugins, handle arguments, and then forked its process. 

When loading the plugins, the keylogger looked for a single hardcoded plugin name “mikey-text.so” and called dlopen to obtain a handle to it.

miKey looks for its hardcoded plugin

Once everything was loaded, the main functionality of the program was handled through the “keylogger” function.

To better understand Linux keyloggers and associated function calls, basic X function knowledge is critical.  As a quick primer, here are some routines used by the “keylogger” function to query information about keystrokes or simply harvest raw keystroke data.




Returns a display structure that serves as the connection to the X server.  Communication is then carried out through TCP or IPC.


Uses the structure from the display to gather information about the state of the keyboard.  Information about which keys are currently pressed can be gathered using this call.


Uses the structure from the display to return the keysym for a particular key.


Converts the previously obtained keysym. 


Controls focus on the desktop.

Once the keycode is retrieved, it’s compared against a large switch table to convert each keycode into a string.  This is no different than most keyloggers.

miKey keylogger converting keycodes to strings

Non-printable keystrokes are then identified and substituted with human-readable outputs.

miKey keylogger becomes human readable

If there is a non-printable character returned, a small method to format the string in parentheses is called to make for nice output into the log.

If there is a non-printable character returned, a small method to format the string in parentheses is called to make for nice output into the log

Once completed, the data is stored into a buffer and passed to a loadable module.  The Linux dlsym method provides similar functionality as “LoadLibrary” on Windows.  The previous handle from dlopen is being passed to dlsym, which we can now use to call the method “getFeed” from mikey-text.so.

Keylogger miKey uses its getFeed method

Peering into the “getFeed” function on mikey-text.so it simply calls the _log function.

Peering into the 'getFeed' function on mikey-text.so it simply calls the _log function.

The _log function will call _time and _localtime (to harvest the timestamps) and build these into a format string.

The _log function will call _time and _localtime to harvest the timestamps and build these into a format string

At this point, the output file is opened for writing with the appended (“a+”) flag and the file is written to using the _fputs method.  If no option for --output was provided to mikey-text.so then the default name of “out.log” is provided.  The screenshot below identifies the contents of cs:outputfile_ptr as a pointer to the name of the output file.

cs:outputfile_ptr as a pointer to the name of the output file

Outside of a small method to parse arguments, there isn’t much more functionality to mikey-text.so.  It’s a simple logging plugin for the main MiKey keylogger.  Morphick assesses that additional plugins may exist (for C2 communication, or to hook to other files), but is unable to confirm at this time.

To run the keylogger and give a custom argument for an output file named keylogged.txt, the following command can be used.  In addition, providing the “-b” option will “background” the process.

Keylogger miKey backgrounds the process

Checking processes on the host, the command “ps aux” was issued.

Checking processes on the host, the command 'ps aux' was issued.

And checking the output of the keylogged file:

Keylogger miKey checks the output of the keylogged file


Small utilities that are built for a specific purpose often bypass AV with ease.  Attackers are able to write a functional keylogger that will dump the contents to a local file.  By having modular code, the authors could build plugins that achieve whatever task they need.  The plugin nature of this code also puts the reverse engineer at a disadvantage.  Without access to each module, only specific known functions of the tool can be documented.  

One unnerving aspect of this keylogger is that, without an active command and control capability, the attacker would need to be confident in their ability to repeatedly gain remote access to the victim computer to retrieve the keylogged information. 

All it takes to catch this is basic process and file monitoring, but if our Linux field experience is any indicator, there aren’t many shops with this level of visibility on non-Windows workstations.