├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _example └── main.go ├── doc.go ├── go.mod ├── input_event.go ├── keylogger.go ├── keylogger_test.go └── keymapper.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.x -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Keylogger 2 | 3 | Capture global keyboard events on Linux 4 | 5 | [![Build Status](https://travis-ci.org/MarinX/keylogger.svg?branch=master)](https://travis-ci.org/MarinX/keylogger) 6 | [![GoDoc](https://godoc.org/github.com/MarinX/keylogger?status.svg)](https://godoc.org/github.com/MarinX/keylogger) 7 | [![License MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](LICENSE) 8 | 9 | ## Notes 10 | * Only Linux based 11 | * Need root privilages 12 | 13 | ## Installation 14 | ```sh 15 | go get github.com/MarinX/keylogger 16 | ``` 17 | 18 | ## Getting started 19 | 20 | ### Finding keyboard device 21 | There is a helper on finding the keyboard. 22 | ```go 23 | keyboard := keylogger.FindKeyboardDevice() 24 | ``` 25 | Which goes through each file device name to find keyword "keyboard" 26 | ```sh 27 | /sys/class/input/event[0-255]/device/name 28 | ``` 29 | and returns the file event path if found 30 | ```sh 31 | /dev/input/event2 32 | ``` 33 | If the function returns empty string, you will need to cat each device name and get the event number. 34 | If you know already, you can easily pass it to constructor 35 | ```sh 36 | keylogger.New("/dev/input/event2") 37 | ``` 38 | 39 | ### Getting keypress 40 | Once the keylogger returns channel event, you can switch by event code as described in [input_event.go](https://github.com/MarinX/keylogger/blob/master/input_event.go) 41 | For start, you can listen on keyboard state change 42 | ```go 43 | keylogger.EvKey 44 | ``` 45 | Once you get desire event, there is a helper to parse code into human readable key. 46 | ```go 47 | event.KeyString() 48 | ``` 49 | 50 | ### Writing keypress 51 | Best way is to open an text editor and see how keyboard will react 52 | There are 2 methods: 53 | ```go 54 | func (k *KeyLogger) WriteOnce(key string) error 55 | ``` 56 | and 57 | ```go 58 | func (k *KeyLogger) Write(direction KeyEvent, key string) error 59 | ``` 60 | `WriteOnce` method simulates single key press, eg: press and release letter M 61 | 62 | `Write` writes to keyboard and sync the event. 63 | This will keep the key pressed or released until you call another write with other direction 64 | eg, if the key is "A" and direction is press, on UI, you will see "AAAAA..." until you stop with release 65 | 66 | Probably you want to use `WriteOnce` method 67 | 68 | **NOTE** 69 | 70 | If you listen on keyboard state change, it will return __double__ results. 71 | This is because pressing and releasing the key are 2 different state change. 72 | There is a helper function which you can call to see which type of state change happend 73 | ```go 74 | // returns true if key on keyboard is pressed 75 | event.KeyPress() 76 | 77 | // returns true if key on keyboard is released 78 | event.KeyRelease() 79 | ``` 80 | 81 | ### Example 82 | You can find a example script in ```example/main.go``` 83 | 84 | ### Running tests 85 | No magic, just run 86 | ```sh 87 | go test -v 88 | ``` 89 | 90 | ## Creating key sniffer (needs update) 91 | * [sniffing global keyboard events in go](https://medium.com/@marin.basic02/sniffing-global-keyboard-events-in-go-e5497e618192/) 92 | 93 | 94 | ## License 95 | This library is under the MIT License 96 | -------------------------------------------------------------------------------- /_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/MarinX/keylogger" 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | func main() { 11 | 12 | // find keyboard device, does not require a root permission 13 | keyboard := keylogger.FindKeyboardDevice() 14 | 15 | // check if we found a path to keyboard 16 | if len(keyboard) <= 0 { 17 | logrus.Error("No keyboard found...you will need to provide manual input path") 18 | return 19 | } 20 | 21 | logrus.Println("Found a keyboard at", keyboard) 22 | // init keylogger with keyboard 23 | k, err := keylogger.New(keyboard) 24 | if err != nil { 25 | logrus.Error(err) 26 | return 27 | } 28 | defer k.Close() 29 | 30 | // write to keyboard example: 31 | go func() { 32 | time.Sleep(5 * time.Second) 33 | // open text editor and focus on it, it should say "marin" and new line will be printed 34 | keys := []string{"m", "a", "r", "i", "n", "ENTER"} 35 | for _, key := range keys { 36 | // write once will simulate keyboard press/release, for long press or release, lookup at Write 37 | k.WriteOnce(key) 38 | } 39 | }() 40 | 41 | events := k.Read() 42 | 43 | // range of events 44 | for e := range events { 45 | switch e.Type { 46 | // EvKey is used to describe state changes of keyboards, buttons, or other key-like devices. 47 | // check the input_event.go for more events 48 | case keylogger.EvKey: 49 | 50 | // if the state of key is pressed 51 | if e.KeyPress() { 52 | logrus.Println("[event] press key ", e.KeyString()) 53 | } 54 | 55 | // if the state of key is released 56 | if e.KeyRelease() { 57 | logrus.Println("[event] release key ", e.KeyString()) 58 | } 59 | 60 | break 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package keylogger is a go library for linux to capture keyboard events. About: 2 | // 3 | // * No C deps 4 | // 5 | // * Events ported from uapi/linux/input.h 6 | // 7 | // * Needs root privilages 8 | // 9 | // * Transfer keyboard code into human readable key 10 | // 11 | // * Capture state changes 12 | // 13 | // * Write back to keyboard 14 | // 15 | // See README at https://github.com/MarinX/keylogger for more info. 16 | package keylogger 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/MarinX/keylogger 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /input_event.go: -------------------------------------------------------------------------------- 1 | package keylogger 2 | 3 | import ( 4 | "syscall" 5 | "unsafe" 6 | ) 7 | 8 | const ( 9 | // EvSyn is used as markers to separate events. Events may be separated in time or in space, such as with the multitouch protocol. 10 | EvSyn EventType = 0x00 11 | // EvKey is used to describe state changes of keyboards, buttons, or other key-like devices. 12 | EvKey EventType = 0x01 13 | // EvRel is used to describe relative axis value changes, e.g. moving the mouse 5 units to the left. 14 | EvRel EventType = 0x02 15 | // EvAbs is used to describe absolute axis value changes, e.g. describing the coordinates of a touch on a touchscreen. 16 | EvAbs EventType = 0x03 17 | // EvMsc is used to describe miscellaneous input data that do not fit into other types. 18 | EvMsc EventType = 0x04 19 | // EvSw is used to describe binary state input switches. 20 | EvSw EventType = 0x05 21 | // EvLed is used to turn LEDs on devices on and off. 22 | EvLed EventType = 0x11 23 | // EvSnd is used to output sound to devices. 24 | EvSnd EventType = 0x12 25 | // EvRep is used for autorepeating devices. 26 | EvRep EventType = 0x14 27 | // EvFf is used to send force feedback commands to an input device. 28 | EvFf EventType = 0x15 29 | // EvPwr is a special type for power button and switch input. 30 | EvPwr EventType = 0x16 31 | // EvFfStatus is used to receive force feedback device status. 32 | EvFfStatus EventType = 0x17 33 | ) 34 | 35 | // EventType are groupings of codes under a logical input construct. 36 | // Each type has a set of applicable codes to be used in generating events. 37 | // See the Ev section for details on valid codes for each type 38 | type EventType uint16 39 | 40 | // eventsize is size of structure of InputEvent 41 | var eventsize = int(unsafe.Sizeof(InputEvent{})) 42 | 43 | // InputEvent is the keyboard event structure itself 44 | type InputEvent struct { 45 | Time syscall.Timeval 46 | Type EventType 47 | Code uint16 48 | Value int32 49 | } 50 | 51 | // KeyString returns representation of pressed key as string 52 | // eg enter, space, a, b, c... 53 | func (i *InputEvent) KeyString() string { 54 | return keyCodeMap[i.Code] 55 | } 56 | 57 | // KeyPress is the value when we press the key on keyboard 58 | func (i *InputEvent) KeyPress() bool { 59 | return i.Value == 1 60 | } 61 | 62 | // KeyRelease is the value when we release the key on keyboard 63 | func (i *InputEvent) KeyRelease() bool { 64 | return i.Value == 0 65 | } 66 | 67 | // KeyEvent is the keyboard event for up/down (press/release) 68 | type KeyEvent int32 69 | 70 | const ( 71 | KeyPress KeyEvent = 1 72 | KeyRelease KeyEvent = 0 73 | ) 74 | -------------------------------------------------------------------------------- /keylogger.go: -------------------------------------------------------------------------------- 1 | package keylogger 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strings" 11 | "syscall" 12 | ) 13 | 14 | // KeyLogger wrapper around file descriptior 15 | type KeyLogger struct { 16 | fd *os.File 17 | } 18 | 19 | type devices []string 20 | 21 | func (d *devices) hasDevice(str string) bool { 22 | for _, device := range *d { 23 | if strings.Contains(str, device) { 24 | return true 25 | } 26 | } 27 | 28 | return false 29 | } 30 | 31 | // use lowercase names for devices, as we turn the device input name to lower case 32 | var restrictedDevices = devices{"mouse"} 33 | var allowedDevices = devices{"keyboard", "logitech mx keys"} 34 | 35 | // New creates a new keylogger for a device path 36 | func New(devPath string) (*KeyLogger, error) { 37 | k := &KeyLogger{} 38 | fd, err := os.OpenFile(devPath, os.O_RDWR, os.ModeCharDevice) 39 | if err != nil { 40 | if os.IsPermission(err) && !k.IsRoot() { 41 | return nil, errors.New("permission denied. run with root permission or use a user with access to " + devPath) 42 | } 43 | return nil, err 44 | } 45 | k.fd = fd 46 | return k, nil 47 | } 48 | 49 | // FindKeyboardDevice by going through each device registered on OS 50 | // Mostly it will contain keyword - keyboard 51 | // Returns the file path which contains events 52 | func FindKeyboardDevice() string { 53 | path := "/sys/class/input/event%d/device/name" 54 | resolved := "/dev/input/event%d" 55 | 56 | for i := 0; i < 255; i++ { 57 | buff, err := ioutil.ReadFile(fmt.Sprintf(path, i)) 58 | if err != nil { 59 | continue 60 | } 61 | 62 | deviceName := strings.ToLower(string(buff)) 63 | 64 | if restrictedDevices.hasDevice(deviceName) { 65 | continue 66 | } else if allowedDevices.hasDevice(deviceName) { 67 | return fmt.Sprintf(resolved, i) 68 | } 69 | } 70 | 71 | return "" 72 | } 73 | 74 | // Like FindKeyboardDevice, but finds all devices which contain keyword 'keyboard' 75 | // Returns an array of file paths which contain keyboard events 76 | func FindAllKeyboardDevices() []string { 77 | path := "/sys/class/input/event%d/device/name" 78 | resolved := "/dev/input/event%d" 79 | 80 | valid := make([]string, 0) 81 | 82 | for i := 0; i < 255; i++ { 83 | buff, err := ioutil.ReadFile(fmt.Sprintf(path, i)) 84 | 85 | // prevent from checking non-existant files 86 | if os.IsNotExist(err) { 87 | break 88 | } 89 | if err != nil { 90 | continue 91 | } 92 | 93 | deviceName := strings.ToLower(string(buff)) 94 | 95 | if restrictedDevices.hasDevice(deviceName) { 96 | continue 97 | } else if allowedDevices.hasDevice(deviceName) { 98 | valid = append(valid, fmt.Sprintf(resolved, i)) 99 | } 100 | } 101 | return valid 102 | } 103 | 104 | // IsRoot checks if the process is run with root permission 105 | func (k *KeyLogger) IsRoot() bool { 106 | return syscall.Getuid() == 0 && syscall.Geteuid() == 0 107 | } 108 | 109 | // Read from file descriptor 110 | // Blocking call, returns channel 111 | // Make sure to close channel when finish 112 | func (k *KeyLogger) Read() chan InputEvent { 113 | event := make(chan InputEvent) 114 | go func(event chan InputEvent) { 115 | for { 116 | e, err := k.read() 117 | if err != nil { 118 | close(event) 119 | break 120 | } 121 | 122 | if e != nil { 123 | event <- *e 124 | } 125 | } 126 | }(event) 127 | return event 128 | } 129 | 130 | // Write writes to keyboard and sync the event 131 | // This will keep the key pressed or released until you call another write with other direction 132 | // eg, if the key is "A" and direction is press, on UI, you will see "AAAAA..." until you stop with release 133 | // Probably you want to use WriteOnce method 134 | func (k *KeyLogger) Write(direction KeyEvent, key string) error { 135 | key = strings.ToUpper(key) 136 | code := uint16(0) 137 | for c, k := range keyCodeMap { 138 | if k == key { 139 | code = c 140 | } 141 | } 142 | if code == 0 { 143 | return fmt.Errorf("%s key not found in key code map", key) 144 | } 145 | err := k.write(InputEvent{ 146 | Type: EvKey, 147 | Code: code, 148 | Value: int32(direction), 149 | }) 150 | if err != nil { 151 | return err 152 | } 153 | return k.syn() 154 | } 155 | 156 | // WriteOnce method simulates single key press 157 | // When you send a key, it will press it, release it and send to sync 158 | func (k *KeyLogger) WriteOnce(key string) error { 159 | key = strings.ToUpper(key) 160 | code := uint16(0) 161 | for c, k := range keyCodeMap { 162 | if k == key { 163 | code = c 164 | } 165 | } 166 | if code == 0 { 167 | return fmt.Errorf("%s key not found in key code map", key) 168 | } 169 | 170 | for _, i := range []int32{int32(KeyPress), int32(KeyRelease)} { 171 | err := k.write(InputEvent{ 172 | Type: EvKey, 173 | Code: code, 174 | Value: i, 175 | }) 176 | if err != nil { 177 | return err 178 | } 179 | } 180 | return k.syn() 181 | } 182 | 183 | // read from file description and parse binary into go struct 184 | func (k *KeyLogger) read() (*InputEvent, error) { 185 | buffer := make([]byte, eventsize) 186 | n, err := k.fd.Read(buffer) 187 | if err != nil { 188 | return nil, err 189 | } 190 | // no input, dont send error 191 | if n <= 0 { 192 | return nil, nil 193 | } 194 | return k.eventFromBuffer(buffer) 195 | } 196 | 197 | // write to keyboard 198 | func (k *KeyLogger) write(ev InputEvent) error { 199 | return binary.Write(k.fd, binary.LittleEndian, ev) 200 | } 201 | 202 | // syn syncs input events 203 | func (k *KeyLogger) syn() error { 204 | return binary.Write(k.fd, binary.LittleEndian, InputEvent{ 205 | Type: EvSyn, 206 | Code: 0, 207 | Value: 0, 208 | }) 209 | } 210 | 211 | // eventFromBuffer parser bytes into InputEvent struct 212 | func (k *KeyLogger) eventFromBuffer(buffer []byte) (*InputEvent, error) { 213 | event := &InputEvent{} 214 | err := binary.Read(bytes.NewBuffer(buffer), binary.LittleEndian, event) 215 | return event, err 216 | } 217 | 218 | // Close file descriptor 219 | func (k *KeyLogger) Close() error { 220 | if k.fd == nil { 221 | return nil 222 | } 223 | return k.fd.Close() 224 | } 225 | -------------------------------------------------------------------------------- /keylogger_test.go: -------------------------------------------------------------------------------- 1 | package keylogger 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestFileDescriptor(t *testing.T) { 9 | k := &KeyLogger{} 10 | 11 | err := k.Close() 12 | if err != nil { 13 | t.Error("Closing empty file descriptor should not yield error", err) 14 | return 15 | } 16 | } 17 | 18 | func TestBufferParser(t *testing.T) { 19 | k := &KeyLogger{} 20 | 21 | // keyboard 22 | input, err := k.eventFromBuffer([]byte{138, 180, 84, 92, 0, 0, 0, 0, 62, 75, 8, 0, 0, 0, 0, 0, 4, 0, 4, 0, 30, 0, 0, 0}) 23 | if err != nil { 24 | t.Error(err) 25 | return 26 | } 27 | if input == nil { 28 | t.Error("Event is empty, expected parsed event") 29 | return 30 | } 31 | 32 | if input.KeyString() != "3" { 33 | t.Errorf("wrong input key. got %v, expected %v", input.KeyString(), "3") 34 | return 35 | } 36 | 37 | if input.Type != EvMsc { 38 | t.Errorf("wrong event type. expected key press but got %v", input.Type) 39 | return 40 | } 41 | } 42 | 43 | func TestWithPermission(t *testing.T) { 44 | fd, err := os.CreateTemp("", "*") 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | // try to create new keylogger with file descriptor which has the permission 49 | k, err := New(fd.Name()) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | k.Close() 54 | fd.Close() 55 | 56 | // try to create new keylogger with file descriptor which has no permission 57 | _, err = New("/dev/tty0") 58 | if err == nil { 59 | t.Fatal("expected error, got nil") 60 | } 61 | if err.Error() != "permission denied. run with root permission or use a user with access to /dev/tty0" { 62 | t.Fatalf("unexpected error: %v", err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /keymapper.go: -------------------------------------------------------------------------------- 1 | package keylogger 2 | 3 | // keyCodeMap connects the code with human readable key 4 | var keyCodeMap = map[uint16]string{ 5 | 1: "ESC", 6 | 2: "1", 7 | 3: "2", 8 | 4: "3", 9 | 5: "4", 10 | 6: "5", 11 | 7: "6", 12 | 8: "7", 13 | 9: "8", 14 | 10: "9", 15 | 11: "0", 16 | 12: "-", 17 | 13: "=", 18 | 14: "BS", 19 | 15: "TAB", 20 | 16: "Q", 21 | 17: "W", 22 | 18: "E", 23 | 19: "R", 24 | 20: "T", 25 | 21: "Y", 26 | 22: "U", 27 | 23: "I", 28 | 24: "O", 29 | 25: "P", 30 | 26: "[", 31 | 27: "]", 32 | 28: "ENTER", 33 | 29: "L_CTRL", 34 | 30: "A", 35 | 31: "S", 36 | 32: "D", 37 | 33: "F", 38 | 34: "G", 39 | 35: "H", 40 | 36: "J", 41 | 37: "K", 42 | 38: "L", 43 | 39: ";", 44 | 40: "'", 45 | 41: "`", 46 | 42: "L_SHIFT", 47 | 43: "\\", 48 | 44: "Z", 49 | 45: "X", 50 | 46: "C", 51 | 47: "V", 52 | 48: "B", 53 | 49: "N", 54 | 50: "M", 55 | 51: ",", 56 | 52: ".", 57 | 53: "/", 58 | 54: "R_SHIFT", 59 | 55: "*", 60 | 56: "L_ALT", 61 | 57: "SPACE", 62 | 58: "CAPS_LOCK", 63 | 59: "F1", 64 | 60: "F2", 65 | 61: "F3", 66 | 62: "F4", 67 | 63: "F5", 68 | 64: "F6", 69 | 65: "F7", 70 | 66: "F8", 71 | 67: "F9", 72 | 68: "F10", 73 | 69: "NUM_LOCK", 74 | 70: "SCROLL_LOCK", 75 | 71: "HOME", 76 | 72: "UP_8", 77 | 73: "PGUP_9", 78 | 74: "-", 79 | 75: "LEFT_4", 80 | 76: "5", 81 | 77: "RT_ARROW_6", 82 | 78: "+", 83 | 79: "END_1", 84 | 80: "DOWN", 85 | 81: "PGDN_3", 86 | 82: "INS", 87 | 83: "DEL", 88 | 84: "", 89 | 85: "", 90 | 86: "", 91 | 87: "F11", 92 | 88: "F12", 93 | 89: "", 94 | 90: "", 95 | 91: "", 96 | 92: "", 97 | 93: "", 98 | 94: "", 99 | 95: "", 100 | 96: "R_ENTER", 101 | 97: "R_CTRL", 102 | 98: "/", 103 | 99: "PRT_SCR", 104 | 100: "R_ALT", 105 | 101: "", 106 | 102: "HOME", 107 | 103: "UP", 108 | 104: "PGUP", 109 | 105: "LEFT", 110 | 106: "RIGHT", 111 | 107: "END", 112 | 108: "DOWN", 113 | 109: "PGDN", 114 | 110: "INSERT", 115 | 111: "DEL", 116 | 112: "", 117 | 113: "", 118 | 114: "", 119 | 115: "", 120 | 116: "", 121 | 117: "", 122 | 118: "", 123 | 119: "PAUSE", 124 | } 125 | --------------------------------------------------------------------------------