Source code for facedancer.filters.hid

#
# USBProxy HID logging
#

from warnings import filterwarnings
import hid_parser
from hid_parser import HIDComplianceWarning
from enum import IntEnum

from facedancer.descriptor import USBDescriptorTypeNumber
from facedancer.device import USBBaseDevice
from facedancer.filters.base import USBProxyFilter
from facedancer.request import USBControlRequest
from facedancer.types import (
    USBDirection,
    USBRequestRecipient,
    USBRequestType,
    USBStandardRequests,
)

from ..logging import log

GET_REPORT = 0x01
SET_REPORT = 0x09

SET_IDLE = 0x0A

filterwarnings("ignore", r"Usage.* has no compatible usage types", HIDComplianceWarning)
filterwarnings("ignore", r"Expecting 60 usages but got 1", HIDComplianceWarning)


[docs] class HIDReportType(IntEnum): HID_TYPE_INPUT = 1 HID_TYPE_OUTPUT = 2 HID_TYPE_FEATURE = 3
[docs] class USBProxyHIDFilter(USBProxyFilter): """ Print HID packets If verbose > 2 - print all fields """ def __init__(self, device: USBBaseDevice, verbose=1): self.device = device self.verbose = verbose self.rdescs = {}
[docs] def filter_control_in(self, req: USBControlRequest | None, data, stalled): if req: if req.type == USBRequestType.STANDARD and USBRequestRecipient.INTERFACE: if req.number == USBStandardRequests.GET_DESCRIPTOR: self._log_desc(req, data) if req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE: self._log_in(req, data) return req, data, stalled
def _log_desc(self, req, data): kind = req.value_high iface = req.index # index = req.value_low if kind == USBDescriptorTypeNumber.HID: log.info(f"GET_DESC HID_DEVICE I{iface} {dump(data)}") if kind == USBDescriptorTypeNumber.REPORT: log.info(f"GET_DESC HID_REPORT I{iface} {dump(data)}") try: self.rdescs[iface] = rdesc = hid_parser.ReportDescriptor(data) except NotImplementedError as e: log.warning(f"Failed to parse report: {e}") self.rdescs[iface] = None return if self.verbose > 2: for rid in rdesc.output_report_ids: log.info(f" output {rid} {rdesc.get_output_report_size(rid)}") for rid in rdesc.input_report_ids: log.info(f" input {rid} {rdesc.get_input_report_size(rid)}") for rid in rdesc.feature_report_ids: log.info(f" feature {rid} {rdesc.get_feature_report_size(rid)}") def _log_in(self, req, data): iface = req.index if req.number == GET_REPORT: kind = HIDReportType(req.value_high) log.info(f"GET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}") self._report(iface, "parse_input_report", data)
[docs] def filter_control_out(self, req, data): if req and req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE: self._log_out(req, data) return req, data
def _log_out(self, req, data): iface = req.index if req.number == SET_REPORT: kind = HIDReportType(req.value_high) log.info(f"SET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}") self._report(iface, "parse_output_report", data) if req.number == SET_IDLE: dur = req.value_high * 4 log.info(f"SET_IDLE {dur}ms RID {req.value_low} I{iface} {dump(data)}")
[docs] def filter_in(self, ep_num, data): if interface := self._find_interface(ep_num): self._log_ep_in(interface.number, ep_num, data) return ep_num, data
def _find_interface(self, ep_num): """Return the interface that has ep_num.""" if not self.device.configuration: return for interface in self.device.configuration.active_interfaces.values(): if interface.has_endpoint(ep_num, USBDirection.IN): return interface def _log_ep_in(self, iface, num, data): log.info(f"EP{num} I{iface} RID {data[0]} {dump(data[1:])}") self._report(iface, "parse_input_report", data) def _report(self, iface: int, kind: str, data: bytes): if self.verbose < 3: return rdesc = self.rdescs.get(iface) if len(data) > 1 and rdesc: try: # TODO - handle feature for usage, value in getattr(rdesc, kind)(data).items(): log.info(f" {usage} {value}") except Exception as e: log.warning(f" {e}")
[docs] def dump(raw: bytes): return raw.hex(" ", -2)