211 lines
9.7 KiB
ReStructuredText
211 lines
9.7 KiB
ReStructuredText
===================
|
|
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.
|
|
|
|
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.
|
|
|
|
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
|
|
|
|
Frederic has also written a payload to extract the sboot.bin file from a connected samsung device (See: :ref:`boot-chain-label`). The extracted boots can be split up in different stages. We're provied with sboot 1-4.bin. Running strings then provides us with some information about each stage.
|
|
|
|
.. code-block:: bash
|
|
|
|
$ strings -n4 sboot.bin.1.bin
|
|
|
|
Memory Layout
|
|
-------------
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
(TODO verify and document)
|
|
|
|
dldata
|
|
^^^^^^
|
|
For uploading a stage to boot, a custom protocol is used. The dldata that has to be send is the following:
|
|
|
|
.. figure:: images/dl_packet.drawio.svg
|
|
|
|
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.
|
|
|
|
.. info::
|
|
|
|
This protocol remains *mostly* the same for newer Exynos SoCs.
|
|
|
|
USB Stack
|
|
---------
|
|
|
|
This information is largely based on the blogpost of Frederic 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>`_. We're looking at the proprietary USB protocol used by the Exynos BootROM.
|
|
|
|
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
|
|
|
|
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>`_.
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
@ELHER
|
|
|
|
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.
|
|
|
|
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.
|
|
|