Files
Embedded-Hacking/WEEK11/WEEK11-01.md
2026-03-19 15:01:07 -04:00

5.9 KiB

Embedded Systems Reverse Engineering

Repository

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.elf and 0x0026_functions.bin available 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:

  1. The movs r0, #0x12 instruction (last pin assignment)
  2. The byte after led3_pin in the struct (where led4_pin would 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
  1. In HxD, open C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin
  2. Navigate to the struct initialization area
  3. Apply the patches identified in Step 6
Step 8: Save and Convert
  1. Click FileSave As0x0026_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
  1. Hold BOOTSEL and plug in your Pico 2
  2. Drag and drop hacked.uf2 onto 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 13 at 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 = 0x13 in hex
  • The struct is likely stack-allocated, so the initialization movs/strb sequence happens every loop iteration
  • Overwriting led1_state (offset 3) with 0x13 means LED 1 will appear as "on" (non-zero boolean) — this may cause LED 1 to be on at startup
  • The get_led_pin function likely uses the LED number as an index into the struct — trace how it calculates the offset