使用不同的供应商和产品 ID 使 USB 设备可见

Posted

技术标签:

【中文标题】使用不同的供应商和产品 ID 使 USB 设备可见【英文标题】:Make USB device visible with different vendor and product ID 【发布时间】:2021-07-11 05:34:31 【问题描述】:

我正在寻找一种使 USB 设备显示为具有不同供应商和产品 ID 的方法。我正在尝试制作一个专有软件来与应该支持但仅仅因为它的 ID 而被拒绝的 USB 设备一起使用。

该软件适用于 Windows,但我可以在 Linux 的 VM 中运行它。因此,无论哪种方法,我都可以接受:

在 Linux 中更改 USB ID 在 Windows 中更改 USB ID 让 Qemu(或其他类似的)在 passthrough 中更改 USB ID

【问题讨论】:

【参考方案1】:

可能有更简单的方法可以做到这一点,但我遇到了类似的问题,并且能够创建一个进程,在该进程中我可以更改设备描述符信息以用于开发目的。流程总结在这张图中:

首先为您的 Raspberry Pi 配置一个静态 IP 地址并配置您的 PC 以太网 TCP/IPv4 设置,以便您能够通过 LAN 连接与您的 Raspberry Pi 通信。

从 Virtual Here website 为您的 PC 下载 Virtual Here Raspberry Pi 服务器和客户端软件。试用版适用于此用例。

将 Virtual Here 服务器软件移至您的 Raspberry Pi。为了运行 USB 服务器,您需要使用 $ sudo chmod +x vhusbdarm 更改文件的权限,然后使用 $ sudo ./vhusbdarm 运行。

在本地计算机上运行客户端软件。您将看到客户端在 USB 设备服务器上的<Your Raspberry Pi IP address>:7575 上检测到 USB 设备。此时连接到设备不会带来任何好处,而是模拟直接连接。

运行下面的 python 文件,它是从我找到的解决方案 here 修改的,但在转发原始数据之前利用 Scapy 嗅探来捕获传入的数据包。链接解决方案中的原始脚本也应该可以正常工作。在脚本中你可以看到我使用了端口12345

#!/usr/bin/env python

from scapy.all import *
import socket
import threading
import select
from queue import Queue
main_queue = Queue()

terminateAll = False

class ClientThread(threading.Thread):
    def __init__(self, clientSocket, targetHost, targetPort):
        threading.Thread.__init__(self)
        self.__clientSocket = clientSocket
        self.__targetHost = targetHost
        self.__targetPort = targetPort

    def run(self):
        print("Client Thread started")

        self.__clientSocket.setblocking(0)

        targetHostSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        targetHostSocket.connect((self.__targetHost, self.__targetPort))
        targetHostSocket.setblocking(0)

        clientData = b""
        targetHostData = b""
        terminate = False
        while not terminate and not terminateAll:
            inputs = [self.__clientSocket, targetHostSocket]
            outputs = []

            if len(clientData) > 0:
                outputs.append(self.__clientSocket)

            if len(targetHostData) > 0:
                outputs.append(targetHostSocket)

            try:
                inputsReady, outputsReady, errorsReady = select.select(inputs, outputs, [], 1.0)
            except Exception as e:
                print(e)
                break

            for inp in inputsReady:
                if inp == self.__clientSocket:
                    try:
                        data = self.__clientSocket.recv(4096)
                        #print(data)
                        #data = b""
                        #while not main_queue.empty():
                        #    data += main_queue.get()
                    except Exception as e:
                        print(e)

                    if data != None:
                        if len(data) > 0:
                            targetHostData += data
                        #else:
                        #    terminate = True
                elif inp == targetHostSocket:
                    try:
                        data = b""
                        while not main_queue.empty():
                           data += main_queue.get()
                    except Exception as e:
                        print(e)

                    if data != None:
                        if len(data) > 0:
                            clientData += data

            for out in outputsReady:
                if out == self.__clientSocket and len(clientData) > 0:
                    #pck = Ether(clientData)
                    #pck.show()
                    bytesWritten = self.__clientSocket.send(clientData)
                    if bytesWritten > 0:
                        clientData = clientData[bytesWritten:]
                elif out == targetHostSocket and len(targetHostData) > 0:
                    #pck = Ether(targetHostData)
                    #pck.show()
                    bytesWritten = targetHostSocket.send(targetHostData)
                    if bytesWritten > 0:
                        targetHostData = targetHostData[bytesWritten:]

        self.__clientSocket.close()
        targetHostSocket.close()
        print ("ClientThread terminating")


