9.5 KiB
Embedded Systems Reverse Engineering
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
- Launch Ghidra and open
week04-ex03-gpio-analysis - Double-click the binary to open the CodeBrowser
- Navigate to the
mainfunction
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:
- Right-click on
movs r0, #0x10 - Select Patch Instruction
- Change
#0x10to#0x11 - 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:
- Right-click on
movs r3, #0x10 - Select Patch Instruction
- Change to
#0x11 - 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:
- Right-click on
movs r4, #0x10 - 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:
- Right-click on
movs r4, #0x10 - 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:
- Right-click on
movs r1, #0x0 - Select Patch Instruction
- Change to
#0x42(66 in decimal) - 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):
- Find where 0xf4 is loaded
- Change it to 0x64
- Find where 0x1 is loaded for the high byte
- 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
- Click File → Export Program
- Set Format: Binary
- Navigate to:
Embedded-Hacking\0x0008_uninitialized-variables\build\ - Filename:
0x0008_uninitialized-variables-h.bin - 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:
- Hold BOOTSEL button
- Plug in USB
- Release BOOTSEL
Flash:
- Drag
build\hacked.uf2to RPI-RP2 drive - 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: 66printing 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?
Question 4: If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?
Tips and Hints
- Use Ghidra's Search → For Scalars to find all instances of a hex value
- Right-click in Listing → Highlight → Highlight 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:
- Adding new gpio_init and gpio_set_dir calls for GPIO 18
- Restructuring the while loop to have three gpio_put sequences
- 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!