Files
Embedded-Hacking/WEEK04/WEEK04-03.md
2026-03-19 15:01:07 -04:00

8.9 KiB
Raw Permalink Blame History

Embedded Systems Reverse Engineering

Repository

Week 4

Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics

Non-Credit Practice Exercise 3: Analyze and Understand GPIO Control

Objective

Import the 0x0008_uninitialized-variables.bin binary, analyze the GPIO initialization and control sequences, understand how gpio_init(), gpio_set_dir(), and gpio_put() work at the assembly level.

Prerequisites

  • Completed Exercises 1 and 2
  • Understanding of GPIO basics from Week 4 Part 3
  • Raspberry Pi Pico 2 with an LED connected to GPIO 16
  • Basic knowledge of ARM Thumb-2 instruction set

Task Description

You will import a new binary that controls GPIO pins, identify the GPIO-related function calls, trace the initialization sequence, and understand how the Pico SDK controls hardware at the low level.

Step-by-Step Instructions

Step 1: Flash the Original Binary

Before analysis, let's see what the program does:

  1. Hold BOOTSEL and plug in your Pico 2
  2. Flash 0x0008_uninitialized-variables.uf2 to the RPI-RP2 drive
  3. Open your serial monitor

Expected output:

age: 0
age: 0
age: 0
...

Expected behavior:

  • The red LED on GPIO 16 blinks on/off every 500ms
  • The value 0 is printed (uninitialized variable)
Step 2: Create a New Ghidra Project
  1. Launch Ghidra: ghidraRun
  2. Click FileNew Project
  3. Select Non-Shared Project
  4. Project Name: week04-ex03-gpio-analysis
  5. Click Finish
Step 3: Import the GPIO Binary
  1. Drag and drop 0x0008_uninitialized-variables.bin into Ghidra
  2. Set Language: ARM Cortex 32 little endian default
  3. Click Options…
    • Block Name: .text
    • Base Address: 10000000
  4. Click OK on all dialogs
  5. Double-click the file and click Yes to analyze
Step 4: Identify the Main Function

Look for the main function (likely FUN_10000234 or similar):

In the Symbol Tree:

  1. Expand Functions
  2. Look for a function that appears to be an entry point
  3. Click on potential main candidates

Look for these patterns in the decompile:

  • Call to stdio_init_all()
  • Call to gpio_init()
  • Infinite while loop with gpio_put() and sleep_ms()
Step 5: Rename the Main Function

Once you identify main:

  1. Right-click on the function name
  2. Select Edit Function Signature
  3. Change to: int main(void)
  4. Click OK

Expected decompiled code structure:

int main(void)
{
    // Some initial value
    stdio_init_all();
    gpio_init(0x10);  // GPIO 16
    // ... more GPIO setup
    
    while (true) {
        printf(...);
        gpio_put(0x10, 1);
        sleep_ms(0x1f4);
        gpio_put(0x10, 0);
        sleep_ms(0x1f4);
    }
}
Step 6: Identify GPIO Function Calls

Look in the decompiled main for function calls. You should see several undefined functions.

Find and rename these GPIO functions:

Auto-Generated Name Actual Function How to Identify
FUN_xxxxx gpio_init Takes one parameter (pin number)
FUN_xxxxx gpio_set_dir Takes two parameters (pin, direction)
FUN_xxxxx gpio_put Takes two parameters (pin, value)
FUN_xxxxx sleep_ms Takes one parameter (milliseconds)
FUN_xxxxx stdio_init_all Takes no parameters, called first
FUN_xxxxx printf Takes variable args, has format string

Example renaming gpio_init:

  1. Click on the function call in the decompile window
  2. Right-click → Edit Function Signature
  3. Change name to: gpio_init
  4. Set signature to: void gpio_init(uint gpio)
  5. Click OK
Step 7: Analyze GPIO Initialization Sequence

After renaming, your decompiled main should look clearer:

int main(void)
{
    stdio_init_all();
    
    gpio_init(0x10);              // Initialize GPIO 16
    gpio_set_dir(0x10, 1);        // Set as output (1 = GPIO_OUT)
    
    while (true) {
        printf("age: %d\r\n", 0);
        gpio_put(0x10, 1);        // LED ON
        sleep_ms(0x1f4);          // Wait 500ms (0x1f4 = 500)
        gpio_put(0x10, 0);        // LED OFF
        sleep_ms(0x1f4);          // Wait 500ms
    }
}

