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.