This weekend, I participated in Google CTF 2019. I was able to solve one challenge during the time allocated, and the writeup is below.
Many thanks to netcat for a nudge in the right direction for this challenge.
This challenge was presented as an archive containing source code and a binary, which you can download here. The challenge flavor text indicated a flag was in a secure EEPROM, and it was our task to fetch it.
A target server and port were also provided. On connecting to this host, I was greeted with a proof of work challenge:
I then brute wrote a (pretty crap) MD5 brute forcer, and patched out the same check in the flagrom binary:
With a little further investigation, the flagrom binary appears to be a Verilator executable (seeprom.sv, referred to as “the seeprom” from here) glued onto an 8051 emulator. The binary would instantiate the seeprom, run firmware.8051 and then run 8051 code which we supplied. The supplied payload needed to be an 8051 ELF file, as confirmed by feeding the program the supplied firmware.8051 as a payload.
At this point, it’s worth mentioning that I wasted a significant amount of time attempting to gain code execution by manipulating the provided ELF file (i.e. manually moving instructions around), which is my typical approach when confronted with strange architectures. It wasn’t until a while later that I realized sdcc, the Small Device C Compiler, could generate working ELF binaries.
We then turn to investigating the provided 8051 firmware, and the seeprom itself. The 8051 code is a wrapper which manipulates a seeprom – it writes a flag to the seeprom via an I2C command (and verifies it after with a second I2C command), “secures” the flag and then writes a test message in an unsecured portion of the seeprom.
The seeprom itself supports 2 I2C commands:
- I2C_CONTROL_EEPROM, accessible at addresses starting with 0b1010
- I2C_CONTROL_SECURE, accessible at addresses starting with 0b0101
Initially, I thought I could get an easy win by simply unsetting the security flag, but the I2C_CONTROL_SECURE function appears to be implemented securely, with only the ability to secure more sectors, not arbitrarily remove the security flag:
Our goal is to read out a flag, so we investigate the… control flow (does control flow apply to Verilog?). In order to read a section we must meet the following conditions:
- At the start of the read command, i2c_address_valid must be true
- This is only set when an address is loaded
- This is unset at the end of each command (indicated by i2c_stop), or
- At the end of each byte read, i2c_address_secure == i2c_next_address_secure must be true
- These are wired, so there’s not really any “set” or “unset” action here.
Unfortunately, none of the I2C helper functions in the provided firmware.c file seemed to allow skipping of the i2c_stop command, but helpfully, the SCL and SDA wires were directly broken out to the 8051 user code – so the solution was to bit-bang I2C.
Not wanting to write I2C bit-bang functions myself, I searched for how to interface 8051 to an EEPROM, and lo and behold, I found this. I stole the bit-bang wrapper functions, and wrote some test harnesses, indicating successful basic control of read and write.
The final trick lay in the timing of the I2C commands: we’re not able to “unmark” page security flags, so in order to ensure i2c_address_secure == i2c_next_address_secure (and not get caught at the page 1 boundary), we need to construct a single I2C transaction which:
- Loads an address before 0x64 (setting i2c_address_valid)
- Sets all the pages to secure (ensuring i2c_address_secure == i2c_next_address_secure)
- Reads from the already loaded address, without loading further addresses.
A little code finesse later, and the flag is revealed:
You can download the completed exploit code here.
Thankyou to Google for hosting this event – this has given me an opportunity to begin rebuilding a community of like-minded CTF players, and I continue to learn alot from the challenges I didn’t attempt to solve.