Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/hardware-physical-access/firmware-analysis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,64 @@ gdb-multiarch /path/to/targetd
target remote <device-ip>:1234
```

### Zigbee / radio-co-processor message mapping

On IoT hubs the RF stack is often split between a **radio MCU** and a Linux userland process. A useful workflow is to map the path:

1. **RF frame** on the air
2. **controller-side parser** on the radio MCU
3. **serial/UART text or TLV protocol** forwarded to Linux (for example `/dev/tty*`)
4. **application dispatcher** in the main daemon
5. **protocol-specific handler / state machine**

This architecture creates two reversing targets instead of one. If the controller converts binary radio frames into a textual protocol such as `Group,Command,arg1,arg2,...`, recover:

- The **message groups** and dispatch tables
- Which messages can come from the **network** versus the controller itself
- The exact **manufacturer-specific discriminator fields** (for example Zigbee `manufacturer_code` and custom `cluster_command`)
- Which handlers are only reachable during **commissioning**, discovery, or firmware/model download phases

For Zigbee specifically, capture pairing traffic and check whether the target still relies on the default **Link Key** `ZigBeeAlliance09`. If so, sniffing commissioning traffic may expose the **Network Key**. Zigbee 3.0 install codes reduce this exposure, so note whether the tested device actually enforces them.

### Manufacturer-specific protocol handlers and FSM-gated reachability

Vendor-specific Zigbee/ZCL commands are often a better target than standardized clusters because they feed **custom parsing code** and internal **FSMs** with less battle-tested validation.

Practical workflow:

- Reverse the command dispatcher until you find the **vendor-only handler**.
- Recover the **FSM state**, **event**, **check**, **action**, and **next-state** tables.
- Identify **transitional states** that auto-advance and retry/error branches that eventually reset or free attacker-controlled state.
- Confirm which legitimate protocol exchanges are required to place the daemon in the vulnerable state instead of assuming the buggy handler is always reachable.

For timing-sensitive protocols, packet replay from a Python framework may be too slow. A more reliable approach is to emulate a legitimate device on real hardware (for example an **nRF52840**) with a vendor-grade stack so you can expose the correct **endpoints**, **attributes**, and commissioning timing.

### Fragmented-download bug class in embedded daemons

A recurring firmware bug class appears in **fragmented blob/model/configuration downloads**:

1. The **first fragment** (`offset == 0`) stores `ctx->total_size` and allocates `malloc(total_size)`.
2. Later fragments only validate the attacker-controlled **packet-local** fields such as `packet_total_size >= offset + chunk_len`.
3. The copy uses `memcpy(&ctx->buffer[offset], chunk, chunk_len)` without checking against the **original allocated size**.

This lets an attacker send:

- A first valid fragment with a **small** declared total size to force a small heap allocation.
- A later fragment with the **expected offset** but a larger `chunk_len`.
- A forged packet-local size that satisfies the fresh checks while still overflowing the originally allocated buffer.

When the vulnerable path sits behind commissioning logic, exploitation must include enough **device emulation** to drive the target into the expected model-download or blob-download state before sending the malformed fragments.

### Protocol-driven `free()` triggers

In embedded daemons, the easiest way to trigger heap metadata exploitation is often not "wait for cleanup" but **force the protocol's own error handling**:

- Send malformed follow-up fragments to push the FSM into **retry** or **error** states.
- Exceed the retry threshold so the daemon **resets context** and frees the corrupted buffer.
- Use this predictable `free()` to trigger allocator-side primitives before the process crashes for unrelated reasons.

This is especially useful against **musl/uClibc/dlmalloc-like** allocators in embedded Linux, where corrupting chunk metadata can turn unlink/unbin logic into a write primitive. A stable pattern is to corrupt a **size field** to redirect allocator traversal into **fake chunks staged inside the overflowed buffer**, instead of immediately clobbering real bin pointers and crashing the process.

## Binary Exploitation and Proof-of-Concept

Developing a PoC for identified vulnerabilities requires a deep understanding of the target architecture and programming in lower-level languages. Binary runtime protections in embedded systems are rare, but when present, techniques like Return Oriented Programming (ROP) may be necessary.
Expand Down Expand Up @@ -432,5 +490,6 @@ To practice discovering vulnerabilities in firmware, use the following vulnerabl
- [Exploiting zero days in abandoned hardware – Trail of Bits blog](https://blog.trailofbits.com/2025/07/25/exploiting-zero-days-in-abandoned-hardware/)
- [How a $20 Smart Device Gave Me Access to Your Home](https://bishopfox.com/blog/how-a-20-smart-device-gave-me-access-to-your-home)
- [Now You See mi: Now You're Pwned](https://labs.taszk.io/articles/post/nowyouseemi/)
- [Make it Blink: Over-the-Air Exploitation of the Philips Hue Bridge](https://www.synacktiv.com/en/publications/make-it-blink-over-the-air-exploitation-of-the-philips-hue-bridge.html)

{{#include ../../banners/hacktricks-training.md}}