Add Windows support with libusbK

This commit is contained in:
Mikaela Szekely 2018-05-17 01:04:42 -04:00
parent 8eaa3d4960
commit 62ff4dba45
No known key found for this signature in database
GPG Key ID: AA45D59374537FB5
2 changed files with 367 additions and 32 deletions

View File

@ -12,7 +12,7 @@
# don't like it? suck it up, or find your own damned exploit ^-^
#
# special thanks to:
# SciresM, motezazer -- guidance and support
# ScirèsM, motezazer -- guidance and support
# hedgeberg, andeor -- dumping the Jetson bootROM
# TuxSH -- for IDB notes that were nice to peek at
#
@ -60,6 +60,9 @@ class HaxBackend:
# USB constants used
STANDARD_REQUEST_DEVICE_TO_HOST_TO_ENDPOINT = 0x82
STANDARD_REQUEST_DEVICE_TO_HOST = 0x80
GET_DESCRIPTOR = 0x6
GET_CONFIGURATION = 0x8
# Interface requests
GET_STATUS = 0x0
@ -67,9 +70,8 @@ class HaxBackend:
# List of OSs this class supports.
SUPPORTED_SYSTEMS = []
def __init__(self, usb_device, skip_checks=False):
def __init__(self, skip_checks=False):
""" Sets up the backend for the given device. """
self.dev = usb_device
self.skip_checks = skip_checks
@ -101,18 +103,37 @@ class HaxBackend:
@classmethod
def create_appropriate_backend(cls, usb_device, system_override=None, skip_checks=False):
def create_appropriate_backend(cls, system_override=None, skip_checks=False):
""" Creates a backend object appropriate for the current OS. """
# Search for a supportive backend, and try to create one.
for subclass in cls.__subclasses__():
if subclass.supported(system_override):
return subclass(usb_device, skip_checks=skip_checks)
return subclass(skip_checks=skip_checks)
# ... if we couldn't, bail out.
raise IOError("No backend to trigger the vulnerability-- it's likely we don't support your OS!")
def read(self, length):
""" Reads data from the RCM protocol endpoint. """
return bytes(self.dev.read(0x81, length, 1000))
def write_single_buffer(self, data):
"""
Writes a single RCM buffer, which should be 0x1000 long.
The last packet may be shorter, and should trigger a ZLP (e.g. not divisible by 512).
If it's not, send a ZLP.
"""
return self.dev.write(0x01, data, 1000)
def find_device(self, vid=None, pid=None):
""" Set and return the device to be used """
self.dev = usb.core.find(idVendor=vid, idProduct=pid)
return self.dev
class MacOSBackend(HaxBackend):
"""
@ -274,11 +295,144 @@ class LinuxBackend(HaxBackend):
raw = f.read()
return int(raw)
class WindowsBackend(HaxBackend):
"""
Use libusbK for most of it, and use the handle libusbK gets for us to call kernel32's DeviceIoControl
"""
BACKEND_NAME = "Windows"
SUPPORTED_SYSTEMS = ["Windows"]
# Windows and libusbK specific constants
WINDOWS_FILE_DEVICE_UNKNOWN = 0x00000022
LIBUSBK_FUNCTION_CODE_GET_STATUS = 0x807
WINDOWS_METHOD_BUFFERED = 0
WINDOWS_FILE_ANY_ACCESS = 0
RAW_REQUEST_STRUCT_SIZE = 24 # 24 is how big the struct is, just trust me
TO_ENDPOINT = 2
# Yoinked (with love) from Windows' CTL_CODE macro
def win_ctrl_code(self, DeviceType, Function, Method, Access):
""" Return a control code for use with DeviceIoControl() """
return ((DeviceType) << 16 | ((Access) << 14) | ((Function)) << 2 | (Method))
def __init__(self, skip_checks):
import libusbK
self.libk = libusbK
# Grab libusbK
self.lib = ctypes.cdll.libusbK
# FIXME: Implement a Windows backend that talks to a patched version of libusbK
# so we can inject WdfUsbTargetDeviceSendControlTransferSynchronously to
# trigger the exploit.
def find_device(self, Vid, Pid):
"""
Windows version of this function
Its return isn't actually significant, but it needs to be not None
"""
# Get a list of devices to use later
device_list = self.libk.KLST_HANDLE()
device_info = ctypes.pointer(self.libk.KLST_DEV_INFO())
ret = self.lib.LstK_Init(ctypes.byref(device_list), 0)
if ret == 0:
raise ctypes.WinError()
# Get info for a device with that vendor ID and product ID
device_info = ctypes.pointer(self.libk.KLST_DEV_INFO())
ret = self.lib.LstK_FindByVidPid(device_list, Vid, Pid, ctypes.byref(device_info))
self.lib.LstK_Free(ctypes.byref(device_list))
if device_info is None or ret == 0:
return None
# Populate function pointers for use with the driver our device uses (which should be libusbK)
self.dev = self.libk.KUSB_DRIVER_API()
ret = self.lib.LibK_LoadDriverAPI(ctypes.byref(self.dev), device_info.contents.DriverID)
if ret == 0:
raise ctypes.WinError()
# Initialize the driver for use with our device
self.handle = self.libk.KUSB_HANDLE(None)
ret = self.dev.Init(ctypes.byref(self.handle), device_info)
if ret == 0:
raise self.libk.WinError()
return self.dev
def read(self, length):
""" Read using libusbK """
# Create the buffer to store what we read
buffer = ctypes.create_string_buffer(length)
len_transferred = ctypes.c_uint(0)
# Call libusbK's ReadPipe using our specially-crafted function pointer and the opaque device handle
ret = self.dev.ReadPipe(self.handle, ctypes.c_ubyte(0x81), ctypes.addressof(buffer), ctypes.c_uint(length), ctypes.byref(len_transferred), None)
if ret == 0:
raise ctypes.WinError()
return buffer.raw
def write_single_buffer(self, data):
""" Write using libusbK """
# Copy construct to a bytearray so we Know™ what type it is
buffer = bytearray(data)
# Convert wrap the data for use with ctypes
cbuffer = (ctypes.c_ubyte * len(buffer))(*buffer)
len_transferred = ctypes.c_uint(0)
# Call libusbK's WritePipe using our specially-crafted function pointer and the opaque device handle
ret = self.dev.WritePipe(self.handle, ctypes.c_ubyte(0x01), cbuffer, len(data), ctypes.byref(len_transferred), None)
if ret == 0:
raise ctypes.WinError()
def ioctl(self, driver_handle: ctypes.c_void_p, ioctl_code: ctypes.c_ulong, input_bytes: ctypes.c_void_p, input_bytes_count: ctypes.c_size_t, output_bytes: ctypes.c_void_p, output_bytes_count: ctypes.c_size_t):
""" Wrapper for DeviceIoControl """
overlapped = self.libk.OVERLAPPED()
ctypes.memset(ctypes.addressof(overlapped), 0, ctypes.sizeof(overlapped))
ret = ctypes.windll.kernel32.DeviceIoControl(driver_handle, ioctl_code, input_bytes, input_bytes_count, output_bytes, output_bytes_count, None, ctypes.byref(overlapped))
# We expect this to error, which matches the others ^_^
if ret == False:
raise ctypes.WinError()
def trigger_vulnerability(self, length):
"""
Go over libusbK's head and get the master handle it's been using internally
and perform a direct DeviceIoControl call to the kernel to skip the length check
"""
# self.handle is KUSB_HANDLE, cast to KUSB_HANDLE_INTERNAL to transparent-ize it
internal = ctypes.cast(self.handle, ctypes.POINTER(self.libk.KUSB_HANDLE_INTERNAL))
# Get the handle libusbK has been secretly using in its ioctl calls this whole time
master_handle = internal.contents.Device.contents.MasterDeviceHandle
if master_handle is None or master_handle == self.libk.INVALID_HANDLE_VALUE:
raise ValueError("Failed to initialize master handle")
# the raw request struct is pretty annoying, so I'm just going to allocate enough memory and set the few fields I need
raw_request = ctypes.create_string_buffer(self.RAW_REQUEST_STRUCT_SIZE)
# set timeout to 1000 ms, timeout offset is 0 (since it's the first member), and it's an unsigned int
timeout_p = ctypes.cast(raw_request, ctypes.POINTER(ctypes.c_uint))
timeout_p.contents = ctypes.c_ulong(1000) # milliseconds
status_p = ctypes.cast(ctypes.byref(raw_request, 4), ctypes.POINTER(self.libk.status_t))
status_p.contents.index = self.GET_STATUS
status_p.contents.recipient = self.TO_ENDPOINT
buffer = ctypes.create_string_buffer(length)
code = self.win_ctrl_code(self.WINDOWS_FILE_DEVICE_UNKNOWN, self.LIBUSBK_FUNCTION_CODE_GET_STATUS, self.WINDOWS_METHOD_BUFFERED, self.WINDOWS_FILE_ANY_ACCESS)
ret = self.ioctl(master_handle, ctypes.c_ulong(code), raw_request, ctypes.c_size_t(24), buffer, ctypes.c_size_t(length))
if ret == False:
raise ctypes.WinError()
class RCMHax:
@ -287,11 +441,6 @@ class RCMHax:
DEFAULT_VID = 0x0955
DEFAULT_PID = 0x7321
# USB constants used
STANDARD_REQUEST_DEVICE_TO_HOST_TO_DEVICE = 0x80
GET_DESCRIPTOR = 0x6
GET_CONFIGURATION = 0x8
# Exploit specifics
COPY_BUFFER_ADDRESSES = [0x40005000, 0x40009000] # The addresses of the DMA buffers we can trigger a copy _from_.
STACK_END = 0x40010000 # The address just after the end of the device's stack.
@ -302,12 +451,19 @@ class RCMHax:
# The first write into the bootROM touches the lowbuffer.
self.current_buffer = 0
# Grab a connection to the USB device itself.
self.dev = self._find_device(vid, pid)
# Keep track of the total amount written.
self.total_written = 0
# Create a vulnerability backend for the given device.
try:
self.backend = HaxBackend.create_appropriate_backend(system_override=os_override, skip_checks=override_checks)
except IOError:
print("It doesn't look like we support your OS, currently. Sorry about that!\n")
sys.exit(-1)
# Grab a connection to the USB device itself.
self.dev = self._find_device(vid, pid)
# If we don't have a device...
if self.dev is None:
@ -315,19 +471,12 @@ class RCMHax:
if wait_for_device:
print("Waiting for a TegraRCM device to come online...")
while self.dev is None:
self.dev = self._find_device()
self.dev = self._find_device(vid, pid)
# ... or bail out.
else:
raise IOError("No TegraRCM device found?")
# Create a vulnerability backend for the given device.
try:
self.backend = HaxBackend.create_appropriate_backend(self.dev, system_override=os_override, skip_checks=override_checks)
except IOError:
print("It doesn't look like we support your OS, currently. Sorry about that!\n")
sys.exit(-1)
# Print any use-related warnings.
self.backend.print_warnings()
@ -343,15 +492,11 @@ class RCMHax:
pid = pid if pid else self.DEFAULT_PID
# ... and use them to find a USB device.
return usb.core.find(idVendor=vid, idProduct=pid)
def get_device_descriptor(self):
return self.dev.ctrl_transfer(self.STANDARD_REQUEST_DEVICE_TO_HOST, self.GET_DESCRIPTOR, 1 << 8, 0, 18)
return self.backend.find_device(vid, pid)
def read(self, length):
""" Reads data from the RCM protocol endpoint. """
return self.dev.read(0x81, length, 1000)
return self.backend.read(length)
def write(self, data):
@ -376,7 +521,7 @@ class RCMHax:
If it's not, send a ZLP.
"""
self._toggle_buffer()
return self.dev.write(0x01, data, 1000)
return self.backend.write_single_buffer(data)
def _toggle_buffer(self):
@ -452,7 +597,7 @@ except IOError as e:
# Print the device's ID. Note that reading the device's ID is necessary to get it into
try:
device_id = switch.read_device_id().tostring()
device_id = switch.read_device_id()
print("Found a Tegra with Device ID: {}".format(device_id))
except usb.core.USBError as e:
# Raise the exception only if we're not being permissive about ID reads.

