Skip to content

Embedded and IoT Security

xwings edited this page Jul 6, 2025 · 1 revision

Embedded and IoT Security with Qiling

Qiling Framework is exceptionally well-suited for analyzing the firmware of embedded and Internet of Things (IoT) devices. These devices often run on non-x86 architectures like ARM and MIPS, making traditional analysis on a standard computer difficult. Qiling solves this by emulating the specific hardware environment, allowing security researchers to analyze firmware without needing the physical device.

Why Qiling for IoT Security?

  • Cross-Architecture Emulation: Analyze ARM, MIPS, and other IoT-common architectures on your x86 machine.
  • No Physical Device Needed: You can statically analyze and dynamically run firmware without access to the physical hardware.
  • Handling Memory-Mapped I/O (MMIO): IoT devices heavily rely on MMIO for communication between the CPU and peripherals (e.g., UART, GPIO, Flash). Qiling can hook memory access to model and simulate these hardware interactions.
  • Full System Control: You have complete control over the device's state, including registers, memory, and the filesystem, enabling deep inspection and manipulation.

Common Workflow for Firmware Analysis

  1. Extract the Filesystem: Firmware images are often packed archives (e.g., SquashFS, UBIFS). Use tools like binwalk or firmware-mod-kit to extract the root filesystem. This will be your rootfs for Qiling.
  2. Identify the Target Binary: Locate the main executables within the filesystem that you want to analyze (e.g., httpd, upnpd).
  3. Determine Architecture and OS: Use file or other tools to identify the architecture (e.g., MIPS, ARM) and OS (usually Linux).
  4. Initial Emulation: Start with a simple emulation of the target binary to see if it runs and what libraries it needs.
  5. Handling MMIO: The binary will likely crash when it tries to access a hardware register via MMIO. This is expected. You need to identify the address being accessed and write a hook to simulate the hardware's behavior.

Simulating Hardware with MMIO Hooks

This is the most critical part of IoT firmware emulation. When the program tries to read from or write to a specific memory address that corresponds to a hardware peripheral, you must intercept this access and provide a realistic response.

Example: Simulating a Status Register

Imagine a binary that checks a status register at address 0x18040004. If the 5th bit is set, the hardware is ready.

from qiling import Qiling
from qiling.const import QL_VERBOSE

# This hook will be called whenever the program tries to read from 0x18040004
def mmio_status_read_hook(ql, access, address, size, value):
    if access == "read":
        print(f"Intercepted MMIO read at {address:#x}")
        # Simulate that the hardware is always ready by setting the 5th bit
        # This value will be returned to the emulated program
        return 0b100000
    return None # Let other hooks handle it if not a read

if __name__ == "__main__":
    # Assume we have a MIPS Linux binary
    ql = Qiling(["/bin/main_app"], "/path/to/mips_rootfs", verbose=QL_VERBOSE.OFF)

    # Hook the specific MMIO address
    ql.hook_mmio_read(mmio_status_read_hook, begin=0x18040004, end=0x18040004)

    # You might need to map the MMIO region first
    ql.mem.map(0x18040000, 0x10000, info="[MMIO_PERIPHERALS]")

    try:
        ql.run()
    except Exception as e:
        print(f"Emulation stopped with error: {e}")

By implementing MMIO hooks, you can trick the firmware into believing it's running on real hardware, allowing you to explore its logic, find vulnerabilities, and test exploits in a controlled environment.

Example: Simulating a Configuration Register (MMIO Write)

Firmware often configures peripherals by writing to specific MMIO addresses. A write hook can be used to track these configurations or to model the hardware's reaction.

Imagine a device where writing a value to 0x1f001000 configures a DMA controller.

Qiling Script:

from qiling import Qiling
from qiling.const import QL_VERBOSE

# This hook is called when the program writes to our target address
def mmio_dma_write_hook(ql, access, address, size, value):
    if access == "write":
        print(f"\n[+] Intercepted MMIO write to DMA config register at {address:#x}")
        print(f"    > Value written: {value:#x}")

        # Here, you could add logic to model the DMA controller's state based on
        # the value being written.
        # For this example, we just log the event.

if __name__ == "__main__":
    # Assume an ARM Linux binary for an IoT device
    ql = Qiling(["/bin/iot_app"], "/path/to/arm_rootfs", verbose=QL_VERBOSE.OFF)

    # Map the MMIO region for the DMA controller
    dma_base = 0x1f000000
    ql.mem.map(dma_base, 0x2000, info="[DMA_CONTROLLER]")

    # Hook the specific configuration register address for writes
    config_reg_addr = 0x1f001000
    ql.hook_mmio_write(mmio_dma_write_hook, begin=config_reg_addr, end=config_reg_addr)

    print("[*] Starting IoT firmware emulation...")
    ql.run()
    print("[*] Emulation finished.")

Analysis

  • The hook_mmio_write function allows us to intercept writes to hardware registers.
  • This is crucial for understanding how firmware initializes and interacts with its underlying hardware.
  • By logging the values written, we can reverse engineer the device's configuration protocols.
  • In more advanced scenarios, the hook could update a Python model of the peripheral, allowing for stateful simulation of complex hardware.
Clone this wiki locally