5.8 KiB
Embedded Systems Reverse Engineering
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.elfand0x0020_dynamic-conditionals.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 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
- In HxD, open
C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin - Press Ctrl+G and go to the case '1' sleep value offset
- Replace
F4 01 00 00with64 00 00 00(100ms) - Press Ctrl+G and go to the case '2' sleep value offset
- Replace
F4 01 00 00withE8 03 00 00(1000ms)
Step 7: Save and Convert
- Click File → Save As →
0x0020_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
- Hold BOOTSEL and plug in your Pico 2
- Drag and drop
hacked.uf2onto 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
100decimal =0x64, fits in one byte:64 00 00 00in little-endian1000decimal =0x3E8:E8 03 00 00in little-endian- If both
sleep_mscalls share one literal pool word, you cannot give them different values by patching data alone — you would need to patch oneldrinstruction to point to a different pool entry or use amovsimmediate - 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