Key observations:

  • 0x10 is hexadecimal for 16 (GPIO 16 - red LED)
  • 0x1f4 is hexadecimal for 500 (milliseconds)
  • 1 means GPIO_OUT (output direction)
  • The LED is controlled by toggling between 1 (on) and 0 (off)
Step 8: Examine gpio_init Assembly

Double-click on gpio_init to jump to its implementation.

Look for these key operations in the assembly:

; Load GPIO pin number into register
movs    r4, r0          ; Save pin number

; Calculate pad register address
; Base address: 0x40038000 (PADS_BANK0)
; Offset: pin * 4
ldr     r3, =0x40038000
lsls    r5, r4, #2      ; pin * 4
add     r3, r5          ; Calculate address

; Configure pad (clear OD bit, set IE bit)
ldr     r2, [r3]        ; Read current config
bic     r2, #0x80       ; Clear output disable
orr     r2, #0x40       ; Set input enable
str     r2, [r3]        ; Write back

; Set GPIO function to SIO (0x05)
ldr     r3, =0x40028000 ; IO_BANK0 base
add     r3, r5          ; Add offset
movs    r2, #5          ; FUNCSEL = SIO
str     r2, [r3]        ; Set function

What this does:

  1. Configures the GPIO pad registers (physical pin properties)
  2. Sets the GPIO function to SIO (Software I/O)
  3. Prepares the pin for software control
Step 9: Examine gpio_put Assembly

Find the gpio_put function and examine its implementation.

Look for the GPIO coprocessor instruction:

gpio_put:
    movs    r4, r0          ; GPIO pin number
    movs    r5, r1          ; Value (0 or 1)
    
    ; Use ARM coprocessor to control GPIO
    mcrr    p0, #4, r4, r5, c0
    
    bx      lr              ; Return

Critical instruction: mcrr p0, #4, r4, r5, c0

  • mcrr = Move to Coprocessor from two ARM Registers
  • p0 = Coprocessor 0 (GPIO coprocessor in RP2350)
  • #4 = Operation code
  • r4, r5 = Source registers (pin number, value)
  • c0 = Coprocessor register (GPIO output control)

This is a single-cycle GPIO operation - extremely fast!

Step 10: Document the GPIO Memory Map

Create a reference table of the addresses you found:

Address Register Purpose
0x40028000 IO_BANK0 GPIO function selection
0x40038000 PADS_BANK0 GPIO pad configuration
0xd0000000 SIO Single-cycle I/O (coprocessor)

GPIO 16 specific addresses:

  • Pad control: 0x40038000 + (16 * 4) = 0x40038040
  • Function select: 0x40028000 + (16 * 4) = 0x40028040

Calculate the actual timing:

sleep_ms(0x1f4):

  • Convert: 0x1f4 = (1 × 256) + (15 × 16) + 4 = 256 + 240 + 4 = 500 decimal
  • So the LED is on for 500ms, off for 500ms
  • Total cycle time: 1000ms = 1 second
  • Blink rate: 1 Hz

Expected Output

After completing this exercise, you should understand:

  • How GPIO initialization configures hardware registers
  • The role of the GPIO coprocessor in the RP2350
  • How gpio_put() uses a single ARM instruction for fast I/O
  • The memory-mapped addresses for GPIO control
  • How timing delays are implemented with sleep_ms()

Questions for Reflection

Question 1: Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?
Question 2: What is the advantage of using the GPIO coprocessor instruction (mcrr) instead of writing to memory-mapped registers?
Question 4: What would happen if you called gpio_put() on a pin that hasn't been initialized with gpio_init() first?

Tips and Hints

  • Use Ghidra's References feature (right-click → Find References) to see where functions are called
  • The DisplayMemory Map shows all memory regions
  • Look for bit manipulation instructions (bic, orr) to understand register configuration
  • The ARM Architecture Reference Manual has complete documentation on coprocessor instructions
  • Use hex-to-decimal converters online if you're unsure about conversions

Next Steps

  • Proceed to Exercise 4 to patch the GPIO binary
  • Try to identify other SDK functions like gpio_get() if they appear
  • Explore the full GPIO initialization in the SDK source code

Additional Challenge

Find the gpio_set_dir() function in Ghidra. Does it also use a GPIO coprocessor instruction? What coprocessor register does it use (c0, c4, or something else)? Compare its implementation to gpio_put() and document the differences.