Android Hardware Reverse Engineering

Custom EDL Programmer Development: Crafting Your Own Sahara/Firehose Loaders

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction

Qualcomm’s Emergency Download (EDL) mode, often referred to as QDL or 9008 mode, is a critical low-level boot mode designed for device recovery and flashing factory firmware. For security researchers, device repair technicians, and hardware enthusiasts, understanding and exploiting EDL mode is a gateway to advanced diagnostics, data recovery, and even bypassing certain device protections. This expert-level guide will dive deep into the Sahara and Firehose protocols, empowering you to develop your own custom EDL programmers for Qualcomm-based Android devices.

Understanding Qualcomm EDL Mode

EDL mode is the lowest-level boot mode available on Qualcomm System-on-Chips (SoCs). It’s typically engaged when a device fails to boot from its internal memory (e.g., due to a corrupted bootloader) or when specific button combinations or test points are triggered. When in EDL mode, the device presents itself as a Qualcomm HS-USB QDLoader 9008 device (USB Vendor ID 05C6, Product ID 9008) to the host PC. This mode is critical for:

  • Brick Recovery: Reviving devices that are hard-bricked and cannot boot into normal or recovery modes.
  • Firmware Flashing: Flashing official or custom firmware, partitions, and bootloaders.
  • Security Analysis: Bypassing software-level security to gain access to internal memory or execute custom code.

Entering EDL mode can be achieved through various methods:

  • Button Combinations: Typically holding Volume Up and Volume Down while connecting USB, or a specific key sequence during power-on.
  • Test Points: Shorting specific pins on the device’s PCB while connecting USB, often required for more locked-down devices.
  • ADB Command: On a rooted device, adb reboot edl can force the device into EDL mode.

The Sahara Protocol: The Initial Handshake

The Sahara protocol is the initial communication layer between the host PC and the Qualcomm SoC in EDL mode. Its primary role is to establish a connection, exchange basic device information, and most importantly, load a secondary bootloader or a more capable programmer (like Firehose) into the device’s RAM. The protocol is relatively simple, operating with a series of commands and responses.

Key Sahara Commands:

  • HELLO: Initiates communication, exchanges protocol versions and buffer sizes.
  • COMMAND_EXECUTE: Executes a command on the device, typically used to jump to an address after loading code.
  • READ_DATA: Reads data from device memory (less common in custom loaders, usually done by Firehose).
  • END_TRANSFER: Signals the completion of data transfer for a specific segment.
  • DONE: Signals the host that the device has completed its operations and is ready for the next stage.

A typical Sahara handshake involves the host sending a HELLO command, receiving a HELLO_RESPONSE, and then streaming the Firehose programmer (MBN file) in chunks using WRITE_DATA (implicitly handled by the driver/library) followed by an END_TRANSFER for each segment. Finally, a COMMAND_EXECUTE with the entry point of the loaded Firehose programmer is sent.

import struct # Simplified Sahara HELLO command structure for illustration def create_sahara_hello_command(): # Command ID, Length, Version, Compatible Version CMD_ID = 0x01 # HELLO command ID CMD_LENGTH = 0x08 # Length of command header (8 bytes) VERSION = 0x02 # Protocol version (e.g., 2 for Sahara) COMPATIBLE_VERSION = 0x01 # Compatible protocol version return struct.pack('<IIII', CMD_ID, CMD_LENGTH, VERSION, COMPATIBLE_VERSION) # Example usage: hello_packet = create_sahara_hello_command() print(f'Sahara HELLO packet (hex): {hello_packet.hex()}')

The Firehose Protocol: The Workhorse

Once the Sahara protocol has successfully loaded a Firehose programmer (an executable image, typically an MBN file) into the device’s RAM and jumped to its entry point, the device transitions to using the Firehose protocol. Firehose is a much more sophisticated, XML-based protocol designed for comprehensive device management. It handles operations such as flashing individual partitions, erasing blocks, reading device information, and interacting with various storage types (eMMC, UFS).

Common Firehose Commands:

  • <program>: Writes data to a specified partition or address.
  • <read>: Reads data from a specified partition or address.
  • <erase>: Erases a specified block or partition.
  • <patch>: Modifies specific bytes at an address.
  • <configure>: Configures various device parameters (e.g., storage type, clock speed).
  • <nop>: No operation, often used for keeping the connection alive.
  • <power>: Performs power-related actions (e.g., reset, shutdown).

The Firehose programmer on the device parses these XML commands received over USB and performs the requested operations. Its robustness and flexibility make it the primary tool for advanced firmware manipulation.

<?xml version="1.0" encoding="UTF-8"?> <data> <program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="boot.img" num_partition_sectors="65536" physical_partition_number="0" start_sector="0" /> </data>

This XML command instructs the Firehose programmer to write the file boot.img to the partition at start_sector="0" (relative to the partition, or absolute depending on the Firehose version) on physical partition number 0, assuming a sector size of 512 bytes.

