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

5.8 KiB

Embedded Systems Reverse Engineering

Repository

Week 10

Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics

Non-Credit Practice Exercise 4: Speed Profile

Objective

Find both sleep_ms(500) calls in the 0x0020_dynamic-conditionals binary using GDB, identify the literal pool values 0x1f4 (500) loaded into r0 before each bl sleep_ms, calculate the file offsets, and patch case '1' to use 0x64 (100ms) for fast movement and case '2' to use 0x3e8 (1000ms) for slow movement, then verify on hardware that the two keys produce visibly different servo speeds.

Prerequisites

  • Completed Week 10 tutorial (GDB and hex editor sections)
  • 0x0020_dynamic-conditionals.elf and 0x0020_dynamic-conditionals.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 SG90 servo motor connected on GPIO 6

Task Description

Both case '1' and case '2' call sleep_ms(500) between their two servo_set_angle calls. The value 500 (0x1F4) is loaded from the literal pool into r0 before each bl sleep_ms. You will find both sleep_ms literal pool entries — one in case '1' and one in case '2' — and patch them to different values: 100 (0x64) for fast snapping and 1000 (0x3E8) for slow sweeping, creating distinct speed profiles per key.

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\0x0020_dynamic-conditionals.elf

Connect to target:

(gdb) target remote :3333
(gdb) monitor reset halt
Step 2: Find Both sleep_ms Calls

Disassemble main and locate the sleep_ms calls:

(gdb) disassemble 0x10000234,+250

Look for the pattern repeated in both cases:

ldr  r0, [pc, #offset]     ; load delay value
bl   sleep_ms

Each case has at least one sleep_ms call. Identify which belongs to case '1' and which to case '2' by tracing the branch targets.

Step 3: Examine the Literal Pool Entries

For each sleep_ms call, examine the referenced literal pool entry:

(gdb) x/wx <case1_sleep_literal>
(gdb) x/wx <case2_sleep_literal>

Both should show 0x000001f4 (500).

Step 4: Calculate the File Offsets
file_offset = literal_pool_address - 0x10000000

Note the file offsets for both 4-byte sleep values.

Step 5: Encode the New Values
Case Original New Speed
Case 1 F4 01 00 00 (500ms) 64 00 00 00 (100ms) Fast snap
Case 2 F4 01 00 00 (500ms) E8 03 00 00 (1000ms) Slow sweep
Question 1: Each case calls sleep_ms twice (once between the first and second servo_set_angle). Do both share the same literal pool entry, or does each have its own?
Step 6: Patch with HxD
  1. In HxD, open C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin
  2. Press Ctrl+G and go to the case '1' sleep value offset
  3. Replace F4 01 00 00 with 64 00 00 00 (100ms)
  4. Press Ctrl+G and go to the case '2' sleep value offset
  5. Replace F4 01 00 00 with E8 03 00 00 (1000ms)
Step 7: Save and Convert
  1. Click FileSave As0x0020_dynamic-conditionals-h.bin
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
Step 8: 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:

  • Press '1' → servo snaps fast (100ms between angles) — almost instant jump
  • Press '2' → servo moves slow (1000ms between angles) — takes a full second at each position
  • The speed difference should be very obvious visually and audibly

Expected Output

After completing this exercise, you should be able to:

  • Distinguish between multiple literal pool entries for the same value
  • Trace which code path references which literal pool entry
  • Patch timing constants independently per branch
  • Understand how sleep duration affects perceived motor behavior

Questions for Reflection

Question 1: Why does 100ms make the servo appear to "snap" while 1000ms makes it appear to "sweep"? Is the servo actually moving faster, or is it about the pause between commands?
Question 2: If the compiler uses a single shared literal pool entry for all sleep_ms(500) calls, what alternative patching strategy would you need to create different speeds per case?
Question 3: What is the minimum sleep_ms value that would still allow the servo to physically reach its target angle before the next command? How would you determine this experimentally?
Question 4: Could you set the sleep to 0 (00 00 00 00)? What would happen to the servo behavior?

Tips and Hints

  • 100 decimal = 0x64, fits in one byte: 64 00 00 00 in little-endian
  • 1000 decimal = 0x3E8: E8 03 00 00 in little-endian
  • If both sleep_ms calls share one literal pool word, you cannot give them different values by patching data alone — you would need to patch one ldr instruction to point to a different pool entry or use a movs immediate
  • The SG90 servo takes about 200-300ms to traverse its full range, so 100ms will cause it to not quite reach the endpoint before the next command fires