4.5 KiB
Embedded Systems Reverse Engineering
Week 4
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
Non-Credit Practice Exercise 4 Solution: Patch GPIO Binary to Change LED Pin
Answers
Patch Summary
| Item | Original | Patched | Hex Change |
|---|---|---|---|
| LED pin | GPIO 16 | GPIO 17 | 0x10 → 0x11 |
| Printed value | 0 (uninitialized) | 66 | 0x00 → 0x42 |
| Blink timing | 500ms | 100ms | 0x1f4 → 0x64 |
Detailed Patch Locations
1. gpio_init parameter:
; Before: movs r0, #0x10 (bytes: 10 20)
; After: movs r0, #0x11 (bytes: 11 20)
2. gpio_set_dir parameter:
; Before: movs r3, #0x10 (bytes: 10 23)
; After: movs r3, #0x11 (bytes: 11 23)
3. gpio_put (LED ON) parameter:
; Before: movs r4, #0x10 (bytes: 10 24)
; After: movs r4, #0x11 (bytes: 11 24)
4. gpio_put (LED OFF) parameter:
; Before: movs r4, #0x10 (bytes: 10 24)
; After: movs r4, #0x11 (bytes: 11 24)
5. printf value:
; Before: movs r1, #0x00 (bytes: 00 21)
; After: movs r1, #0x42 (bytes: 42 21)
6. sleep_ms (both calls):
; Before: loads 0x1f4 (500ms)
; After: movs r0, #0x64 (100ms)
Hex Conversions
GPIO 17: 17 = 0x11 = 0001 0001 binary
Value 66: 66 = 0x42 = 0100 0010 binary
100ms: 100 = 0x64 = 0110 0100 binary
Decompiled Result After All Patches
int main(void)
{
stdio_init_all();
gpio_init(0x11); // GPIO 17 (green LED)
gpio_set_dir(0x11, 1); // Output
while (true) {
printf("age: %d\r\n", 0x42); // Prints 66
gpio_put(0x11, 1); // Green LED ON
sleep_ms(0x64); // 100ms
gpio_put(0x11, 0); // Green LED OFF
sleep_ms(0x64); // 100ms
}
}
Hardware Verification
- GREEN LED (GPIO 17) blinks at 10 Hz (100ms on, 100ms off)
- RED LED (GPIO 16) remains off
- Serial output:
age: 66repeating
Reflection Answers
-
Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)? Each function takes the GPIO pin number as a separate parameter.
gpio_init(16)configures the pad and function mux for pin 16.gpio_set_dir(16, 1)sets pin 16's direction to output.gpio_put(16, value)toggles pin 16's output state. These are independent function calls with independent immediate values in the instruction stream—the compiler doesn't share or reuse a single "pin number variable." Eachmovs rN, #0x10loads the pin number fresh for its respective function call. Missing any one patch would result in a mismatch: e.g., initializing pin 17 but toggling pin 16. -
What would happen if you forgot to patch one of the gpio_put calls? You would get asymmetric behavior. For example, if you patched the "LED ON"
gpio_putto pin 17 but left the "LED OFF" at pin 16: GPIO 17 (green) would turn on but never turn off (staying permanently lit), while GPIO 16 (red) would receive the "off" command for a pin that was never initialized—which would have no visible effect. The result: green LED stuck on, no blinking. -
How does the instruction encoding differ for immediate values less than 256 vs. greater than 255? In 16-bit Thumb encoding,
movs Rd, #imm8can only encode immediate values 0–255 in a single 2-byte instruction. For values > 255 (like 500 = 0x1f4), the compiler must use either: (a) a 32-bit Thumb-2movw Rd, #imm16instruction (4 bytes, can encode 0–65535), (b) a multi-instruction sequence that constructs the value (e.g.,movs+lsls+add), or (c) anldr Rd, [pc, #offset]that loads the constant from a literal pool in flash. This is why patchingsleep_ms(500)may be more complex than patchinggpio_put(16, 1). -
If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use? At 5 Hz, each complete cycle is
1000 / 5 = 200ms. With twosleep_ms()calls per cycle (ON and OFF), each call should be200 / 2 = 100ms. In hex:100 = 0x64. Sosleep_ms(0x64)for each call—which is exactly the value used in this exercise's patch. For a different duty cycle (e.g., 150ms on, 50ms off), you'd use different values for each call while keeping the total at 200ms.