Developing a Custom EDL Programmer

Crafting your own EDL programmer requires careful attention to USB communication and protocol specifics. Python, with its excellent USB libraries like pyusb, is an ideal language for this task.

Prerequisites:

  • Python 3.x: Your development environment.
  • pyusb: Install with pip install pyusb.
  • Qualcomm Firehose Programmer (MBN): Obtain a suitable prog_emmc_firehose_XXXX.mbn or prog_ufs_firehose_XXXX.mbn file for your device’s SoC. These are typically found in stock firmware packages.
  • Basic Understanding of USB: Endpoints, packets, bulk transfers.

Step-by-Step Development:

Step 1: Identifying the Device

First, identify the Qualcomm 9008 device. You’ll need its Vendor ID (VID) and Product ID (PID).

import usb.core import usb.util # Qualcomm QDLoader 9008 VID:PID QUALCOMM_VID = 0x05C6 QUALCOMM_PID = 0x9008 dev = usb.core.find(idVendor=QUALCOMM_VID, idProduct=QUALCOMM_PID) if dev is None: raise ValueError('Device not found. Is it in EDL mode?') print(f'Found Qualcomm device: {dev}') # Set configuration and claim interface dev.set_configuration() usb.util.claim_interface(dev, 0) # Assume interface 0 for simplicity, adjust if needed

Step 2: Implementing Sahara Communication

After finding the device, you’ll engage the Sahara protocol. This involves sending the HELLO command, parsing its response, and then transmitting the Firehose MBN loader.

# Sahara protocol commands and response parsing (simplified) def sahara_hello(dev): hello_cmd = b'
als

' # Actual Sahara HELLO is binary, construct as per spec endpoint_out = dev[0][(0,0)][0] # Output endpoint endpoint_in = dev[0][(0,0)][1] # Input endpoint # Send HELLO command endpoint_out.write(hello_cmd) # Read HELLO response hello_resp = endpoint_in.read(512, 1000) # Max 512 bytes, 1s timeout # Parse hello_resp to get max_payload_size and other params (complex binary parsing) print(f'Sahara HELLO response: {hello_resp.hex()}') # Simplified placeholder for loading Firehose MBN with open('prog_emmc_firehose_8953.mbn', 'rb') as f: firehose_mbn = f.read() # Chunk and send firehose_mbn via Sahara WRITE_DATA-like operations # Then send COMMAND_EXECUTE to jump to Firehose entry point # Example: Assuming your firehose_mbn has been loaded, and device is ready. # The actual implementation involves many more intricate details of Sahara protocol. # For brevity, we focus on the transition to Firehose.

Step 3: Firehose Protocol Interaction

Once the Firehose programmer is loaded and executed, all subsequent communication is via Firehose XML commands. You’ll send XML strings to the device and parse its XML responses.

def send_firehose_command(dev, command_xml): endpoint_out = dev[0][(0,0)][0] endpoint_in = dev[0][(0,0)][1] # Send XML command endpoint_out.write(command_xml.encode('utf-8')) # Read XML response response_xml = endpoint_in.read(4096, 5000) # Max 4KB, 5s timeout print(f'Firehose response: {response_xml.decode('utf-8')}') # Example Firehose command: configure to acknowledge device's capabilities config_xml = '<?xml version="1.0" encoding="UTF-8"?><data><configure MemoryName="eMMC" /></data>' send_firehose_command(dev, config_xml) # Example: flash a partition flash_boot_xml = '<?xml version="1.0" encoding="UTF-8"?><data><program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="boot.img" num_partition_sectors="65536" physical_partition_number="0" start_sector="0" /></data>' # send_firehose_command(dev, flash_boot_xml) # In a real scenario, you'd also send the 'boot.img' data after this command.

Key Challenges and Considerations:

  • Signed Loaders: Newer Qualcomm devices often require digitally signed Firehose programmers, making custom loader development harder unless you have access to official tools or exploits.
  • Checksums: Sahara and Firehose often involve various checksums (CRC16/CRC32) for data integrity.
  • Timeouts and Error Handling: Robust error handling for USB communication and protocol timeouts is crucial.
  • Device-Specific Loaders: Firehose programmers are SoC-specific; a loader for a Snapdragon 855 won’t work on a Snapdragon 450.
  • Partition Tables: Understanding the device’s GPT (GUID Partition Table) is essential for accurate flashing.

Conclusion

Developing custom EDL programmers is a powerful skill in the realm of Android hardware reverse engineering. By mastering the Sahara and Firehose protocols, you gain unparalleled control over Qualcomm-powered devices in their most vulnerable state. This capability extends beyond simple flashing, enabling deep-seated diagnostics, forensic data extraction, and a profound understanding of device boot processes. Always ensure ethical considerations and legal compliance when working with such low-level device access. The journey from a basic understanding of EDL to crafting your own tools is challenging but immensely rewarding, opening doors to advanced device exploitation and recovery techniques.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner