5.9 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 1: Add a Fourth LED
Objective
Find the struct initialization pattern in the 0x0026_functions binary using GDB where led1_pin (0x10), led2_pin (0x11), and led3_pin (0x12) are stored, locate an unused byte in the struct memory region, and patch it to include a fourth LED on GPIO 19 (0x13) by extending the struct data and modifying the ir_to_led_number function to handle a fourth button mapping.
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 (and GPIO 19 wired for the new LED)
Task Description
The simple_led_ctrl_t struct stores three LED pin numbers: led1_pin (16/0x10), led2_pin (17/0x11), led3_pin (18/0x12). These are stored as consecutive bytes in the struct initialization. You will find where the struct is initialized in the binary, locate the movs instructions that set the pin values, and add led4_pin = 19 (0x13) by patching a nearby unused or default byte. You will also need to find where ir_to_led_number returns values 1, 2, or 3 and adjust the NEC command comparison to map a fourth button to LED 4.
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 Struct Initialization
Disassemble main and look for the struct pin assignments:
(gdb) disassemble 0x10000234,+300
Look for consecutive movs instructions:
movs r0, #0x10 ; led1_pin = 16
strb r0, [r4, #0] ; store to struct offset 0
movs r0, #0x11 ; led2_pin = 17
strb r0, [r4, #1] ; store to struct offset 1
movs r0, #0x12 ; led3_pin = 18
strb r0, [r4, #2] ; store to struct offset 2
Step 3: Examine the Struct in Memory
Set a breakpoint after initialization and examine the struct:
(gdb) break *0x10000280
(gdb) monitor reset halt
(gdb) continue
(gdb) x/8bx <struct_base_address>
You should see: 10 11 12 00 00 00 — the three pin values followed by the state booleans (all false/0x00).
Step 4: Find the get_led_pin Function
Look for the function that reads from the struct based on LED number:
(gdb) disassemble 0x100002a0,+50
This function takes a struct pointer and LED number and returns the GPIO pin by reading from a struct offset.
Step 5: Calculate File Offsets
file_offset = address - 0x10000000
Note offsets for:
- The
movs r0, #0x12instruction (last pin assignment) - The byte after
led3_pinin the struct (whereled4_pinwould go)
Step 6: Plan the Patches
| Patch Target | Original | New | Purpose |
|---|---|---|---|
| Struct byte after 0x12 | 00 |
13 |
Add led4_pin = GPIO 19 |
Question 1: The struct layout has led3_pin at offset 2 and led1_state at offset 3. If you write 0x13 to offset 3, what happens to led1_state?
Step 7: Patch with HxD
- In HxD, open
C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin - Navigate to the struct initialization area
- Apply the patches identified in Step 6
Step 8: 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 9: Flash and Verify
- Hold BOOTSEL and plug in your Pico 2
- Drag and drop
hacked.uf2onto the RPI-RP2 drive
Check the behavior:
- Existing buttons 1, 2, 3 should still control their LEDs
- Verify with GDB that the struct now contains
10 11 12 13at the pin offsets
Expected Output
After completing this exercise, you should be able to:
- Understand struct memory layout and member offsets
- Identify struct initialization patterns in ARM assembly
- Patch struct data members in binary firmware
- Reason about the consequences of overwriting adjacent struct fields
Questions for Reflection
Question 1: The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite led1_state. What is the practical impact on LED 1 behavior?
Question 2: How would you verify the exact struct layout and offsets using GDB's memory examination commands?
Question 3: If the get_led_pin function uses a bounds check (e.g., if led_num > 3 return 0), what additional patch would you need?
Question 4: Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce?
Tips and Hints
- GPIO 19 =
0x13in hex - The struct is likely stack-allocated, so the initialization
movs/strbsequence happens every loop iteration - Overwriting
led1_state(offset 3) with0x13means LED 1 will appear as "on" (non-zero boolean) — this may cause LED 1 to be on at startup - The
get_led_pinfunction likely uses the LED number as an index into the struct — trace how it calculates the offset