$ cat writeup.md…
$ cat writeup.md…
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.
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 microcontrollerThe 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).
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) |
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:
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:
0x196E6AA ≈ 26.5 million iterations)0x8A — repeat transmissionKey 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.
#!/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}")
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!@#$%"
Use this technique when:
sbi/cbi instructions on one port| 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).
┌──┐ ┌──┐ ┌──┐ ┌──┐
SCK │ │ │ │ │ │ │ │
───┘ └──┘ └──┘ └──┘ └──
↑ ↑ ↑ ↑
sample sample sample sample
MOSI ─X────X────X────X────X───
D7 D6 D5 D4 ...
Bit-banging is a software implementation of a protocol through GPIO. Used when:
Signs of bit-banged SPI in disassembly:
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