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

9.5 KiB

Embedded Systems Reverse Engineering

Repository

Week 4

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

Non-Credit Practice Exercise 4: Patch GPIO Binary to Change LED Pin

Objective

Patch the 0x0008_uninitialized-variables.bin binary to change which LED blinks, modify the printed value, and change the blink timing, then verify all changes work correctly on hardware.

Prerequisites

  • Completed Exercise 3 (GPIO binary analyzed in Ghidra)
  • Understanding of how GPIO pin numbers are encoded
  • Knowledge of hexadecimal-to-decimal conversion
  • Pico 2 with ability to test multiple GPIO pins

Task Description

You will locate all instances where GPIO 16 is used, patch them to GPIO 17 (changing from red LED to green LED), modify the printed value from 0 to 66, and adjust the blink timing from 500ms to 100ms for faster blinking.

Step-by-Step Instructions

Step 1: Plan Your Patches

Before we start patching, let's identify what needs to change:

Current Value New Value Description Hex Conversion
GPIO 16 GPIO 17 Change LED pin 0x10 → 0x11
age = 0 age = 66 Change printed value 0x00 → 0x42
500ms 100ms Change blink timing 0x1f4 → 0x64

Verify the hex conversions:

  • 17 decimal = 0x11 hex ✓
  • 66 decimal = 0x42 hex ✓
  • 100 decimal = 0x64 hex ✓
Step 2: Open the GPIO Project in Ghidra
  1. Launch Ghidra and open week04-ex03-gpio-analysis
  2. Double-click the binary to open the CodeBrowser
  3. Navigate to the main function

Review the decompiled code:

int main(void)
{
    stdio_init_all();
    gpio_init(0x10);
    gpio_set_dir(0x10, 1);
    
    while (true) {
        printf("age: %d\r\n", 0);
        gpio_put(0x10, 1);
        sleep_ms(0x1f4);
        gpio_put(0x10, 0);
        sleep_ms(0x1f4);
    }
}
Step 3: Find and Patch gpio_init Parameter

Look at the Listing window (assembly view) for the main function.

Find the gpio_init call:

1000023a    10 20    movs    r0, #0x10
1000023c    xx xx    bl      gpio_init

Patch instruction:

  1. Right-click on movs r0, #0x10
  2. Select Patch Instruction
  3. Change #0x10 to #0x11
  4. Press Enter

Result:

1000023a    11 20    movs    r0, #0x11

The instruction bytes change from 10 20 to 11 20.

Step 4: Find and Patch gpio_set_dir Parameter

Continue down the assembly listing:

10000240    10 23    movs    r3, #0x10
10000242    01 22    movs    r2, #1
10000244    xx xx    bl      gpio_set_dir

Patch the r3 load:

  1. Right-click on movs r3, #0x10
  2. Select Patch Instruction
  3. Change to #0x11
  4. Press Enter

Why r3 instead of r0? The SDK might pass GPIO pin as the first parameter differently, or this could be due to register allocation. Trust the analysis!

Step 5: Find All gpio_put Calls

Inside the while loop, there are two gpio_put calls:

First gpio_put (LED ON):

10000252    10 24    movs    r4, #0x10
10000254    01 25    movs    r5, #1
10000256    xx xx    bl      gpio_put

Patch:

  1. Right-click on movs r4, #0x10
  2. Change to #0x11

Second gpio_put (LED OFF):

1000025e    10 24    movs    r4, #0x10
10000260    00 25    movs    r5, #0
10000262    xx xx    bl      gpio_put

Patch:

  1. Right-click on movs r4, #0x10
  2. Change to #0x11

Note: The exact addresses will vary, but the pattern is consistent.

Step 6: Patch the Printed Value

Find the printf call in the loop:

