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/
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:
| Address | Contents |
|---|---|
0x00–0x67 | AVR interrupt vector table (26 vectors, all → reset 0x7c, except vector 0 → 0x68) |
0x68–0x7c | Standard C runtime init: clear r1, set SREG, initialize stack at 0x08FF, call main |
0x80–0x86 | GPIO initialization on PORTB |
0x88–0x8CA | Massive block of sbi/cbi instructions — this is the bit-banging |
0x8CC–0x8E8 | Clear lines and delay loop |
0x8EA–0x8EC | cli + 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:
- MOSI and SCK are reset to 0
- SS → HIGH (deactivate slave)
- Long delay loop (countdown from
0x196E6AA≈ 26.5 million iterations) - 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/cbiinstructions 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)
| Register | I/O address | Purpose |
|---|---|---|
| DDRB | 0x04 (0x0a in asm) | Data Direction Register B (0=input, 1=output) |
| PORTB | 0x05 (0x0b in asm) | Port B Data Register (write values to pins) |
| PINB | 0x03 (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/cbion 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]BadUSB— duckerz
- [hardware][free]Trace— hackthebox
- [hardware][free]Defusal— hackthebox
- [reverse][free]Hexecution— HackTheBox
- [reverse][free]Debugme— HackTheBox