mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-18 22:08:08 +02:00
346 lines
9.5 KiB
Markdown
346 lines
9.5 KiB
Markdown
# 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!
|