IOKit Writing to USB interface hanging/timing out

2019-08-01 08:23发布

问题:

There is probably something really simple I am not doing or overlooked but I am out of ideas as I am new to IOKit.

I want to recreate some Windows-only software that a USB device I have came with. I installed Snoopy on VM and clicked a button on the program that makes an LED turn off then looked at the logs hoping to get an idea of what is going on. It looks like for every button click on the software, 2 packets are sent. One to the interface and one to the endpoint with the first 6 bytes being the same. I have no idea why that is happening.

Snoopy log:

I also attached the device to my Ubuntu VM and ran sudo lsusb -v to gather some more information on the device:

 Bus 001 Device 009: ID 1234:1234 
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0        64
  idVendor           0x1234
  idProduct          0x1234 
  bcdDevice            1.00
  iManufacturer           1 (error)
  iProduct                2 (error)
  iSerial                 3 (error)
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           34
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.01
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      40
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

The next step I took was to write a codeless kext to prevent macOS from grabbing it before my user-land software could. Kexstat does confirm it is running. The plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>KEXT</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>IOKitPersonalities</key>
    <dict>
        <key>HID Device</key>
        <dict>
            <key>bInterfaceNumber</key>
            <integer>0</integer>
            <key>bConfigurationValue</key>
            <integer>1</integer>
            <key>idProduct</key>
            <integer>1234</integer>
            <key>idVendor</key>
            <integer>1234</integer>
            <key>IOProbeScore</key>
            <integer>9000</integer>
            <key>CFBundleIdentifier</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>IOClass</key>
            <string>IOService</string>
            <key>IOProviderClass</key>
            <string>IOUSBInterface</string>
        </dict>
    </dict>
    <key>OSBundleLibraries</key>
    <dict>
        <key>com.apple.kpi.iokit</key>
        <string>8.0</string>
        <key>com.apple.kpi.libkern</key>
        <string>8.0</string>
    </dict>
</dict>
</plist>

Now I wrote an application in C using IOKit to try to write to the only pipe (that I can see) on the device. The application makes it all the way to the WritePipe function and then just hangs. Nothing happens. Here is the code:

CFMutableDictionaryRef matchingDictionary = NULL;
CFNumberRef numberRef;
SInt32 idVendor = 0x1234;
SInt32 idProduct = 0x1234;
// Create a matching dictionary for IOUSBDevice
matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);

// Add the USB Vendor ID to the matching dictionary
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor);

CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorID), numberRef);
CFRelease(numberRef);

// Add the USB Product ID to the matching dictionary
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct);

CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductID), numberRef);
CFRelease(numberRef);

io_iterator_t        iterator = 0;
io_service_t        usbDeviceRef;
kern_return_t        err;

