Swiss Hacking Challenge 2023 - ezrev

Posted on Apr 29, 2023

Information

Challenge category: re

Challenge Description

Since we’re having so much fun together, have some low level microcontroller stuff. Maybe have a look at the ATmega328P Datasheet. (PS: yeah the eeprom.txt dump is kinda fake but be happy it is)

Files

We are given an ezrev.zip archive. It contains the following files:

  • eeprom.txt: An EEPROM dump of the first 64 values
  • EZRev.hex: An atmega328p firmware dump

Exploitation

It turns out that there are actually two ways of solving this challenge. One is by just flashing the firmware and EEPROM correctly and then reading the flag via UART with the right baudrate. The other one involves just reading the disassembly.

The hardware way

I had an atmega328p board (Arduino Nano) laying arround and tried this way first. We need to do several steps before being able to read the flag:

Flashing the EEPROM

I’ve crated the following arduino program to write the values from eeprom.txt into the EEPROM and read them again to verify that they’re correct:

#include <EEPROM.h>

void setup() {
  // Serial output
  Serial.begin(9600);
  // eeprom.txt
  int eepromValues[] = {84, 115, 125, 85, 104, 123, 61, 39, 91, 69, 88, 48, 37, 70, 79, 77, 47, 122, 107, 83, 41, 82, 51, 82, 36, 93, 122, 107, 75, 110, 62, 124, 50, 99, 59, 68, 78, 78, 73, 37, 38, 115, 80, 125, 80, 81, 73, 102, 38, 104, 39, 74, 50, 66, 105, 77, 85, 56, 68, 40, 51, 85, 62, 49};
 
  // Loop through every value and set it, log the set value to serial
  for (int i = 0; i < 64; i++) {
    EEPROM.write(i, eepromValues[i]);
    Serial.print(eepromValues[i]);
    Serial.print(" ");
  }
  Serial.println(" ");
  // Read the EEPROM and dump it to serial
  for (int i = 0; i < 64; i++) {
    int value = EEPROM.read(i);
    Serial.print(value);
    Serial.print(" ");
  }
}

void loop() {}

The serial console should print the exact contents of eeprom.txt twice.

Flashing the EZRev.hex file

In a video from LiveOverflow I learned how to flash the binary onto the device:

avrdude -c arduino -p atmega328p -P /dev/ttyUSB0 -b115200 -u -V -U flash:w:EZRev.hex

Note: Chinese Arduino Nano clones use an older bootloader with a lower baudrate and thus need -b57600

Calculating the baudrate

I hooked up a logic analyzer to the TX pin of the Arduino and got the following output:

Saleae Logic with the Arduino serial output

We can see that the smallest signal gap is 3.652 ms. To calculate the baudrate we do the following:

>>> math.floor(1000 / 3.652)
273

Reading the flag

I then just used the async serial analyzer in Logic and used the calculated baudrate:

The flag in Saleae Logic

The assembly way

Afterwards I looked at the file in ghidra again. I won’t go into how everything works but below is how the flag is hidden:

  • The entire flag is in the eeprom.txt, even in plaintext.
  • However every number is a letter and the numbers are randomly scrambled.
  • In the assembly code we can identify the code where the EEPROM reads happen to reverse engineer the flag.

The following part of the assembly is relevant:

LAB_code_0002fb  
code:0002fb 81 e0           ldi        R24,0x1
code:0002fc 90 e0           ldi        R25,0x0
code:0002fd 0e 94 04 02     call       read_eeprom 
code:0002ff 0e 94 ba 01     call       serial_print
code:000301 84 e0           ldi        R24,0x4
code:000302 90 e0           ldi        R25,0x0
code:000303 0e 94 04 02     call       read_eeprom 
code:000305 0e 94 ba 01     call       serial_print
code:000307 81 e2           ldi        R24,0x21
code:000308 90 e0           ldi        R25,0x0
code:000309 0e 94 04 02     call       read_eeprom 
code:00030b 0e 94 ba 01     call       serial_print
code:00030d 84 e3           ldi        R24,0x34
code:00030e 90 e0           ldi        R25,0x0
code:00030f 0e 94 04 02     call       read_eeprom 
code:000311 0e 94 ba 01     call       serial_print
code:000313 8b e0           ldi        R24,0xb
code:000314 90 e0           ldi        R25,0x0
code:000315 0e 94 04 02     call       read_eeprom 
code:000317 0e 94 ba 01     call       serial_print
code:000319 80 e2           ldi        R24,0x20
code:00031a 90 e0           ldi        R25,0x0
code:00031b 0e 94 04 02     call       read_eeprom 
code:00031d 0e 94 ba 01     call       serial_print
code:00031f 86 e1           ldi        R24,0x16
code:000320 90 e0           ldi        R25,0x0
code:000321 0e 94 04 02     call       read_eeprom 
code:000323 0e 94 ba 01     call       serial_print
code:000325 85 e0           ldi        R24,0x5
code:000326 90 e0           ldi        R25,0x0
; This goes on for some time

The first ldi instruction for R25 specifies the EEPROM address to load. That means we can just read the right elements from the eeprom.txt and convert the integers to letters. I did this in the following example for the first 7 characters (shc2023):

data = open("eeprom.txt", "r").read().split(" ")
key_order = [0x1, 0x4, 0x21, 0x34, 0xb, 0x20, 0x16]
for key in key_order:
    print(chr(int(data[key])), end="")

This successfully prints shc2023 and you could do this for the rest of the flag as well. As it’s less than 24h before SHC ends at the time of writing I’ll be skipping this.

Flag

The flag is shc2023{EEPROMFUUN}

Conclusion

I spent a lot of time with the assembly code in advance without knowing how it worked. After finally figuring out that the serial console does work but just at a very low baud rate the challenge got a lot easier.

References

  1. https://www.youtube.com/watch?v=u_U6F2Kkbb0