Compare commits

..

24 Commits

Author SHA1 Message Date
Jonathan Herrewijnen
ac755b81f1 Updating docs 2024-09-09 18:19:34 +02:00
Jonathan Herrewijnen
a75bf965cc Boots BL2 and returns to debugger. BL2 not yet patcheable. 2024-09-09 17:23:10 +02:00
Jonathan Herrewijnen
b0c2b414ca Updated ghidra project file 2024-09-09 10:12:37 +02:00
Jonathan Herrewijnen
c8dd132ef9 Some spaces/devices 2024-09-05 20:55:39 +02:00
Jonathan Herrewijnen
c03af09de2 Adding ghidra BL31 file for later inspection 2024-09-05 20:18:05 +02:00
Jonathan Herrewijnen
20ad0cdb45 Found area where 02035600 becomes unaccessible. Trying to patch it. 2024-09-04 18:16:37 +02:00
Jonathan Herrewijnen
66621d36d7 Add descriptions to commit and cleanup 2024-09-04 14:16:26 +02:00
Jonathan Herrewijnen
906629b80f Small docs update 2024-09-03 19:31:41 +02:00
Jonathan Herrewijnen
e59478187d Loads and executes BL31, then returns debugger, then continues bootflow and enters recovery 2024-08-29 21:06:15 +02:00
Jonathan Herrewijnen
a12453cbd3 TTBR0_EL3 visible after BL31 2024-08-28 18:45:05 +02:00
Jonathan Herrewijnen
91c7d60638 Adding third (variable) debugger 2024-08-27 20:18:39 +02:00
Jonathan Herrewijnen
a9f426292c Reversing draw order of boot diagram 2024-08-27 20:16:45 +02:00
Jonathan Herrewijnen
5044941619 Better boot memory overview. Boots into recovery. 2024-08-26 17:45:29 +02:00
Jonathan Herrewijnen
fb2c105bf3 Adding memdump with potential keys (NIST_P25) from before any boot other than bootrom 2024-08-26 13:09:19 +02:00
Jonathan Herrewijnen
df71d537ec small blocks in blocks memory map update 2024-08-25 20:00:07 +02:00
Jonathan Herrewijnen
3039e1dbc7 Debugger overwritten by BL2. Working on better memory map 2024-08-23 18:05:06 +02:00
Jonathan Herrewijnen
98033c5d61 Update .gitlab-ci.yml file 2024-08-23 08:27:08 +00:00
Jonathan Herrewijnen
ee605f567e Adjust for non-sudo 2024-08-22 19:56:46 +02:00
Jonathan Herrewijnen
d45b9eeb1b Add xvfb to build pipeline 2024-08-22 19:55:56 +02:00
Jonathan Herrewijnen
4ab063cc71 Unable to get firmware loader to return to debugger (yet) 2024-08-22 19:50:46 +02:00
Jonathan Herrewijnen
416521c8c7 Revert changes in exploit.py 2024-08-21 19:00:18 +02:00
Jonathan Herrewijnen
34ca995109 Adding gitlab pages 2024-08-20 17:28:57 +02:00
Jonathan Herrewijnen
5154096998 Updating readme and adding some necessary binaries to run the exploit 2024-08-20 16:31:49 +02:00
Jonathan Herrewijnen
ac9b7630b9 update gitignore 2024-08-20 15:23:50 +02:00
34 changed files with 6028 additions and 202 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ dump/
venv/
reven/
!dump/exynos-usbdl/
!source/exploit/stage1/**

26
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,26 @@
variables:
http_proxy: http://outboundproxy.jubit2.minvenj.nl:8080
https_proxy: http://outboundproxy.jubit2.minvenj.nl:8080
no_proxy: git.eminjenv.nl
image: python:3.8
pages:
stage: deploy
script:
- apt update
- apt install -y xvfb
- mkdir .public
- python -m venv venv
- source venv/bin/activate
- pip install -r <(head -n -1 requirements.txt)
- cd documentation
- make html
- cd ..
- cp -r documentation/build/* .public
- mv .public public
artifacts:
paths:
- public
only:
- main

View File

@ -2,6 +2,8 @@
Code to exploit and research the Exynos Bootrom.
## Setting up
### Python module
Create a venv, and intall ghidra_assistant.
```bash
python3 -m venv venv
@ -9,6 +11,16 @@ python3 -m venv venv
pip install -r requirements.txts
```
To get to work, run `source/exploit/exploit.py`
### Building the debugger
The debugger needs to be built using [gupje](https://git.herreweb.nl/EljakimHerrewijnen/Gupje). Move the contents of `source/gupje_device` to `Gupje/devices/samsung_s7`. Then proceed to build the debugger.
To view documentation, ensure you have sphinx installed. If not, run `sudo apt install python3-sphinx`. Then proceed to build the documentation by running `make livehtml`.
```bash
mv source/gupje_device/* {your-gupje-root}/devices/samsung_s7
cd {your-gupje-root}
make -f devices/samsung_s7/Makefile
```
Then proceed to move the debugger to `dump/debugger.bin`. To get to work, run `source/exploit/exploit.py`. The launch.json's are located in source/exploit, so its recommended to open this folder in your VSCode/favourite IDE.
### Viewing/building documentation
To view documentation, ensure you have sphinx installed. If not, run `sudo apt install python3-sphinx`. Then proceed to build the documentation by running `make livehtml` in `documentation`.

Binary file not shown.

View File

@ -0,0 +1,45 @@
==========
Start/Home
==========
The Exynos 8890 BootROM is a small piece of code that runs on the Exynos SoC at boot runtime. It is responsible for initializing the hardware and loading the first stage bootloader from storage. The BootROM is stored in a read-only memory and cannot be modified.
Useful links:
- `DTSI files for exynos8890 <https://github.com/LineageOS/android_kernel_samsung_universal8890/tree/lineage-18.1/arch/arm64/boot/dts>`_
- `Frederic exynos-usbdl on Github <https://github.com/frederic/exynos-usbdl/tree/master>`_'
- `Exynos8890 usbdl-recovery images/firmwares <https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery>`_
Be sure to use the correct firmware and firmware version for your S7 when trying this exploit/Frederic's recovery boot (otherwise the booting will likely fail after sending BL31)!
Protections
-----------
There are no stack canaries or guard pages, and no ASLR. Meaning there are almost no protections in place. There is however an SMC and a MMU. The SMC is used to communicate with the secure world, and the MMU is used to map the memory.
Rom is at address 0x0 and is unwritable (Sometimes this is writeable due to MMU caching) and is 0x20000 bytes long.
Samsung Firmware
----------------
Samsung releases firmware files for their devices. These files contain the bootloader, modem, and other firmware files. To see how the ROM works we are interested in the sboot firmware, which contains multiple stages of the bootloader.
These files can then be used to boot the device into USB recovery. To extract the sboot.bin file from a samsung firmware file:
.. code-block:: bash
$ unzip -p firmware.zip 'BL_*.tar.md5' | tar -Oxf - 'sboot.bin.lz4' | lz4 -d - sboot.bin
For additional bootloaders, see: `Github exynos8890-exynos-usbdl-recovery <https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery>`_
Memory Layout
-------------
The memory layout of the Exynos 8890 is as follows:
.. raw:: html
<iframe src="../_static/stack_and_functions.html" width="100%" height="1000px" frameborder="0" float='center'></iframe>
Download protocol
-----------------
When the ROM is unable to boot from the internal storage, it enters ``Exynos Recovery Mode``.
In this mode the bootROM accepts data over USB. There is little functionality other than receiving data, meaning almost no additional attack surface except for the download protocol.
The Exynos BootROM uses a custom protocol to download a bootable image over USB. This image is verified and executed by the BootROM. Unauthorized images are rejected. Initial authorisation is done using the '_auth_bl1' function. Frederic has exploited a vulnerability in the download protocol to load Unauthorized images.

View File

@ -0,0 +1,151 @@
==================
Frederic's Exploit
==================
Frederic published a blogpost on reversing the `USB stack of the Exynos BootROM <https://fredericb.info/2020/06/reverse-engineer-usb-stack-of-exynos-bootrom.html#reverse-engineer-usb-stack-of-exynos-bootrom>`_ and a blogpost on exploiting the `Exynos 8890 BootROM <https://fredericb.info/2020/06/exynos-usbdl-unsigned-code-loader-for-exynos-bootrom.html#exynos-usbdl-unsigned-code-loader-for-exynos-bootrom>`_. Here we will discuss the exploit in more detail.
USB Stack in BootROM
--------------------
The bootRom is the first code that runs on the Exynos SoC. It is responsible for initializing the hardware and loading the first stage bootloader from storage. The BootROM is stored in a read-only memory and cannot be modified (making vulnerabilities in this permanent/non-patcheable). The BootROM is responsible for initializing the USB controller and receiving the first stage bootloader from the USB host. It will be waiting for a package in the following format (dldata == download data):
dldata
^^^^^^
A key For uploading a stage to boot, a custom protocol is used. The dldata that has to be send is 512 bytes long, and has the following format:
.. figure:: images/dl_packet.drawio.svg
:align: center
The dldata packet is used to send data to the BootROM.
The size in the packet is the total size of the packet, including header and footer. If we modify this, we will have a payload size of around 502 bytes.
.. caution::
This protocol remains *mostly* the same for newer Exynos SoCs.
USB Controller / DWC3
^^^^^^^^^^^^^^^^^^^^^
The Exynos 8890 uses the Synopsys DesignWare USB 3.0 controller. Much of the code is shared with the DWC3 driver in the Linux kernel, except that the ROM does not do any scheduling and a lot of features have been removed(OTG handling, etc).
The base address of the usb controller (dwusb3) is mapped at 0x1540000, with a size of 0x10000: (can be found at: `Exynos8890 dtsi <https://github.com/LineageOS/android_kernel_samsung_universal8890/tree/lineage-18.1/arch/arm64/boot/dts>`_).
.. code-block:: dts
udc: usb@15400000 {
compatible = "samsung,exynos8890-dwusb3";
clocks = <&clock 700>, <&clock 703>, <&clock 708>, <&clock 709>;
clock-names = "aclk", "sclk", "phyclock", "pipe_pclk";
reg = <0x0 0x15400000 0x10000>;
#address-cells = <2>;
#size-cells = <1>;
ranges;
usb-pm-qos-int = <255000>;
status = "disabled";
usbdrd_dwc3: dwc3 {
compatible = "synopsys,dwc3";
reg = <0x0 0x15400000 0x10000>;
interrupts = <0 213 0>;
phys = <&usbdrd_phy0 0>, <&usbdrd_phy0 1>;
phy-names = "usb2-phy", "usb3-phy";
};
};c
This is a basic USB controller, but some functions, that are also present in the linux kernel, should be visible in the bootROM as well. Available functions could be: `linux-kernel-dwc3 <https://android.googlesource.com/kernel/msm/+/android-msm-dory-3.10-kitkat-wear/drivers/usb/dwc3/core.h>`_.
The USB host sends a USB_REQ_SET_ADDRESS, `'0x05' <https://asf.microchip.com/docs/latest/common.services.usb.class.composite.device.example.hidms_msc.saml21_xplained_pro/html/group__usb__protocol__group.html>`_, which the connected device has to acknowledge, and will then start sending data to this address. Initially, the device will send data to '0x00'.
.. code:: c
usb_reqid {
USB_REQ_GET_STATUS = 0,
USB_REQ_CLEAR_FEATURE = 1,
USB_REQ_SET_FEATURE = 3,
USB_REQ_SET_ADDRESS = 5,
USB_REQ_GET_DESCRIPTOR = 6,
USB_REQ_SET_DESCRIPTOR = 7,
USB_REQ_GET_CONFIGURATION = 8,
USB_REQ_SET_CONFIGURATION = 9,
USB_REQ_GET_INTERFACE = 10,
USB_REQ_SET_INTERFACE = 11,
USB_REQ_SYNCH_FRAME = 12
}
Ghidra shows `DWC3_DCFG & 0xfffffc00 | DWC3_DCFG & 7 | (param_1 & 0x7f) << 3;`, essentially preserves bits 0-2 and 10-31, and sets bits 3-9 to the value of param_1, which is then likely the address of the device.
.. figure:: images/ghidra_dwc3_dcfg_devaddr.png
:align: center
bootrom exynos 8890 dwc3_dcfg_devaddr
Other general device descriptors are also sent from the device to the host (to describe the device), these are visible in/at 'usb_init_device_descriptor' (6098) and usb_init_descriptors (610c). Two end point addresses are visible: bEndpointAddress 0x81 and 0x02. 0x81 is 10000001 in binary, with bit 7 being '1', which means that the bulk transfer direction is IN. 0x02 is 00000010 in binary, with bit '7' being '0', which means that the bulk transfer direction is OUT.
Data is transferred via Transfer Request Blocks (TRB), dwc3_depcmd_starttransfer is used. The TRB then contains a buffer address, where transferred data from the host is written to. The buffer allocation is done by 'usb_setup_event_buffer', which sets bufferHigh (DWC3_GEVNTADRLO), bufferLow (DWC3_GEVNTADRHI) and bufferSize (DWC3_GEVNTSIZ).
Bug 1 (Integer overflow)
========================
Originally described in this `blogpost <https://fredericb.info/2020/06/exynos-usbdl-unsigned-code-loader-for-exynos-bootrom.html#exynos-usbdl-unsigned-code-loader-for-exynos-bootrom>`_. Our if-statement is written a bit different, but boils down to the same thing.
The exynos bootrom uses a simple USB protocol to receive a bootloader binary from a USB host. The binary sent is called 'dldata'. In Ghidra, at 21518, we can see that it consists of unit32_t: ready?, uint32: size, ? : data, uint16: footer. The contents of this data are checked before being being written.
.. figure:: images/usb_setup_ready_to_0.png
:align: center
The ready flag is set to 0 in the Exynos 8890 BootROM in an earlier function on pdVar1->size (pdVar1.size)
.. figure:: images/dl_data_struct.png
:align: center
The dldata struct in the Exynos 8890 BootROM
.. code:: c
if ((pdVar1->size < 0x206ffff) && (0x206ffff < pdVar1->size + remaining)) {
*(undefined *)&pdVar1->ready = 2;
}
In essence, the payload is not allowed to be larger than 0x206fff (34013183), it checks so with 2 seperate checks
1) In the first condition, the size has to be smaller than 0x206ffff (`pdVar1->size < 0x206ffff`) (34013183 in decimal),
2) and in the second condition, it checks whether 0x206ffff is indeed still less than the size of the payload + remaining (size + remaining)(`0x206ffff < pdVar1->size + remaining`).
If both conditions are met, the payload will NOT be loaded. But this makes sense, as both checks just ensure that the payload is not larger than 0x206ffff.
The bug is however, that the check that the check is done on a uint32_t (2^32 = 4294967296), but the max value that can be checked by a uint32 is 0xFDFDE7FF = 4294967295. So a value of 0xFDFDE7FF + 1 = 0xFDFDE800 = 4294967296, which is larger than the max value of a uint32. So if a payload of this size or more is used, which is much larger than the max requested value 0x206ffff, the check will pass and the payload will still be loaded.
.. figure:: images/usb_payload_size_check.jpeg
:align: center
The size check in the Exynos 8890 BootROM
Sending such large amounts of data can cause a memory overflow, and will cause the target to crash. Not interesting for exploitation in this context. However, the USB packages that are sent, are split into smaller packages with a size of 0xFFFE00.
.. figure:: images/max_allowed_chunck_size.jpeg
:align: center
The max allowed chunk size, after which the payload is split.
The dl_buf pointer is set to the amount it expects to write, instead to the amount that it has written. By transferring a large amount of data, without actually writing it (so in a package, send no data, but tell the target that you're sending data with a length larger than 0xFDFDE800), will cause the pointer to move, without actually writing data.
The trick then becomes, to get the pointer to an address we would like to exploit unto. Then we have a little less than 512 bytes (502 according to dldata) to write our payload.
.. code:: c
typedef struct dldata_s {
u_int32_t ready; //start = 02021518, end = 0202151C. Length = 4
u_int32_t size; //start = 0202151C, end = 02021520. Length = 4
u_int8_t data[n]; //start = 02021520, end = 02021714. Length = 502 == MAX TRANSFER SIZE
u_int16_t footer; //start = 02021714, end = 02021716. Length = 2
} dldata;
Bug 2
=====
.. caution::
Might be a 0/N-day if exploitable
There is a bug(unpatched?) in receiving the last packet of the usb image:
.. figure:: images/underflow_bug.png
The bug is an integer underflow in the calculation of the remaining size of the image.

View File

@ -0,0 +1,311 @@
==================
Exploit boot chain
==================
This part describes the boot chain of the ``Exynos 8890`` SoC.
.. important::
This is all still under development and will change.
Memory overview
---------------
Keep this overview in mind when reading through the boot chain exploit.
.. raw:: html
<iframe src="../_static/stack_and_functions.html" width="100%" height="1000px" frameborder="0" float='center'></iframe>
Exploitation
------------
After exploitation the goal is to fully boot the device. We're trying to use Frederic's exploit to load a debugger and then boot the device. The debugger needs to be kept 'alive' through the boot chain, so we can patch the boot chain at the right time. In order to run the debugger, a small amount of the bootROM was reversed in order to implement send/recv functionality.
.. topic:: gupje
Gupje is the debugger we'll be loading onto the device and will be replacing throughout the bootchain.
Loading the debugger
^^^^^^^^^^^^^^^^^^^^
The debugger is based on `Gupje <https://git.eminjenv.nl/nfi-exploitdev/gupje>`_.
TODO
Boot stages/payloads
--------------------
Get the correct payloads for the bootROM stages from samsung firmware files, or from `Exynos8890 usbdl-recovery images/firmwares <https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery>`_.
.. 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?
Stage0/entry
============
After loading the stage0 (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.
TODO
Initial 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
Stage1/BL1
==========
The first stage is downloading BL1, authenticating it and patching it after authentication. This is done by overwriting the USB return address pointer and jumping back to the debugger. In the debugger we can authenticate BL1, patch it and boot it. An overview of this process is shown below:
Booting an authenticated and patched BL1:
.. figure:: images/boot_chain_bl1.drawio.svg
:align: center
Boot chain
.. note::
git commit 8cb5f2e1 fully boots, you can use this commit to patch bl1 only.
Stage2/BL31
===========
Next up is BL31, which is loaded by BL1. BL31 is written at ``0x02024000`` with the entry point at ``0x02024010``, it ends at ``0x02048000``. ``BL31`` is the secure monitor. The monitor uses memory that is also being used by the debugger, so we will have to relocate it to keep code exeuction.
.. figure:: images/bl31_debugger_memory_example.png
:align: center
Example of BL31 using debugger memory.
BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage (preparation for trustzone?).
It would be nice to patch BL31 before it is being executed. However the current exploit boot flow does not allow this because the ROM function downloads the next stage.
Initial boot function (BL1)
---------------------------
.. figure:: images/initial_boot_function.png
:align: center
Overview of the initial boot function in the exynos 8890
.. caution::
This part needs to be rewritten (contains lies)
BL1 needs to be authenticated. BL31 loads at address ``0x02022000`` 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 loaded at the download buffer and self copies to ``0x02022000`` and resumes execution there, with a size of 0x2000 (``0x02022000`` to ``0x02024000``).
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. After having modified the LR to jump back to the debugger and jumping into auth_bl1 at ``0x00012848`` we jump back to the debugger. Jumping into BL1 at ``2c0`` does not return us to the debugger. Here we need to hijack ``020200dc`` and ``02021880`` we're able to boot into BL1. We store the address of the hijacked function, to restore it later for a proper boot flow.
.. code:: python
auth_bl1(DEBUGGER_ADDR)
self.usb_write(b"FLSH") # Flush cache
hijacked_fun = u32(self.cd.memdump_region(0x020200dc, 4))
# BL1 patches
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"))
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:: bash
/* 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";
};
Probably the only thing it does is set some clocks and prepare for BL31.
OLD
---
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.

View File

@ -0,0 +1,39 @@
=====
Notes
=====
General notes on interesting/peculiar things found on the S7 USB recovery boot process
Emulator
--------
What is interesting about the ROM is that it starts by checking MPIDR_EL1 register and doing a conditional branch to 0x20e0000.
.. code-block:: bash
undefined w0:1 <RETURN>
Reset XREF[1]: Entry Point(*)
00000000 bb 00 38 d5 mrs x27,mpidr_el1
00000004 7b 0f 78 92 and x27,x27,#0xf00
00000008 7f 03 00 f1 cmp x27,#0x0
0000000c 41 00 00 54 b.ne LAB_00000014
00000010 fc 7f 83 14 b LAB_020e0000
Week 35 - 2024
--------------
After booting BL31, the MMU seems to be set up, and we're unable to do get any data off of spaces we're not 'allowed' to access. Patching the if-statement at 0x020244e8, disables the bit that says that the MMU is setup, but booting into recovery is possible (meaning the MMU is setup). Additionally, the memory at 0x02035600 is still not dumpable. At 0x02048000 is still accessible.
Weird space found at 0x105c2400. Seems to contain references to usb buffer (about 48-64 bytes).
Also space at 0x020307f0
.. code-block:: python
self.cd.memdump_region(0x105c2400, 0x40).hex()
'0f0f00000f0008002100000000000000ffffffffffffffffffffffffffffffff0f0f00000f0008002101000000000000ffffffffffffffffffffffffffffffff'
Week 36 - 2024
--------------
Interesting links:
- `Heap overflow <https://highaltitudehacks.com/2020/09/05/arm64-reversing-and-exploitation-part-1-arm-instruction-set-heap-overflow.html>`_
- `UART on S8 <https://grimler.se/posts/exynos-uart/>`_
By accident found space at 0x11207010. Seems to be a memory read/write space. Not executable however, unless the MMU is turned off.

View File

@ -4,9 +4,13 @@ Booting
=======
This part describes the boot chain of the ``Exynos 8890`` SoC.
Booting Protocol
================
TODO document normal samsung boot chain
Memory overview
===============
.. raw:: html
<iframe src="../_static/stack_and_functions.html" width="100%" height="1000px" frameborder="0" float='center'></iframe>
Exploitation
============
@ -17,8 +21,6 @@ After exploitation the goal is to fully boot the device. The following part desc
This is under development and will still change.
BL1
---
The first stage is downloading BL1, authenticating it and patching it after authentication.
This is done by overwriting the USB return address pointer and jumping back to the debugger.
In the debugger we can authenticate BL1, patch it and boot it. An overview of this process is shown below:
@ -36,8 +38,6 @@ Booting an authenticated and patched BL1:
Next up is BL31, which is loaded by BL1.
BL31
----
``BL31`` is the secure monitor. The monitor uses memory that is also being used by the debugger, so we will have to relocate it to keep code exeuction.
.. figure:: images/bl31_debugger_memory_example.png
@ -45,14 +45,12 @@ BL31
Example of BL31 using debugger memory.
BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage.
BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage (preparation for trustzone?).
It would be nice to patch BL31 before it is being executed. However the current exploit boot flow does not allow this because the ROM function downloads the next stage.
Notes
-----
As done by Frederic, the bootrom can be dumped using his provided scripts, and can the be split into different boots:
.. code-block:: bash
@ -83,9 +81,8 @@ This results in the following files:
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
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):
@ -130,8 +127,8 @@ The processor state reported then is:
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
---
Initial boot function (BL1)
---------------------
.. figure:: images/initial_boot_function.png
:align: center
@ -186,13 +183,81 @@ After authentication the bootROM jumps to this function at, we can execute this
jump_fwbl1()
BL1 is laoded at the download buffer and self copies to ``0x02024000`` and resumes execution there (``0x02024010``).
BL1 is loaded at the download buffer and self copies to ``0x02022000`` and resumes execution there, with a size of 0x2000 (``0x02022000`` to ``0x02024000``).
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`.
By adding the IMEM to ghidra, we can have a look at what is going here. After having modified the LR to jump back to the debugger and jumping into auth_bl1 at ``0x00012848`` we jump back to the debugger. Jumping into BL1 at ``2c0`` does not return us to the debugger. Here we need to hijack ``020200dc`` and ``02021880`` we're able to boot into BL1. We store the address of the hijacked function, to restore it later for a proper boot flow.
.. code:: python
auth_bl1(DEBUGGER_ADDR)
self.usb_write(b"FLSH") # Flush cache
hijacked_fun = u32(self.cd.memdump_region(0x020200dc, 4))
# BL1 patches
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"))
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";
};
Probably the only thing it does is set some clocks and prepare for BL31.
BL31
----
BL31 is written at ``0x02024000`` with the entry point at ``0x02024010``.
BL2
---
0x02e8dc mentions 'Onyx-OPR6-8511R1', which is likely sboot.
OLD
---
TODO TODO TODO
The reason for this is the following code in bl1:
.. code-block:: c
@ -242,56 +307,3 @@ Replacing this function with our debugger makes us jump back:
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";
};
Probably the only thing it does is set some clocks and prepare for BL31
BL31
----
Setups EL3 stuff, probably in preperation of loading trustzone

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -1,21 +1,18 @@
===================
Exynos BootROM 8890
===================
The Exynos 8890 BootROM is a small piece of code that runs on the Exynos SoC at boot time.
It is responsible for initializing the hardware and loading the first stage bootloader from storage.
The BootROM is stored in a read-only memory and cannot be modified.
Protections
-----------
There are no stack canaries or guard pages, and no ASLR. Meaning there are almost no protections in place. There is however an SMC and a MMU. The SMC is used to communicate with the secure world, and the MMU is used to map the memory.
There are no stack canaries or guard pages, and no ASLR. Meaning there are almost no protections in place.
Rom is at address 0x0 and is unwritable(Sometimes this is writeable due to MMU caching).
Rom is at address 0x0 and is unwritable (Sometimes this is writeable due to MMU caching).
Samsung Firmware
----------------
Samsung releases firmware files for their devices. These files contain the bootloader, modem, and other firmware files.
To see how the ROM works we are interested in the sboot firmware, which contains multiple stages of the bootloader.
@ -39,11 +36,16 @@ TODO make memory layout of ROM, IMEM and some devices @JONHE
.. figure:: images/memory_layout.drawio.svg
The memory layout of the Exynos 8890
- 0x00000000 to 0x00020000: BootROM
- 0x000002c0: BL1 boot entry point
- 0x00012848: bootrom authentication function
- 0x00019310: BL1 boot function
- 0x02069000: First debugger location
Download protocol
-----------------
When the ROM is unable to boot from the internal storage, it enters ``Exynos Recovery Mode``.
In this mode the bootROM accepts data over USB.
There is little functionality other than receiving data, meaning almost no additional attack surface except for the download protocol.
@ -193,12 +195,10 @@ There is a bug(unpatched?) in receiving the last packet of the usb image:
DWC3
^^^^
The Exynos 8890 uses the Synopsys DesignWare USB 3.0 controller.
Much of the code is shared with the DWC3 driver in the Linux kernel, except that the ROM does not do any scheduling and a lot of features have been removed(OTG handling, etc).
Gupje
^^^^^
In order to run the debugger, a small amount of the bootROM was reversed in order to implement send/recv functionality.

View File

@ -14,3 +14,15 @@ What is interesting about the ROM is that it starts by checking MPIDR_EL1 regist
0000000c 41 00 00 54 b.ne LAB_00000014
00000010 fc 7f 83 14 b LAB_020e0000
Week 35 - 2024
===============
After booting BL31, the MMU seems to be set up, and we're unable to do get any data off of spaces we're not 'allowed' to access. Patching the if-statement at 0x020244e8, disables the bit that says that the MMU is setup, but booting into recovery is possible (meaning the MMU is setup). Additionally, the memory at 0x02035600 is still not dumpable. At 0x02048000 is still accessible.
Weird space found at 0x105c2400. Seems to contain references to usb buffer (about 48-64 bytes).
Also space at 0x020307f0
.. code-block:: python
self.cd.memdump_region(0x105c2400, 0x40).hex()
'0f0f00000f0008002100000000000000ffffffffffffffffffffffffffffffff0f0f00000f0008002101000000000000ffffffffffffffffffffffffffffffff'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
start,end,name,order,comment,X0,LR
0x00000000,0x00020000,BootROM,,,,
0x02020f60,0x02020f68,_boot_usb_ra,,,,
0x00012848,0x000128e8,auth_bl1,,,,
0x000064e0,0x0000658c,_boot_usb,,,,
0x020c0000,0x020c0004,_frederic_dest_ptr,,,,
0x000002c0,0x000002c4,_jump_bl1,,,,
0x02022000,0x02024000,BL1,,,,
0x02024000,0x02048000,BL31,,,,
0x02048000,0x0206ed10,BL2,,,,
0x02069000,0x0206f000,Debugger,,,,
0x020c0000,0x020c7000,Debugger relocated,,,,
0x02048000,0x0204daf0,BL2 empty space?,,,,
0x0204eb00,0x0204eb00,BL2 copy start/source,,,,
0x020c2000,0x020e8d10,BL2 load address?,,,,
0x0206ed10,0x02070000,End/Start peripheral space?,,,,
0x02019e5c,0x02020e5c,Tried debugger space,,,,
0x020C7800,0x020C8000,modem_interface,,,,
0x14AC0000,0x14ac5000,mali@14AC0000,,,,
0x02035600,0x02035608,TTBR0_EL3 address ptr,,,,
0x11207010,0x11207010,memread/write space,,,,
1 start end name order comment X0 LR
2 0x00000000 0x00020000 BootROM
3 0x02020f60 0x02020f68 _boot_usb_ra
4 0x00012848 0x000128e8 auth_bl1
5 0x000064e0 0x0000658c _boot_usb
6 0x020c0000 0x020c0004 _frederic_dest_ptr
7 0x000002c0 0x000002c4 _jump_bl1
8 0x02022000 0x02024000 BL1
9 0x02024000 0x02048000 BL31
10 0x02048000 0x0206ed10 BL2
11 0x02069000 0x0206f000 Debugger
12 0x020c0000 0x020c7000 Debugger relocated
13 0x02048000 0x0204daf0 BL2 empty space?
14 0x0204eb00 0x0204eb00 BL2 copy start/source
15 0x020c2000 0x020e8d10 BL2 load address?
16 0x0206ed10 0x02070000 End/Start peripheral space?
17 0x02019e5c 0x02020e5c Tried debugger space
18 0x020C7800 0x020C8000 modem_interface
19 0x14AC0000 0x14ac5000 mali@14AC0000
20 0x02035600 0x02035608 TTBR0_EL3 address ptr
21 0x11207010 0x11207010 memread/write space

File diff suppressed because one or more lines are too long

View File

@ -23,6 +23,8 @@ extensions = [ 'myst_parser',
templates_path = ['_templates']
exclude_patterns = []
# Ignore
exclude_patterns = ['_ignore']
# select the theme
html_theme = 'sphinx_wagtail_theme'

View File

@ -11,8 +11,7 @@ Documentation on Samsung devices, currently mainly the Samsung S7.
:maxdepth: 2
:caption: BootROMs:
BootROM_8890/index.rst
BootROM_8890/boot_chain.rst
BootROM_8890/01_start.rst
BootROM_8890/02_frederics_exploit.rst
BootROM_8890/03_exploit_boot_chain.rst
BootROM_8890/04_notes.rst

BIN
dump/debugger.bin Executable file

Binary file not shown.

BIN
dump/reloc_debugger.bin Executable file

Binary file not shown.

Binary file not shown.

View File

@ -10,4 +10,11 @@ ghidra_bridge
tqdm
pyhidra
sphinxcontrib.confluencebuilder
sphinxcontrib.drawio
sphinxcontrib.drawio
sphinx_wagtail_theme
plotly
numpy
nbformat==5.10.4
pandas
xvfbwrapper #Required for sphinx drawio
source/ghidra_assistant/ghidra_assistant-0.0.1-py3-none-any.whl

Binary file not shown.

View File

@ -306,25 +306,20 @@ class ExynosDevice():
count += 1
def dump_memory(self, start: hex=0x0, end: hex=0x0206ffff, write=False):
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.
"""
# 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)
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
@ -344,14 +339,16 @@ class ExynosDevice():
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()
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):
@ -368,39 +365,33 @@ class ExynosDevice():
_setup_debugger()
def relocate_debugger(self):
# 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)
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!
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)
def relocate_debugger_2(self):
# 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()
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:
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)
debugger_reloc = debugger
self.cd.memwrite_region(0x020c0000, debugger_reloc)
self.cd.memwrite_region(entry, debugger_reloc)
# self.usb_write(b"FLSH") # Flush cache
self.cd.restore_stack_and_jump(0x020c0000)
self.cd.restore_stack_and_jump(entry)
assert self.usb_read(0x200) == b"GiAs", "Failed to relocate debugger"
self.cd.relocate_debugger(0x020c7000, 0x020c0000, 0x020c4000)
self.cd.relocate_debugger(g_data_received+alternative_size, entry, storage) #0x20c7000, 0x20c0000, 0x20c4000
def dumb_interact(self, dump_imems=False):
@ -542,7 +533,7 @@ class ExynosDevice():
shellcode = f"""
ldr x0, debugger_addr
blr x0
debugger_addr: .quad 0x020c0000
debugger_addr: .quad 0x02022000
"""
shellcode = ks.asm(shellcode, as_bytes=True)[0]
@ -566,25 +557,84 @@ class ExynosDevice():
# 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)
def get_ttbr0_el3(self):
"""
Get the TTBR0_EL3 register using opcode.
"""
shellcode= f"""
mov x1, lr
mrs x0, ttbr0_el3
ldr x2, =0x206fd10
str x0, [x2]
mov lr, x1
ret
"""
shellcode = ks.asm(shellcode, as_bytes=True)[0]
self.cd.memwrite_region(0x206ed10, shellcode)
self.cd.jump_to(0x0206ed10)
ttbr0 = u64(self.cd.memdump_region(0x0206fd10, 0x8))
print(f"TTBR0_EL3: {hex(ttbr0)}")
print(f"Bits: {ttbr0:064b}")
# Overwrite it with 0's
self.cd.memwrite_region(0x0206ed10, b"\x00" * 0x8)
ttbr0 = self.cd.memdump_region(0x206ed10, 0x8)
assert ttbr0 == b"\x00" * 0x8, "TTBR0_EL3 not overwritten"
def test_write_execute(self, address):
"""
At given address, test if it is possible to write and execute code, by writing a simple jump to, and jump back.
"""
self.usb_write(b'PING')
assert self.usb_read(0x200) == b'PONG', "Debugger not alive before test"
shellcode = f"""
mov x1, lr
ret
"""
shellcode = ks.asm(shellcode, as_bytes=True)[0]
self.cd.memwrite_region(address, shellcode)
self.cd.jump_to(address)
self.usb_write(b"PING")
assert self.usb_read(0x200) == b"PONG", "Failed to jump back to debugger"
print(f'Jumped to {hex(address)} and back')
def debugger_boot(self):
"""
Boot into USB recovery mode using the debugger.
"""
### Setup debugger
# Setup initial debugger
self.setup_guppy_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()
DEBUGGER_ADDR = 0x2069000 # 0x2069000
# self.relocate_debugger()
DEBUGGER_ADDR = 0x2069000 #0x020c0000
# # Relocate to other debugger to 02048000 (after BL31, space in 0x020c0000 is no longer reachable -> 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
### Overwrite boot_usb_ra to our debugger
# # # Relocate to other debugger to 020c0000
# debugger = open("../../dump/reloc_debugger.bin", "rb").read()
# self.relocate_debugger(debugger)
# DEBUGGER_ADDR = 0x020c0000
# Relocate debugger
debugger = open("../../dump/reloc_debugger_0x11200000.bin", "rb").read()
self.relocate_debugger(debugger=debugger, entry=0x11200000, storage=0x11201200, g_data_received=0x11201400)
DEBUGGER_ADDR = 0x11200000
# 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))
@ -592,11 +642,13 @@ class ExynosDevice():
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 authentication
AUTH_BL1 = 0x00012848
# 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
@ -606,7 +658,8 @@ class ExynosDevice():
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
@ -616,99 +669,172 @@ class ExynosDevice():
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 authenticat and patch it
# BL1 is loaded, now authenticate and patch it
auth_bl1(DEBUGGER_ADDR)
self.usb_write(b"FLSH") # Flush cache
self.usb_write(b"FLSH") # Flush cache (Frederic does this..)
# Hijack ROM download function
hijacked_fun = u32(self.cd.memdump_region(0x020200dc, 4))
# INSERT YOUR BL1 PATCHES HERE
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"))
# self.cd.memwrite_region(0x020200a0, p32(DEBUGGER_ADDR))
# self.cd.memwrite_region(0x020200d0, p32(DEBUGGER_ADDR))
# self.cd.memwrite_region(0x020200b4, p32(DEBUGGER_ADDR))
# self.cd.memwrite_region(0x020200a4, p32(DEBUGGER_ADDR))
# self.cd.memwrite_region(0x0202297c, self.cd.arch_dbg.sc.mov_0_w0_ins + self.cd.arch_dbg.sc.ret_ins)
GADGET_RET0 = 0x00000d58
# self.cd.memwrite_region(0x20296da , p32(GADGET_RET0))
# self.cd.memwrite_region(0x20296da + 4, p32(GADGET_RET0))
# END
# 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)
# Download next stage via ROM_DOWNLOAD_USB
# 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
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)) # Resore oginal boot flow
# TODO patch verification
# self.cd.memwrite_region(0x0202010c - 52, p32(GADGET_RET0))
# self.cd.memwrite_region(0x02024774, self.cd.arch_dbg.sc.mov_0_w0_ins + self.cd.arch_dbg.sc.ret_ins)
# self.cd.arch_dbg.state.LR = DEBUGGER_ADDR
# self.cd.arch_dbg.state.X0 = 0x020347f0
# self.cd.arch_dbg.state.X1 = 0
# self.cd.restore_stack_and_jump(0x02030464)
# 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")
# ====== PATCHES TO BL31 here! ======
# 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
# Jump BL31
self.cd.restore_stack_and_jump(0x02024010)
time.sleep(2)
self.connect_device()
# self.usb_read(0x200) # GiAs
self.cd.arch_dbg.state.LR = DEBUGGER_ADDR
# self.cd.memwrite_region(0x20219b8, p32(DEBUGGER_ADDR))
# self.cd.restore_stack_and_jump(hijacked_fun)
# ==== Stage 3 BL2 ====
self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.3.bin", "rb").read())
# 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
# self.cd.memwrite_region(0x02032008, struct.pack('>I', 0x1f2003d5)) # Overwrite MAIR to NOP
# self.cd.memwrite_region(0x0202ee74, struct.pack('>I', 0x63650094)) # Return to debugger. (not working, it continues booting..)
# self.cd.memwrite_region(0x0202eb7c, struct.pack('>I', 0x21650014)) # Change check to always false
# Jump back to debugger, after TTBR0 is no longer accessible
# self.cd.memwrite_region(0x0202f288, struct.pack('>I', 0x5e630094)) # Change check to always false
# 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
BL31_ra = self.cd.arch_dbg.state.LR
self.cd.arch_dbg.fetch_special_regs()
print(f'MMU is {hex(self.cd.arch_dbg.state.R_SCTLR_EL3.mmu)} (0x1=enabled, 0x0=disabled)')
# 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
# ================= WORKS TO DISABLE DEBUGGER. BUT UNNECESSARY =================
# # Disable MMU and branch to 0x02048000
# shellcode=f"""
# mrs x0, sctlr_el3
# bic x0, x0, #1
# msr sctlr_el3, x0
# ldr x0, =0x2048000
# br x0
# """
# shellcode = ks.asm(shellcode, as_bytes=True)[0]
# self.cd.memwrite_region(0x02060000, shellcode)
# self.cd.jump_to(0x02060000)
# time.sleep(1)
# self.usb_read(0x200) # GiAs
# self.cd.arch_dbg.fetch_special_regs()
# # Print status of MMU
# print(f'MMU is {hex(self.cd.arch_dbg.state.R_SCTLR_EL3.mmu)} (0x1=enabled, 0x0=disabled)')
# =================
# # Change the values of MAIR_EL3 to a different value
# shellcode=f"""
# mov x0, #0x000000ff
# msr mair_el3, x0
# ldr x0, =0x2048000
# br x0
# """
# shellcode = ks.asm(shellcode, as_bytes=True)[0]
# self.cd.memwrite_region(0x02060000, shellcode)
# self.cd.jump_to(0x02060000)
# time.sleep(1)
# self.usb_read(0x200) # GiAs
# self.cd.arch_dbg.fetch_special_regs()
# print(self.cd.arch_dbg.state.R_MAIR_EL3)
self.test_write_execute(0x11207010)
# # Relocate debugger
# debugger = open("../../dump/reloc_debugger_0x2019e5c.bin", "rb").read()
# self.relocate_debugger(debugger=debugger, entry=0x11200000, storage=0x11201200, g_data_received=0x11201400)
# DEBUGGER_ADDR = 0x11200000
# 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 ====
stage3 = open("../S7/g930f_latest/g930f_sboot.bin.3.bin", "rb").read()
# stage3_len = len(stage3)
# patch_len = len(b"MNGS_QUAD")
# patch = b"Patch" + (b"\x00" * (patch_len - len(b"Patch")))
# patch_offset = stage3.find(b"MNGS_QUAD")
# stage3 = stage3[:patch_offset] + patch + stage3[patch_len + patch_offset:]
# assert len(stage3) == stage3_len, "Invalid stage4 length"
self.send_normal_stage(stage3)
time.sleep(2)
self.connect_device()
self.usb_read(0x200) # GiAs
# Restore bootflow
self.cd.memwrite_region(0x020200dc, p32(hijacked_fun))
self.cd.restore_stack_and_jump(hijacked_fun)
# ==== Stage 4 ====
stage4 = open("../S7/g930f_latest/g930f_sboot.bin.4.bin", "rb").read()
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"
# 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)
@ -739,4 +865,4 @@ if __name__ == "__main__":
exynos.setup_guppy_debugger()
exynos.dumb_interact()
sys.exit(0)
sys.exit(0)

View File

@ -1,8 +0,0 @@
# Open a binary file and modify it
bl1 = open('../S7/bl1.bin', 'rb').read()
# Modify the binary file at 1C10
bl1 = bl1[:0x1C1C] + b'\x48' + bl1[0x1C1D:]
# Write the modified binary file
open('../S7/bl1_mod.bin', 'wb').write(bl1)

Binary file not shown.

BIN
source/exploit/stage1/stage1.bin Executable file

Binary file not shown.

BIN
source/exploit/stage1/stage1.elf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,7 +8,7 @@ OBJCOPY := $(ANDROID_NDK_ROOT)/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64
LD := $(ANDROID_NDK_ROOT)/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld.bfd
#==================Target Samsung S7 (8890)==================
all: samsung_s7 samsung_s7_reloc
all: samsung_s7 samsung_s7_reloc samsung_s7_reloc_0x2019e5c
CFLAGS_SAMSUNGS7 = -Os -Idevices/samsung_s7/
samsung_s7:
@ -26,3 +26,10 @@ samsung_s7_reloc:
$(LD) -T devices/samsung_s7/reloc_linkscript.ld bin/samsung_s7/reloc_entry.o bin/samsung_s7/reloc_debugger.o -o bin/samsung_s7/reloc_debugger.elf --just-symbols=devices/samsung_s7/reloc_symbols.txt
$(OBJCOPY) -O binary bin/samsung_s7/reloc_debugger.elf bin/samsung_s7/reloc_debugger.bin
CFLAGS_SAMSUNGS7_RELOC_0x2019e5c = -Os -DRELOC_DEBUGGER_2=1 -Idevices/samsung_s7/
samsung_s7_reloc_0x2019e5c:
[ -d bin/samsung_s7 ] || mkdir -p bin/samsung_s7/
$(CC) arm64_stub.S -c -o bin/samsung_s7/reloc_entry_0x2019e5c.o $(CFLAGS_SAMSUNGS7_RELOC_0x2019e5c)
$(CC) debugger.c -c -o bin/samsung_s7/reloc_debugger_0x2019e5c.o $(CFLAGS_SAMSUNGS7_RELOC_0x2019e5c)
$(LD) -T devices/samsung_s7/reloc_linkscript_0x2019e5c.ld bin/samsung_s7/reloc_entry_0x2019e5c.o bin/samsung_s7/reloc_debugger_0x2019e5c.o -o bin/samsung_s7/reloc_debugger_0x2019e5c.elf --just-symbols=devices/samsung_s7/reloc_symbols_0x2019e5c.txt
$(OBJCOPY) -O binary bin/samsung_s7/reloc_debugger_0x2019e5c.elf bin/samsung_s7/reloc_debugger_0x2019e5c.bin

View File

@ -29,6 +29,14 @@ int mystrlen(char *data) {
#define data_received 0x206f100
#endif
#ifdef RELOC_DEBUGGER_2
#define recv_buffer 0x14AC4200
#define data_received 0x14AC4000
#else
#define recv_buffer 0x206f000 //0x02021800 + 0x3000
#define data_received 0x206f100
#endif
void recv_data_cb(uint32_t endpoint, uint32_t len){
char *dest_buf = (char *)recv_buffer;
volatile void *dref = (void *)data_received;

View File

@ -0,0 +1,14 @@
MEMORY {
ROM (rwx): ORIGIN = 0x14AC0000, LENGTH = 0x1000
}
SECTIONS
{
. = 0x14AC0000;
.text . : {
*(.text*)
*(.data*)
*(.rodata*)
} >ROM
}

View File

@ -0,0 +1,12 @@
debugger_storage = 0x14AC3000;
debugger_stack = 0x14AC2000;
debugger_entry = 0x14AC0000;
maybe_usb_setup_read = 0x00006f88;
dwc3_ep0_start_trans = 0x0000791c;
usb_event_handler = 0x00007bac;
get_endpoint_recv_buffer = 0x00007a7c;
exynos_sleep = 0x000027c8;
g_recv_buffer = 0x14AC4200;
g_data_received = 0x14AC4000;