// Find all kernel objects that match the dictionary
err = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &iterator);
if (err == 0)
{
    // Iterate over all matching kernel objects
    while ((usbDeviceRef = IOIteratorNext(iterator)) != 0)
    {
        // Create a driver for this device instance
        SInt32                        score;
        IOUSBDeviceInterface300**    usbDevice = NULL;
        io_iterator_t iterator;
        IOCFPlugInInterface** plugin;
        IOUSBConfigurationDescriptorPtr config;
        IOUSBFindInterfaceRequest interfaceRequest;
        IOUSBInterfaceInterface300** usbInterface;
        IOReturn ret;

        /*
        * All the different packs I tried sending
        */
        char out[] = { 0x02, 0x00, 0x02, 0x4a, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
        /*char out[] = { 0x02, 0x00, 0x02, 0x4a, 0x30, 0x78, 0x7a, 0xf5, 0x25, 0xa8, 0x1e, 0x7c, 0x46, 0x3c, 0x5d, 0x7f, 0xf8, 0xd4, 0x9b, 0x8a, 0xa2, 0xf1, 0xc8, 0xa8, 0x88, 0x4d, 0xba, 0x7b, 0xf4, 0x2f, 0x42, 0x28, 0xef, 0xa3, 0xee, 0x8e, 0x0f, 0x1a, 0x57, 0x1f, 0x7d, 0xed, 0x3b, 0x49, 0x8d, 0xed, 0x64, 0x93, 0x40, 0x75, 0x5a, 0x29, 0x98, 0x59, 0x6f, 0x7b, 0x39, 0xe8, 0xe8, 0x2e, 0xe9, 0x69, 0xe7, 0x7f }; // set data to send*/
        //char out[] = { 0x02, 0x00, 0x02, 0x4a, 0x30, 0x78, 0x00, 0x00};
        //char out[] = { 0x21, 0x09, 0x00, 0x03, 0x00, 0x00, 0x40, 0x00};

        IOCreatePlugInInterfaceForService(usbDeviceRef, kIOUSBDeviceUserClientTypeID,
                                          kIOCFPlugInInterfaceID, &plugin, &score);
        IOObjectRelease(usbDeviceRef);
        (*plugin)->QueryInterface(plugin,
                                  CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID300),
                                  (LPVOID)&usbDevice);
        (*plugin)->Release(plugin);
        if ((*usbDevice)->USBDeviceOpen(usbDevice) == kIOReturnSuccess) {
            ret = (*usbDevice)->GetConfigurationDescriptorPtr(usbDevice, 0, &config);
            if (ret == kIOReturnSuccess)
            {
                (*usbDevice)->SetConfiguration(usbDevice, config->bConfigurationValue);
                interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare;
                interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
                interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
                interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare;
                (*usbDevice)->CreateInterfaceIterator(usbDevice,
                                                      &interfaceRequest, &iterator);

                usbDeviceRef = IOIteratorNext(iterator);
                IOObjectRelease(iterator);
                IOCreatePlugInInterfaceForService(usbDeviceRef,
                                                  kIOUSBInterfaceUserClientTypeID,
                                                  kIOCFPlugInInterfaceID, &plugin, &score);
                IOObjectRelease(usbDeviceRef);
                (*plugin)->QueryInterface(plugin,
                                          CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID300),
                                          (LPVOID)&usbInterface);
                (*plugin)->Release(plugin);
                ret = (*usbInterface)->USBInterfaceOpen(usbInterface);
                if (ret == kIOReturnSuccess) {
                    UInt8 pipe_ref = 1;
                    ret = (*usbInterface)->GetPipeStatus(usbInterface, pipe_ref);
                    switch (ret) {
                        case kIOReturnNoDevice:
                            puts("No Device");
                            break;
                        case kIOReturnNotOpen:
                            puts("Not Open");
                            break;
                        case kIOReturnSuccess:
                            puts("Open");
                            break;
                        case kIOReturnBusy:
                            puts("Busy");
                            break;
                        default:
                            printf("%08x\n", ret);
                    }

                    ret = (*usbInterface)->WritePipe(usbInterface, pipe_ref, out, sizeof(out)); //<-- Here
                    if (ret != kIOReturnSuccess) {
                        puts("Could not write to pipe");
                    }

                    (*usbInterface)->USBInterfaceClose(usbInterface);
                    (*usbDevice)->USBDeviceClose(usbDevice);
                    return 0;
                } else {
                    puts("Could not open interface");
                }
            }

        } else {
            printf("Could not open device\n");
            return -1;
        }
        IOObjectRelease(usbDeviceRef);
    }

    IOObjectRelease(iterator);
}
return 0;

While it was hanging, I opened Activity Monitor and looked at the sample and it seems like it is hitting a mach_msg_trap (whether I run with sudo or not). The call graph:

Call graph:
    2713 Thread_48027   DispatchQueue_1: com.apple.main-thread  (serial)
    + 2713 start  (in libdyld.dylib) + 1  [0x7fffc7122235]
    +   2713 main  (in HID Driver) + 1322  [0x100000db6]  main.c:111
    +     2713 IOUSBInterfaceClass::WritePipe(unsigned char, void*, unsigned int, unsigned int, unsigned int)  (in     IOUSBLib) + 161  [0x1020aa217]
    +       2713 IOConnectCallMethod  (in IOKit) + 256  [0x7fffb38fa1a2]
    +         2713 io_connect_method  (in IOKit) + 375  [0x7fffb3974c91]
    +           2713 mach_msg  (in libsystem_kernel.dylib) + 55  [0x7fffc7248797]
    +             2713 mach_msg_trap  (in libsystem_kernel.dylib) + 10  [0x7fffc724934a]

Activity Monitor does confirm that it has attached to /dev/ttys005:

/private/var/db/dyld/dyld_shared_cache_x86_64h
0
/dev/ttys005
1
/dev/ttys005
2
/dev/ttys005

I have no idea why this is happening. Hopefully someone can point out something I missed here because I am all out of ideas.

标签: c macos usb hid iokit