Android Hardware Reverse Engineering

Real-World Android RE: A Case Study on Reverse Engineering an Embedded SPI Peripheral

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction: Unveiling Hidden Android Peripherals

Modern Android devices are complex ecosystems, integrating numerous specialized peripherals to deliver features like NFC, secure elements, advanced sensors, and haptics. Many of these communicate with the System-on-Chip (SoC) via high-speed serial interfaces, with the Serial Peripheral Interface (SPI) being a common choice due to its simplicity and efficiency. While often documented for developers, proprietary or obscure peripherals require a deeper dive: reverse engineering. This article provides an expert-level guide and case study on how to approach reverse engineering an embedded SPI peripheral on an Android device, from physical identification to protocol analysis.

Phase 1: Identifying the Target and Gaining Physical Access

Locating Potential SPI Devices

The first step is identifying a peripheral of interest. This can stem from a desire to understand a specific feature, debug an issue, or exploit a vulnerability. Without schematics, physical inspection of the Printed Circuit Board (PCB) is crucial. Look for:

  • Small ICs with few pins (8-20 pins): These are often simple controllers or sensors.
  • Proximity to specialized functionalities: An NFC chip near the antenna, a secure element chip near payment components.
  • Obscure markings or lack thereof: Chips without readily available datasheets are prime targets for RE.

Once identified, search online for any available datasheets for the chip’s markings. Even if no direct datasheet is found, similar components from the same manufacturer can offer clues about common pinouts or operating modes.

Identifying SPI Pins and Test Points

After pinpointing a suspect IC, the next challenge is to identify its SPI bus lines: SCK (Serial Clock), MOSI (Master Out, Slave In), MISO (Master In, Slave Out), and CS (Chip Select). Often, these will be routed to test points or vias:

  1. Visual Inspection: SPI lines typically run parallel for a short distance. Look for traces originating from the SoC and leading to the peripheral.
  2. Continuity Test: Using a multimeter in continuity mode, probe pins on the peripheral and check for continuity to known SPI pins on the SoC (if SoC documentation is available) or to larger test pads. A common approach is to look for four adjacent traces on the peripheral connecting to the SoC.
  3. Logic Analyzer Probing: For unknown pinouts, connect a multi-channel logic analyzer to several promising pins. Triggering on any activity can reveal clock signals (SCK) and data lines (MOSI/MISO). SCK will show a regular square wave during communication, while MOSI/MISO will show data transitions synchronized with the clock.

Phase 2: Software-Level Discovery and Setup

Kernel and Device Tree Analysis

Before connecting hardware tools, investigate the Android system’s software configuration for clues. This often starts with an `adb shell`:

  • Device Tree Overlays (DTS/DTB): On modern Linux-based systems (like Android), device tree files describe hardware. Look for SPI node definitions. These are often compiled into a `.dtb` file in the boot partition or loaded dynamically as overlays. You might find references in /proc/device-tree/ or by searching kernel source if available.
  • Kernel Configuration: Check if SPI drivers are enabled.
    adb shellcat /proc/config.gz | gunzip | grep -i 'CONFIG_SPI'

  • Kernel Log Messages: Boot logs often reveal driver probing.
    adb shell dmesg | grep -i 'spi'

  • SPI Device Enumeration: See which SPI devices the kernel recognizes.
    adb shell ls /sys/bus/spi/devices/

    This path might list devices like spi0.0, spi0.1, corresponding to bus number and chip select.

Identifying Userspace Interaction

