Added WEEK06 and WEEK07

This commit is contained in:
Kevin Thomas
2026-03-01 12:45:26 -05:00
parent a751314efd
commit 7c3017c942
11 changed files with 3423 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 1: Change the Static Variable Initial Value from 42 to 100
#### Objective
Use GDB to locate the static variable `static_fav_num` in the `.data` section of the `0x0014_static-variables` binary, calculate the corresponding file offset, patch the initial value from `42` (`0x2A`) to `100` (`0x64`) using a hex editor, convert the patched binary to UF2 format, and flash it to the Pico 2 to verify the change.
#### Prerequisites
- Completed Week 6 tutorial (GDB section)
- `0x0014_static-variables.bin` binary 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 connected via USB
- Serial monitor software (PuTTY, minicom, or screen)
#### Task Description
You will use GDB to examine the static variable at its known RAM address (`0x200005a8`), trace back to where the initial value is stored in the `.data` section of flash, calculate the file offset in the `.bin` file, patch the byte from `0x2A` to `0x64` with a hex editor, and verify on hardware that the serial output now starts counting from `100` instead of `42`.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```bash
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:**
```bash
arm-none-eabi-gdb build/0x0014_static-variables.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Examine the Static Variable in RAM
We know from the tutorial that the static variable lives at `0x200005a8`. Examine its current value:
```gdb
(gdb) x/1db 0x200005a8
```
You should see the current value (probably `42` or a count if the program has been running).
##### Step 3: Find Where the Initial Value Lives in Flash
Static variables that are initialized get their starting values from the `.data` section in flash. The startup code copies these values from flash to RAM before `main()` runs.
Examine the disassembly to find data copy references. The initial value `42` (`0x2A`) is stored somewhere in flash. Use GDB to search:
```gdb
(gdb) find /b 0x10000000, 0x10010000, 0x2a
```
This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section).
##### Step 4: Confirm the Address
Once you identify a candidate address, verify it by examining the surrounding bytes:
```gdb
(gdb) x/8xb <candidate_address>
```
The `0x2A` byte at this address should be the initialization value for our static variable.
##### Step 5: Calculate the File Offset
The binary is loaded at base address `0x10000000`. To find the file offset:
```
file_offset = address - 0x10000000
```
For example, if the initial value is at `0x10004xxx`:
- File offset = `0x10004xxx` - `0x10000000` = `0x4xxx`
##### Step 6: Patch the Value with a Hex Editor
1. Open `0x0014_static-variables.bin` in HxD
2. Press **Ctrl+G** (Go to offset)
3. Enter the calculated offset
4. You should see the byte `2A` at this position
5. Change `2A` to `64` (100 in decimal)
6. Click **File****Save As**`0x0014_static-variables-h.bin`
##### Step 7: Convert to UF2 and Flash
```bash
python ../uf2conv.py build/0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2
```
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
3. Open your serial monitor
##### Step 8: Verify the Hack
**Expected serial output:**
```
regular_fav_num: 42
static_fav_num: 100 ← Starts at 100 now!
regular_fav_num: 42
static_fav_num: 101
regular_fav_num: 42
static_fav_num: 102
...
```
The static variable now starts counting from 100 instead of 42!
#### Expected Output
After completing this exercise, you should be able to:
- Use GDB `find` command to search for byte patterns in flash
- Distinguish between RAM addresses (where the variable lives at runtime) and flash addresses (where the initial value is stored)
- Calculate file offsets from memory addresses
- Patch initialization values with a hex editor
- Understand the `.data` section copy mechanism at startup
#### Questions for Reflection
###### Question 1: Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?
###### Question 2: The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0?
###### Question 3: If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?
###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?
#### Tips and Hints
- The `find` command in GDB can search for bytes, halfwords, or words in any memory range
- Static variables with initial values are in `.data`; without initial values they're in `.bss`
- The startup code (`crt0`) copies the entire `.data` section from flash to RAM before calling `main()`
- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*`
#### Next Steps
- Proceed to Exercise 2 to try a more complex hack (reversing GPIO logic)
- Try patching the static variable to `0xFF` (255) and observe immediate overflow behavior
- Look at the startup code in GDB to find the memcpy that copies `.data` from flash to RAM
+185
View File
@@ -0,0 +1,185 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 2: Reverse Engineer gpio_set_pulls with GDB
#### Objective
Use GDB to disassemble the `gpio_set_pulls` function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level.
#### Prerequisites
- Completed Week 6 tutorial (GDB section)
- `0x0014_static-variables.elf` binary available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- Understanding of GPIO pull-up resistors from Week 6 Part 2
- Understanding of function inlining from Week 6 Part 3
#### Task Description
You will use GDB to locate the `gpio_set_pulls` function (remember: `gpio_pull_up` is inlined and becomes `gpio_set_pulls`), disassemble it, step through it instruction by instruction, and identify the PADS_BANK0 register address it writes to. You will document the bit fields being set and explain how the hardware implements pull-up and pull-down resistors.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```bash
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:**
```bash
arm-none-eabi-gdb build/0x0014_static-variables.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Find Where gpio_set_pulls is Called
From the tutorial, we know `gpio_set_pulls` is called at `0x10000250` with arguments for GPIO 15:
```gdb
(gdb) x/5i 0x1000024a
```
You should see:
```
0x1000024a: movs r0, #15 ; GPIO pin 15
0x1000024c: movs r1, #1 ; up = true
0x1000024e: movs r2, #0 ; down = false
0x10000250: bl 0x100002dc ; gpio_set_pulls
```
##### Step 3: Disassemble gpio_set_pulls
Now examine the function itself:
```gdb
(gdb) x/30i 0x100002dc
```
Study the disassembly carefully. Look for:
- Address calculations (base address of PADS_BANK0)
- Register loads and stores
- Bit manipulation instructions (AND, OR, BIC)
- The write to the hardware register
##### Step 4: Set a Breakpoint at gpio_set_pulls
```gdb
(gdb) b *0x100002dc
(gdb) monitor reset halt
(gdb) c
```
When the breakpoint hits, examine the arguments:
```gdb
(gdb) info registers r0 r1 r2
```
You should see:
- `r0 = 15` (GPIO pin)
- `r1 = 1` (pull-up enable)
- `r2 = 0` (pull-down disable)
##### Step 5: Step Through the Function
Use `si` (step instruction) to execute one instruction at a time:
```gdb
(gdb) si
(gdb) info registers
```
Repeat, watching the registers change. Pay attention to:
1. **Address calculation**: The function computes the PADS_BANK0 register address for GPIO 15. The base address is `0x40038000`, and each GPIO pad has a 4-byte register. GPIO 0 starts at offset `0x04`, so GPIO 15 is at:
$$0x40038000 + 0x04 + (15 \times 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040$$
2. **Read the current register value**: The function reads the existing pad configuration
3. **Modify the pull bits**: It sets or clears the pull-up (bit 3) and pull-down (bit 2) bits
4. **Write back**: It stores the modified value
##### Step 6: Examine the PADS_BANK0 Register
After the function completes, examine the result:
```gdb
(gdb) x/1wx 0x40038040
```
Document the value you see. The relevant bits are:
| Bit | Name | Value | Meaning |
| --- | -------- | ----- | ----------------------- |
| 3 | PUE | 1 | Pull-up enable |
| 2 | PDE | 0 | Pull-down enable |
| 1 | SCHMITT | 1 | Schmitt trigger enabled |
| 0 | SLEWFAST | 0 | Slow slew rate |
##### Step 7: Compare with gpio_set_pulls for the LED Pin
Continue execution until gpio_set_pulls is called again (if it is). Or, examine the pad register for GPIO 16 (the LED pin):
```gdb
(gdb) x/1wx 0x40038044
```
Compare the values. The LED pin (output) should NOT have pull-up enabled.
##### Step 8: Document Your Findings
Create a table documenting the function's behavior:
| Step | Instruction(s) | Register Changes |
| ------------------------ | ------------------ | --------------------------- |
| Load PADS base address | `ldr rX, =...` | rX = `0x40038000` |
| Calculate pad offset | `adds`, `lsls` | offset = `0x04 + pin * 4` |
| Read current pad config | `ldr rX, [addr]` | rX = current register value |
| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 and 3 |
| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE) |
| Write back | `str rX, [addr]` | Updated pad register |
#### Expected Output
After completing this exercise, you should be able to:
- Disassemble and trace through a hardware configuration function
- Identify PADS_BANK0 register addresses for any GPIO pin
- Understand how pull-up and pull-down resistors are controlled at the register level
- Explain why `gpio_pull_up(pin)` becomes `gpio_set_pulls(pin, true, false)` in the binary
#### Questions for Reflection
###### Question 1: Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?
###### Question 2: The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?
###### Question 3: What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?
###### Question 4: The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?
#### Tips and Hints
- PADS_BANK0 base address on RP2350 is `0x40038000`
- Each GPIO has a 4-byte pad control register starting at offset `0x04`
- The `bic` instruction clears bits (Bit Clear); `orr` sets bits (OR)
- Use `x/1wx` to examine a 32-bit word; use `x/1tb` to see individual bits
- The RP2350 datasheet section on PADS_BANK0 has the full register bit map
#### Next Steps
- Proceed to Exercise 3 to reverse engineer the `eor` (XOR) instruction in the GPIO logic
- Try enabling pull-down instead of pull-up by modifying the `gpio_set_pulls` arguments in GDB: `set $r1 = 0` and `set $r2 = 1` before the function call
- Examine the PADS registers for all GPIOs to see which pins have pull-ups enabled after boot
+181
View File
@@ -0,0 +1,181 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 3: Make the Overflow Happen Faster
#### Objective
Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner.
#### Prerequisites
- Completed Week 6 tutorial (GDB and hex editor sections)
- `0x0014_static-variables.bin` binary 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 connected via USB
- Serial monitor software (PuTTY, minicom, or screen)
#### Task Description
The static variable `static_fav_num` is a `uint8_t` that counts from 42 to 255 before wrapping to 0. Currently it increments by 1 each iteration, so it takes 214 steps to overflow. You will change the increment value from 1 to 10 so that it overflows after only ~22 steps. This exercise teaches you how Thumb immediate encoding works for small arithmetic instructions and demonstrates the real-world impact of patching arithmetic operations.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```bash
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:**
```bash
arm-none-eabi-gdb build/0x0014_static-variables.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Locate the Increment Instruction
From the tutorial, we know the static variable operations are around address `0x10000260`. Disassemble the loop body:
```gdb
(gdb) x/20i 0x10000260
```
Look for this sequence:
```
ldrb r3, [r4, #0x0] ; Load static_fav_num from RAM
adds r3, #0x1 ; Increment by 1 ← THIS IS OUR TARGET
strb r3, [r4, #0x0] ; Store back to RAM
```
Note the exact address of the `adds r3, #0x1` instruction.
##### Step 3: Examine the Instruction Encoding
Look at the raw bytes of the instruction:
```gdb
(gdb) x/2bx <address_of_adds>
```
You should see:
```
01 33
```
**Thumb encoding breakdown:**
- `01` = the immediate value `0x01` (decimal 1)
- `33` = the opcode for `adds r3, #imm8`
##### Step 4: Test the Change in GDB First
Before making a permanent patch, test the change in RAM:
```gdb
(gdb) b *<address_of_adds>
(gdb) c
```
When the breakpoint hits:
```gdb
(gdb) x/1db 0x200005a8
```
Note the current value of `static_fav_num`. Now continue a few iterations and check how it increments.
##### Step 5: Calculate the File Offset
```
file_offset = address - 0x10000000
```
For the `adds r3, #0x1` instruction at its address, calculate the offset.
##### Step 6: Patch with the Hex Editor
1. Open `0x0014_static-variables.bin` in HxD
2. Press **Ctrl+G** (Go to offset) and enter the calculated offset
3. You should see the byte `01` followed by `33`
4. Change `01` to `0A` (10 in decimal)
5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa`
6. Click **File****Save As**`0x0014_static-variables-h.bin`
> 🔍 **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe.
##### Step 7: Convert to UF2 and Flash
```bash
python ../uf2conv.py build/0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2
```
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
3. Open your serial monitor
##### Step 8: Verify the Hack
**Expected serial output:**
```
regular_fav_num: 42
static_fav_num: 42
regular_fav_num: 42
static_fav_num: 52 ← Jumped by 10!
regular_fav_num: 42
static_fav_num: 62
...
regular_fav_num: 42
static_fav_num: 242
regular_fav_num: 42
static_fav_num: 252
regular_fav_num: 42
static_fav_num: 6 ← Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6
```
Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`.
#### Expected Output
After completing this exercise, you should be able to:
- Locate arithmetic instructions in disassembled code
- Understand Thumb `adds rD, #imm8` encoding
- Patch an immediate operand in a hex editor
- Predict the effects of changing an increment value on overflow behavior
#### Questions for Reflection
###### Question 1: The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).
###### Question 2: What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255?
###### Question 3: If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?
###### Question 4: Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?
#### Tips and Hints
- In Thumb encoding, `adds rD, #imm8` has the immediate in the first byte and opcode in the second
- The `imm8` field is 8 bits, so valid ranges are `0x00` to `0xFF` (0-255)
- Overflow for `uint8_t` is $(value) \bmod 256$
- Use GDB to verify: `set *(unsigned char*)0x200005a8 = 250` then continue to watch the wrap quickly
#### Next Steps
- Proceed to Exercise 4 to learn about inverting button logic with XOR
- Try changing the increment to other values (2, 5, 50, 128) and predict the wrap behavior before flashing
- Consider: what would happen if you changed `adds` to `subs` (subtract)?
+188
View File
@@ -0,0 +1,188 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 4: Invert the Button Logic with XOR
#### Objective
Find the `eor r3, r3, #0x1` instruction that implements the ternary operator's button inversion, patch it to `eor r3, r3, #0x0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior.
#### Prerequisites
- Completed Week 6 tutorial (all GDB and hex editor sections)
- `0x0014_static-variables.bin` binary 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 button on GP15 and LED on GP16
#### Task Description
The original program uses `gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO))` which the compiler implements as an XOR (`eor r3, r3, #0x1`) to invert the button state. With the pull-up resistor, button released = HIGH, so `HIGH XOR 1 = 0` (LED off). You will patch the XOR operand from `#0x1` to `#0x0`, which effectively removes the inversion: `HIGH XOR 0 = 1` (LED on when released). This exercise demonstrates how a single-byte binary patch can completely reverse hardware behavior.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```bash
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:**
```bash
arm-none-eabi-gdb build/0x0014_static-variables.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Locate the GPIO Logic
From the tutorial, the GPIO input/output logic is near address `0x10000274`. Disassemble:
```gdb
(gdb) x/10i 0x10000274
```
Look for this sequence:
```
mov.w r1, #0xd0000000 ; SIO base address
ldr r3, [r1, #offset] ; Read GPIO input register
ubfx r3, r3, #0xf, #0x1 ; Extract bit 15 (button state)
eor r3, r3, #0x1 ; XOR with 1 — INVERT ← OUR TARGET
mcrr p0, 0x4, r2, r3, cr0 ; Write to GPIO output
```
Note the exact address of the `eor r3, r3, #0x1` instruction.
##### Step 3: Understand the Current Logic
Trace the logic with the pull-up resistor:
| Button State | GPIO 15 Input | After UBFX | After EOR #1 | LED (GPIO 16) |
| ------------ | ------------- | ---------- | ------------ | -------------- |
| Released | 1 (HIGH) | 1 | 0 | OFF |
| Pressed | 0 (LOW) | 0 | 1 | ON |
The `eor #0x1` flips the bit, implementing the `!` (NOT) from the C code.
##### Step 4: Verify with GDB
Set a breakpoint at the `eor` instruction:
```gdb
(gdb) b *<address_of_eor>
(gdb) c
```
When it hits, check what value is about to be XORed:
```gdb
(gdb) info registers r3
```
- If button is **released**: `r3 = 1` → after EOR: `r3 = 0`
- If button is **pressed**: `r3 = 0` → after EOR: `r3 = 1`
##### Step 5: Test the Patch in GDB
Modify the EOR operand in RAM to see the effect live:
```gdb
(gdb) set $r3 = 0
(gdb) si
(gdb) info registers r3
```
Or skip the EOR entirely by advancing the PC past it, then observe the LED behavior.
##### Step 6: Examine the Instruction Encoding
Look at the raw bytes:
```gdb
(gdb) x/4bx <address_of_eor>
```
The `eor` instruction in Thumb-2 (32-bit encoding) will contain the immediate value `0x01`. Study the bytes carefully — the immediate operand is encoded within the instruction word.
##### Step 7: Patch with the Hex Editor
1. Open `0x0014_static-variables.bin` in HxD
2. Calculate the file offset: `address - 0x10000000`
3. Press **Ctrl+G** and enter the offset
4. Locate the byte that encodes the `#0x1` immediate value
5. Change `01` to `00`
6. Verify the surrounding bytes are unchanged
7. Click **File****Save As**`0x0014_static-variables-h.bin`
> 🔍 **Thumb-2 encoding note:** The `eor` instruction is 4 bytes (32-bit Thumb-2). The immediate value may not be in the most obvious position — it is typically encoded in bit fields spread across the instruction. Look for the `01` byte within the 4-byte sequence.
##### Step 8: Predict the New Behavior
After patching, the logic changes:
| Button State | GPIO 15 Input | After UBFX | After EOR #0 | LED (GPIO 16) |
| ------------ | ------------- | ---------- | ------------ | -------------- |
| Released | 1 (HIGH) | 1 | 1 | **ON** |
| Pressed | 0 (LOW) | 0 | 0 | **OFF** |
The LED behavior is now **inverted** from the original!
##### Step 9: Convert to UF2 and Flash
```bash
python ../uf2conv.py build/0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2
```
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
##### Step 10: Verify the Hack
Test the button:
- **Button NOT pressed**: LED should now be **ON** (was OFF before patching)
- **Button PRESSED**: LED should now be **OFF** (was ON before patching)
The LED behavior is completely reversed by changing a single byte!
#### Expected Output
After completing this exercise, you should be able to:
- Locate XOR / EOR instructions in disassembled GPIO logic
- Understand how XOR implements logical NOT for single-bit values
- Patch a Thumb-2 encoded immediate operand
- Predict hardware behavior changes from binary patches
#### Questions for Reflection
###### Question 1: Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1.
###### Question 2: Instead of changing `eor r3, r3, #0x1` to `eor r3, r3, #0x0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?
###### Question 3: The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not?
###### Question 4: The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin?
#### Tips and Hints
- `eor r3, r3, #0x1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction
- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction
- Use GDB `x/1tw` to view a word in binary format, making bit manipulation easier to see
- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000`
#### Next Steps
- Review all four exercises and verify you can patch any part of the binary: data values, arithmetic operations, and logic operations
- Try combining multiple hacks in a single binary: change the initial value, speed up the overflow, AND invert the button logic
- Compare your patched binary with the original using `fc /b original.bin patched.bin` in the command prompt to see all changed bytes
+957
View File
@@ -0,0 +1,957 @@
# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
## 🎯 What You'll Learn This Week
By the end of this tutorial, you will be able to:
- Understand the difference between regular (automatic) variables and static variables
- Know where different types of variables are stored (stack vs static storage)
- Configure GPIO pins as inputs and use internal pull-up resistors
- Read button states using `gpio_get()` and control LEDs based on input
- Use GDB to examine how the compiler handles static vs automatic variables
- Identify compiler optimizations by stepping through assembly
- Hack variable values and invert GPIO input/output logic using a hex editor
- Convert patched binaries to UF2 format for flashing
---
## 📚 Part 1: Understanding Static Variables
### What is a Static Variable?
A **static variable** is a special kind of variable that "remembers" its value between function calls or loop iterations. Unlike regular variables that get created and destroyed each time, static variables **persist** for the entire lifetime of your program.
Think of it like this:
- **Regular variable:** Like writing on a whiteboard that gets erased after each class
- **Static variable:** Like writing in a notebook that you keep forever
```
┌─────────────────────────────────────────────────────────────────┐
│ Regular vs Static Variables │
│ │
│ REGULAR (automatic): │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Loop 1: Create → Set to 42 → Increment to 43 → Destroy │ │
│ │ Loop 2: Create → Set to 42 → Increment to 43 → Destroy │ │
│ │ Loop 3: Create → Set to 42 → Increment to 43 → Destroy │ │
│ │ Result: Always appears as 42! │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ STATIC: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Loop 1: Already exists → Read 42 → Increment → Store 43 │ │
│ │ Loop 2: Already exists → Read 43 → Increment → Store 44 │ │
│ │ Loop 3: Already exists → Read 44 → Increment → Store 45 │ │
│ │ Result: Keeps incrementing! │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### The `static` Keyword
In C, you declare a static variable by adding the `static` keyword:
```c
uint8_t regular_fav_num = 42; // Regular - recreated each time
static uint8_t static_fav_num = 42; // Static - persists forever
```
### Where Do Variables Live in Memory?
Different types of variables are stored in different memory locations:
| Variable Type | Storage Location | Lifetime | Example |
| ----------------- | ---------------- | ------------------------- | ------------------------------------ |
| Automatic (local) | Stack | Until function/block ends | `uint8_t x = 5;` |
| Static | Static Storage | Entire program lifetime | `static uint8_t x = 5;` |
| Global | Static Storage | Entire program lifetime | `uint8_t x = 5;` (outside functions) |
| Dynamic (heap) | Heap | Until `free()` is called | `malloc(sizeof(int))` |
### Stack vs Static Storage vs Heap
```
┌─────────────────────────────────────────────────────────────────┐
│ Memory Layout │
│ │
│ ┌───────────────────┐ High Address (0x20082000) │
│ │ STACK │ ← Automatic/local variables │
│ │ (grows down) │ Created/destroyed per function │
│ ├───────────────────┤ │
│ │ │ │
│ │ (free space) │ │
│ │ │ │
│ ├───────────────────┤ │
│ │ HEAP │ ← Dynamic allocation (malloc/free) │
│ │ (grows up) │ │
│ ├───────────────────┤ │
│ │ .bss section │ ← Uninitialized static/global vars │
│ ├───────────────────┤ │
│ │ .data section │ ← Initialized static/global vars │
│ └───────────────────┘ Low Address (0x20000000) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
**Key Point:** Static variables are NOT on the heap! They live in a fixed location in the `.data` section (if initialized) or `.bss` section (if uninitialized). This is different from heap memory which is dynamically allocated at runtime.
### What Happens with Overflow?
Since `static_fav_num` is a `uint8_t` (unsigned 8-bit), it can only hold values 0-255. What happens when it reaches 255 and we add 1?
```
255 + 1 = 256... but that doesn't fit in 8 bits!
Binary: 11111111 + 1 = 100000000 (9 bits)
The 9th bit is lost, so we get: 00000000 = 0
```
This is called **overflow** or **wrap-around**. The value "wraps" back to 0 and starts counting again!
---
## 📚 Part 2: Understanding GPIO Inputs
### Input vs Output
So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to use them as **inputs** to read button states!
```
┌─────────────────────────────────────────────────────────────────┐
│ GPIO Direction │
│ │
│ OUTPUT (what we've done before): │
│ ┌─────────┐ │
│ │ Pico │ ───────► LED │
│ │ GPIO 16 │ (We control the LED) │
│ └─────────┘ │
│ │
│ INPUT (new this week): │
│ ┌─────────┐ │
│ │ Pico │ ◄─────── Button │
│ │ GPIO 15 │ (We read the button state) │
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### The Floating Input Problem
When a GPIO pin is set as an input but nothing is connected, it's called a **floating input**. The voltage on the pin is undefined and can randomly read as HIGH (1) or LOW (0) due to electrical noise.
```
┌─────────────────────────────────────────────────────────────────┐
│ Floating Input = Random Values! │
│ │
│ GPIO Pin (no connection): │
│ Reading 1: HIGH │
│ Reading 2: LOW │
│ Reading 3: HIGH │
│ Reading 4: HIGH │
│ Reading 5: LOW │
│ (Completely unpredictable!) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Pull-Up and Pull-Down Resistors
To solve the floating input problem, we use **pull resistors**:
| Resistor Type | Default State | When Button Pressed |
| ------------- | ------------- | ------------------- |
| **Pull-Up** | HIGH (1) | LOW (0) |
| **Pull-Down** | LOW (0) | HIGH (1) |
The Pico 2 has **internal** pull resistors that you can enable with software - no external components needed!
```
┌─────────────────────────────────────────────────────────────────┐
│ Pull-Up Resistor (what we're using) │
│ │
│ 3.3V │
│ │ │
│ ┴ (internal pull-up resistor) │
│ │ │
│ ├──────► GPIO 15 (reads HIGH normally) │
│ │ │
│ ┌─┴─┐ │
│ │BTN│ ← Button connects GPIO to GND when pressed │
│ └─┬─┘ │
│ │ │
│ GND │
│ │
│ Button NOT pressed: GPIO reads 1 (HIGH) │
│ Button PRESSED: GPIO reads 0 (LOW) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### GPIO Input Functions
| Function | Purpose |
| ---------------------------- | --------------------------------------- |
| `gpio_init(pin)` | Initialize a GPIO pin for use |
| `gpio_set_dir(pin, GPIO_IN)` | Set pin as INPUT |
| `gpio_pull_up(pin)` | Enable internal pull-up resistor |
| `gpio_pull_down(pin)` | Enable internal pull-down resistor |
| `gpio_get(pin)` | Read the current state (returns 0 or 1) |
### The Ternary Operator
The code uses a **ternary operator** to control the LED based on button state:
```c
gpio_put(LED_GPIO, pressed ? 0 : 1);
```
This is a compact if-else statement:
- If `pressed` is **true (1)**: output `0` (LED OFF... wait, that seems backwards!)
- If `pressed` is **false (0)**: output `1` (LED ON)
**Why is it inverted?** Because of the pull-up resistor!
- Button **released** → GPIO reads `1``pressed = 1` → output `0` → LED OFF
- Button **pressed** → GPIO reads `0``pressed = 0` → output `1` → LED ON
A clearer way to write this:
```c
gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO));
```
---
## 📚 Part 3: Understanding Compiler Optimizations
### Why Does Code Disappear?
When you compile code, the compiler tries to make it faster and smaller. This is called **optimization**. Sometimes the compiler removes code that it thinks has no effect!
**Example from our code:**
```c
while (true) {
uint8_t regular_fav_num = 42; // Created
regular_fav_num++; // Incremented to 43
// But then it's destroyed and recreated as 42 next loop!
}
```
The compiler sees that incrementing `regular_fav_num` has no lasting effect (because it's recreated as 42 each loop), so it may **optimize away** the increment operation entirely!
### Function Inlining
Sometimes the compiler **inlines** functions, meaning it replaces a function call with the function's code directly.
**Original code:**
```c
gpio_pull_up(BUTTON_GPIO);
```
**What the compiler might do:**
```c
// Instead of calling gpio_pull_up, it calls the underlying function:
gpio_set_pulls(BUTTON_GPIO, true, false);
```
This is why when you look for `gpio_pull_up` in the binary, you might find `gpio_set_pulls` instead!
---
## 📚 Part 4: Setting Up Your Environment
### Prerequisites
Before we start, make sure you have:
1. A Raspberry Pi Pico 2 board
2. A Raspberry Pi Pico Debug Probe
3. OpenOCD installed and configured
4. GDB (`arm-none-eabi-gdb`) installed
5. Python installed (for UF2 conversion)
6. A serial monitor (PuTTY, minicom, or screen)
7. A push button connected to GPIO 15
8. An LED connected to GPIO 16 (or use the breadboard LED)
9. A hex editor (HxD, ImHex, or similar)
10. The sample project: `0x0014_static-variables`
### Hardware Setup
Connect your button like this:
- One side of button → GPIO 15
- Other side of button → GND
The internal pull-up resistor provides the 3.3V connection, so you only need to connect to GND!
```
┌─────────────────────────────────────────────────────────────────┐
│ Breadboard Wiring │
│ │
│ Pico 2 │
│ ┌──────────┐ │
│ │ │ │
│ │ GPIO 15 │────────┐ │
│ │ │ │ │
│ │ GPIO 16 │────────┼───► LED (with resistor to GND) │
│ │ │ │ │
│ │ GND │────────┼───┐ │
│ │ │ │ │ │
│ └──────────┘ ┌─┴─┐ │ │
│ │BTN│─┘ │
│ └───┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Project Structure
```
Embedded-Hacking/
├── 0x0014_static-variables/
│ ├── build/
│ │ ├── 0x0014_static-variables.uf2
│ │ └── 0x0014_static-variables.bin
│ └── 0x0014_static-variables.c
└── uf2conv.py
```
---
## 🔬 Part 5: Hands-On Tutorial - Static Variables and GPIO Input
### Step 1: Review the Source Code
Let's examine the static variables code:
**File: `0x0014_static-variables.c`**
```c
#include <stdio.h>
#include "pico/stdlib.h"
int main(void) {
stdio_init_all();
const uint BUTTON_GPIO = 15;
const uint LED_GPIO = 16;
bool pressed = 0;
gpio_init(BUTTON_GPIO);
gpio_set_dir(BUTTON_GPIO, GPIO_IN);
gpio_pull_up(BUTTON_GPIO);
gpio_init(LED_GPIO);
gpio_set_dir(LED_GPIO, GPIO_OUT);
while (true) {
uint8_t regular_fav_num = 42;
static uint8_t static_fav_num = 42;
printf("regular_fav_num: %d\r\n", regular_fav_num);
printf("static_fav_num: %d\r\n", static_fav_num);
regular_fav_num++;
static_fav_num++;
pressed = gpio_get(BUTTON_GPIO);
gpio_put(LED_GPIO, pressed ? 0 : 1);
}
}
```
**What this code does:**
1. **Line 6-8:** Defines constants for button (GPIO 15) and LED (GPIO 16) pins
2. **Line 10-12:** Sets up GPIO 15 as input with internal pull-up resistor
3. **Line 14-15:** Sets up GPIO 16 as output for the LED
4. **Line 18-19:** Creates two variables:
- `regular_fav_num` - a normal local variable (recreated each loop)
- `static_fav_num` - a static variable (persists across loops)
5. **Line 21-22:** Prints both values to the serial terminal
6. **Line 24-25:** Increments both values
7. **Line 27-28:** Reads button and controls LED accordingly
### Step 2: Flash the Binary to Your Pico 2
1. Hold the BOOTSEL button on your Pico 2
2. Plug in the USB cable (while holding BOOTSEL)
3. Release BOOTSEL - a drive called "RPI-RP2" appears
4. Drag and drop `0x0014_static-variables.uf2` onto the drive
5. The Pico will reboot and start running!
### Step 3: Open Your Serial Monitor
Open PuTTY, minicom, or screen and connect to your Pico's serial port.
**You should see output like this:**
```
regular_fav_num: 42
static_fav_num: 42
regular_fav_num: 42
static_fav_num: 43
regular_fav_num: 42
static_fav_num: 44
regular_fav_num: 42
static_fav_num: 45
...
```
**Notice the difference:**
- `regular_fav_num` stays at 42 every time (it's recreated each loop)
- `static_fav_num` increases each time (it persists and remembers its value)
### Step 4: Test the Button
Now test the button behavior:
- **Button NOT pressed:** LED should be ON (because of the inverted logic)
- **Button PRESSED:** LED should turn OFF
Wait... that seems backwards from what you'd expect! That's because of the pull-up resistor and the ternary operator. We'll hack this later to make it more intuitive!
### Step 5: Watch for Overflow
Keep the program running and watch `static_fav_num`. After 255, you'll see:
```
static_fav_num: 254
static_fav_num: 255
static_fav_num: 0 ← Wrapped around!
static_fav_num: 1
static_fav_num: 2
...
```
This demonstrates unsigned integer overflow!
---
## 🔬 Part 6: Debugging with GDB (Dynamic Analysis)
> 🔄 **REVIEW:** This setup is identical to previous weeks. If you need a refresher on OpenOCD and GDB connection, refer back to Week 3 Part 6.
### Starting the Debug Session
**Terminal 1 - Start OpenOCD:**
```bash
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:**
```bash
arm-none-eabi-gdb build/0x0014_static-variables.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
### Step 6: Examine Main Function
Let's examine the main function at its entry point. First, disassemble from the start:
```
x/50i 0x10000234
```
You should see output like:
```
0x10000234: push {r4, r5, r6, r7, lr}
0x10000236: sub sp, #12
0x10000238: bl 0x10003014 ; stdio_init_all
0x1000023c: movs r0, #15 ; BUTTON_GPIO = 15
0x1000023e: bl 0x100002b4 ; gpio_init
0x10000242: movs r0, #15
0x10000244: movs r1, #0 ; GPIO_IN
0x10000246: bl 0x100002c8 ; gpio_set_dir
0x1000024a: movs r0, #15
0x1000024c: movs r1, #1 ; up = true
0x1000024e: movs r2, #0 ; down = false
0x10000250: bl 0x100002dc ; gpio_set_pulls (inlined gpio_pull_up)
0x10000254: movs r0, #16 ; LED_GPIO = 16
0x10000256: bl 0x100002b4 ; gpio_init
0x1000025a: movs r0, #16
0x1000025c: movs r1, #1 ; GPIO_OUT
0x1000025e: bl 0x100002c8 ; gpio_set_dir
...
```
### Step 7: Set a Breakpoint at Main
```
b *0x10000234
c
```
GDB responds:
```
Breakpoint 1 at 0x10000234
Continuing.
Breakpoint 1, 0x10000234 in ?? ()
```
### Step 8: Examine the Static Variable Location
Static variables live at fixed RAM addresses. Let's find where `static_fav_num` is stored:
```
x/20i 0x10000260
```
Look for instructions that load/store to addresses in the `0x20000xxx` range - that's where static variables live in RAM.
### Step 9: Step Through the Loop
```
si
```
Use `si` (step instruction) to execute one instruction at a time and watch how the static variable gets loaded, incremented, and stored back.
### Step 10: Examine Register Values
```
i r
```
This shows all register values. Pay attention to:
- `r0-r3` - Used for function arguments and return values
- `sp` - Stack pointer
- `pc` - Program counter (current instruction)
### Step 11: Watch the Static Variable Change
Once you identify the static variable address (e.g., `0x200005a8`), you can watch it:
```
x/1db 0x200005a8
```
This displays 1 byte in decimal format. Step through a few loop iterations and re-examine to see it increment.
### Step 12: Examine GPIO State
Read the GPIO input register to see button state:
```
x/1wx 0xd0000004
```
The SIO GPIO input register at `0xd0000004` shows the current state of all GPIO pins. Bit 15 corresponds to our button on GPIO 15.
---
## 🔬 Part 7: Understanding the Assembly
Now that we've explored the binary in GDB, let's make sense of the key patterns.
### Step 13: Analyze the Regular Variable
In GDB, examine the code around the first `printf` call:
```gdb
(gdb) x/5i 0x1000028c
```
Look for this instruction:
```
0x1000028e: movs r1, #0x2a
```
This loads the value `0x2a` (42 in decimal) directly into register `r1` for the first `printf` call.
**Key insight:** The compiler **optimized away** the `regular_fav_num` variable entirely! Since it's always 42 when printed, the compiler just uses the constant `42` directly. The `regular_fav_num++` after the print is also removed because it has no observable effect.
### Step 14: Analyze the Static Variable
Examine the static variable operations:
```gdb
(gdb) x/10i 0x10000260
```
Look for references to addresses in the `0x200005xx` range:
```
ldrb r1,[r4,#0x0] ; Load static_fav_num from RAM
...
ldrb r3,[r4,#0x0] ; Load it again
adds r3,#0x1 ; Increment by 1
strb r3,[r4,#0x0] ; Store it back to RAM
```
**Key insight:** The static variable lives at a **fixed RAM address** (`0x200005a8`). It's loaded, incremented, and stored back — unlike the regular variable which was optimized away!
Verify the static variable value:
```gdb
(gdb) x/1db 0x200005a8
```
### Step 15: Analyze the GPIO Logic
Examine the GPIO input/output code:
```gdb
(gdb) x/10i 0x10000274
```
Look for this sequence:
```
mov.w r1,#0xd0000000 ; SIO base address
ldr r3,[r1,#offset] ; Read GPIO input register
ubfx r3,r3,#0xf,#0x1 ; Extract bit 15 (button state)
eor r3,r3,#0x1 ; Invert the value (the ternary operator!)
mcrr p0,0x4,r2,r3,cr0 ; Write to GPIO output
```
**Breaking this down:**
| Instruction | Purpose |
| ----------------------- | ------------------------------------------- |
| `mov.w r1,#0xd0000000` | Load SIO (Single-cycle I/O) base address |
| `ldr r3,[r1,#offset]` | Read GPIO input state |
| `ubfx r3,r3,#0xf,#0x1` | Extract bit 15 (GPIO 15 = button) |
| `eor r3,r3,#0x1` | XOR with 1 to invert (implements `? 0 : 1`) |
| `mcrr p0,0x4,...` | Write result to GPIO output (LED) |
### Step 16: Find the Infinite Loop
At the end of the function, look for:
```
b 0x10000264
```
This is an **unconditional branch** back to the start of the loop — this is the `while (true)` in our code!
---
## 🔬 Part 8: Hacking the Binary with a Hex Editor
Now for the fun part — we'll patch the `.bin` file directly using a hex editor!
> 💡 **Why a hex editor?** GDB can modify values in RAM at runtime, but those changes are lost when the device reboots. To make **permanent** changes, we edit the `.bin` file on disk and re-flash it.
### Step 17: Open the Binary in a Hex Editor
1. Open **HxD** (or your preferred hex editor: ImHex, 010 Editor, etc.)
2. Click **File****Open**
3. Navigate to `0x0014_static-variables/build/`
4. Open `0x0014_static-variables.bin`
### Step 18: Calculate the File Offset
The binary is loaded at base address `0x10000000`. To find the file offset of any address:
```
file_offset = address - 0x10000000
```
For example:
- Address `0x1000028e` → file offset `0x28e` (654 in decimal)
- Address `0x10000274` → file offset `0x274` (628 in decimal)
### Step 19: Hack #1 — Change regular_fav_num from 42 to 43
From GDB, we know the instruction at `0x1000028e` is:
```
movs r1, #0x2a → bytes: 2a 21
```
To change the value from 42 (`0x2a`) to 43 (`0x2b`):
1. In HxD, press **Ctrl+G** (Go to offset)
2. Enter offset: `28E`
3. You should see the byte `2A` at this position
4. Change `2A` to `2B`
5. The instruction is now `movs r1, #0x2b` (43 in decimal)
> 🔍 **How Thumb encoding works:** In `movs r1, #imm8`, the immediate value is the first byte, and the opcode `21` is the second byte. So the bytes `2a 21` encode `movs r1, #0x2a`.
### Step 20: Hack #2 — Invert the Button Logic
From GDB, we found the `eor r3, r3, #0x1` instruction that inverts the button value. We need to find where this instruction is in the binary.
In GDB, examine the exact address and bytes:
```gdb
(gdb) x/2bx <address_of_eor_instruction>
```
The `eor` instruction with `#0x1` needs to become `eor` with `#0x0`:
1. Calculate the file offset of the EOR instruction
2. In HxD, go to that offset
3. Find the byte encoding the immediate value `0x01`
4. Change it to `0x00`
Now the logic is:
- Button released (input = 1): `1 XOR 0 = 1` → LED **ON**
- Button pressed (input = 0): `0 XOR 0 = 0` → LED **OFF**
This is the **opposite** of the original behavior!
### Step 21: Save the Patched Binary
1. Click **File****Save As**
2. Save as `0x0014_static-variables-h.bin` in the build directory
3. Close the hex editor
---
## 🔬 Part 9: Converting and Flashing the Hacked Binary
### Step 22: Convert to UF2 Format
Open a terminal and navigate to your project directory:
```bash
cd Embedded-Hacking/0x0014_static-variables
```
Run the conversion command:
```bash
python ../uf2conv.py build/0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2
```
**What this command means:**
- `uf2conv.py` = the conversion script
- `--base 0x10000000` = the XIP base address where code runs from
- `--family 0xe48bff59` = the RP2350 family ID
- `--output build/hacked.uf2` = the output filename
### Step 23: Flash the Hacked Binary
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
3. Open your serial monitor
### Step 24: Verify the Hacks
**Check the serial output:**
```
regular_fav_num: 43 ← Changed from 42!
static_fav_num: 42
regular_fav_num: 43
static_fav_num: 43
...
```
**Check the LED behavior:**
- LED should now be **ON by default** (when button is NOT pressed)
- LED should turn **OFF** when you press the button
🎉 **BOOM! We successfully:**
1. Changed the printed value from 42 to 43
2. Inverted the LED/button logic
---
## 📊 Part 10: Summary and Review
### What We Accomplished
1. **Learned about static variables** - How they persist across function calls and loop iterations
2. **Understood memory layout** - Stack vs static storage vs heap
3. **Configured GPIO inputs** - Using pull-up resistors and reading button states
4. **Analyzed compiled code in GDB** - Saw how the compiler optimizes code
5. **Discovered function inlining** - `gpio_pull_up` became `gpio_set_pulls`
6. **Hacked variable values** - Changed 42 to 43 using a hex editor
7. **Inverted GPIO logic** - Made LED behavior opposite
### Static vs Automatic Variables
| Aspect | Automatic (Regular) | Static |
| ------------------ | ------------------------ | --------------------------- |
| **Storage** | Stack | Static storage (.data/.bss) |
| **Lifetime** | Block/function scope | Entire program |
| **Initialization** | Every time block entered | Once at program start |
| **Persistence** | Lost when scope exits | Retained between calls |
| **Compiler view** | May be optimized away | Always has memory location |
### GPIO Input Configuration
```
┌─────────────────────────────────────────────────────────────────┐
│ GPIO Input Setup Steps │
│ │
│ 1. gpio_init(pin) - Initialize the pin │
│ 2. gpio_set_dir(pin, GPIO_IN) - Set as input │
│ 3. gpio_pull_up(pin) - Enable pull-up │
│ OR gpio_pull_down(pin) - OR enable pull-down │
│ 4. gpio_get(pin) - Read the state │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### The Binary Hacking Workflow
```
┌─────────────────────────────────────────────────────────────────┐
│ 1. Analyze the binary with GDB │
│ - Disassemble functions with x/Ni │
│ - Identify key instructions and addresses │
├─────────────────────────────────────────────────────────────────┤
│ 2. Understand compiler optimizations │
│ - Some functions get inlined (gpio_pull_up → gpio_set_pulls)│
│ - Some variables are optimized away │
├─────────────────────────────────────────────────────────────────┤
│ 3. Calculate file offsets │
│ - file_offset = address - 0x10000000 │
├─────────────────────────────────────────────────────────────────┤
│ 4. Patch the .bin file with a hex editor │
│ - Open the .bin file in HxD / ImHex │
│ - Go to the calculated offset │
│ - Change the target byte(s) │
├─────────────────────────────────────────────────────────────────┤
│ 5. Convert to UF2 │
│ python uf2conv.py file.bin --base 0x10000000 │
│ --family 0xe48bff59 --output hacked.uf2 │
├─────────────────────────────────────────────────────────────────┤
│ 6. Flash and verify │
│ - Hold BOOTSEL, plug in, drag UF2 │
│ - Check serial output and button/LED behavior │
└─────────────────────────────────────────────────────────────────┘
```
### Key Memory Addresses
| Address | Description |
| ------------ | ----------------------------------- |
| `0x10000234` | Typical main() entry point |
| `0x10003014` | stdio_init_all() function |
| `0x200005a8` | Static variable storage (example) |
| `0xd0000000` | SIO (Single-cycle I/O) base address |
---
## ✅ Practice Exercises
### Exercise 1: Change Static Variable Initial Value
The static variable starts at 42. Hack the binary to make it start at 100 instead.
**Hint:** Find where `DAT_200005a8` is initialized in the .data section.
### Exercise 2: Make the LED Blink
Instead of responding to button presses, hack the binary to make the LED blink continuously.
**Hint:** You'll need to change the GPIO output logic to toggle instead of following button state.
### Exercise 3: Reverse Engineer gpio_set_pulls
Using GDB, disassemble the `gpio_set_pulls` function and figure out what registers it writes to.
**Hint:** Look for writes to addresses around `0x40038000` (PADS_BANK0).
### Exercise 4: Add a Second Static Variable
If you had two static variables, where would they be stored in memory? Would they be next to each other?
**Hint:** Static variables in the same compilation unit are typically placed consecutively in the .data section.
### Exercise 5: Overflow Faster
The static variable overflows after 255 iterations. Can you hack it to overflow sooner?
**Hint:** Change the increment from `+1` to `+10` by modifying the `adds r3,#0x1` instruction.
---
## 🎓 Key Takeaways
1. **Static variables persist** - They keep their value between function calls and loop iterations.
2. **Static storage ≠ heap** - Static variables are in a fixed location, not dynamically allocated.
3. **Compilers optimize aggressively** - Regular variables may be optimized away if the compiler sees no effect.
4. **Function inlining is common** - `gpio_pull_up` becomes `gpio_set_pulls` in the binary.
5. **Pull-up resistors invert logic** - Button pressed = LOW, button released = HIGH.
6. **XOR is useful for inverting** - `eor r3,r3,#0x1` flips a bit between 0 and 1.
7. **Static variables have fixed addresses** - You can find them in the .data section at known RAM addresses.
8. **Overflow wraps around** - A `uint8_t` at 255 becomes 0 when incremented.
9. **UBFX extracts bits** - Used to read a single GPIO pin from a register.
10. **Binary patching is powerful** - Change values and logic without source code!
---
## 📖 Glossary
| Term | Definition |
| --------------------- | ---------------------------------------------------------------- |
| **Automatic** | Variable that's created and destroyed automatically (local vars) |
| **eor/XOR** | Exclusive OR - flips bits where operands differ |
| **Floating Input** | GPIO input with undefined voltage (reads random values) |
| **Function Inlining** | Compiler replaces function call with the function's code |
| **gpio_get** | Function to read the current state of a GPIO pin |
| **Heap** | Memory area for dynamic allocation (malloc/free) |
| **Overflow** | When a value exceeds its type's maximum and wraps around |
| **Pull-Down** | Resistor that holds a pin LOW when nothing drives it |
| **Pull-Up** | Resistor that holds a pin HIGH when nothing drives it |
| **SIO** | Single-cycle I/O - fast GPIO access on RP2350 |
| **Stack** | Memory area for local variables and function call frames |
| **Static Storage** | Fixed memory area for static and global variables |
| **Static Variable** | Variable declared with `static` that persists across calls |
| **Ternary Operator** | `condition ? value_if_true : value_if_false` |
| **UBFX** | Unsigned Bit Field Extract - extracts bits from a register |
| **Varargs** | Variable arguments - functions that take unlimited parameters |
---
## 🔗 Additional Resources
### GPIO Input Reference
| Function | Purpose |
| ----------------------------- | -------------------------- |
| `gpio_init(pin)` | Initialize GPIO pin |
| `gpio_set_dir(pin, GPIO_IN)` | Set pin as input |
| `gpio_set_dir(pin, GPIO_OUT)` | Set pin as output |
| `gpio_pull_up(pin)` | Enable internal pull-up |
| `gpio_pull_down(pin)` | Enable internal pull-down |
| `gpio_disable_pulls(pin)` | Disable all pull resistors |
| `gpio_get(pin)` | Read pin state (0 or 1) |
| `gpio_put(pin, value)` | Set pin output (0 or 1) |
### Key Assembly Instructions
| Instruction | Description |
| ----------------------- | -------------------------------------------- |
| `movs rN, #imm` | Move immediate value to register |
| `ldrb rN, [rM, #off]` | Load byte from memory |
| `strb rN, [rM, #off]` | Store byte to memory |
| `adds rN, #imm` | Add immediate value to register |
| `eor rN, rM, #imm` | Exclusive OR (XOR) with immediate |
| `ubfx rN, rM, #lsb, #w` | Extract unsigned bit field |
| `mcrr p0, ...` | Move to coprocessor (GPIO control on RP2350) |
| `b LABEL` | Unconditional branch (jump) |
### Memory Map Quick Reference
| Address Range | Description |
| --------------------- | ------------------------------ |
| `0x10000000` | XIP Flash (code execution) |
| `0x20000000-200005xx` | SRAM (.data section) |
| `0x20082000` | Stack top (initial SP) |
| `0x40038000` | PADS_BANK0 (pad configuration) |
| `0xd0000000` | SIO (single-cycle I/O) |
---
**Remember:** Static variables are your friends when you need to remember values across function calls. But they also make your program's behavior more complex to analyze - which is exactly why we practice reverse engineering!
Happy hacking! 🔧