hardwarefreeeasy/medium

Bare Metal

hackthebox

Task: Analyze ATmega328p firmware dump to find what data was sent to a slave device. Solution: Disassemble Intel HEX firmware, identify bit-banged SPI pattern (MOSI/SCK triplets), decode transmitted bytes to reveal the flag.

$ ls tags/ techniques/
avr_disassemblyspi_bitbang_decodingintel_hex_conversiongpio_pattern_recognitionspi_mode0_analysis

Bare Metal — HackTheBox

Description

Concerned about the integrity of devices produced at a remote fabrication plant, management has ordered a review of our production line. This revealed many things, including a weird embedded device in one of our serial networks. In attempting to remove it, we accidentally triggered a hardware failsafe, which resulted in the device stopping working. However, luckily we extracted the firmware prior to doing so. We need to find out what it did to the slave device it was tapped into, can you help us? The microcontroller of the device appears to be an atmega328p.

Provided files:

  • extracted_firmware.hex — firmware dump in Intel HEX format from ATmega328p microcontroller

Analysis

Conversion and Disassembly

The firmware is provided in Intel HEX format. Convert to ELF and disassemble:

avr-objcopy -I ihex -O elf32-avr extracted_firmware.hex firmware.elf avr-objdump -D -m avr5 firmware.elf > disasm.txt

Key point: use -m avr5 — this is the ATmega328p architecture (AVR with 16-bit instructions, 16 KB flash).

Firmware Structure

Disassembly revealed a clear structure:

AddressContents
0x00–0x67AVR interrupt vector table (26 vectors, all → reset 0x7c, except vector 0 → 0x68)
0x68–0x7cStandard C runtime init: clear r1, set SREG, initialize stack at 0x08FF, call main
0x80–0x86GPIO initialization on PORTB
0x88–0x8CAMassive block of sbi/cbi instructions — this is the bit-banging
0x8CC–0x8E8Clear lines and delay loop
0x8EA–0x8ECcli + infinite loop (halt)

GPIO Initialization

in r24, 0x0a ; Read DDRB (Data Direction Register B) ori r24, 0xE0 ; Set bits 5,6,7 (0xE0 = 1110_0000) out 0x0a, r24 ; PB5, PB6, PB7 = OUTPUT cbi 0x0a, 4 ; PB4 = INPUT (SS pin — not used as output)

ATmega328p pin mapping:

  • PB5 (bit 5 PORTB): MOSI — Master Out Slave In (data line)
  • PB6 (bit 6 PORTB): SCK — Serial Clock (clock signal)
  • PB7 (bit 7 PORTB): SS/CS — Slave Select / Chip Select (slave selection)

SPI Protocol Identification

The firmware implements bit-banged SPI — software emulation of SPI through direct GPIO pin control, instead of using the ATmega328p hardware SPI module.

Initialization sequence:

sbi 0x0b, 7 ; SS HIGH (deactivate slave) cbi 0x0b, 7 ; SS LOW (activate slave — active low)

Transmission of each bit (SPI Mode 0: CPOL=0, CPHA=0):

cbi/sbi 0x0b, 5 ; Set MOSI to 0 or 1 sbi 0x0b, 6 ; SCK HIGH (rising edge — slave captures data) cbi 0x0b, 6 ; SCK LOW (falling edge)

Data is transmitted MSB first (most significant bit first), 8 bits per byte.

After transmitting all data:

  1. MOSI and SCK are reset to 0
  2. SS → HIGH (deactivate slave)
  3. Long delay loop (countdown from 0x196E6AA ≈ 26.5 million iterations)
  4. Jump back to address 0x8A — repeat transmission

Pattern Recognition

Key insight: each data bit is encoded by a triplet of instructions:

┌─────────────────────────────────────────────┐
│ cbi/sbi 0x0b, 5  →  MOSI = 0 or 1           │
│ sbi     0x0b, 6  →  SCK ↑ (rising edge)     │  = 1 SPI bit
│ cbi     0x0b, 6  →  SCK ↓ (falling edge)    │
└─────────────────────────────────────────────┘

If sbi 0x0b, 5 → bit = 1. If cbi 0x0b, 5 → bit = 0.

Solution

Decoding Script

