I've gotten as far as making the packet and stuff.. The problem is I can't figure out how to "transfer I/O control" still.. None of the code posted on this thread works to transfer it.. I've made the code cross-platform for iOS and MacOS.. So it can be ran on MacOS and you can see.. Once it runs on MacOS, it'll run on iOS no problem and we'd be done
The code I have now is (entirely C++ for a reason):
USBDevice.hxx
Code:// // USBDevice.h // iOUSB // // Created by Brandon on 2018-05-28. // Copyright © 2018 XIO. All rights reserved. // extern "C" { #import <IOKit/IOKitLib.h> #import <IOKit/usb/IOUSBLib.h> #import <IOKit/IOCFPlugIn.h> #import <IOKit/IOMessage.h> #import <IOKit/IOBSD.h> #import <CoreFoundation/CoreFoundation.h> } #include <iostream> #include <vector> /// Allow formatting of C++ strings template<typename... Args> std::string string_format(const std::string& format, Args... args) { size_t size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; std::unique_ptr<char[]> buf(new char[size]); std::snprintf(buf.get(), size, format.c_str(), args...); return std::string(buf.get(), buf.get() + size - 1); } /// Error Codes extern std::string human_error_string(IOReturn errorCode); /// Devices class USBDevice { private: IOUSBDeviceInterface300** deviceInterface; IOUSBInterfaceInterface300** interface; io_object_t device; std::uint8_t controlPipeRef; std::uint8_t readPipeRef; std::uint8_t writePipeRef; /// Returns an arbitrary interface from a plugin interface void** GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid); /// Get Device Property int GetProperty(std::string propertyName); /// Get String Descriptor std::string GetStringDescriptor(std::uint8_t index); /// Load Pipe Refs void GetPipeRefs(); public: USBDevice(io_object_t device); ~USBDevice(); /// Properties std::string GetName(); std::string GetClass(); std::string GetPath(); std::int32_t GetVendorID(); std::int32_t GetProductID(); std::string GetSerialNumber(); std::string GetManufacturer(); std::uint32_t GetLocationID(); /// Debug Print an interface std::string PrintInterface(); /// Get Max Packet Size for a pipe std::uint16_t GetMaxPipePacketSize(std::uint8_t pipeRef); /// Clear the pipe void ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout); /// Open the Device Interface bool Open(); /// Close the Device Interface void Close(); /// Get Pipe Status kern_return_t GetPipeStatus(std::uint8_t pipeRef); /// Get Driver Version NumVersion GetDriverVersion(); /// Get Control pipe reference std::uint8_t GetControlPipeRef(); /// Get Read pipe reference std::uint8_t GetReadPipeRef(); /// Get Write pipe reference std::uint8_t GetWritePipeRef(); /// Read from the device int Read(std::uint8_t* buffer, std::size_t bufferSize); /// Read from the device asynchronously int ReadAsync(std::uint8_t* buffer, std::size_t bufferSize); /// Write to the device int Write(std::uint8_t* buffer, std::size_t bufferSize); /// Write to the device asynchronously int WriteAsync(std::uint8_t* buffer, std::size_t bufferSize); /// Send Device Request int SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0); /// Send I/O Control Request int SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout = 0); /// Send Raw Request int SendRawRequest(IOUSBDevRequest *request); /// Send Raw I/O Control Request int SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef); }; /// Device Manager class USBDeviceManager { private: void* notificationRegistry; public: USBDeviceManager(); ~USBDeviceManager(); std::vector<std::unique_ptr<USBDevice>> GetDevicesMatching(std::string service, int vendorId, int productId); void RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device)); };
USBDevice.cxx:
Code:// // USBDevice.m // iOUSB // // Created by Brandon on 2018-05-28. // Copyright © 2018 XIO. All rights reserved. // #include "USBDevice.hxx" #include <memory> struct USBNotification { USBDeviceManager* _this; IONotificationPortRef notificationPort; io_iterator_t deviceAddedIterator; io_iterator_t deviceRemovedIterator; io_object_t notification; CFRunLoopRef runLoop; void(*onDeviceAdded)(USBDevice *device); void(*onDeviceDisconnected)(USBDevice *device); }; struct DeviceMessage { USBDevice* _this; USBNotification* notification; }; #pragma mark - Utilities #define CFSAFERELEASE(ref) if (ref) CFRelease(ref) void DeviceAdded(void *userInfo, io_iterator_t iterator); void DeviceDisconnected(void *userInfo, io_iterator_t iterator); void DeviceMessageReceived(void *userInfo, io_service_t service, natural_t messageType, void *messageArgument); /// Converts IOKit errors to human readable strings. std::string usb_human_error_string(IOReturn errorCode) { // #define err_get_system(err) (((err)>>26)&0x3f) // #define err_get_sub(err) (((err)>>14)&0xfff) // #define err_get_code(err) ((err)&0x3fff) switch (errorCode) { case kIOUSBUnknownPipeErr: return string_format("Pipe Ref Not Recognized (%08x)", errorCode); case kIOUSBTooManyPipesErr: return string_format("Too Many Pipes (%08x)", errorCode); case kIOUSBNoAsyncPortErr: return string_format("No Async Port (%08x)", errorCode); case kIOUSBNotEnoughPipesErr: return string_format("Not Enough Pipes in Interface (%08x)", errorCode); case kIOUSBNotEnoughPowerErr: return string_format("Not Enough Power for Selected Configuration (%08x)", errorCode); case kIOUSBEndpointNotFound: return string_format("Endpoint Not Found (%08x)", errorCode); case kIOUSBConfigNotFound: return string_format("Configuration Not Found (%08x)", errorCode); case kIOUSBTransactionTimeout: return string_format("Transaction Timed Out (%08x)", errorCode); case kIOUSBTransactionReturned: return string_format("Transaction has been returned to the caller (%08x)", errorCode); case kIOUSBPipeStalled: return string_format("Pipe has stalled, Error needs to be cleared (%08x)", errorCode); case kIOUSBInterfaceNotFound: return string_format("Interface Ref Not Recognized (%08x)", errorCode); case kIOUSBLowLatencyBufferNotPreviouslyAllocated: return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the data buffer) first (%08x)", errorCode); case kIOUSBLowLatencyFrameListNotPreviouslyAllocated: return string_format("Attempted to use user land low latency isoc calls w/out calling PrepareBuffer (on the frame list) first (%08x)", errorCode); case kIOUSBHighSpeedSplitError: return string_format("Error to hub on high speed bus trying to do split transaction (%08x)", errorCode); case kIOUSBSyncRequestOnWLThread: return string_format("A synchronous USB request was made on the workloop thread (from a callback?). Only async requests are permitted in that case (%08x)", errorCode); case kIOUSBDeviceTransferredToCompanion: return string_format("The device has been tranferred to another controller for enumeration (%08x)", errorCode); case kIOUSBClearPipeStallNotRecursive: return string_format("ClearPipeStall should not be called recursively (%08x)", errorCode); case kIOUSBDevicePortWasNotSuspended: return string_format("Port was not suspended (%08x)", errorCode); case kIOUSBEndpointCountExceeded: return string_format("The endpoint was not created because the controller cannot support more endpoints (%08x)", errorCode); case kIOUSBDeviceCountExceeded: return string_format("The device cannot be enumerated because the controller cannot support more devices (%08x)", errorCode); case kIOUSBStreamsNotSupported: return string_format("The request cannot be completed because the XHCI controller does not support streams (%08x)", errorCode); case kIOUSBInvalidSSEndpoint: return string_format("An endpoint found in a SuperSpeed device is invalid (usually because there is no Endpoint Companion Descriptor) (%08x)", errorCode); case kIOUSBTooManyTransactionsPending: return string_format("The transaction cannot be submitted because it would exceed the allowed number of pending transactions (%08x)", errorCode); default: return string_format("Error Code (%08x)\n-- System: (%02X), SubSystem: (%02X), Code: (%02X) ", errorCode, err_get_system(errorCode), err_get_sub(errorCode), err_get_code(errorCode)); } } std::string human_error_string(IOReturn errorCode) { switch (errorCode) { case kIOReturnSuccess: return string_format("Success (%08x)", errorCode); case kIOReturnError: return string_format("General Error (%08x)", errorCode); case kIOReturnNoMemory: return string_format("Cannot Allocate Memory (%08x)", errorCode); case kIOReturnNoResources: return string_format("Resource Shortage (%08x)", errorCode); case kIOReturnIPCError: return string_format("IPC Error (%08x)", errorCode); case kIOReturnNoDevice: return string_format("No Such Device (%08x)", errorCode); case kIOReturnNotPrivileged: return string_format("Insufficient Privileges (%08x)", errorCode); case kIOReturnBadArgument: return string_format("Invalid Argument (%08x)", errorCode); case kIOReturnLockedRead: return string_format("Device Read Locked (%08x)", errorCode); case kIOReturnLockedWrite: return string_format("Device Write Locked (%08x)", errorCode); case kIOReturnExclusiveAccess: return string_format("Exclusive Access and Device already opened (%08x)", errorCode); case kIOReturnBadMessageID: return string_format("Sent/Received Messages had different MSG_ID (%08x)", errorCode); case kIOReturnUnsupported: return string_format("Unsupported Function (%08x)", errorCode); case kIOReturnVMError: return string_format("Misc. VM Failure (%08x)", errorCode); case kIOReturnInternalError: return string_format("Internal Error (%08x)", errorCode); case kIOReturnIOError: return string_format("General I/O Error (%08x)", errorCode); case kIOReturnCannotLock: return string_format("Can't Acquire Lock (%08x)", errorCode); case kIOReturnNotOpen: return string_format("Device Not Open (%08x)", errorCode); case kIOReturnNotReadable: return string_format("Read Not Supported (%08x)", errorCode); case kIOReturnNotWritable: return string_format("Write Not Supported (%08x)", errorCode); case kIOReturnNotAligned: return string_format("Alignment Error (%08x)", errorCode); case kIOReturnBadMedia: return string_format("Media Error (%08x)", errorCode); case kIOReturnStillOpen: return string_format("Device(s) Still Open (%08x)", errorCode); case kIOReturnRLDError: return string_format("RLD Failure (%08x)", errorCode); case kIOReturnDMAError: return string_format("DMA Failure (%08x)", errorCode); case kIOReturnBusy: return string_format("Device Busy (%08x)", errorCode); case kIOReturnTimeout: return string_format("I/O Timeout (%08x)", errorCode); case kIOReturnOffline: return string_format("Device Offline (%08x)", errorCode); case kIOReturnNotReady: return string_format("Not Ready (%08x)", errorCode); case kIOReturnNotAttached: return string_format("Device Not Attached (%08x)", errorCode); case kIOReturnNoChannels: return string_format("No DMA Channels Left (%08x)", errorCode); case kIOReturnNoSpace: return string_format("No Space For Data (%08x)", errorCode); case kIOReturnPortExists: return string_format("Port Already Exists (%08x)", errorCode); case kIOReturnCannotWire: return string_format("Can't Write Down (%08x)", errorCode); case kIOReturnNoInterrupt: return string_format("No Interrupt Attached (%08x)", errorCode); case kIOReturnNoFrames: return string_format("No DMA Frames Enqueued (%08x)", errorCode); case kIOReturnMessageTooLarge: return string_format("Oversized MSG Received On Interrupt Port (%08x)", errorCode); case kIOReturnNotPermitted: return string_format("Not Permitted (%08x)", errorCode); case kIOReturnNoPower: return string_format("No Power To Device (%08x)", errorCode); case kIOReturnNoMedia: return string_format("Media Not Present (%08x)", errorCode); case kIOReturnUnformattedMedia: return string_format("Media Not Formatted (%08x)", errorCode); case kIOReturnUnsupportedMode: return string_format("No Such Mode (%08x)", errorCode); case kIOReturnUnderrun: return string_format("Buffer Underflow (%08x)", errorCode); case kIOReturnOverrun: return string_format("Buffer Overflow (%08x)", errorCode); case kIOReturnDeviceError: return string_format("The Device Is Not Working Properly! (%08x)", errorCode); case kIOReturnNoCompletion: return string_format("A Completion Routine Is Required (%08x)", errorCode); case kIOReturnAborted: return string_format("Operation Aborted (%08x)", errorCode); case kIOReturnNoBandwidth: return string_format("Bus Bandwidth Would Be Exceeded (%08x)", errorCode); case kIOReturnNotResponding: return string_format("Device Not Responding (%08x)", errorCode); case kIOReturnIsoTooOld: return string_format("ISOChronous I/O request for distance past! (%08x)", errorCode); case kIOReturnIsoTooNew: return string_format("ISOChronous I/O request for distant future! (%08x)", errorCode); case kIOReturnNotFound: return string_format("Data Not Found (%08x)", errorCode); case kIOReturnInvalid: return string_format("Should Never Be Seen(%08x)", errorCode); default: /// See here for more: https://developer.apple.com/library/content/qa/qa1075/_index.html return usb_human_error_string(errorCode); } } #pragma mark - Device Private USBDevice::USBDevice(io_object_t device) : deviceInterface(nullptr), interface(nullptr), device(device), controlPipeRef(0x00), readPipeRef(0x01), writePipeRef(0x02) { IOObjectRetain(device); } USBDevice::~USBDevice() { Close(); IOObjectRelease(device); } void** USBDevice::GetInterface(io_object_t device, CFUUIDRef type, CFUUIDRef uuid) { /// Create a plugin interface for the service. IOCFPlugInInterface **plugInInterface = nullptr; SInt32 score = 0; kern_return_t kr = IOCreatePlugInInterfaceForService(device, type, kIOCFPlugInInterfaceID, &plugInInterface, &score); if ((kIOReturnSuccess != kr) || !plugInInterface) { std::cerr<<human_error_string(kr)<<"\n"; return nullptr; } /// Get an interface from the plugin.. and release the plugin void **interface = nullptr; HRESULT res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(uuid), (LPVOID*) &interface); (*plugInInterface)->Release(plugInInterface); if (res || !interface) { return nullptr; } return interface; } int USBDevice::GetProperty(std::string propertyName) { CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, propertyName.c_str(), kCFStringEncodingUTF8); CFNumberRef number = (CFNumberRef)IORegistryEntryCreateCFProperty(device, name, kCFAllocatorDefault, 0); if (number) { int value = 0; CFNumberGetValue(number, kCFNumberSInt32Type, &value); CFSAFERELEASE(number); CFSAFERELEASE(name); return value; } CFSAFERELEASE(name); return -1; } std::string USBDevice::GetStringDescriptor(std::uint8_t index) { std::uint8_t requestBuffer[256]; IOUSBDevRequest request = { .bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice), .bRequest = kUSBRqGetDescriptor, .wValue = static_cast<std::uint16_t>((kUSBStringDesc << 8) | index), .wIndex = 0x409, //English .wLength = sizeof(requestBuffer), .pData = requestBuffer }; kern_return_t kr = (*deviceInterface)->DeviceRequest(deviceInterface, &request); if (kr != KERN_SUCCESS) { std::cerr<<human_error_string(kr)<<"\n"; return ""; } CFStringRef descriptor = CFStringCreateWithBytes(kCFAllocatorDefault, &requestBuffer[2], requestBuffer[0] - 2, kCFStringEncodingUTF16LE, false); CFIndex length = CFStringGetLength(descriptor); CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; std::string result(maxSize, '\0'); CFStringGetCString(descriptor, &result[0], maxSize, kCFStringEncodingUTF8); CFSAFERELEASE(descriptor); return result; } void USBDevice::GetPipeRefs() { controlPipeRef = 0x00; readPipeRef = 0x01; writePipeRef = 0x02; if (interface) { UInt8 interfaceNumEndpoints = 0; IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints); if (err) { std::cerr<<human_error_string(err)<<"\n"; return; } for (UInt8 ref = 1; ref <= interfaceNumEndpoints; ++ref) { UInt8 direction; UInt8 number; UInt8 transferType; UInt16 maxPacketSize; UInt8 interval; IOReturn kr = (*interface)->GetPipeProperties(interface, ref, &direction, &number, &transferType, &maxPacketSize, &interval); if (kr == kIOReturnSuccess) { if (transferType == kUSBControl) { controlPipeRef = 0; } else if (transferType == kUSBBulk) { if (direction == kUSBIn) { readPipeRef = ref; } else if (direction == kUSBOut) { writePipeRef = ref; } } } } } } std::string USBDevice::GetName() { io_string_t name = {0}; kern_return_t kr = IORegistryEntryGetName(device, name); if (kr != KERN_SUCCESS) { std::cerr<<human_error_string(kr)<<"\n"; } return std::string(name); } std::string USBDevice::GetClass() { io_string_t name = {0}; kern_return_t kr = IOObjectGetClass(device, name); if (kr != KERN_SUCCESS) { std::cerr<<human_error_string(kr)<<"\n"; } return std::string(name); } std::string USBDevice::GetPath() { io_string_t path = {0}; kern_return_t kr = IORegistryEntryGetPath(device, kIOServicePlane, path); if (kr != KERN_SUCCESS) { std::cerr<<human_error_string(kr)<<"\n"; } return std::string(path); } std::int32_t USBDevice::GetVendorID() { return GetProperty(kUSBVendorID); } std::int32_t USBDevice::GetProductID() { return GetProperty(kUSBProductID); } std::string USBDevice::GetSerialNumber() { if (deviceInterface) { std::uint8_t index = 0; IOReturn kr = (*deviceInterface)->USBGetSerialNumberStringIndex(deviceInterface, &index); if (kr != kIOReturnSuccess) { std::cerr<<human_error_string(kr)<<"\n"; return ""; } return GetStringDescriptor(index); } return ""; } std::string USBDevice::GetManufacturer() { if (deviceInterface) { std::uint8_t index = 0; IOReturn kr = (*deviceInterface)->USBGetManufacturerStringIndex(deviceInterface, &index); if (kr != kIOReturnSuccess) { std::cerr<<human_error_string(kr)<<"\n"; return ""; } return GetStringDescriptor(index); } return ""; } std::uint32_t USBDevice::GetLocationID() { UInt32 locationID = -1; IOUSBDeviceInterface **deviceInterface = reinterpret_cast<IOUSBDeviceInterface **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID)); if (deviceInterface) { // Get the location ID from the USB Interface (*deviceInterface)->GetLocationID(deviceInterface, &locationID); // Cleanup (*deviceInterface)->Release(deviceInterface); deviceInterface = nil; } return locationID; } std::string USBDevice::PrintInterface() { UInt8 interfaceNumEndpoints = 0; IOReturn err = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints); if (err) { std::cerr<<human_error_string(err)<<"\n"; return string_format("Unable to get number of endpoints (%08x)", err); } std::string output = string_format("Interface has %d endpoints\n", interfaceNumEndpoints); /// USUALLY.. /// Index 0 = DeviceControl/ControlRequest. /// Index 1 = DeviceRead /// Index 2 = DeviceWrite for (UInt8 pipeRef = 1; pipeRef <= interfaceNumEndpoints; ++pipeRef) { UInt8 direction; UInt8 number; UInt8 transferType; UInt16 maxPacketSize; UInt8 interval; IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval); if (kr != kIOReturnSuccess) { output += string_format("Unable to get properties of pipe %d (%08x)\n", pipeRef, kr); } else { std::string directions[] = {"Out", "In", "None", "Any"}; std::string transferTypes[] = {"Control", "ISOC", "Bulk", "Interrupt", "Any"}; output += string_format("Pipe: %d, Direction: %s, TransferType: %s, MaxPacketSize: %d\n", pipeRef, directions[direction].c_str(), transferTypes[transferType].c_str(), maxPacketSize); } } return output; } std::uint16_t USBDevice::GetMaxPipePacketSize(std::uint8_t pipeRef) { UInt8 direction; UInt8 number; UInt8 transferType; UInt16 maxPacketSize; UInt8 interval; IOReturn kr = (*interface)->GetPipeProperties(interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval); if (kr != kIOReturnSuccess) { std::cerr<<human_error_string(kr)<<"\n"; return 0; } return maxPacketSize; } void USBDevice::ClearPipe(std::uint8_t pipeRef, std::uint32_t timeout) { uint16_t maxSize = GetMaxPipePacketSize(pipeRef); std::unique_ptr<char[]> readBuffer(new char[maxSize]); while(true) { std::uint32_t readSize = maxSize; bzero(readBuffer.get(), maxSize * sizeof(char)); IOReturn kr = (*interface)->ReadPipeTO(interface, pipeRef, readBuffer.get(), (UInt32 *)&readSize, timeout, timeout); if (kr != kIOReturnSuccess) { std::cerr<<human_error_string(kr)<<"\n"; break; } if (readSize == 0) { break; } } IOReturn kr = (*interface)->ClearPipeStallBothEnds(interface, pipeRef); if (kr) { std::cerr<<human_error_string(kr)<<"\n"; return; } } bool USBDevice::Open() { // Open the USB device for communication. deviceInterface = reinterpret_cast<IOUSBDeviceInterface300 **>(GetInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300)); if (deviceInterface) { kern_return_t kr = (*deviceInterface)->USBDeviceOpen(deviceInterface); if (kr != kIOReturnSuccess) { (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; std::cerr<<human_error_string(kr)<<"\n"; return false; } //Get the configuration.. IOUSBConfigurationDescriptorPtr config; kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config); if (kr != kIOReturnSuccess) { (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; std::cerr<<human_error_string(kr)<<"\n"; return false; } //Set the configuration.. (*deviceInterface)->SetConfiguration(deviceInterface, config->bConfigurationValue); //Find the USB interface.. IOUSBFindInterfaceRequest interfaceRequest; interfaceRequest.bInterfaceClass = kIOUSBFindInterfaceDontCare; interfaceRequest.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; interfaceRequest.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; interfaceRequest.bAlternateSetting = kIOUSBFindInterfaceDontCare; //Get an interface iterator.. io_iterator_t iterator; kr = (*deviceInterface)->CreateInterfaceIterator(deviceInterface, &interfaceRequest, &iterator); if (kr != kIOReturnSuccess) { (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; std::cerr<<human_error_string(kr)<<"\n"; return false; } //Get the device object io_object_t usbDevice = IOIteratorNext(iterator); if (!usbDevice) { IOObjectRelease(iterator); (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; return false; } //Get a USB Interface interface = reinterpret_cast<IOUSBInterfaceInterface300 **>(GetInterface(usbDevice, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300)); if (!interface) { IOObjectRelease(usbDevice); IOObjectRelease(iterator); (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; return false; } kr = (*interface)->USBInterfaceOpen(interface); if (kr != kIOReturnSuccess) { (*interface)->Release(interface); interface = nullptr; IOObjectRelease(usbDevice); IOObjectRelease(iterator); (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; return false; } //Load the pipe references GetPipeRefs(); //Create Async Notifications //TODO: Release this event.. CFRunLoopSourceRef asyncEventSource; kr = (*interface)->CreateInterfaceAsyncEventSource(interface, &asyncEventSource); CFRunLoopAddSource(CFRunLoopGetCurrent(), asyncEventSource, kCFRunLoopDefaultMode); IOObjectRelease(usbDevice); IOObjectRelease(iterator); return true; } return false; } void USBDevice::Close() { if (interface) { (*interface)->USBInterfaceClose(interface); (*interface)->Release(interface); interface = nullptr; } if (deviceInterface) { (*deviceInterface)->USBDeviceClose(deviceInterface); (*deviceInterface)->Release(deviceInterface); deviceInterface = nullptr; } } kern_return_t USBDevice::GetPipeStatus(std::uint8_t pipeRef) { if (interface) { return (*interface)->GetPipeStatus(interface, pipeRef); } return kIOReturnNotOpen; } NumVersion USBDevice::GetDriverVersion() { if (interface) { NumVersion ioUSBLibVersion; NumVersion usbFamilyVersion; (*interface)->GetIOUSBLibVersion(interface, &ioUSBLibVersion, &usbFamilyVersion); return ioUSBLibVersion; } return NumVersion{0}; } std::uint8_t USBDevice::GetControlPipeRef() { return controlPipeRef; } std::uint8_t USBDevice::GetReadPipeRef() { return readPipeRef; } std::uint8_t USBDevice::GetWritePipeRef() { return writePipeRef; } void onReadAsync(void *refcon, IOReturn result, void *arg0) { } void onWriteAsync(void *refcon, IOReturn result, void *arg0) { } int USBDevice::Read(std::uint8_t* buffer, std::size_t bufferSize) { if (interface) { UInt8 pipeRef = readPipeRef; UInt32 size = static_cast<UInt32>(bufferSize); kern_return_t res = (*interface)->ReadPipe(interface, pipeRef, buffer, &size); return res != kIOReturnSuccess ? -1 : static_cast<int>(size); } return -1; } int USBDevice::ReadAsync(std::uint8_t* buffer, std::size_t bufferSize) { if (interface) { UInt8 pipeRef = readPipeRef; UInt32 size = static_cast<UInt32>(bufferSize); kern_return_t res = (*interface)->ReadPipeAsync(interface, pipeRef, buffer, size, onReadAsync, this); return res != kIOReturnSuccess ? -1 : static_cast<int>(size); } return -1; } int USBDevice::Write(uint8_t* buffer, size_t bufferSize) { UInt8 pipeRef = writePipeRef; int packetSize = GetMaxPipePacketSize(pipeRef); size_t bytesWritten = 0; int bytesRemaining = static_cast<int>(bufferSize); auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) { kern_return_t res = (*interface)->WritePipe(interface, pipeRef, buffer, static_cast<int>(bufferSize)); return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize); }; while (bytesRemaining > 0) { size_t bytesToWrite = std::min(bytesRemaining, packetSize); auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite); if (retVal < 0) { return retVal; } else if (retVal < static_cast<int>(bytesToWrite)) { return static_cast<int>(bytesWritten) + retVal; } bytesWritten += retVal; bytesRemaining -= retVal; } return static_cast<int>(bytesWritten); } int USBDevice::WriteAsync(std::uint8_t* buffer, std::size_t bufferSize) { UInt8 pipeRef = writePipeRef; int packetSize = GetMaxPipePacketSize(pipeRef); size_t bytesWritten = 0; int bytesRemaining = static_cast<int>(bufferSize); auto internal_write = [&](std::uint8_t pipeRef, std::uint8_t* buffer, std::size_t bufferSize) { kern_return_t res = (*interface)->WritePipeAsync(interface, pipeRef, buffer, static_cast<int>(bufferSize), onWriteAsync, this); return res != kIOReturnSuccess ? -1 : static_cast<int>(bufferSize); }; while (bytesRemaining > 0) { size_t bytesToWrite = std::min(bytesRemaining, packetSize); auto retVal = internal_write(pipeRef, &buffer[bytesWritten], bytesToWrite); if (retVal < 0) { return retVal; } else if (retVal < static_cast<int>(bytesToWrite)) { return static_cast<int>(bytesWritten) + retVal; } bytesWritten += retVal; bytesRemaining -= retVal; } return static_cast<int>(bytesWritten); } int USBDevice::SendDeviceRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout) { if (!deviceInterface) { return -1; } //If there's no timeout.. just send a regular request.. if (timeout <= 0) { IOUSBDevRequest req; req.bmRequestType = requesttype; req.bRequest = request; req.wValue = value; req.wIndex = index; req.wLength = size; req.pData = bytes; kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, &req); return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred.. } //Send Timeout request.. IOUSBDevRequestTO req; req.bmRequestType = requesttype; req.bRequest = request; req.wValue = value; req.wIndex = index; req.wLength = size; req.pData = bytes; req.completionTimeout = timeout; kern_return_t result = (*deviceInterface)->DeviceRequestTO(deviceInterface, &req); return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred.. } int USBDevice::SendControlRequest(int requesttype, int request, int value, int index, char *bytes, int size, int timeout) { if (!interface) { return -1; } //If there's no timeout.. just send a regular request.. if (timeout <= 0) { IOUSBDevRequest req; req.bmRequestType = requesttype; req.bRequest = request; req.wValue = value; req.wIndex = index; req.wLength = size; req.pData = bytes; kern_return_t result = (*interface)->ControlRequest(interface, controlPipeRef, &req); return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred.. } //Send Timeout request.. IOUSBDevRequestTO req; req.bmRequestType = requesttype; req.bRequest = request; req.wValue = value; req.wIndex = index; req.wLength = size; req.pData = bytes; req.completionTimeout = timeout; kern_return_t result = (*interface)->ControlRequestTO(interface, controlPipeRef, &req); return result != kIOReturnSuccess ? -1 : req.wLenDone; //Bytes Transferred.. } int USBDevice::SendRawRequest(IOUSBDevRequest *request) { if (!deviceInterface) { return -1; } kern_return_t result = (*deviceInterface)->DeviceRequest(deviceInterface, request); return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred.. } int USBDevice::SendRawControlRequest(IOUSBDevRequest *request, std::uint8_t pipeRef) { if (!interface) { return -1; } kern_return_t result = (*interface)->ControlRequest(interface, pipeRef, request); return result != kIOReturnSuccess ? -1 : request->wLenDone; //Bytes Transferred.. } #pragma mark - USB Public USBDeviceManager::USBDeviceManager() : notificationRegistry(nullptr) { } USBDeviceManager::~USBDeviceManager() { if (this->notificationRegistry) { USBNotification *notification = static_cast<USBNotification *>(this->notificationRegistry); delete notification; } } std::vector<std::unique_ptr<USBDevice>> USBDeviceManager::GetDevicesMatching(std::string service, int vendorId, int productId) { io_iterator_t iter = 0; /// Find Matching Services CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str()); if (matchingDict == nil) { return std::vector<std::unique_ptr<USBDevice>> {}; } /// Added VendorID and ProductID to query CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId); CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef); CFRelease(numberRef); numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId); CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef); CFRelease(numberRef); numberRef = nullptr; /// Find devices matching the above criteria kern_return_t kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter); if (kr != KERN_SUCCESS) { std::cerr<<human_error_string(kr)<<"\n"; return std::vector<std::unique_ptr<USBDevice>> {}; } /// Iterate over all devices io_service_t device = 0; std::vector<std::unique_ptr<USBDevice>> devices; while ((device = IOIteratorNext(iter))) { devices.push_back(std::unique_ptr<USBDevice>(new USBDevice(device))); } IOObjectRelease(iter); return devices; } void USBDeviceManager::RegisterForDeviceNotifications(std::string service, int vendorId, int productId, void(*onDeviceAdded)(USBDevice *device), void(*onDeviceDisconnected)(USBDevice *device)) { /// Find Matching Services CFMutableDictionaryRef matchingDict = IOServiceMatching(service.c_str()); if (matchingDict == nil) { return; } /// Added VendorID and ProductID to query CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &vendorId); CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID), numberRef); CFRelease(numberRef); numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &productId); CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID), numberRef); CFRelease(numberRef); numberRef = nullptr; // Setup notifications for when the device is available USBNotification *notificationInfo = new USBNotification(); notificationInfo->_this = this; notificationInfo->onDeviceAdded = onDeviceAdded; notificationInfo->onDeviceDisconnected = onDeviceDisconnected; notificationInfo->notificationPort = IONotificationPortCreate(kIOMasterPortDefault); notificationInfo->runLoop = CFRunLoopGetCurrent(); notificationInfo->deviceAddedIterator = 0; notificationInfo->deviceRemovedIterator = 0; this->notificationRegistry = notificationInfo; CFRunLoopSourceRef runLoopSource = IONotificationPortGetRunLoopSource(notificationInfo->notificationPort); CFRunLoopAddSource(notificationInfo->runLoop, runLoopSource, kCFRunLoopDefaultMode); // Now set up a notification to be called when a device is matched and detached by I/O Kit. kern_return_t rc; // rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort, // kIOTerminatedNotification, // matchingDict, // DeviceDisconnected, // notificationInfo, //userInfo // ¬ificationInfo->deviceRemovedIterator); // // if (rc != kIOReturnSuccess) // { // IONotificationPortDestroy(notificationInfo->notificationPort); // delete notificationInfo; // std::cerr<<human_error_string(rc)<<"\n"; // return; // } // Now set up a notification to be called when a device is matched and attached by I/O Kit. rc = IOServiceAddMatchingNotification(notificationInfo->notificationPort, kIOMatchedNotification, matchingDict, DeviceAdded, notificationInfo, //userInfo ¬ificationInfo->deviceAddedIterator); if (rc != kIOReturnSuccess) { IONotificationPortDestroy(notificationInfo->notificationPort); delete notificationInfo; std::cerr<<human_error_string(rc)<<"\n"; return; } // DeviceDisconnected(notificationInfo, notificationInfo->deviceRemovedIterator); DeviceAdded(notificationInfo, notificationInfo->deviceAddedIterator); } // When a device has been plugged into the phone's serial port, this function will get called void DeviceAdded(void *userInfo, io_iterator_t iterator) { kern_return_t kr; io_service_t usbDevice; USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo); // Iterate over all devices.. while ((usbDevice = IOIteratorNext(iterator))) { DeviceMessage* notificationMessage = new DeviceMessage(); notificationMessage->_this = new USBDevice(usbDevice); notificationMessage->notification = notificationInfo; if (notificationInfo->onDeviceAdded) { notificationInfo->onDeviceAdded(notificationMessage->_this); } // Register for Device Notifications.. IE: Disconnected Notification.. kr = IOServiceAddInterestNotification(notificationInfo->notificationPort, usbDevice, kIOGeneralInterest, DeviceMessageReceived, notificationMessage, //userInfo ¬ificationInfo->notification); // Cleanup kr = IOObjectRelease(usbDevice); } } // When a device has been unplugged from the phone's serial port, this function will get called. void DeviceDisconnected(void *userInfo, io_iterator_t iterator) { USBNotification *notificationInfo = static_cast<USBNotification *>(userInfo); if (notificationInfo->onDeviceDisconnected) { notificationInfo->onDeviceDisconnected(nullptr); } // Cleanup IOObjectRelease(notificationInfo->notification); delete notificationInfo; } // When a device message has been received.. void DeviceMessageReceived(void* userInfo, io_service_t service, uint32_t messageType, void* messageArgument) { DeviceMessage *message = static_cast<DeviceMessage *>(userInfo); // If it's the disconnect message, we need to cleanup.. if (messageType == kIOMessageServiceIsTerminated) { if (message->notification && message->notification->onDeviceDisconnected) { message->notification->onDeviceDisconnected(message->_this); } // Cleanup // IOObjectRelease(message->notification->notificationPort); delete message; } }
Then you use it for Nintendo Switch like:
main.mm:
Code:// // main.m // USBLoader // // Created by Brandon on 2018-05-30. // Copyright © 2018 XIO. All rights reserved. // #import <Foundation/Foundation.h> #import <IOKit/IOKitLib.h> #import <IOKit/usb/IOUSBLib.h> #import <IOKit/IOCFPlugIn.h> #import <IOKit/IOMessage.h> #import <IOKit/IOBSD.h> #include <vector> #include <iostream> #include "USBDevice.hxx" //Include some headers that read the fusee.bin and intermezzo and create the Fusee Gelee Payload.. #include "Fusee.h" // Helper file to try to smash the stack.. #include "Smash.hpp" //Nintendo Constants #define kNintendoSwitchVendorID 0x0955 #define kNintendoSwitchProductID 0x7321 #pragma mark - Utilities void displayStrings(std::vector<std::string> strings) { for (std::string& str : strings) { std::cout<<str<<"\n"; } } void displayStrings(NSArray<NSString *> *strings) { for (NSString *str in strings) { NSLog(@"%@", str); } } void onDeviceAdded(USBDevice *device) { if (!device->Open()) { std::cerr<<"Cannot Open Device\n"; return; } std::string manufacturer = device->GetManufacturer(); std::string name = device->GetName(); std::string clazz = device->GetClass(); std::string path = device->GetPath(); std::int32_t vendorId = device->GetVendorID(); std::int32_t productId = device->GetProductID(); std::uint32_t locationId = device->GetLocationID(); displayStrings({ " Device Information", "-----------------------------------", string_format("Manufacturer: %s", manufacturer.c_str()), string_format("Name: %s", name.c_str()), string_format("Class: %s", clazz.c_str()), string_format("Path: %s", path.c_str()), string_format("VendorId: 0x%04X", vendorId), string_format("ProductId: 0x%04X", productId), string_format("LocationId: 0x%04X", locationId) }); //device->ClearPipe(device->GetReadPipeRef(), 10); //device->ClearPipe(device->GetWritePipeRef(), 10); //I don't have async event in the above class yet (it'll take a few lines of code to fix.. this function uses async read.. if changed to just read, it'll work).. in fact I think I can remove the read all together and just call clear stall.. - TODO: Brandon T investigate. uint8_t deviceId[0x10] = {0}; if (device->Read(deviceId, sizeof(deviceId)) != sizeof(deviceId)) { displayStrings({"Cannot Read Device-ID"}); return; } std::cout<<"\n\n"; std::string deviceSerialNumber = string_format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", (uint32_t)deviceId[0], (uint32_t)deviceId[1], (uint32_t)deviceId[2], (uint32_t)deviceId[3], (uint32_t)deviceId[4], (uint32_t)deviceId[5], (uint32_t)deviceId[6], (uint32_t)deviceId[7], (uint32_t)deviceId[8], (uint32_t)deviceId[9], (uint32_t)deviceId[10], (uint32_t)deviceId[11], (uint32_t)deviceId[12], (uint32_t)deviceId[13], (uint32_t)deviceId[14], (uint32_t)deviceId[15]); displayStrings({ string_format("Device Id: %s\n\n", deviceSerialNumber.c_str()), "Loading Payload" }); auto payload = createRCMPayload(intermezzo, fusee); displayStrings({"Uploading Payload"}); smash(device, payload); device->Close(); return; } void onDeviceDisconnected(USBDevice *device) { std::cout<<"Device Disconnected\n"; } int main(int argc, const char * argv[]) { @autoreleasepool { USBDeviceManager device; device.RegisterForDeviceNotifications("IOUSBHostDevice", kNintendoSwitchVendorID, kNintendoSwitchProductID, onDeviceAdded, onDeviceDisconnected); CFRunLoopRun(); } return 0; }
It uploads the payload successfully.. I just can't get it to freaking smash the damn stack.. grr.. Also, once you "read" the deviceId, the control of the device is switched so you CANNOT run the code again without restarting the device.. I tried libusb and it behaves the same way so it means the code above is working as it should.. I just have to figure out how to smash the stack and that's it.
Functions I added: "human_error_string" will print out any errors from IOKit as human readable strings =]
The USBDeviceManager will load all the devices that match the vendor and product. You will see when you plug in the switch, it will display:
Once I figure out how to "Transfer Control" after uploading the payload, I will post here or upload the code or something to the github.
I think we got fairly far.. just need the last step.. Btw, I tested the above on my iPhone 6S.. Same output. Still no control transfer..
Once you send the payload, you ask the Switch for the status with a control request with USB_REQ_GET_STATUS, with a ridiculously large length (0x7000). That is what smashes the stack. Check the last 5 lines of code of
https://github.com/DavidBuchanan314/fusee-nano/blob/master/src/exploit.c
and last 30 or so lines of
https://github.com/DavidBuchanan314/fusee-nano/blob/master/src/usb.c
Last edited by Dread_Pirate_PJ,