1000024a    00 21    movs    r1, #0x0
1000024c    xx xx    ldr     r0, [pc, #offset]
1000024e    xx xx    bl      printf

Patch the value:

  1. Right-click on movs r1, #0x0
  2. Select Patch Instruction
  3. Change to #0x42 (66 in decimal)
  4. Press Enter

Result:

1000024a    42 21    movs    r1, #0x42
Step 7: Patch the Sleep Timing (First)

Find the first sleep_ms(0x1f4) call:

10000258    f4 21    movs    r1, #0xf4
1000025a    01 20    movs    r0, #1
1000025c    00 04    lsls    r0, r0, #16
1000025e    08 44    add     r0, r1
10000260    xx xx    bl      sleep_ms

Wait, this looks complex! The value 0x1f4 (500) is being constructed:

  • Load 1 into r0
  • Shift left 16 bits: 1 << 16 = 0x10000
  • Load 0xf4 into r1
  • Add them: 0x10000 + 0xf4 = 0x1f4

Alternative pattern (simpler):

10000xxx    f4 20    movs    r0, #0xf4
10000xxx    01 20    movs    r1, #0x01
10000xxx    ...

For 100ms (0x64): Simply find where 0x1f4 is loaded and change it to 0x64.

If the instruction is:

movs    r0, #0x1f4

Change to:

movs    r0, #0x64

Note: The exact encoding depends on the instruction. For immediate values > 255, Thumb-2 uses different encodings.

Step 8: Handle Large Immediate Values

If sleep_ms(500) uses a multi-instruction sequence to load 0x1f4, you have two options:

Option A: Patch both instructions If it's loading 0x1f4 as (0x100 + 0xf4):

  1. Find where 0xf4 is loaded
  2. Change it to 0x64
  3. Find where 0x1 is loaded for the high byte
  4. Change it to 0x0

Option B: Simplify to single instruction Since 0x64 (100) fits in an 8-bit immediate, you can replace the multi-instruction sequence with:

movs    r0, #0x64
nop
nop
Step 9: Verify All Patches

Check the Decompile window to confirm changes:

int main(void)
{
    stdio_init_all();
    gpio_init(0x11);        // Changed from 0x10!
    gpio_set_dir(0x11, 1);
    
    while (true) {
        printf("age: %d\r\n", 0x42);  // Changed from 0!
        gpio_put(0x11, 1);            // Changed from 0x10!
        sleep_ms(0x64);               // Changed from 0x1f4!
        gpio_put(0x11, 0);            // Changed from 0x10!
        sleep_ms(0x64);               // Changed from 0x1f4!
    }
}

Perfect! All changes are reflected.

Step 10: Export the Patched Binary
  1. Click FileExport Program
  2. Set Format: Binary
  3. Navigate to: Embedded-Hacking\0x0008_uninitialized-variables\build\
  4. Filename: 0x0008_uninitialized-variables-h.bin
  5. Click OK
Step 11: Convert to UF2

Open PowerShell and navigate to the project:

cd C:\path\to\Embedded-Hacking\0x0008_uninitialized-variables

Run conversion:

python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2

Expected output:

Converting to uf2, output size: 61952, start address: 0x10000000
Wrote 61952 bytes to build\hacked.uf2
Step 12: Flash and Test

Enter bootloader:

  1. Hold BOOTSEL button
  2. Plug in USB
  3. Release BOOTSEL

Flash:

  1. Drag build\hacked.uf2 to RPI-RP2 drive
  2. Pico reboots automatically

Test with serial monitor:

age: 66
age: 66
age: 66
...

Hardware verification:

  • GREEN LED (GPIO 17) should be blinking
  • RED LED (GPIO 16) should be off
  • Blink rate should be much faster (10 Hz instead of 1 Hz)
  • Serial output shows 66 instead of 0

🎉 Triple success! You've patched three different aspects of the program!

Expected Output

After completing this exercise, you should:

  • See age: 66 printing continuously
  • Observe the green LED (GPIO 17) blinking rapidly
  • Understand how to find and patch all instances of a value
  • Know how to handle different immediate value encoding schemes

Questions for Reflection

Question 1: Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)?
Question 2: What would happen if you forgot to patch one of the gpio_put calls?
Question 3: How does the instruction encoding differ for immediate values less than 256 vs. greater than 255?

Tips and Hints

  • Use Ghidra's SearchFor Scalars to find all instances of a hex value
  • Right-click in Listing → HighlightHighlight Instruction helps track your patches
  • Make notes of addresses you've patched to avoid confusion
  • Test incrementally - patch one thing at a time if you're unsure
  • Keep the original UF2 to revert if needed

Next Steps

  • Try patching to use GPIO 18 (blue LED) instead
  • Change the printf format string to display in hexadecimal
  • Experiment with different timing patterns (e.g., 200ms on, 800ms off)

Additional Challenge

Advanced Multi-LED Pattern:

Patch the binary to create an alternating pattern:

  • GPIO 16 (red) blinks for 100ms
  • GPIO 17 (green) blinks for 100ms
  • GPIO 18 (blue) blinks for 100ms
  • Repeat

This requires:

  1. Adding new gpio_init and gpio_set_dir calls for GPIO 18
  2. Restructuring the while loop to have three gpio_put sequences
  3. Finding space in the binary or replacing existing code

Hint: You might need to NOP out some instructions and carefully insert new ones. This is advanced patching!

Verification Checklist

Before moving on, confirm:

  • GPIO 17 LED blinks (not GPIO 16)
  • Blink rate is approximately 10 Hz (100ms on/off)
  • Serial output shows "age: 66"
  • You can explain each patch you made
  • You understand why each patch was necessary
  • You successfully converted and flashed the UF2

Congratulations! You've completed all Week 4 exercises and mastered variable analysis, binary patching, and GPIO manipulation!