news 2026/4/20 13:27:47

告别抓瞎!手把手教你用Python脚本模拟USB主机获取设备描述符

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
告别抓瞎!手把手教你用Python脚本模拟USB主机获取设备描述符

用Python脚本解码USB设备:从底层协议到实战解析

USB设备早已渗透进我们数字生活的每个角落——从键盘鼠标到移动硬盘,这些看似简单的硬件背后都遵循着一套复杂的通信协议。但你是否好奇过,当插入U盘的瞬间,电脑究竟是如何识别它的?本文将带你用Python直接与USB设备对话,通过发送标准请求获取原始描述符数据,揭开硬件通信的神秘面纱。

1. 环境准备与工具链搭建

要直接与USB设备通信,我们需要绕过操作系统的高级抽象层,直接访问底层协议。Python的pyusb库提供了这样的能力,但它依赖于libusb这个跨平台的USB访问库。

安装基础依赖(Ubuntu/Debian示例)

sudo apt-get install libusb-1.0-0-dev pip install pyusb

在Windows系统上,除了安装libusb的驱动程序外,还需要特别注意设备权限问题。USB设备的访问通常需要管理员权限,但我们可以通过设置设备权限规则来避免每次都需要sudo:

import usb.core dev = usb.core.find(idVendor=0x1234, idProduct=0x5678) dev.set_configuration()

提示:如果遇到权限错误,可以创建/etc/udev/rules.d/50-myusb.rules文件,添加以下内容后重新插拔设备:SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", MODE="0666"

2. 发现与识别USB设备

连接上USB设备后,第一步是定位它在USB总线上的"住址"。每个USB设备在连接时都会被分配一个唯一的设备地址(1-127),我们需要先扫描总线找到目标设备。

典型设备发现代码

import usb.core # 列出所有USB设备 for device in usb.core.find(find_all=True): print(f"发现设备: {hex(device.idVendor)}:{hex(device.idProduct)}") print(f"制造商: {usb.util.get_string(device, device.iManufacturer)}") print(f"产品名: {usb.util.get_string(device, device.iProduct)}")

设备描述符中的几个关键字段:

字段名描述示例值
idVendor厂商ID0x0781 (SanDisk)
idProduct产品ID0x5567 (Ultra Fit)
bDeviceClass设备类0x00 (由接口定义)
bNumConfigurations配置数1

3. 构造Get Descriptor请求

USB协议定义了标准请求格式,Get Descriptor是最基础且重要的请求之一。我们需要手动构造这个请求包,包含以下关键字段:

  • bmRequestType:请求方向(0x80表示设备到主机)、请求类型(标准请求)和接收方(设备)
  • bRequest:请求代码,Get Descriptor对应0x06
  • wValue:高字节为描述符类型,低字节为描述符索引
  • wIndex:语言ID(仅字符串描述符)或0
  • wLength:期望返回的数据长度

Python实现请求构造

