# 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 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:** ```c 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:** ```assembly 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:** ```assembly 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: ```assembly 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):** ```assembly 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):** ```assembly 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: ```assembly 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:** ```assembly 1000024a 42 21 movs r1, #0x42 ``` ##### Step 7: Patch the Sleep Timing (First) Find the first `sleep_ms(0x1f4)` call: ```assembly 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):** ```assembly 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:** ```assembly movs r0, #0x1f4 ``` **Change to:** ```assembly 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: ```assembly movs r0, #0x64 nop nop ``` ##### Step 9: Verify All Patches Check the **Decompile** window to confirm changes: ```c 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 **File** → **Export 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: ```powershell cd C:\path\to\Embedded-Hacking\0x0008_uninitialized-variables ``` **Run conversion:** ```powershell 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? ###### 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: 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!