5.9 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 2: Add a Third Command
Objective
Find the comparison instruction cmp r4, #0x32 ('2') in the 0x0020_dynamic-conditionals binary using GDB, locate the branch target for case '2', and modify existing code so that pressing '3' (0x33) moves the servo to the center position (90°) by patching one of the existing comparisons and its corresponding angle value to 0x42b40000 (90.0f).
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
The program has two active commands: '1' (0x31) moves the servo 0°→180° and '2' (0x32) moves it 180°→0°. Adding a completely new code path would require rewriting branch offsets throughout the binary. Instead, you will repurpose the '2' command by changing its comparison value from 0x32 ('2') to 0x33 ('3') and patching both of its angle values to 0x42b40000 (90.0f), so pressing '3' moves the servo to center and holds it there.
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 the Comparison Instructions
Disassemble main and locate both cmp instructions:
(gdb) disassemble 0x10000234,+250
Look for:
cmp r4, #0x31 ; compare with '1'
beq <target1>
cmp r4, #0x32 ; compare with '2'
beq <target2>
Note the address of the cmp r4, #0x32 instruction.
Step 3: Examine the Comparison Byte
The cmp r4, #0x32 instruction encodes 0x32 as an immediate. Examine the instruction bytes:
(gdb) x/2bx <address_of_cmp_0x32>
You should see a byte containing 0x32.
Step 4: Find the Angle Values for Case '2'
Follow the beq target for case '2' and look for the literal pool loads:
(gdb) x/wx <literal_pool_for_case2_angle1>
(gdb) x/wx <literal_pool_for_case2_angle2>
Case '2' loads 0x43340000 (180.0f) first, then 0x00000000 (0.0f).
Step 5: Calculate the File Offsets
file_offset = address - 0x10000000
Note offsets for:
- The
0x32byte in thecmpinstruction - Both angle literal pool entries for case '2'
Step 6: Encode the New Values
| Patch Target | Original | New | Purpose |
|---|---|---|---|
| Compare byte | 32 |
33 |
Match '3' instead of '2' |
| Angle 1 (180.0f) | 00 00 34 43 |
00 00 b4 42 |
90.0f center position |
| Angle 2 (0.0f) | 00 00 00 00 |
00 00 b4 42 |
90.0f center position |
Step 7: 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 compare byte offset
- Change
32to33 - Go to the first angle offset for case '2' and replace
00 00 34 43with00 00 b4 42 - Go to the second angle offset for case '2' and replace
00 00 00 00with00 00 b4 42
Question 1: Why do we set both angle values to 90.0f instead of just one?
Step 8: 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 9: 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 sweeps 0° to 180° (unchanged)
- Press '3' → servo moves to 90° center and stays
- Press '2' → now falls through to default "??" (no longer mapped)
Expected Output
After completing this exercise, you should be able to:
- Locate and patch comparison immediate values in ARM Thumb instructions
- Repurpose existing code paths instead of adding new ones
- Understand how
cmpimmediates are encoded in the instruction stream - Combine multiple patches (comparison + data) for a single behavior change
Questions for Reflection
Question 1: Why is it easier to repurpose an existing command than to add a truly new third code path in the binary?
Question 2: The cmp instruction uses an 8-bit immediate field. What range of ASCII characters could you use as command keys?
Question 3: After your patch, pressing '2' now triggers the default "??" branch. Could you keep both '2' AND '3' working? What would that require?
Question 4: What would happen if you changed the comparison to 0x00? Could a user ever trigger that command via getchar()?
Tips and Hints
- The
cmp rN, #imm8instruction in Thumb has the immediate in the lower byte of the instruction word - IEEE-754 for 90.0f:
0x42b40000→ little-endian00 00 b4 42 - Be careful not to confuse literal pool entries between case '1' and case '2' — trace the branch targets carefully
- The
getchar()function reads one byte from UART, so any byte value 0x00-0xFF is theoretically possible