Samsung_S7/documentation/source/BootROM_8890/02_frederics_exploit.rst
Jonathan Herrewijnen d9d9ae332a Cleaning up code and rewriting documentation. Now mostly finalized.
Boots BL2 and returns to debugger. BL2 is not yet patcheable.
2024-09-10 18:59:32 +02:00

154 lines
8.4 KiB
ReStructuredText

==================
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.
The code can be found in the `exynos-usbdl repository <https://github.com/frederic/exynos-usbdl/tree/master>`_.
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.