# Embedded Systems Reverse Engineering [Repository](https://github.com/mytechnotalent/Embedded-Hacking) ## Week 4 Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics ### 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 **File** → **New 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:** ```c 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: ```c 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:** ```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:** ```assembly 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` ##### Step 11: Trace the Blink Timing 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 3: If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use? ###### 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 **Display** → **Memory 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.