Our DYI Weather Station is fully secure! No, really! Why are you laughing?! OK, to prove it we’re going to put a flag in the internal ROM, give you the
source code, datasheet, and network access to the interface.
For this challenge, we were provided with the firmware sourcecode and a datasheet. Having not worked on hardware challenges by now, this was kinda new to me, but turned out to be quite some fun :)
Going through the source code reveals that commands have to be sent in the format:
for reading and writing to/from a port.
The network interface provided access to some ports of the “weather station”, by which data can be read and write (though writing to the existing sensors won’t do much).
The available sensors seemed to be a dead end, so we need to find a way to access “something else”.
Before passing our input data to the specified i2c port, the firmware will check, if we specified a valid port. At first glance, it seems that is_port_allowed compares the specified port and the allowed ports char by char and also checks that both end with a null byte.
But pb (our input) is not checked for a null-byte, which enables us to specify a port string that “starts” with a valid port (like “101”). "1010", "1011" and so on would also be valid.
The datasheet states though, that the port in I2C_ADDRESS needs to be set as a 7-bit address, so only ports 0-127 would be valid.
Specifying a port >1010 thus wouldn’t be much of a help, but the way, the firmware converts our input string to a port can help with that :)
It first checks, if the port is valid (which we can now fulfill by prepending a valid port), and then converts our input number to an uint8_t, which will just overflow when passing it bigger numbers.
Abusing this, we can specify any port we need.
Since we’re now able to read and write from any port, I did exactly that and tried to read from every port from 0-127 to see, if we get any valid response.
Apart from the existing weather sensors, only port 33 answered with a successful response.
This would probably be the CTF-55930 EPROM interface mentioned in the data sheet, which allows us to access the eprom data and reprogram it.
Let’s validate that by dumping the firmware itself
So we have to first send the page index to the port and after that we can read the page data from it.
Opening the received file with ghidra (use ‘8051 Microcontroller Family’ for Language/processor) shows that we have indeed fetched the complete firmware.
One step further, but how can we now read the flag? Obviously, it’s not part of the firmware itself, but resides in the FlagROM.
Sounds easy enough: write the index of a flag char to 0xee and then read the character from 0xef.
Those sfr registers are never accessed anywhere in the firmware, though.
But since we have access to the EPROM interface, we now also have the ability to reprogram it and write our own code into it.
First thought: let’s just overwrite an existing function in the firmware to dump the flag for us.
Checking the datasheet how reprogramming is done:
Uhm, ok, we can only “clear” bits, but won’t be able to set any 0 bit to 1 again, except by doing a full reset (which obviously is not possible through the network access). So overwriting existing code might be a bit too difficult.
But we have a lot of memory at the end of the firmware, which is completely filled with 1 bits, so we can start by putting our “flag dumping” code there.
Let’s start with writing data to uninitialized pages
Since we can only clear bits, we just invert the byte we want to write, resulting in a clear mask, that zeroes all bits, which are not set in our write data. As long as the destination page is filled with 0xff bytes, we can now write arbitrary code there.
Having never worked with 8051 before, it was time now to learn some basics on 8051 asm (8051 opcodes).
So, first we need to initialize an index counter and write it to FLAGROM_ADDR (0xee)
Now we should be able to read the first character of the flag from FLAGROM_DATA (0xef)
Having the character in a register, we now want to print it back via the serial controller.
For this, we’ll first have to wait for SERIAL_OUT_READY (0xf3)
After that, we can write the current flag character to SERIAL_OUT_DATA (0xf2)
This should print the first character to the network interface. All that’s left now, is to increase the index and jump back to our loop (which should end up in infinitely printing out the content of FLAGROM).
To see, if I got it right, I patched it into the dumped eprom and checked the decompilation in ghidra.
Doesn’t look too bad :)
Now, we’re just left with having our code being called from the firmware.
Overwriting the existing firmware with our complete flag dumper code might have been a quite hard (to impossible) task, but now we just need to write a single ljmp instruction somewhere.
With eprom_write I wrote the flag dumper to address 0xa04 in the firmware, so we just need to put a LJMP 0xa04 somewhere.
For this, I wrote some quick&dirty code to scan the firmware for 3 consecutive bytes, in which all bits were set, so that we could transform them into 02 0A 04 by just clearing the superfluous bits.
This gave some possible addresses (though not all of them were located at the start of a valid opcode).
But from those, 0x341 looked the most promising, which was located in the str_to_uint8 function. Overwriting it and then trigger a conversion to uint8 in the firmware should effectively jump into our code.
Let’s combine this and write the flag dumper code into the eprom, overwrite the code in str_to_uint8 to jump there and then trigger it by reading from any port (which will try to convert the input string into a port number).
In hindsight, the challenge wasn’t too difficult, but it was a really nice entry to hardware challenges and even learned a thing or two in this ride :)