254 lines
9.6 KiB
ReStructuredText
254 lines
9.6 KiB
ReStructuredText
.. _boot-chain-label:
|
|
=======
|
|
Booting
|
|
=======
|
|
|
|
After exploitation the goal is to fully boot the device.
|
|
|
|
Current boot chain:
|
|
|
|
.. figure:: images/boot_chain.drawio.svg
|
|
:align: center
|
|
|
|
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
|
|
|
|
This results in the following files:
|
|
|
|
.. list-table:: bootrom stages
|
|
:header-rows: 1
|
|
|
|
* - File
|
|
- Strings output
|
|
- Likely boot stage?
|
|
* - sboot.bin.1.bin
|
|
- Exynos BL1
|
|
- BL1
|
|
* - sboot.bin.2.bin
|
|
- BL31 %s
|
|
- BL31
|
|
* - sboot.bin.3.bin
|
|
- Unsure. Contains strings like: TOP_DIV_ACLK_MFC_600 and APOLLO_DIV_APOLLO_RUN_MONITOR
|
|
- BL2?
|
|
* - sboot.bin.4.bin
|
|
- Contains more textual information, and references to post BL2 boot, and android information
|
|
- Kernel boot/BL33?
|
|
|
|
After loading the stage1 (entry.S - Frederic's exploit), we're allowed to send custom payloads to the device. The first payload that is then sent, is the debugger.
|
|
|
|
debugger
|
|
========
|
|
The initial debugger is written to ``0x2069000``, with debugger_stack and _storage at ``0x0206b000`` and ``0x0206d000`` respectively.
|
|
|
|
After the initial loading of the debugger, the processor state reported is (using ghidra assistant):
|
|
|
|
.. code-block:: bash
|
|
|
|
root | DEBUG |
|
|
X0 : 0x0 | X1 : 0xffffffff | X2 : 0x20215d8 | X3 : 0x2021894 | X4 : 0x4 | X5 : 0x0 | X6 : 0x0 |
|
|
X7 : 0x136c0008 | X8 : 0x2069000 | X9 : 0x0 | X10 : 0x2070000 | X11 : 0x0 | X12 : 0x0 | X13 : 0x0 |
|
|
X14 : 0xf | X15 : 0x206d000 | X16 : 0x9 | X17 : 0x0 | X18 : 0x1 | X19 : 0x2000 | X20 : 0x2069000 |
|
|
X21 : 0x0 | X22 : 0x0 | X23 : 0x0 | X24 : 0x0 | X25 : 0x0 | X26 : 0x0 | X27 : 0x1 |
|
|
X28 : 0x0 | X29 : 0x2020f00 | LR/X30 : 0x20219b8 | SP/X31 : 0x2020ef0
|
|
|
|
LR/X30 being the line register. This is the address the processor will jump to when the function is done (important to keep track off).
|
|
|
|
After a cache flush, the debugger seems to be cleared as well, so the debugger is relocated to ``0x20c0000``, with _stack and _storage now at ``0x20c2000`` and ``0x20c4000`` respectively. This is done by running:
|
|
|
|
.. code-block:: python
|
|
|
|
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()
|
|
|
|
The processor state reported then is:
|
|
|
|
.. code-block:: bash
|
|
|
|
root | DEBUG |
|
|
X0 : 0x0 | X1 : 0x1 | X2 : 0x20215d8 | X3 : 0x2021894 | X4 : 0x4 | X5 : 0x0 | X6 : 0x0 |
|
|
X7 : 0x136c0008 | X8 : 0x2069000 | X9 : 0x0 | X10 : 0x2070000 | X11 : 0x0 | X12 : 0x0 | X13 : 0x0 |
|
|
X14 : 0xf | X15 : 0x20c4000 | X16 : 0x9 | X17 : 0x0 | X18 : 0x1 | X19 : 0x2000 | X20 : 0x2069000 |
|
|
X21 : 0x0 | X22 : 0x0 | X23 : 0x0 | X24 : 0x0 | X25 : 0x0 | X26 : 0x0 | X27 : 0x1 |
|
|
X28 : 0x0 | X29 : 0x2020f00 | LR/X30 : 0x20c0000 | SP/X31 : 0x2020ef0
|
|
|
|
bl1
|
|
===
|
|
|
|
.. figure:: images/initial_boot_function.png
|
|
:align: center
|
|
|
|
Overview of the initial boot function in the exynos 8890
|
|
|
|
BL1 needs to be authenticated. BL1 loads at address ``0x02024000`` and contains some form of header (ramdump). 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``. Authentication seems to be done at ``0x00012848``. Initially we thought that 0x0 indicated a verified boot state (as is plausible when reading the decompiled code in Ghidra). But after modifying BL1 in the header and contents, this value did not change.
|
|
|
|
.. code-block:: python
|
|
|
|
# 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)
|
|
|
|
.. figure:: images/bl1_auth_references.png
|
|
:align: center
|
|
|
|
BL1 authentication
|
|
|
|
At this point, we assumed that the authentication was succesful, and the bootROM would jump back to the debugger after loading, but this was not the case. After running this function, we were able to send a single packet, but never received a response. Indicating that the function we were executing never returned on us.
|
|
|
|
If authentication at auth_bl1 is succesful, the returns a value from a function at ``1230c``. This function does some things, but eventually jumps to a function at:
|
|
|
|
.. figure:: images/bl1_auth_follow-up_1230c.png
|
|
:align: center
|
|
|
|
BL1 authentication
|
|
|
|
After authentication the bootROM jumps to this function at, we can execute this function with the debugger.
|
|
|
|
.. code-block:: python
|
|
|
|
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)
|
|
|
|
jump_fwbl1()
|
|
|
|
BL1 is laoded at the download buffer and self copies to ``0x02024000`` and resumes execution there (``0x02024010``).
|
|
|
|
However, this does not result in a jump back to the debugger. But the ROM still allows receival of one data package from the USB host (this is likely the system 'waiting' to receive the bootloader).
|
|
|
|
By adding the IMEM to ghidra, we can have a look at what is going here. (How is this done in Ghidra?). We seem to lose control of our debugger once we step into the `some_weird_brom_function`.
|
|
|
|
TODO TODO TODO
|
|
The reason for this is the following code in bl1:
|
|
|
|
.. code-block:: c
|
|
|
|
iVar3 = FUN_02024320();
|
|
if (iVar3 == 1) {
|
|
(*(code *)(ulong)uRam0000000002020108)(0,1);
|
|
}
|
|
|
|
This code uses a predefined ROM function(I was looking for it) and jumps back to that function when it's done.
|
|
This function is at address ``0x020200e8``, looking in our IMEM dump we can see where in the ROM this points to:
|
|
|
|
.. code-block:: c
|
|
|
|
DAT_02020108 XREF[2]: FUN_00001708:000018b4(W),
|
|
FUN_02021970:02021a40(R)
|
|
02020108 90 57 00 00 undefined4 00005790h
|
|
|
|
Replacing this function with our debugger makes us jump back:
|
|
|
|
.. code-block:: python
|
|
|
|
# Overwrite jump back
|
|
self.cd.memwrite_region(0x02020108, p32(0x2069000))
|
|
self.cd.memwrite_region(0x020200e8, p32(0x2069000))
|
|
|
|
def jump_bl1():
|
|
self.cd.arch_dbg.state.LR = 0x2069000
|
|
self.cd.restore_stack_and_jump(0x02024010)
|
|
# self.cd.restore_stack_and_jump(0x02021810)
|
|
|
|
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()
|
|
|
|
root | DEBUG |
|
|
X0 : 0xc00000 | X1 : 0x2069000 | X2 : 0x0 | X3 : 0x2023114 | X4 : 0x4 | X5 : 0x0 | X6 : 0x0 |
|
|
X7 : 0x136c0008 | X8 : 0x2069000 | X9 : 0x0 | X10 : 0x2070000 | X11 : 0x0 | X12 : 0x0 | X13 : 0x0 |
|
|
X14 : 0xf | X15 : 0x206d000 | X16 : 0x9 | X17 : 0x0 | X18 : 0x1 | X19 : 0x20200e8 | X20 : 0x0 |
|
|
X21 : 0x80000000 | X22 : 0x0 | X23 : 0x0 | X24 : 0x0 | X25 : 0x0 | X26 : 0x0 | X27 : 0x1 |
|
|
X28 : 0x0 | X29 : 0x2020ed8 | LR/X30 : 0x202419c | SP/X31 : 0x2020ec0
|
|
|
|
However this does not fully run bl1, so we will have to dig a bit deeper to see the puropose and when to jump back to the debugger.
|
|
|
|
Authentication of BL1 seems to be done at ``0x0012848``. With return value '0' expected when this function is executed, to execute other functions.
|
|
|
|
.. figure:: images/bl1_auth_references.png
|
|
:align: center
|
|
|
|
BL1 authentication.
|
|
|
|
purpose
|
|
-------
|
|
bl1 interacts with several pheriperals, from the DTB these are:
|
|
|
|
.. code-block:: dtsi
|
|
|
|
/* FSYS0 */
|
|
pinctrl_5: pinctrl@10E60000 {
|
|
compatible = "samsung,exynos8890-pinctrl";
|
|
reg = <0x0 0x10E60000 0x1000>;
|
|
interrupts = <0 212 0>;
|
|
};
|
|
|
|
/* FSYS1 */
|
|
pinctrl_6: pinctrl@15690000 {
|
|
compatible = "samsung,exynos8890-pinctrl";
|
|
reg = <0x0 0x15690000 0x1000>;
|
|
interrupts = <0 202 0>;
|
|
};
|
|
|
|
/* PERIC1 */
|
|
pinctrl_9: pinctrl@14CC0000 {
|
|
compatible = "samsung,exynos8890-pinctrl";
|
|
reg = <0x0 0x14CC0000 0x1000>;
|
|
interrupts = <0 460 0>;
|
|
};
|
|
|
|
pmu_system_controller: system-controller@105C0000 {
|
|
compatible = "samsung,exynos8890-pmu", "syscon";
|
|
reg = <0x0 0x105C0000 0x10000>;
|
|
};
|
|
|
|
rtc@10070000 {
|
|
compatible = "samsung,s3c6410-rtc";
|
|
reg = <0x0 0x10070000 0x100>;
|
|
interrupts = <0 73 0>, <0 74 0>;
|
|
clocks = <&clock 157>;
|
|
clock-names = "gate_rtc";
|
|
};
|
|
|
|
|
|
BL31
|
|
----
|
|
|
|
Setups EL3 stuff, probably in preperation of loading trustzone |