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

5.9 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 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.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

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:

  1. The 0x32 byte in the cmp instruction
  2. 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
  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 compare byte offset
  3. Change 32 to 33
  4. Go to the first angle offset for case '2' and replace 00 00 34 43 with 00 00 b4 42
  5. Go to the second angle offset for case '2' and replace 00 00 00 00 with 00 00 b4 42
Question 1: Why do we set both angle values to 90.0f instead of just one?
Step 8: 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 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:

  • 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 cmp immediates 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, #imm8 instruction in Thumb has the immediate in the lower byte of the instruction word
  • IEEE-754 for 90.0f: 0x42b40000 → little-endian 00 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