gpdaniels / spike-prime

Experiments with the LEGO Mindstorms (51515) and SPIKE Prime (45678)
MIT License
282 stars 39 forks source link

Dump the `hub` module #12

Closed pitust closed 3 years ago

pitust commented 3 years ago

I theorycrafted a way to dump the contents of the built-in hub module to hopefully be able to implement a full-featured open-source firmware for the LEGO SPIKE Hub. Here is how it works (on paper):

# The hub module
import hub
# For memory access
import machine
module_ptr = id(hub)
# module->globals.map.table[0]
name_ptr = machine.mem32[machine.mem32[machine.mem32[module_ptr + 4] + 12] + 0 * 8]
data_ptr = machine.mem32[machine.mem32[machine.mem32[module_ptr + 4] + 12] + 0 * 8 + 4]
def read_str(ptr):
    len = machine.mem32[ptr + 8]
    addr = machine.mem32[ptr + 12]
    return bytes_at(addr, len).decode()
print(read_str(name_ptr))
pitust commented 3 years ago

Sometimes strings are qstrs though, and the above method doesn't work. Continuing to investigate.

pitust commented 3 years ago

Dumping QSTRs:

def dump_qstr(qstr):
    victim = { 'key': 'XXXX' }
    machine.mem32[machine.mem32[id(victim)+12] + 4] = qstr
    return victim['key']
pitust commented 3 years ago

Here is an impl of fakeobj from the paper (about JS, but same idea) http://www.phrack.org/papers/attacking_javascript_engines.html. Note that the built-in id is really addrof from there.

def fakeobj(qstr):
    victim = { 'key': '_attemptfailed' }
    machine.mem32[machine.mem32[id(victim)+12] + 4] = qstr
    return victim['key']

Basically QSTR dumper.

pitust commented 3 years ago

machine.mem32[id(hub) + 4] is the property dict. That's useful. Now we need to dump the bytecode.

pitust commented 3 years ago

This can run arbitrary arm assembly on the lego device:

class Victim:
    def __call__(self):
        print('Code Exec Failed :(')
victim_obj = Victim()

def run_shellcode(shellcode: bytes):
    # Get the address of the shellcode
    data_addr = uctypes.addressof(shellcode)
    # Write victim_obj.base.type->call to the address of shellcode | 1 (to enable thumb code)
    w4(id(Victim) + 16, data_addr | 1)
    # Finally, call victim_obj, thus achieving code execution.
    return victim_obj()
pitust commented 3 years ago

OK, the whole idea fell apart now since i realized that hub is native code :facepalm:

mohankrr commented 3 years ago

There is a open source firmware for both spike hub and mindstorm inventor hub.. its pybricks.. there is an alpha version in the works.. it does dual boot too... check here..

https://github.com/pybricks/pybricks-micropython

https://github.com/pybricks/support/issues/167