def get_descriptor(dev, desc_type, desc_index=0, langid=0, length=255): return dev.ctrl_transfer( 0x80, # bmRequestType (设备到主机) 0x06, # bRequest (GET_DESCRIPTOR) (desc_type << 8) | desc_index, # wValue langid, # wIndex length # wLength )

常见描述符类型代码:

描述符类型说明
DEVICE1设备描述符
CONFIGURATION2配置描述符
STRING3字符串描述符
INTERFACE4接口描述符
ENDPOINT5端点描述符

4. 解析原始描述符数据

获取到的描述符数据是原始的二进制格式,需要按照USB规范进行解析。以设备描述符为例,它的结构固定为18字节,各字段含义如下:

设备描述符解析示例

data = get_descriptor(dev, 1) # 获取设备描述符 descriptor = { 'bLength': data[0], 'bDescriptorType': data[1], 'bcdUSB': (data[3] << 8) | data[2], 'bDeviceClass': data[4], 'bDeviceSubClass': data[5], 'bDeviceProtocol': data[6], 'bMaxPacketSize': data[7], 'idVendor': (data[9] << 8) | data[8], 'idProduct': (data[11] << 8) | data[10], 'bcdDevice': (data[13] << 8) | data[12], 'iManufacturer': data[14], 'iProduct': data[15], 'iSerialNumber': data[16], 'bNumConfigurations': data[17] } print(f"USB版本: {hex(descriptor['bcdUSB'])}") print(f"厂商ID: {hex(descriptor['idVendor'])}") print(f"产品ID: {hex(descriptor['idProduct'])}")

字符串描述符的获取稍显特殊,需要先获取支持的语言ID列表,然后再用特定语言ID请求字符串内容:

# 首先获取支持的语言ID langids = get_descriptor(dev, 3, 0, 0, 255) langid_list = [langids[i] | (langids[i+1] << 8) for i in range(2, langids[0], 2)] # 然后获取特定字符串 string = get_descriptor(dev, 3, descriptor['iProduct'], langid_list[0], 255) product_name = string[2:2+string[0]-2].decode('utf-16le')

5. 深入配置描述符与接口

设备描述符只是冰山一角,更丰富的信息藏在配置描述符中。一个配置描述符实际上是一个描述符集合,包含:

  1. 配置描述符本身
  2. 接口描述符
  3. 端点描述符
  4. 可能的类特定描述符

完整配置描述符解析流程

# 获取配置描述符总长度 config_desc = get_descriptor(dev, 2, 0, 0, 4) total_length = (config_desc[3] << 8) | config_desc[2] # 获取完整配置描述符集 full_config = get_descriptor(dev, 2, 0, 0, total_length) # 解析过程需要遍历描述符集合 position = 0 while position < len(full_config): length = full_config[position] desc_type = full_config[position+1] if desc_type == 2: # 配置描述符 print(f"配置值: {full_config[position+5]}") print(f"最大功耗: {full_config[position+8]}mA") elif desc_type == 4: # 接口描述符 print(f"接口号: {full_config[position+2]}") print(f"端点数: {full_config[position+4]}") elif desc_type == 5: # 端点描述符 print(f"端点地址: {hex(full_config[position+2])}") print(f"最大包大小: {full_config[position+4]}") position += length

6. 实战:USB键盘协议解析

让我们以一个实际的USB键盘为例,看看如何解析其HID报告描述符。HID(人机接口设备)类设备有特殊的描述符结构:

# 首先找到HID接口 for cfg in dev: for intf in cfg: if intf.bInterfaceClass == 3: # HID类 print("找到HID接口!") hid_desc = get_descriptor(dev, 0x21, intf.bInterfaceNumber) # HID描述符 report_length = (hid_desc[7] << 8) | hid_desc[6] report_desc = dev.ctrl_transfer( 0x81, 0x06, 0x2200, intf.bInterfaceNumber, report_length ) print("HID报告描述符:", report_desc)

HID报告描述符定义了设备如何打包和解释数据。虽然解析它需要理解HID规范,但我们可以观察到一些关键信息:

  • 输入报告的长度(按键数据)
  • 使用的用法页(键盘、鼠标等)
  • 修饰键和普通键的位域布局

7. 高级技巧与错误处理

在实际操作中,我们会遇到各种边界情况和错误。以下是一些实用技巧:

超时处理

try: data = dev.ctrl_transfer(..., timeout=1000) # 1秒超时 except usb.core.USBError as e: if e.errno == 110: # 操作超时 print("设备响应超时,请检查连接")

多配置设备处理

if dev.bNumConfigurations > 1: print("设备支持多种配置:") for i in range(dev.bNumConfigurations): cfg = dev[i] print(f"配置{i}: {cfg.bConfigurationValue}") selected = int(input("选择配置(0-{}): ".format(dev.bNumConfigurations-1))) dev.set_configuration(dev[selected])

批量传输端点通信

# 找到批量传输端点 endpoint_in = None endpoint_out = None for cfg in dev: for intf in cfg: for ep in intf: if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN: endpoint_in = ep else: endpoint_out = ep # 进行批量传输 if endpoint_in: data = dev.read(endpoint_in.bEndpointAddress, endpoint_in.wMaxPacketSize, timeout=1000) print("收到数据:", data)

通过这套方法,我们不仅能读取标准USB描述符,还能与设备进行更复杂的交互。比如对U盘发送SCSI命令,或者与自定义USB设备通信。这种底层访问能力为设备调试、逆向工程和自动化测试打开了新的大门。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 13:20:23

AudioSeal Pixel Studio应用场景:智能音箱唤醒词音频嵌入厂商ID防克隆

AudioSeal Pixel Studio应用场景&#xff1a;智能音箱唤醒词音频嵌入厂商ID防克隆 1. 引言&#xff1a;当你的智能音箱可能被“山寨”唤醒 想象一下这个场景&#xff1a;你花了几百块钱买了一个知名品牌的智能音箱&#xff0c;每天回家喊一声“小X同学”&#xff0c;它就能帮…

作者头像 李华
网站建设 2026/4/20 13:19:24

WebSpoon 中文界面部署实战:从 Docker 安装到页面汉化全解析

1. WebSpoon 是什么&#xff1f;为什么需要中文界面&#xff1f; 如果你正在寻找一个开源的 ETL&#xff08;数据抽取、转换、加载&#xff09;工具&#xff0c;那么 WebSpoon 绝对值得一试。它是传统 Kettle&#xff08;现在叫 Pentaho Data Integration&#xff09;的 Web 版…

作者头像 李华