#!/usr/bin/env python3 """ Decode bit-banged SPI data from disassembled ATmega328p firmware. Pattern: for each data bit: cbi/sbi 0x0b, 5 → MOSI value (0 or 1) sbi 0x0b, 6 → SCK HIGH (slave samples on rising edge) cbi 0x0b, 6 → SCK LOW """ import re with open('disasm.txt', 'r') as f: lines = f.readlines() # Parse all sbi/cbi instructions on port 0x0b for bits 5 (MOSI) and 6 (SCK) data_ops = [] for line in lines: m = re.match( r'\s*([0-9a-f]+):\s+[0-9a-f ]+\s+(sbi|cbi)\s+0x0b,\s+(\d+)', line.strip() ) if m: bit = int(m.group(3)) if bit in (5, 6): data_ops.append((m.group(2), bit)) # Extract bits: MOSI value, then SCK high, then SCK low bits = [] i = 0 while i < len(data_ops): op, bit = data_ops[i] if bit == 5: # MOSI mosi_val = 1 if op == 'sbi' else 0 # Check that MOSI is followed by SCK HIGH + SCK LOW if (i + 1 < len(data_ops) and data_ops[i+1] == ('sbi', 6) and i + 2 < len(data_ops) and data_ops[i+2] == ('cbi', 6)): bits.append(mosi_val) i += 3 continue i += 1 # Group into bytes (MSB first) result = [] for j in range(0, len(bits), 8): byte_val = 0 for k in range(8): byte_val = (byte_val << 1) | bits[j + k] result.append(byte_val) decoded = bytes(result).decode() print(f"Decoded {len(bits)} bits → {len(result)} bytes") print(f"Data: {decoded}")

Result

Decoded 352 bits → 44 bytes
Data: set_flag:HTB{817_84n91n9_15_3v32ywh323!@#$%}

The firmware transmits 44 bytes (352 bits) over SPI to the slave device. The transmitted data is an ASCII string set_flag: with the flag.

The flag in leet speak reads as: "bit banging is everywhere!@#$%"

Key Indicators

Use this technique when:

  • AVR/embedded firmware contains massive blocks of sbi/cbi instructions on one port
  • Repeating triplets are visible: set data → clock high → clock low
  • GPIO pins are configured as outputs via DDR register (3 pins = data + clock + select)
  • No use of hardware SPI module (SPCR/SPDR registers not used)
  • Intel HEX file from ATmega328p microcontroller (or other AVR)
  • Task mentions "serial network", "slave device", "embedded device"

Notes

AVR I/O Registers (ATmega328p)

RegisterI/O addressPurpose
DDRB0x04 (0x0a in asm)Data Direction Register B (0=input, 1=output)
PORTB0x05 (0x0b in asm)Port B Data Register (write values to pins)
PINB0x03 (0x09 in asm)Port B Input Pins (read pin state)

Important: In AVR assembly, sbi/cbi/in/out use I/O addresses (offset +0x20 from memory-mapped addresses). DDRB = 0x24 in memory = 0x04 in I/O space, but avr-objdump shows 0x0a (depends on version).

SPI Mode 0 (CPOL=0, CPHA=0)

     ┌──┐  ┌──┐  ┌──┐  ┌──┐
SCK  │  │  │  │  │  │  │  │
  ───┘  └──┘  └──┘  └──┘  └──
     ↑     ↑     ↑     ↑
     sample sample sample sample

MOSI ─X────X────X────X────X───
      D7   D6   D5   D4   ...
  • Data is set on MOSI when SCK is LOW
  • Slave captures data on rising edge of SCK (LOW→HIGH)
  • MSB is transmitted first

Bit-banging vs Hardware SPI

Bit-banging is a software implementation of a protocol through GPIO. Used when:

  • Hardware SPI module is busy or unavailable
  • Non-standard pins are needed
  • Firmware intentionally hides SPI usage (as in this case — malicious device)

Signs of bit-banged SPI in disassembly:

  • No access to SPCR (SPI Control Register, 0x2C)
  • No access to SPDR (SPI Data Register, 0x2E)
  • Instead — direct sbi/cbi on PORTB

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups

  • [forensics][Pro]BadUSBduckerz
  • [hardware][free]Tracehackthebox
  • [hardware][free]Defusalhackthebox
  • [reverse][free]HexecutionHackTheBox
  • [reverse][free]DebugmeHackTheBox