If the kernel drivers are present, the next step is to find out how userspace applications interact with them. This often involves:

  • JNI Calls: Android apps frequently use Java Native Interface (JNI) to call C/C++ libraries that directly interact with hardware. Decompile relevant APKs and look for native library calls (`lib*.so`).
  • Shared Libraries: Analyze the associated native libraries (e.g., /system/lib/hw/*.so or app-specific libraries) using tools like Ghidra or IDA Pro. Look for calls to standard Linux SPI device file operations (`/dev/spidevX.Y`) or direct memory-mapped I/O (MMIO) if a custom driver is in play.
  • Proprietary HALs: Hardware Abstraction Layers (HALs) define how Android frameworks interact with hardware. Look for HAL modules related to your peripheral’s functionality.

Phase 3: Capturing and Analyzing SPI Traffic

Setting Up the Logic Analyzer

With physical access to the SPI lines and a general idea of the software stack, it’s time to capture data. A multi-channel logic analyzer (e.g., Saleae Logic, Open Bench Logic Sniffer, or custom FPGA-based solutions) is indispensable. Connect it as follows:

  • SCK: Connect to the clock line.
  • MOSI: Connect to the Master Out, Slave In line.
  • MISO: Connect to the Master In, Slave Out line.
  • CS: Connect to the Chip Select line.
  • Ground: Ensure a common ground between the logic analyzer and the Android device.

Triggering and Capturing Data

Trigger the logic analyzer to capture data when the peripheral is active. This can be done by:

  • Edge Trigger on CS: Trigger on the falling edge of the CS line (indicating the start of communication).
  • Activity Trigger: Trigger on any activity on SCK or data lines.

Perform an action on the Android device that you suspect will activate the peripheral (e.g., toggle an NFC setting, use a sensor-dependent app). Capture a sufficient amount of data (e.g., several seconds at a high sample rate).

Decoding and Protocol Analysis

Most logic analyzer software includes SPI decoders. Configure the decoder with the correct parameters:

  • SCK polarity (CPOL) and phase (CPHA): These determine when data is sampled relative to the clock edge. There are four modes (0,0; 0,1; 1,0; 1,1). Try all combinations if unsure.
  • Bit order: Most Significant Bit (MSB) first is common.
  • Bits per transfer: Usually 8 bits (one byte).

Analyze the decoded transactions. Look for patterns:

  • Command/Response Structure: Masters typically send a command byte/word, followed by parameters, then expect a response.
  • Register Addresses: Often, the first byte(s) after CS goes low indicate a register address to read from or write to.
  • Data Formats: Identify if data is ASCII, raw binary, or a structured protocol.
  • Initialization Sequences: Observe patterns during device boot-up or feature activation. These often involve configuring the peripheral’s internal registers.

For example, you might see a sequence like:

CS ↓ (start transaction)SCK: 0x80 (Write command to register 0x00)MOSI: 0x01 (Data: Enable feature A)SCK: 0x01 (Read command from register 0x01)MISO: 0x01 (Response: Feature A enabled)CS ↑ (end transaction)

Case Study Example: Reversing a Custom Sensor Interface

Scenario: A proprietary environmental sensor connected via SPI

Let’s assume we’ve identified an unknown IC, U1, on the PCB, physically connected to the SoC via what appear to be SPI lines. Our goal is to read its data.

Step-by-Step Reverse Engineering

  1. Physical Identification & Pinout:

    After finding U1, we use a multimeter to map its pins. We find continuity from four pins to the SoC’s documented SPI controller (e.g., GPIO_SPI0_SCK, GPIO_SPI0_MOSI, GPIO_SPI0_MISO, GPIO_SPI0_CS0). We also identify VCC and GND. We carefully solder thin wires to these points for logic analyzer connection.

  2. Software Analysis:

    We `adb shell` into the device. Running `ls /sys/bus/spi/devices/` reveals `spi0.0`. This suggests the kernel recognizes a device on SPI bus 0, chip select 0. We might then look for associated drivers. If it’s a generic `spidev` entry, we know userspace likely controls it directly. We decompile an application that uses the sensor and find JNI calls to `libsensors.so`. Analyzing `libsensors.so` in Ghidra reveals `open(“/dev/spidev0.0”, O_RDWR)` and `ioctl` calls for SPI transfers.

  3. Logic Analyzer Capture:

    We connect our Saleae Logic analyzer to SCK, MOSI, MISO, and CS. We launch the sensor application on the Android device and observe SPI traffic. We capture a several-second trace.

  4. Protocol Decoding:

    Using the Saleae software, we apply the SPI decoder. After trying different CPOL/CPHA modes, (0,0) looks correct. We observe repeated patterns. For instance, an initial sequence like `0x90 0x01` (MOSI) might be followed by `0x00 0x00` (MISO) – perhaps a sensor enable command. Then, a repeating pattern of `0xA0` (MOSI) followed by several bytes on MISO: `0xA0 -> 0x12 0x34 0x56 0x78`. This strongly suggests a read command (0xA0) and subsequent sensor data (e.g., a 32-bit float or integer).

  5. Proof-of-Concept Interaction:

    Armed with this knowledge, we can write a simple Python script on a Linux machine (or even a custom Android app if `spidev` is accessible) to replicate these interactions using the `spidev` module:

    import spidevimport time# Open SPI bus 0, device (CS) 0spi = spidev.SpiDev()spi.open(0, 0)# SPI configuration (mode 0, max speed in Hz)spi.max_speed_hz = 1000000 # 1 MHzspi.mode = 0b00 # CPOL=0, CPHA=0# Send enable command and read response (if any)try:    # Example: Write 0x01 to register 0x90    spi.xfer2([0x90, 0x01])    time.sleep(0.1) # Wait for sensor to stabilize    # Example: Read data (4 bytes) by sending command 0xA0    # Note: xfer2 sends data on MOSI and returns data from MISO    read_command = [0xA0, 0x00, 0x00, 0x00, 0x00] # Send dummy bytes to clock out data    response = spi.xfer2(read_command)    # The first byte of response might be echo of command, subsequent are data    sensor_data_bytes = response[1:] # Adjust based on actual protocol    # Convert bytes to meaningful data (e.g., 32-bit integer)    if len(sensor_data_bytes) == 4:        sensor_value = int.from_bytes(sensor_data_bytes, byteorder='big')        print(f"Sensor Value: {sensor_value:#x}")    else:        print(f"Unexpected response length: {len(sensor_data_bytes)}")    # Further interactions...finally:    spi.close()

    By iterating on these steps, refining our understanding of the protocol, and testing interactions, we can fully reverse engineer the sensor’s communication protocol.

Challenges and Tips

  • Timing: SPI communication is synchronous. Pay attention to delays between transactions.
  • Checksums/CRC: Some protocols include checksums for data integrity. Identifying these requires more complex analysis.
  • Encryption/Obfuscation: While less common for simple peripherals, some secure elements might encrypt data.
  • Custom Protocols: Beyond standard SPI, vendors might implement custom protocols on top, requiring deeper packet analysis.
  • Power Management: Peripherals might enter low-power states, affecting communication. Triggering activity might require toggling power or specific registers.

Conclusion

Reverse engineering embedded SPI peripherals on Android devices is a multi-disciplinary challenge that combines hardware analysis, software forensics, and protocol decoding. By systematically approaching physical identification, software analysis, and logic analyzer capture, even proprietary and undocumented hardware interfaces can be deciphered. This detailed methodology provides a robust framework for anyone looking to delve into the intricate world of Android hardware reverse engineering.

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