Starting point
First thing we see is the bootloader, u-boot in this case, printing informations.
U-Boot 2023.07.02 (Jul 11 2023 - 15:20:44 +0000)
DRAM: 24 MiB
Core: 41 devices, 10 uclasses, devicetree: board
Loading Environment from nowhere... OK
In: pl011@9000000
Out: pl011@9000000
Err: pl011@9000000
Autoboot in 2 seconds
## Booting kernel from Legacy Image at 40200000 ...
Image Name: EFI Shell
Created: 1980-01-01 0:00:00 UTC
Image Type: AArch64 EFI Firmware Kernel Image (no loading done) (uncompressed)
Data Size: 1028096 Bytes = 1004 KiB
Load Address: 00000000
Entry Point: 00000000
Verifying Checksum ... OK
XIP Kernel Image (no loading done)
No EFI system partition
No EFI system partition
Failed to persist EFI variables
## Transferring control to EFI (at address 40200040) ...
Booting /MemoryMapped(0x0,0x40200040,0xfb000)
We can therefore see a lot of interesting stuff, but the main thing is that we boot on an UEFI shell, not a linux system. We can not access the u-boot prompt, so we have to find another way to get the flag.
Search for a flaw
U-Boot need a boot command to automatically boot the system, so the first things to look for is the content of the bootcmd variable.
Using strings on the bootloader.bin file, we can find the following line: bootcmd=bootm 0x40200000; poweroff. This means that the bootloader will try to execute the code at address 0x40200000 and then power off the system.
We can then assume that, if the bootm command is returning an error, the command will stop there and give us the prompt back, without powering off the system.
However, we can not write to the memory address 0x40200000, because it would require to find where the UEFI image is stored in memory, tamper with it and hope that u-boot is not checking the integrity of the image before executing it but thanks to the boot information, we know that the image has a checksum, so it is likely that the integrity of the image is checked before executing it.
Although we can not tamper directly with the image on the disk, we can try to find a way to tamper with the memory.
Memory tampering
U-Boot is loaded into memory and, since we have the image of the bootloader, we can reverse it to see how it is executed and if there is a way to tamper with it.
At this point, the plan is clear: Reverse the bootloader, find the function poweroff, replace the first line of the function by “return 0” and then type exit in the shell to end the bootm command, which will then jump on the poweroff function, which will do nothing and return 0, giving us the prompt back without powering off the system.
To print memory, we have the function dmem, which is a simple function that takes an address as argument and prints the content of the memory at that address. To write to memory, we have the mm function, which takes an address and a value as argument and writes the value to the memory at that address.
But now, we need to figure out where the poweroff function is located !
Reverse time !
I used Ghidra to reverse the bootloader. To start, i searched for the string “poweroff” and found where it is stored in the binary.
s_poweroff_00065092 XREF[2]: 00072ec0(*), 00088fa0(*)
00065092 70 6f 77 ds "poweroff"
65 72 6f
66 66 00
We can see that there is two references to this string. If we follow them, we end up on the same function.
PTR_s_poweroff_00072ec0 XREF[1]: 00088f90(*)
00072ec0 92 50 06 addr s_poweroff_00065092
00 00 00
00 00
We can see that there are two functions called afterwards. The first function is too little to be the poweroff function, meaning it is probably a wrapper function or a set up. Let’s look into the second one:
This function is looking way more like a poweroff function, and we can even see the print "Powering off..." (s_power_off...) ! However, since U-Boot’s command table points to the wrapper function (0x0000d314) as the default entry point for the command, this is the one we need to patch. By replacing the first instruction of this wrapper with a “RET”, it will return immediately and never call the actual poweroff routine
Now, we have the address of the first instruction of the poweroff function, which is at the address 0x0000d314. The plan is to replace the first instruction by “RET” which is 0xd65f03c0 in arm64. However, we need to keep in mind that we are in little endian, so we must write ‘C0 03 5F D6’ to the memory address of the first instruction of the poweroff function.
Easy as that ?!
To write the value to memory, we do mm 0xADDRESS, address being the address we found (0x0000d314), so in theory if we do the command and write C0 03 5F D6 to the memory, we should be able to type exit in the shell and get the prompt back without powering off the system.
let’s try it !
Shell> mm 0x0000d314
MEM 0x000000000000D314 : 0x9F > C0
MEM 0x000000000000D315 : 0x00 > 03
MEM 0X000000000000D316 : 0x00 > 5F
MEM 0x000000000000D317 : 0xB9 > D6
MEM 0x000000000000D318 : 0x04 > q
Shell> mm 0x0000d314
MEM 0x000000000000D314 : 0x9F >
Why does it not work ? We are writing the correct value to the correct address, so it should work, but it does not.
The problem is that the address we found is the address of the instruction in the binary, but when the binary is loaded in memory, it is loaded at a different address. We need to find the base address of the binary in memory and then add the offset of the instruction to get the correct address to write to.
Let’s end it
Lucky for us, we have the dmem function that allows us to print the content of memory at a given address. I searched for the bootcmd variable in memory and found it.
417BD800: 61 79 20 30 00 62 6F 6F-74 63 6D 64 3D 62 6F 6F *ay 0.bootcmd=boo*
417BD810: 74 6D 20 30 78 34 30 32-30 30 30 30 30 3B 20 70 *tm 0x40200000; p*
417BD820: 6F 77 65 72 6F 66 66 00-62 6F 6F 74 64 65 6C 61 *oweroff.bootdela*
Now that I have the address of a variable in memory, I can search for the address of this exact variable in the binary. It is found at 0x006c805.
We need to calculate the offset of the memory. The address of the variable in the binary is 0x0006c805, and the address of the variable in memory is 0x417bd805:, so the offset is 0x41751000.
We now have the offset where U-Boot is loaded in memory. If we add this offset to the address of the first instruction of the poweroff function (0x0000d314), we get the correct address to write to, which is 0x4175e314.
Now, we can write the value as we did before but with the correct address :
Shell> mm 0x4175e314
MEM 0x000000004175E314 : 0x9F > C0
MEM 0x000000004175E315 : 0x00 > 03
MEM 0x000000004175E316 : 0x00 > 5F
MEM 0x000000004175E317 : 0xB9 > D6
MEM 0x000000004175E318 : 0x04 > q
Shell> dmem 0x4175e314 5
Memory Address 000000004175E314 5 Bytes
4175E314: CO 03 5F D6 04 *.. ..*
Flag
After we type exit in the shell, we get the prompt back without powering off the system ! Now, we need to find the flag. A quick printenv shows us that there is an environment variable called printflag :
printflag=hash sha256 40900100 1000 flaghash; echo FCSC{$flaghash};
We only need to execute this command to get the flag !