def handle_sniff(pck):
    if IP in pck:
        if pck[IP].src == "192.168.1.48":
            if Raw in pck:
                payload = pck[Raw].load
                if b'\x12\x01\x00\x01\x00\x00\x00\x08$\x07\x04\x00\x88#\x01\x02\x00\x01' in payload:
                    payload = payload.replace(b'\x00\x08$\x07\x04\x00\x88#\x01\x02\x00', b'\x00\x08$\x07\x04\x00\x88\x15\x01\x02\x00')
                    
                print(payload)
                main_queue.put(payload)


if __name__ == '__main__':

    localHost = "localhost"
    localPort = 12345
    targetHost = "192.168.1.12"
    targetPort = 7575

    serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serverSocket.bind((localHost, localPort))
    serverSocket.listen(5)
    print("Waiting for client...")
    while True:
        try:
            clientSocket, address = serverSocket.accept()
        except KeyboardInterrupt:
            print("\nTerminating...")
            terminateAll = True
            break
        print("starting client")
        ClientThread(clientSocket, targetHost, targetPort).start()
        sniff(iface="Ethernet", prn=lambda pck: handle_sniff(pck))

    serverSocket.close()

脚本运行后,配置 Virtual Here 客户端以侦听位于 localhost:12345 的 USB 服务器。 handle_sniff 函数是更改 USB 设备描述符信息的地方。连接后,您应该能够双击下拉树中的 USB 设备。您将看到 USB 数据开始在您的 python 控制台中打印。

在上面的示例中,我更改了 USB 描述符的设备 bcdDevice 字节。

我用来识别包含我所针对的信息的数据包的另一个有用脚本如下。我修改了在此solution 中找到的脚本。它被修改为打印原始数据以及解包的设备描述符信息,然后可以在 TCP 原始数据中搜索以确定要替换的字节。

#!/usr/bin/env python

from __future__ import print_function
import argparse
import string
import struct
import sys

import win32api
import win32file
import pywintypes

BUFF=b""
def CTL_CODE(DeviceType, Function, Method, Access):
    return (DeviceType << 16) | (Access << 14) | (Function << 2) | Method
def USB_CTL(id):
   # CTL_CODE(FILE_DEVICE_USB, (id), METHOD_BUFFERED, FILE_ANY_ACCESS)
    return CTL_CODE(0x22, id, 0, 0)

IOCTL_USB_GET_ROOT_HUB_NAME = USB_CTL(258)                   # HCD_GET_ROOT_HUB_NAME
IOCTL_USB_GET_NODE_INFORMATION = USB_CTL(258)                # USB_GET_NODE_INFORMATION
IOCTL_USB_GET_NODE_CONNECTION_INFORMATION = USB_CTL(259)     # USB_GET_NODE_CONNECTION_INFORMATION
IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME = USB_CTL(264)  # USB_GET_NODE_CONNECTION_DRIVERKEY_NAME
IOCTL_USB_GET_NODE_CONNECTION_NAME = USB_CTL(261)            # USB_GET_NODE_CONNECTION_NAME
IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION = USB_CTL(260) # USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION

USB_CONFIGURATION_DESCRIPTOR_TYPE = 2
USB_STRING_DESCRIPTOR_TYPE = 3
USB_INTERFACE_DESCRIPTOR_TYPE = 4
MAXIMUM_USB_STRING_LENGTH = 255


def open_dev(name):
    try:
        handle = win32file.CreateFile(name,
                                  win32file.GENERIC_WRITE,
                                  win32file.FILE_SHARE_WRITE,
                                  None,
                                  win32file.OPEN_EXISTING,
                                  0,
                                  None)
    except pywintypes.error as e:
        return None
    return handle


def get_root_hub_name(handle):
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_ROOT_HUB_NAME,
                                None,
                                6,
                                None)
    act_len, _ = struct.unpack('LH', buf)
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_ROOT_HUB_NAME,
                                None,
                                act_len,
                                None)
    return buf[4:].decode('utf-16le')


def get_driverkey_name(handle, index):
    key_name = bytes(chr(index) + '\0'*9, 'utf-8')
    try:
        buf = win32file.DeviceIoControl(handle,
                                    IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
                                    key_name,
                                    10,
                                    None)
    except pywintypes.error as e:
        print(e.strerror, index)
        sys.exit(1)
    _, act_len, _ = struct.unpack('LLH', buf)
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME,
                                key_name,
                                act_len,
                                None)
    return buf[8:].decode('utf-16le')


def get_ext_hub_name(handle, index):
    hub_name = chr(index) + '\0'*9
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_NODE_CONNECTION_NAME,
                                hub_name,
                                10,
                                None)
    _, act_len, _ = struct.unpack('LLH', buf)
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_NODE_CONNECTION_NAME,
                                hub_name,
                                act_len,
                                None)
    return buf[8:].decode('utf-16le')