190
libusbK.py Normal file
View File

@ -0,0 +1,190 @@
# This file is part of Fusée Launcher
# Copyright (C) 2018 Mikaela Szekely <qyriad@gmail.com>
# Copyright (C) 2018 Kate Temkin <k@ktemkin.com>
# Fusée Launcher is licensed under the terms of the GNU GPLv2
from ctypes import *
KLST_HANDLE = c_void_p
KUSB_HANDLE = c_void_p
INVALID_HANDLE_VALUE = -1
class DUMMYSTRUCTNAME(Structure):
_fields_ = \
[
("Offset", c_ulong),
("OfsettHigh", c_ulong)
]
class DUMMYUNIONNAME(Union):
_fields_ = \
[
("DUMMYSTRUCTNAME", DUMMYSTRUCTNAME),
("Pointer", c_void_p)
]
class OVERLAPPED(Structure):
_fields_ = \
[
("Internal", POINTER(c_ulong)),
("InternalHigh", POINTER(c_ulong)),
("DUMMYUNIONNAME", DUMMYUNIONNAME),
("hEvent", c_void_p)
]
class KLST_DEV_COMMON_INFO(Structure):
_fields_ = \
[
("Vid", c_int),
("Pid", c_int),
("MI", c_int),
("InstanceID", c_char * 256)
]
class KLST_DEV_INFO(Structure):
_fields_ = \
[
("Common", KLST_DEV_COMMON_INFO),
("DriverID", c_int),
("DeviceInterfaceGUID", c_char * 256),
("DeviceID", c_char * 256),
("ClassGUID", c_char * 256),
("Mfg", c_char * 256),
("DeviceDesc", c_char * 256),
("Service", c_char * 256),
("SymbolicLink", c_char * 256),
("DevicePath", c_char * 256),
("LUsb0FilterIndex", c_int),
("Connected", c_bool),
("KLST_SYNC_FLAG", c_int),
("BusNumber", c_int),
("DeviceAddress", c_int),
("SerialNumber", c_char * 256)
]
class KUSB_DRIVER_API_INFO(Structure):
_fields_ = \
[
("DriverID", c_int),
("FunctionCount", c_int)
]
class KUSB_DRIVER_API(Structure):
_fields_ = \
[
("Info", KUSB_DRIVER_API_INFO),
("Init", WINFUNCTYPE(c_bool, KUSB_HANDLE, POINTER(KLST_DEV_INFO))),
("Free", c_void_p), # Unused, but pointer sized
("ClaimInterface", c_void_p), # Unused, but pointer sized
("ReleaseInterface", c_void_p), # Unused, but pointer sized
("SetAltInterface", c_void_p), # Unused, but pointer sized
("GetAltInterface", c_void_p), # Unused, but pointer sized
("GetDescriptor", c_void_p), # Unused, but pointer sized
("ControlTransfer", c_void_p), # Unused, but pointer sized
("SetPowerPolicy", c_void_p), # Unused, but pointer sized
("GetPowerPolicy", c_void_p), # Unused, but pointer sized
("SetConfiguration", c_void_p), # Unused, but pointer sized
("GetConfiguration", c_void_p), # Unused, but pointer sized
("ResetDevice", c_void_p), # Unused, but pointer sized
("Initialize", c_void_p), # Unused, but pointer sized
("SelectInterface", c_void_p), # Unused, but pointer sized
("GetAssociatedInterface", c_void_p), # Unused, but pointer sized
("Clone", c_void_p), # Unused, but pointer sized
("QueryInterfaceSettings", c_void_p), # Unused, but pointer sized
("QueryDeviceInformation", c_void_p), # Unused, but pointer sized
("SetCurrentAlternateSetting", c_void_p), # Unused, but pointer sized
("GetCurrentAlternateSetting", c_void_p), # Unused, but pointer sized
("QueryPipe", c_void_p), # Unused, but pointer sized
("SetPipePolicy", c_void_p), # Unused, but pointer sized
("GetPipePolicy", c_void_p), # Unused, but pointer sized
# BOOL KUSB_API ReadPipe(_in_ KUSB_HANDLE InterfaceHandle, _in_ UCHAR PipeID, _out_ PUCHAR Buffer, _in_ UINT BufferLength, _outopt_ PUINT LengthTransferred, _intopt_ LPOVERLAPPED Overlapped)
("ReadPipe", WINFUNCTYPE(c_bool, KUSB_HANDLE, c_ubyte, c_void_p, c_uint, POINTER(c_uint), POINTER(OVERLAPPED))),
# BOOL KUSB_API WritePipe(_in_ KUSB_HANDLE InterfaceHandle, _in_ UCHAR PipeID, _in_ PUCHAR Buffer, _in_ UINT BufferLength, _outopt_ PUINT LengthTransferred, _inopt_ LPOVERLAPPED Overlapped)
("WritePipe", WINFUNCTYPE(c_bool, KUSB_HANDLE, c_ubyte, POINTER(c_ubyte), c_uint, POINTER(c_uint), POINTER(OVERLAPPED))),
("ResetPipe", c_void_p), # Unused, but pointer sized
("AbortPipe", c_void_p), # Unused, but pointer sized
("FlushPipe", c_void_p), # Unused, but pointer sized
("IsoReadPipe", c_void_p), # Unused, but pointer sized
("IsoWritePipe", c_void_p), # Unused, but pointer sized
("GetCurrentFrameNumber", c_void_p), # Unused, but pointer sized
("GetOverlappedResult", c_void_p), # Unused, but pointer sized
("GetProperty", c_void_p), # Unused, but pointer sized
# 34: Amount of functions in this struct
("z_F_i_x_e_d", c_ubyte * (512 - (sizeof(KUSB_DRIVER_API_INFO) - sizeof(POINTER(c_uint)) * 34)))
]
class Evt_t(Structure):
_fields_ = \
[
("Cleanup", c_void_p)
]
class Count_t(Structure):
_fields_ = \
[
("Use", c_ulong),
("Ref", c_ulong)
]
class User_t(Structure):
_fields_ = \
[
("Valid", c_int), # BOOL
("CleanupCB", c_void_p), # Unused, but pointer sized
("Context", c_void_p) # Unused, but pointer sized
]
class KOBJ_BASE(Structure):
_fields_ = \
[
("Disposing", c_ulong),
("Evt", Evt_t),
("Count", Count_t),
("User", User_t)
]
class KDEV_HANDLE_INTERNAL(Structure):
_fields_ = \
[
("Base", KOBJ_BASE),
("MasterDeviceHandle", c_void_p),
("MasterInterfaceHandle", c_void_p),
("DevicePath", c_char_p),
("ConfigDescriptor", c_void_p), # Unused, pointer sized
("SharedInterfaces", c_void_p),
("DriverAPI", POINTER(KUSB_DRIVER_API)),
("UsbStack", c_void_p), # Unused, pointer sized
("Backend", c_void_p), # Unused, pointer sized
]
class Move_t(Structure):
_fields_ = \
[
("End", c_int),
("InterfaceEL", c_void_p), # Unused, pointer sized
("AltInterfaceEL", c_void_p), # Unused, pointer sized
("PipeEL", c_void_p), # Unused, pointer sized
]
class KUSB_HANDLE_INTERNAL(Structure):
""" What KUSB_HANDLE actually points to """
_fields_ = \
[
("Base", KOBJ_BASE),
("Device", POINTER(KDEV_HANDLE_INTERNAL)),
("Selected_SharedInterface_Index", c_long),
("IsClone", c_int), # BOOL
("Move", Move_t)
]
class status_t(Structure):
_fields_ = \
[
("recipient", c_uint),
("index", c_uint),
("status", c_uint)
]