如何模拟USB键盘向Windows 11发送按键指令?
USB键盘数据包重放问题解答
1. 如何将数据包发送至PC的USB集线器?
首先纠正一个关键误解:USB是主从架构,PC作为主机,键盘是从设备(向主机发送数据)。你不能直接从PC向USB集线器发送键盘数据包来模拟按键——Windows只接受USB从设备发送的HID输入报告,而非主机侧主动发送的数据包。
根据你使用的两个库,具体实现思路如下:
用pywinusb(Windows专用,更简单)
pywinusb针对Windows HID设备做了封装,无需直接操作USB集线器,核心是模拟HID键盘的输入报告:
- 第一步:找到你抓包的真实键盘的VID(厂商ID)和PID(产品ID),或者提取它的HID报告描述符(Wireshark抓包中可找到)。Windows需要匹配报告描述符才会识别为合法键盘。
- 第二步:通过pywinusb打开HID设备(或模拟设备),构造对应的按键输入报告并发送。
用pyusb(底层通用,需驱动)
pyusb更贴近USB协议底层,但Windows下需要额外配置:
- 先安装libusb驱动(用Zadig工具将目标设备的驱动替换为libusb-win32)。
- 你需要一个支持USB HID模拟的硬件(比如Arduino Leonardo,它自带USB HID模拟功能),因为纯软件无法在Windows上模拟USB从设备(Windows USB栈要求硬件存在)。
- 枚举设备、找到对应的中断端点,然后向端点发送HID输入报告。
2. 待发送数据包的结构:需要剔除哪些字节?
Wireshark捕获的USB数据包包含USB总线协议层字节(比如令牌包、URB头、地址/端点标识等),这些都要剔除,只保留HID输入报告的有效载荷。
标准键盘HID报告结构
普通USB键盘的输入报告通常是6-8字节,格式如下:
- 第1字节:修饰键位(比如Ctrl=0x01、Shift=0x02,无修饰键则为0x00)
- 第2字节:保留位(固定为0x00)
- 第3-8字节:按键扫描码(按下某键时填入对应扫描码,松开时填0x00)
示例:
- 按下W键的报告(6字节):
[0x00, 0x00, 0x1A, 0x00, 0x00, 0x00](W的扫描码是0x1A) - 松开W键的报告:
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
从Wireshark抓包提取有效数据
在Wireshark中找到标记为URB_INTERRUPT in的数据包(键盘向PC发送的中断包),展开Urb -> Data字段,里面的字节就是你需要的HID输入报告,直接提取这部分即可,无需添加其他字节。
关键注意事项
- 必须匹配目标键盘的HID报告描述符:不同键盘的报告长度、格式可能有差异(比如带多媒体键的键盘报告更长),如果报告格式不匹配,Windows无法正确识别按键。
- 模拟按键需要成对发送:按下报告后必须发送松开报告,否则系统会认为按键一直被按住。
- 纯软件模拟限制:Windows不允许主机侧程序直接模拟USB从设备,必须借助支持HID模拟的硬件(如Arduino、USB HID模拟模块)。
代码示例
pywinusb示例(模拟W键按下松开)
import pywinusb.hid as hid def simulate_key_press(vid, pid, scan_code): # 筛选目标HID设备(替换为你抓包的键盘VID/PID) devices = hid.HidDeviceFilter(vendor_id=vid, product_id=pid).get_devices() if not devices: print("未找到目标设备") return device = devices[0] device.open() # 构造按下W的报告(以8字节标准报告为例) press_report = [0x00, 0x00, scan_code, 0x00, 0x00, 0x00, 0x00, 0x00] device.send_input_report(press_report) # 构造松开报告 release_report = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] device.send_input_report(release_report) device.close() # 替换为你的键盘VID、PID,W键扫描码0x1A simulate_key_press(0xXXXX, 0xXXXX, 0x1A)
pyusb示例(基于Arduino Leonardo模拟)
import usb.core import usb.util # Arduino Leonardo的VID/PID VID = 0x2341 PID = 0x8036 def send_key_report(scan_code): dev = usb.core.find(idVendor=VID, idProduct=PID) if not dev: raise ValueError("未找到Arduino设备") # 配置设备 dev.set_configuration() cfg = dev.get_active_configuration() intf = cfg[(0, 0)] # 找到中断输出端点(向PC发送数据的端点) ep = usb.util.find_descriptor( intf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT ) # 发送按下报告 ep.write([0x00, 0x00, scan_code, 0x00, 0x00, 0x00]) # 发送松开报告 ep.write([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) # 模拟W键按下松开 send_key_report(0x1A)
内容的提问来源于stack exchange,提问作者noir
相关产品推荐
相关产品推荐

