5.3 KiB
Embedded Systems Reverse Engineering
Week 11
Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics
Non-Credit Practice Exercise 2: Change Blink Count
Objective
Find the blink_led(pin, 3, 50) call in the 0x0026_functions binary using GDB, identify the immediate value #3 being loaded into r1 (the blink count parameter), calculate the file offset, and patch it to #5 so that each LED blinks 5 times instead of 3 when activated by the IR remote.
Prerequisites
- Completed Week 11 tutorial (GDB and hex editor sections)
0x0026_functions.elfand0x0026_functions.binavailable in your build directory- GDB (
arm-none-eabi-gdb) and OpenOCD installed - A hex editor (HxD, ImHex, or similar)
- Python installed (for UF2 conversion)
- Raspberry Pi Pico 2 with IR remote and LEDs on GPIO 16, 17, 18
Task Description
The blink_led function is called with three parameters: the GPIO pin number, a blink count of 3, and a delay of 50ms. The blink count is loaded as a small immediate value (movs r1, #3) directly in the instruction before the bl blink_led call. You will locate this instruction, find the byte encoding the #3 immediate, and patch it to #5 so the LEDs blink 5 times per button press.
Step-by-Step Instructions
Step 1: Start the Debug Session
Terminal 1 - Start OpenOCD:
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
Terminal 2 - Start GDB:
arm-none-eabi-gdb build\0x0026_functions.elf
Connect to target:
(gdb) target remote :3333
(gdb) monitor reset halt
Step 2: Find the blink_led Call
Disassemble main and look for the function call sequence:
(gdb) disassemble 0x10000234,+300
Look for the parameter setup before bl blink_led:
movs r0, <pin> ; GPIO pin number (from get_led_pin)
movs r1, #3 ; blink count = 3
movs r2, #0x32 ; delay = 50ms
bl blink_led
Note the address of the movs r1, #3 instruction.
Step 3: Examine the Instruction Encoding
Look at the raw bytes of the movs r1, #3 instruction:
(gdb) x/2bx <address_of_movs_r1_3>
In Thumb encoding, movs r1, #imm8 has the immediate in the lower byte. You should see a byte containing 03.
Step 4: Calculate the File Offset
file_offset = address - 0x10000000
Note the file offset of the byte containing 03.
Step 5: Encode the New Value
| Parameter | Original | New | Encoding |
|---|---|---|---|
| Blink count | 03 |
05 |
movs r1, #5 |
Step 6: Patch with HxD
- In HxD, open
C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin - Press Ctrl+G and enter the file offset
- You should see:
03 - Replace with:
05
Question 1: The movs r1, #3 is a 2-byte Thumb instruction. Which byte contains the immediate — the first or the second?
Step 7: Save and Convert
- Click File → Save As →
0x0026_functions-h.bin
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions
python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
Step 8: Flash and Verify
- Hold BOOTSEL and plug in your Pico 2
- Drag and drop
hacked.uf2onto the RPI-RP2 drive
Check the behavior:
- Press button 1 → Red LED blinks 5 times (was 3), then stays on
- Press button 2 → Green LED blinks 5 times (was 3), then stays on
- Press button 3 → Yellow LED blinks 5 times (was 3), then stays on
- Count carefully — you should see exactly 5 on/off cycles
Expected Output
After completing this exercise, you should be able to:
- Locate small immediate values in Thumb
movsinstructions - Understand Thumb instruction encoding for immediate operands
- Patch function parameters by modifying instruction immediates
- Verify behavioral changes by counting observable events
Questions for Reflection
Question 1: The movs rN, #imm8 instruction can encode values 0-255. What is the maximum blink count you could set with a single byte patch?
Question 2: Why is the blink count passed in r1 and not r0? What does r0 hold at this point in the calling convention?
Question 3: If you wanted to set the blink count to 256 or higher, the movs immediate would not be enough. What instruction sequence would the compiler need to generate instead?
Question 4: The same blink_led function is called for all three buttons. Does that mean there is only one movs r1, #3 to patch, or could there be multiple call sites?
Tips and Hints
- Small immediates (0-255) are encoded directly in the
movsinstruction — no literal pool needed - The ARM calling convention uses
r0,r1,r2,r3for the first four function parameters in order - Look for
movs r1, #3right beforebl blink_led— there may be one shared call site or multiple per button - If there are multiple
movs r1, #3instructions (one per case), you need to patch all of them for consistent behavior