|
| 1 | +# |
| 2 | +# USBProxy HID logging |
| 3 | +# |
| 4 | + |
| 5 | +from warnings import filterwarnings |
| 6 | +import hid_parser |
| 7 | +from hid_parser import HIDComplianceWarning |
| 8 | +from enum import IntEnum |
| 9 | + |
| 10 | +from facedancer.descriptor import USBDescriptorTypeNumber |
| 11 | +from facedancer.device import USBBaseDevice |
| 12 | +from facedancer.filters.base import USBProxyFilter |
| 13 | +from facedancer.request import USBControlRequest |
| 14 | +from facedancer.types import ( |
| 15 | + USBDirection, |
| 16 | + USBRequestRecipient, |
| 17 | + USBRequestType, |
| 18 | + USBStandardRequests, |
| 19 | +) |
| 20 | + |
| 21 | +from ..logging import log |
| 22 | + |
| 23 | +GET_REPORT = 0x01 |
| 24 | +SET_REPORT = 0x09 |
| 25 | + |
| 26 | +SET_IDLE = 0x0A |
| 27 | + |
| 28 | +filterwarnings("ignore", r"Usage.* has no compatible usage types", HIDComplianceWarning) |
| 29 | +filterwarnings("ignore", r"Expecting 60 usages but got 1", HIDComplianceWarning) |
| 30 | + |
| 31 | + |
| 32 | +class HIDReportType(IntEnum): |
| 33 | + HID_TYPE_INPUT = 1 |
| 34 | + HID_TYPE_OUTPUT = 2 |
| 35 | + HID_TYPE_FEATURE = 3 |
| 36 | + |
| 37 | + |
| 38 | +class USBProxyHIDFilter(USBProxyFilter): |
| 39 | + """ |
| 40 | + Print HID packets |
| 41 | +
|
| 42 | + If verbose > 2 - print all fields |
| 43 | + """ |
| 44 | + |
| 45 | + def __init__(self, device: USBBaseDevice, verbose=1): |
| 46 | + self.device = device |
| 47 | + self.verbose = verbose |
| 48 | + self.rdescs = {} |
| 49 | + |
| 50 | + def filter_control_in(self, req: USBControlRequest | None, data, stalled): |
| 51 | + if req: |
| 52 | + if req.type == USBRequestType.STANDARD and USBRequestRecipient.INTERFACE: |
| 53 | + if req.number == USBStandardRequests.GET_DESCRIPTOR: |
| 54 | + self._log_desc(req, data) |
| 55 | + |
| 56 | + if req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE: |
| 57 | + self._log_in(req, data) |
| 58 | + |
| 59 | + return req, data, stalled |
| 60 | + |
| 61 | + def _log_desc(self, req, data): |
| 62 | + kind = req.value_high |
| 63 | + iface = req.index |
| 64 | + # index = req.value_low |
| 65 | + |
| 66 | + if kind == USBDescriptorTypeNumber.HID: |
| 67 | + log.info(f"GET_DESC HID_DEVICE I{iface} {dump(data)}") |
| 68 | + |
| 69 | + if kind == USBDescriptorTypeNumber.REPORT: |
| 70 | + log.info(f"GET_DESC HID_REPORT I{iface} {dump(data)}") |
| 71 | + try: |
| 72 | + self.rdescs[iface] = rdesc = hid_parser.ReportDescriptor(data) |
| 73 | + except NotImplementedError as e: |
| 74 | + log.warning(f"Failed to parse report: {e}") |
| 75 | + self.rdescs[iface] = None |
| 76 | + return |
| 77 | + |
| 78 | + if self.verbose > 2: |
| 79 | + for rid in rdesc.output_report_ids: |
| 80 | + log.info(f" output {rid} {rdesc.get_output_report_size(rid)}") |
| 81 | + |
| 82 | + for rid in rdesc.input_report_ids: |
| 83 | + log.info(f" input {rid} {rdesc.get_input_report_size(rid)}") |
| 84 | + |
| 85 | + for rid in rdesc.feature_report_ids: |
| 86 | + log.info(f" feature {rid} {rdesc.get_feature_report_size(rid)}") |
| 87 | + |
| 88 | + def _log_in(self, req, data): |
| 89 | + iface = req.index |
| 90 | + |
| 91 | + if req.number == GET_REPORT: |
| 92 | + kind = HIDReportType(req.value_high) |
| 93 | + log.info(f"GET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}") |
| 94 | + |
| 95 | + self._report(iface, "parse_input_report", data) |
| 96 | + |
| 97 | + def filter_control_out(self, req, data): |
| 98 | + if req and req.type == USBRequestType.CLASS and USBRequestRecipient.INTERFACE: |
| 99 | + self._log_out(req, data) |
| 100 | + |
| 101 | + return req, data |
| 102 | + |
| 103 | + def _log_out(self, req, data): |
| 104 | + iface = req.index |
| 105 | + |
| 106 | + if req.number == SET_REPORT: |
| 107 | + kind = HIDReportType(req.value_high) |
| 108 | + log.info(f"SET_REPORT {kind} RID {req.value_low} I{iface} {dump(data)}") |
| 109 | + self._report(iface, "parse_output_report", data) |
| 110 | + |
| 111 | + if req.number == SET_IDLE: |
| 112 | + dur = req.value_high * 4 |
| 113 | + log.info(f"SET_IDLE {dur}ms RID {req.value_low} I{iface} {dump(data)}") |
| 114 | + |
| 115 | + def filter_in(self, ep_num, data): |
| 116 | + if interface := self._find_interface(ep_num): |
| 117 | + self._log_ep_in(interface.number, ep_num, data) |
| 118 | + |
| 119 | + return ep_num, data |
| 120 | + |
| 121 | + def _find_interface(self, ep_num): |
| 122 | + """Return the interface that has ep_num.""" |
| 123 | + if not self.device.configuration: |
| 124 | + return |
| 125 | + |
| 126 | + for interface in self.device.configuration.active_interfaces.values(): |
| 127 | + if interface.has_endpoint(ep_num, USBDirection.IN): |
| 128 | + return interface |
| 129 | + |
| 130 | + def _log_ep_in(self, iface, num, data): |
| 131 | + log.info(f"EP{num} I{iface} RID {data[0]} {dump(data[1:])}") |
| 132 | + self._report(iface, "parse_input_report", data) |
| 133 | + |
| 134 | + def _report(self, iface: int, kind: str, data: bytes): |
| 135 | + if self.verbose < 3: |
| 136 | + return |
| 137 | + |
| 138 | + rdesc = self.rdescs.get(iface) |
| 139 | + if len(data) > 1 and rdesc: |
| 140 | + try: |
| 141 | + # TODO - handle feature |
| 142 | + for usage, value in getattr(rdesc, kind)(data).items(): |
| 143 | + log.info(f" {usage} {value}") |
| 144 | + except Exception as e: |
| 145 | + log.warning(f" {e}") |
| 146 | + |
| 147 | + |
| 148 | +def dump(raw: bytes): |
| 149 | + return raw.hex(" ", -2) |
0 commit comments