6.5 KiB
Embedded Systems Reverse Engineering
Week 4
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
Non-Credit Practice Exercise 2: Patch Binary to Change Variable Value
Objective
Use Ghidra's patching capabilities to modify the compiled binary, changing the value printed by the program from 43 to a different value of your choice, then convert and flash the modified binary to the Pico 2.
Prerequisites
- Completed Exercise 1 (binary imported and analyzed in Ghidra)
- Python installed for UF2 conversion
uf2conv.pyscript available in your project directory- Raspberry Pi Pico 2 connected via USB
- Serial monitor software (PuTTY, minicom, or screen)
Task Description
You will locate the instruction that loads the value 43 into a register, patch it to use a different value (70 in this example), export the modified binary, convert it to UF2 format, and flash it to verify the change.
Step-by-Step Instructions
Step 1: Open Your Ghidra Project
If you closed Ghidra from Exercise 1:
- Launch
ghidraRun - Open the project:
week04-ex01-intro-to-variables - Double-click the binary file to open it
Step 2: Navigate to the Value Load Instruction
In Exercise 1, we found that main() calls printf("age: %d\r\n", 0x2b).
Find the assembly instruction:
- Click on the
mainfunction in the Symbol Tree - Look at the Listing window (assembly view)
- Find the line with
movs r1, #0x2b
The instruction should look like:
10000xxx 21 2b movs r1, #0x2b
What this instruction means:
- Opcode:
21 2b(encoded instruction bytes) - Mnemonic:
movs r1, #0x2b - Operation: Move the immediate value 0x2b into register r1
- Register r1 will be used as the argument to printf
Step 3: Choose Your New Value
Let's change the value from 43 (0x2b) to 70 (0x46).
Convert 70 to hexadecimal:
- 70 ÷ 16 = 4 remainder 6
- Therefore: 70 decimal = 0x46 hexadecimal
You can verify this in Python:
>>> hex(70)
'0x46'
>>> 0x46
70
Step 4: Patch the Instruction
Now we'll modify the binary:
- Right-click on the instruction
movs r1, #0x2b - Select Patch Instruction
- A dialog appears showing the current instruction
- Change
#0x2bto#0x46 - Press Enter or click OK
The instruction now reads:
10000xxx 21 46 movs r1, #0x46
Visual confirmation:
- The patched instruction should be highlighted (usually in red or orange)
- The bytes should change from
21 2bto21 46 - The decompiled view should update to show
printf("age: %d\r\n", 0x46);
Step 5: Verify the Patch in Decompile Window
Click on the main function again and check the Decompile window:
int main(void)
{
stdio_init_all();
do {
printf("age: %d\r\n", 0x46); // Changed from 0x2b!
} while (true);
}
Perfect! The decompiler recognized our patch.
Step 6: Export the Patched Binary
Now we need to save the modified binary:
- Click File → Export Program
- Set Format to: Binary
- Navigate to your build directory:
Embedded-Hacking\0x0005_intro-to-variables\build\
- Set Filename to:
0x0005_intro-to-variables-h.bin- The
-hsuffix means "hacked"
- The
- Click OK
Important: Make sure you're exporting to the correct location!
Step 7: Convert to UF2 Format
The Pico 2 requires UF2 format. Open a terminal and run:
Navigate to the project directory:
cd C:\path\to\Embedded-Hacking\0x0005_intro-to-variables
Run the conversion:
python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
Command breakdown:
uf2conv.py- The conversion script--base 0x10000000- XIP flash base address (where code runs from)--family 0xe48bff59- RP2350 family ID--output build\hacked.uf2- Output filename
Expected output:
Converting to uf2, output size: 57856, start address: 0x10000000
Wrote 57856 bytes to build\hacked.uf2
Step 8: Flash the Hacked Binary
Enter bootloader mode:
- Disconnect your Pico 2 from USB
- Hold down the BOOTSEL button
- While holding BOOTSEL, plug in the USB cable
- Release BOOTSEL
- A drive called RPI-RP2 appears
Flash the binary:
- Open the RPI-RP2 drive
- Drag and drop
build\hacked.uf2onto the drive - The Pico will automatically reboot
Step 9: Verify the Changes
Open your serial monitor:
For PuTTY:
- Select Serial connection type
- Set the COM port (check Device Manager)
- Set speed to 115200
- Click Open
For PowerShell:
# Find the COM port
Get-PnpDevice -Class Ports | Where-Object {$_.FriendlyName -like "*USB Serial*"}
# Connect (replace COM3 with your port)
$port = new-Object System.IO.Ports.SerialPort COM3,115200,None,8,one
$port.Open()
while($true) { $port.ReadLine() }
Expected output:
age: 70
age: 70
age: 70
age: 70
...
🎉 Success! You've successfully patched a binary and changed its behavior!
Expected Output
After completing this exercise, you should:
- See
age: 70printing instead ofage: 43 - Have a patched binary file (
0x0005_intro-to-variables-h.bin) - Have a UF2 file (
hacked.uf2) - Understand the complete patching workflow
Questions for Reflection
Question 1: Why do we need to convert to UF2 format instead of flashing the raw .bin file?
Question 2: What is the significance of the base address 0x10000000 in the conversion command?
Question 3: What would happen if you patched the wrong instruction by mistake?
Question 4: How can you verify a patch was applied correctly before exporting?
Tips and Hints
- Always make a backup of the original binary before patching
- Use descriptive names like
-h(hacked) or-patchfor modified binaries - Test your patches on hardware to ensure they work as expected
- If something goes wrong, you can always flash the original UF2 file
- Use
File → Export Program → Original Fileto revert all patches
Next Steps
- Try patching to different values (100, 255, etc.)
- Proceed to Exercise 3 to learn about GPIO control
- Experiment with patching multiple values in the same binary
Additional Challenge
Instead of changing the value to 70, change it to 255 (0xFF) - the maximum value for a uint8_t. What do you observe? Now try changing it to 256 (0x100). What happens and why? (Hint: Think about the size limits of the instruction encoding.)