def get_str_desc(handle, conn_idx, str_idx):
    req = struct.pack('LBBHHH',
                  conn_idx,
                  0,
                  0,
                  (USB_STRING_DESCRIPTOR_TYPE<<8) | str_idx,
                  win32api.GetSystemDefaultLangID(),
                  MAXIMUM_USB_STRING_LENGTH)
    try:
        buf = win32file.DeviceIoControl(handle,
                                    IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,
                                    req,
                                    12+MAXIMUM_USB_STRING_LENGTH,
                                    None)
    except pywintypes.error as e:
         return 'ERROR: no String Descriptor for index '.format(str_idx)
    if len(buf) > 16:
        return buf[14:].decode('utf-16le')
    return ''


def exam_hub(name, verbose, level):
    handle = open_dev(r'\\.\'.format(name))
    if not handle:
        print('Failed to open device '.format(name))
        return
    buf = win32file.DeviceIoControl(handle,
                                IOCTL_USB_GET_NODE_INFORMATION,
                                None,
                                76,
                                None)
    print_hub_ports(handle, ord(buf[6]), verbose, level)
    handle.close()


def print_str_or_hex(to_be_print):
    if all(c in string.printable for c in to_be_print):
        print('""'.format(to_be_print))
        return
    print('Hex: ', end='')
    for x in to_be_print:
        print(':02x '.format(ord(x)), end='')
    print('')


def print_hub_ports(handle, num_ports, verbose, level):
    print(handle, num_ports, verbose, level)
    for idx in range(1, num_ports+1):
        info = bytes(chr(idx) + '\0'*34, 'utf-8')
        try:
            buf = win32file.DeviceIoControl(handle,
                                        IOCTL_USB_GET_NODE_CONNECTION_INFORMATION,
                                        info,
                                        34 + 11*30,
                                        None)
        except pywintypes.error as e:
            print(e)
            print(e.winerror, e.funcname, e.strerror)
            return
        print(buf)
        _, vid, pid, vers, manu, prod, seri, _, ishub, _, stat = struct.unpack('=12sHHHBBB3s?6sL', buf[:35])

        if ishub:
            if verbose:
                print('  [Port] '.format('  '*level, idx, 'USB Hub'))
            exam_hub(get_ext_hub_name(handle, idx), verbose, level)
        elif stat == 0 and verbose:
            print('  [Port] '.format('  '*level, idx, 'NoDeviceConnected'))
        elif stat == 1:
            if verbose or (manu != 0 or prod != 0 or seri != 0):
                print('  [Port] '.format('  '*level, idx, get_driverkey_name(handle, idx)))
                print('    Vendor ID:    0x:04X'.format('  '*level, vid))
                print('    Product ID:  0x:04X'.format('  '*level, pid))
                print('    Device BCD:  0x:04X'.format('  '*level, vers))
                print(vers)
                if manu != 0:
                    print('    Manufacturer (0x:x) -> '.format('  '*level, manu), end='')
                    print_str_or_hex(get_str_desc(handle, idx, manu))
                if prod != 0:
                    print('    Product      (0x:x) -> '.format('  '*level, prod), end='')
                    print_str_or_hex(get_str_desc(handle, idx, prod))
                if seri != 0:
                    print('    Serial No    (0x:x) -> '.format('  '*level, seri), end='')
                    print_str_or_hex(get_str_desc(handle, idx, seri))


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', action='store_true',
                    help="Increase output verbosity.")
    args = parser.parse_args()

    for i in range(10):
        name = r"\\.\HCD".format(i)
        handle = open_dev(name)
        if not handle:
            continue

        root = get_root_hub_name(handle)
        print('RootHub: '.format('\n' if i != 0 else '', root))

        dev_name = r'\\.\'.format(root)
        dev_handle = open_dev(dev_name)
        if not dev_handle:
            print('Failed to open device '.format(dev_name))
            continue

        buf = win32file.DeviceIoControl(dev_handle,
                                    IOCTL_USB_GET_NODE_INFORMATION,
                                    None,
                                    76,
                                    None)
        global BUFF
        BUFF=buf
        print_hub_ports(dev_handle, buf[6], args.verbose, 0)
        dev_handle.close()
        handle.close()

if __name__ == '__main__':
    main()

附:这对于过滤和修改正在传输的任何 USB 数据也非常有帮助,而不仅仅是设备描述符。

【讨论】:

以上是关于使用不同的供应商和产品 ID 使 USB 设备可见的主要内容,如果未能解决你的问题,请参考以下文章

怎么查看U盘的PID和VID信息

X86-asm 代码使 USB 磁盘不可启动

USB的VID和PID,以及分类(Class,SubClass,Protocol)

windows平台下获取USB设备“身份证”

通过设备路径或句柄获取 U 盘盘符

USBLogView(U S B 记录查看器)