Updated WEEK04

This commit is contained in:
Kevin Thomas
2026-05-09 11:42:33 -04:00
parent 005fd08646
commit ee664b6733
165 changed files with 3952 additions and 13308 deletions
-53
View File
@@ -1,53 +0,0 @@
# 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
### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100
#### Answers
##### Static Variable Location
| Item | Value | Notes |
|---------------------------|-------------|--------------------------------|
| RAM address (runtime) | 0x200005a8 | Where variable lives at runtime |
| Flash address (init value)| Calculated | In .data section of flash |
| Original value (hex) | 0x2A | 42 decimal |
| Patched value (hex) | 0x64 | 100 decimal |
| File offset | flash_addr - 0x10000000 | Binary base subtraction |
##### GDB Session
```gdb
(gdb) x/1db 0x200005a8
0x200005a8: 42
(gdb) find /b 0x10000000, 0x10010000, 0x2a
```
##### Serial Output After Patch
```
regular_fav_num: 42
static_fav_num: 100
regular_fav_num: 42
static_fav_num: 101
regular_fav_num: 42
static_fav_num: 102
...
```
#### Reflection Answers
1. **Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?**
Static variables need to be **modifiable** at runtime—the program increments `static_fav_num` each iteration. Flash memory is read-only during normal execution (it requires a special erase/program sequence to modify). So the initial value is stored in flash as a template, and the startup code (`crt0.S`) copies the entire `.data` section from flash to RAM before `main()` runs. This gives the variable its correct starting value (42) in writable RAM where subsequent `adds` and `strb` instructions can modify it freely.
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?**
A `uint8_t` overflows from 255 to 0. Starting at 100 and incrementing by 1: it takes `255 - 100 = 155` increments to reach 255, then one more to wrap to 0. So it overflows after **156 iterations**. Compare to the original: starting at 42, it takes `255 - 42 + 1 = 214` iterations.
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?**
No. `regular_fav_num` is a **local variable** that the compiler optimized to an immediate constant (`movs r1, #0x2a`), just like Week 4's `age` variable. It's encoded directly in the instruction opcode in the `.text` section, not in the `.data` section. You would need to find the `movs r1, #0x2a` instruction in the code and patch the immediate byte from `0x2a` to your desired value. The `.data` section only contains initialized static/global variables.
4. **What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?**
Yes. The linker places all initialized static variables contiguously in the `.data` section. Their initial values are stored in the same order in flash, packed adjacent to each other (possibly with alignment padding). The startup code performs a single `memcpy`-like loop that copies the entire `.data` block from flash to RAM. So if you had `static uint8_t a = 42;` and `static uint8_t b = 99;`, the bytes `0x2A` and `0x63` would be adjacent (or nearly so) in flash, and both would be copied to their respective RAM addresses during boot.
-157
View File
@@ -1,157 +0,0 @@
# 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
### Non-Credit Practice 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:**
```powershell
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:**
```powershell
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. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin`
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` (in the same `build` directory)
##### Step 7: Convert to UF2 and Flash
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables
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
-63
View File
@@ -1,63 +0,0 @@
# 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
### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB
#### Answers
##### Function Arguments
```gdb
(gdb) b *0x100002d8
(gdb) c
(gdb) info registers r0 r1 r2
r0 = 15 ; GPIO pin
r1 = 1 ; pull-up enable (true)
r2 = 0 ; pull-down disable (false)
```
##### PADS_BANK0 Address Calculation
```
Base: 0x40038000 (PADS_BANK0)
GPIO 0 offset: 0x04 (first pad register)
GPIO N offset: 0x04 + (N × 4)
GPIO 15: 0x40038000 + 0x04 + (15 × 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040
```
##### Function Behavior Summary
| Step | Instruction(s) | Effect |
|------------------------|---------------------|-----------------------------------|
| Load PADS base address | `ldr rX, =0x40038000` | rX = PADS_BANK0 base |
| Calculate pad offset | `adds`, `lsls` | offset = 0x04 + pin × 4 |
| Read current config | `ldr rX, [addr]` | Read existing pad register value |
| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 (PDE) and 3 (PUE) |
| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE = pull-up enable) |
| Write back | `str rX, [addr]` | Write updated config to hardware |
##### Pad Register Bits
| Bit | Name | Value | Meaning |
|-----|----------|-------|-------------------------|
| 3 | PUE | 1 | Pull-up enable |
| 2 | PDE | 0 | Pull-down disable |
| 1 | SCHMITT | 1 | Schmitt trigger enabled |
| 0 | SLEWFAST | 0 | Slow slew rate |
#### Reflection Answers
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?**
The pad register contains multiple configuration fields (drive strength, slew rate, Schmitt trigger, input enable, output disable, pull-up, pull-down). If the function wrote a new value without reading first, it would overwrite all other fields with zeros or arbitrary values, potentially disabling the Schmitt trigger, changing drive strength, or disabling input/output. The read-modify-write pattern uses `bic` to clear only the pull bits (2 and 3) and `orr` to set the desired pull configuration, leaving all other bits untouched.
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?**
The formula is: offset = `0x04 + (GPIO_number × 4)`. For GPIO 15: `0x04 + (15 × 4) = 0x04 + 0x3C = 0x40`. The `0x04` initial offset exists because offset `0x00` is the VOLTAGE_SELECT register, not a pad register. For GPIO 0: offset = `0x04 + (0 × 4) = 0x04`. So GPIO 0's pad register is at `0x40038004`.
3. **What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?**
Enabling both creates a **resistive voltage divider** between VDD and GND through the internal pull resistors. On the RP2350, both pull resistors are typically ~50kΩ. The pin voltage would settle at approximately VDD/2 (1.65V for 3.3V supply), which is in the undefined region between logic HIGH and LOW thresholds. This makes the digital input unreliable—the Schmitt trigger may oscillate or read randomly. While not damaging to the hardware, it wastes power and produces unpredictable input reads. The SDK intentionally never sets both bits simultaneously.
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?**
This indicates at least `-O1` or higher optimization (the Pico SDK defaults to `-O2`). The `gpio_pull_up` function is declared `static inline` in the SDK header, and the compiler eliminates the function call overhead by inserting `gpio_set_pulls(pin, true, false)` directly. For binary analysis, inlining means: (a) the original function name `gpio_pull_up` doesn't appear in the symbol table, (b) you see `gpio_set_pulls` called directly with hardcoded arguments, making it harder to identify the programmer's original intent, and (c) multiple calls to `gpio_pull_up` with different pins each become separate `gpio_set_pulls` calls rather than referencing a single function body.
-187
View File
@@ -1,187 +0,0 @@
# 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
### Non-Credit Practice 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:**
```powershell
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:**
```powershell
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 `0x1000024e` with arguments for GPIO 15:
```gdb
(gdb) x/5i 0x10000240
```
You should see:
```
0x10000240: movs r0, #15 ; GPIO pin 15
0x10000242: mov.w r3, #0 ; GPIO_IN (direction)
0x10000246: mcrr 0, 4, r0, r3, cr4 ; gpio_set_dir via coprocessor
0x1000024a: movs r2, #0 ; down = false
0x1000024c: movs r1, #1 ; up = true
0x1000024e: bl 0x100002d8 ; gpio_set_pulls
```
##### Step 3: Disassemble gpio_set_pulls
Now examine the function itself:
```gdb
(gdb) x/30i 0x100002d8
```
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 *0x100002d8
(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
-57
View File
@@ -1,57 +0,0 @@
# 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
### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster
#### Answers
##### Patch Details
| Item | Original | Patched |
|--------------------|-----------------|-----------------|
| Instruction | adds r3, #0x1 | adds r3, #0xa |
| Address | 0x1000027c | 0x1000027c |
| Hex bytes | 01 33 | 0A 33 |
| Increment value | 1 | 10 |
| File offset | 0x27c | 0x27c |
##### Instruction Encoding
```
Thumb adds rD, #imm8:
Byte 0: immediate value (0x01 → 0x0A)
Byte 1: opcode + register (0x33 = adds r3)
```
##### Serial Output After Patch
```
regular_fav_num: 42
static_fav_num: 42
regular_fav_num: 42
static_fav_num: 52
regular_fav_num: 42
static_fav_num: 62
...
regular_fav_num: 42
static_fav_num: 252
regular_fav_num: 42
static_fav_num: 6 ← Overflow! 252 + 10 = 262 mod 256 = 6
```
#### Reflection Answers
1. **The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).**
A `uint8_t` stores values modulo 256. Starting at 42 and incrementing by 10: the sequence passes through 42, 52, 62, ..., 242, 252. The next value is 252 + 10 = 262. Since `uint8_t` can only hold 0255: $262 \bmod 256 = 6$. The wrap value is non-zero because the increment (10) does not evenly divide into 256. With increment 1, the value hits exactly 255, and $255 + 1 = 256 \bmod 256 = 0$. With increment 10, it skips from 252 directly to 262, bypassing 0 and landing on 6.
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?**
The maximum is **255** (`0xFF`). The `adds rD, #imm8` Thumb encoding has an 8-bit immediate field, so valid values are 0255. For an increment larger than 255, you would need to: (a) use a 32-bit Thumb-2 `adds.w` instruction which supports a wider range of modified immediates, (b) use a `movs` + `adds` two-instruction sequence to load a larger value, or (c) load the value from a literal pool with `ldr` then use a register-register `add`. Each approach requires different instruction sizes, so patching in a hex editor becomes more complex—you may need to shift code or use NOP padding.
3. **If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?**
Starting at 42, incrementing by 128: 42 → 170 → 42 → 170 → ... Wait, let's compute: $42 + 128 = 170$ (first iteration), $170 + 128 = 298 \bmod 256 = 42$ (second iteration). It wraps after **2 iterations** back to 42. The variable alternates between 42 and 170 forever because $2 \times 128 = 256$, and $42 + 256 = 42 \bmod 256$. The value never reaches 0—it cycles between exactly two values.
4. **Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?**
No. Changing `strb` to `strh` would store 16 bits instead of 8, which means the variable would be treated as a `uint16_t` (range 065535). This would actually **slow down** overflow—it would take 65,535 42 = 65,493 iterations to wrap instead of 213. Additionally, `strh` writes 2 bytes to RAM, potentially corrupting the adjacent byte at `0x200005a9`. The proper way to speed up overflow is to increase the increment value, not change the storage width. The `strb` truncation to 8 bits is what enforces the `uint8_t` modular arithmetic.
-183
View File
@@ -1,183 +0,0 @@
# 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
### Non-Credit Practice 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:**
```powershell
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:**
```powershell
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 in the loop body starting at `0x10000274`. Disassemble the loop body:
```gdb
(gdb) x/20i 0x10000274
```
Look for this sequence:
```
0x10000278: ldrb r3, [r4, #0] ; Load static_fav_num from RAM
0x1000027a: movs r2, #16 ; LED GPIO pin number
0x1000027c: adds r3, #1 ; Increment by 1 ? THIS IS OUR TARGET
0x1000027e: strb r3, [r4, #0] ; Store back to RAM
```
The `adds r3, #1` instruction is at address `0x1000027c`.
##### Step 3: Examine the Instruction Encoding
Look at the raw bytes of the instruction:
```gdb
(gdb) x/2bx 0x1000027c
```
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 *0x1000027c
(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. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin`
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` (in the same `build` directory)
> ?? **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
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables
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)?
-60
View File
@@ -1,60 +0,0 @@
# 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
### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR
#### Answers
##### Patch Details
| Item | Original | Patched |
|--------------------|-----------------------|-----------------------|
| Instruction | eor.w r3, r3, #1 | eor.w r3, r3, #0 |
| Address | 0x10000286 | 0x10000286 |
| Hex bytes | 83 F0 01 03 | 83 F0 00 03 |
| Patched byte offset| 0x288 (3rd byte) | 01 → 00 |
##### Logic Table Comparison
**Original (EOR #1):**
| Button State | GPIO 15 | After UBFX | After EOR #1 | LED (GPIO 16) |
|-------------|---------|------------|-------------|---------------|
| Released | 1 (HIGH)| 1 | 0 | OFF |
| Pressed | 0 (LOW) | 0 | 1 | ON |
**Patched (EOR #0):**
| Button State | GPIO 15 | After UBFX | After EOR #0 | LED (GPIO 16) |
|-------------|---------|------------|-------------|---------------|
| Released | 1 (HIGH)| 1 | 1 | **ON** |
| Pressed | 0 (LOW) | 0 | 0 | **OFF** |
##### Hardware Result
- Button NOT pressed: LED **ON** (was OFF)
- Button PRESSED: LED **OFF** (was ON)
- Behavior completely reversed by changing a single byte (01 → 00)
#### Reflection Answers
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.**
| x | x XOR 1 | x XOR 0 |
|---|---------|---------|
| 0 | 1 | 0 |
| 1 | 0 | 1 |
`x XOR 1` always flips the bit (acts as NOT): 0→1, 1→0. `x XOR 0` always preserves the bit (acts as identity): 0→0, 1→1. This works because XOR returns 1 when inputs differ and 0 when they match. XOR with 1 forces a difference; XOR with 0 forces a match. This property only applies to the single affected bit—for multi-bit values, each bit is XORed independently.
2. **Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?**
Yes. Removing the EOR instruction entirely would have the same effect as EOR #0—the value passes through unchanged. A Thumb NOP is encoded as `00 BF` (2 bytes). Since `eor.w` is a 32-bit Thumb-2 instruction (4 bytes), you would need **two** NOPs to replace it: `00 BF 00 BF`. In the hex editor, replace bytes at offset 0x2860x289 from `83 F0 01 03` to `00 BF 00 BF`. Both approaches yield identical behavior, but the EOR #0 patch is "cleaner" because it preserves the instruction structure—making the modification more obvious during reverse engineering.
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?**
It would be unreliable. Without a pull-up or pull-down resistor, the GPIO input is **floating** when the button is not pressed—there's no defined voltage on the pin. The input would pick up electrical noise, stray capacitance, and electromagnetic interference, causing random readings (0 or 1 unpredictably). When the button IS pressed, it connects to ground (LOW), which works. But when released, the pin has no path to any voltage, so `gpio_get(15)` returns garbage. The pull-up provides a defined HIGH state when the button circuit is open.
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?**
You would be reading **GPIO 16**, which is the LED output pin. Since GPIO 16 is configured as an output (not input), reading its input register returns the current output state—either 0 or 1 depending on whether the LED is currently on or off. This would create a **feedback loop**: the LED's current state determines its next state (after the XOR), causing unpredictable oscillation or a stuck state. If GPIO 16 had nothing connected and was unconfigured, the floating input would return random values, similar to Q3's scenario.
-193
View File
@@ -1,193 +0,0 @@
# 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
### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR
#### Objective
Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` 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.w r3, r3, #1`) 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 `#1` to `#0`, 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:**
```powershell
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:**
```powershell
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:
```
0x10000274: mov.w r1, #0xd0000000 ; SIO base address
0x10000280: ldr r3, [r1, #4] ; Read GPIO input register
0x10000282: ubfx r3, r3, #15, #1 ; Extract bit 15 (button state)
0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT ? OUR TARGET
0x1000028a: mcrr 0, 4, r2, r3, cr0 ; Write to GPIO output
```
The `eor.w r3, r3, #1` instruction is at address `0x10000286`.
##### 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.w #1` flips the bit, implementing the `!` (NOT) from the C code.
##### Step 4: Verify with GDB
Set a breakpoint at the `eor.w` instruction:
```gdb
(gdb) b *0x10000286
(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 0x10000286
0x10000286 <main+82>: 0x83 0xf0 0x01 0x03
```
The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as:
- `0x83 0xF0` — opcode + source register (r3)
- `0x01`**the immediate value (`#1`)** ? this is what we change
- `0x03` — destination register (r3)
##### Step 7: Patch with the Hex Editor
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin`
2. The instruction starts at file offset: `0x10000286 - 0x10000000 = 0x286`
3. The immediate byte is the 3rd byte: offset `0x286 + 2 = 0x288`
4. Press **Ctrl+G** and enter offset: `288`
5. You should see `01` — change it to `00`
6. Verify the surrounding bytes (`83 F0` before and `03` after) are unchanged
7. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
> ?? **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`.
##### 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
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables
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.w r3, r3, #1` to `eor.w r3, r3, #0`, 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.w r3, r3, #1` 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
+213 -238
View File
@@ -1,6 +1,6 @@
# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
?# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
## 🎯 What You'll Learn This Week
## ? 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
@@ -14,7 +14,7 @@ By the end of this tutorial, you will be able to:
---
## 📚 Part 1: Understanding Static Variables
## Part 1: Understanding Static Variables
### What is a Static Variable?
@@ -25,26 +25,26 @@ Think of it like this:
- **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! │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
@@ -70,26 +70,26 @@ Different types of variables are stored in different memory locations:
### 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)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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.
@@ -108,29 +108,29 @@ This is called **overflow** or **wrap-around**. The value "wraps" back to 0 and
---
## 📚 Part 2: Understanding GPIO Inputs
## 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)
│ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
@@ -138,18 +138,18 @@ So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to
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!)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
@@ -164,25 +164,25 @@ To solve the floating input problem, we use **pull resistors**:
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)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
@@ -208,8 +208,8 @@ This is a compact if-else statement:
- 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
- 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
@@ -218,7 +218,7 @@ gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO));
---
## 📚 Part 3: Understanding Compiler Optimizations
## Part 3: Understanding Compiler Optimizations
### Why Does Code Disappear?
@@ -254,7 +254,7 @@ This is why when you look for `gpio_pull_up` in the binary, you might find `gpio
---
## 📚 Part 4: Setting Up Your Environment
## Part 4: Setting Up Your Environment
### Prerequisites
@@ -273,46 +273,46 @@ Before we start, make sure you have:
### Hardware Setup
Connect your button like this:
- One side of button GPIO 15
- Other side of button GND
- 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│─┘
│ └───┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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.elf
└── 0x0014_static-variables.c
└── uf2conv.py
+-- 0x0014_static-variables/
| +-- build/
| | +-- 0x0014_static-variables.uf2
| | +-- 0x0014_static-variables.elf
| +-- 0x0014_static-variables.c
+-- uf2conv.py
```
---
## 🔬 Part 5: Hands-On Tutorial - Static Variables and GPIO Input
## ? Part 5: Hands-On Tutorial - Static Variables and GPIO Input
### Step 1: Review the Source Code
@@ -412,7 +412,7 @@ 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: 0 ?? Wrapped around!
static_fav_num: 1
static_fav_num: 2
...
@@ -422,9 +422,9 @@ This demonstrates unsigned integer overflow!
---
## 🔬 Part 6: Debugging with GDB (Dynamic Analysis)
## ? 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.
> ? **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
@@ -432,7 +432,7 @@ This demonstrates unsigned integer overflow!
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-s "C:\Users\assem.KEVINTHOMAS\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
@@ -511,17 +511,17 @@ c
GDB responds:
```
Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c, line 5.
Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0014_static-variables/0x0014_static-variables.c, line 5.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
Thread 1 "rp2350.cm0" hit Breakpoint 1, main ()
at C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c:5
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0014_static-variables/0x0014_static-variables.c:5
5 stdio_init_all();
```
> ⚠️ **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint.
> **Note:** If GDB says `The program is not being run.` when you type `c`, the target hasn't been started yet. Use `monitor reset halt` first, then `c` to continue to your breakpoint.
### Step 8: Examine the Static Variable Location
@@ -531,16 +531,16 @@ Static variables live at fixed RAM addresses. But how do we find that address? L
0x10000262: ldr r4, [pc, #44] @ (0x10000290 <main+92>)
```
This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding in this case, a 32-bit RAM address. Let's examine what's stored there:
This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding - in this case, a 32-bit RAM address. Let's examine what's stored there:
```gdb
(gdb) x/1wx 0x10000290
0x10000290 <main+92>: 0x200005a8
```
That's `0x200005a8` the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction.
That's `0x200005a8` - the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction.
> 💡 **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value.
> Tip: **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data - it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value.
### Step 9: Step Through the Loop
@@ -568,10 +568,10 @@ After stepping to `0x10000262` or later, check the registers:
```
Pay attention to:
- `r4` Should hold `0x200005a8` (static variable's RAM address, loaded from literal pool)
- `r1` Used for `printf` arguments (holds `42` or the static variable value)
- `r3` Used for load/increment/store of the static variable
- `pc` Program counter (current instruction address)
- `r4` - Should hold `0x200005a8` (static variable's RAM address, loaded from literal pool)
- `r1` - Used for `printf` arguments (holds `42` or the static variable value)
- `r3` - Used for load/increment/store of the static variable
- `pc` - Program counter (current instruction address)
### Step 11: Watch the Static Variable Change
@@ -613,7 +613,7 @@ TRY IT!
---
## 🔬 Part 7: Understanding the Assembly
## ? Part 7: Understanding the Assembly
Now that we've explored the binary in GDB, let's make sense of the key patterns.
@@ -658,9 +658,9 @@ Look for the load-increment-store pattern using `r4` (which holds the static var
0x1000028e <main+90>: b.n 0x10000264 <main+48>
```
Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`.
Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` - this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`.
**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!
**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 which should be `43`:
@@ -706,7 +706,7 @@ Look for this sequence:
| `0x1000028a` | `mcrr 0, 4, r2, r3, cr0` | Write `r3` (button) and `r2` (pin 16) to GPIO output |
| `0x1000028e` | `b.n 0x10000264` | Loop back to start (`while (true)`) |
> 💡 **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads.
> Tip: **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** - the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads.
### Step 16: Find the Infinite Loop
@@ -716,21 +716,21 @@ The last instruction at `0x1000028e` is already covered in the table above:
0x1000028e: b.n 0x10000264
```
This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits.
This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) - this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits.
---
## 🔬 Part 8: Hacking the Binary with a Hex Editor
## ? 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!
Now for the fun part - we'll patch the `.bin` file directly using a hex editor!
> 💡 **Why a hex editor?** GDB **cannot write to flash memory** the `0x10000000+` address range where program instructions live. Trying `set *(char *)0x10000264 = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor and re-flash it.
> Tip: **Why a hex editor?** GDB **cannot write to flash memory** - the `0x10000000+` address range where program instructions live. Trying `set *(char *)0x10000264 = 0x2b` in GDB gives `Writing to flash memory forbidden in this context`. To make **permanent** patches that survive a power cycle, we edit the `.bin` file directly with a hex editor 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 `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\`
2. Click **File** -> **Open**
3. Navigate to `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables\build\`
4. Open `0x0014_static-variables.bin`
### Step 18: Calculate the File Offset
@@ -742,29 +742,29 @@ file_offset = address - 0x10000000
```
For example:
- Address `0x10000264` file offset `0x264` (612 in decimal)
- Address `0x10000286` file offset `0x286` (646 in decimal)
- Address `0x10000264` -> file offset `0x264` (612 in decimal)
- Address `0x10000286` -> file offset `0x286` (646 in decimal)
### Step 19: Hack #1 Change regular_fav_num from 42 to 43
### Step 19: Hack #1 - Change regular_fav_num from 42 to 43
From our GDB analysis, we know the instruction at `0x10000264` is:
```
movs r1, #0x2a bytes: 2a 21
movs r1, #0x2a -> bytes: 2a 21
```
To change the value from 42 (`0x2a`) to 43 (`0x2b`):
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin`
1. In HxD, open `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables\build\0x0014_static-variables.bin`
2. Press **Ctrl+G** (Go to offset)
3. Enter offset: `264`
4. You should see the byte `2A` at this position
5. Change `2A` to `2B`
6. 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`.
> ?? **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
### Step 20: Hack #2 - Invert the Button Logic
#### Understand the Encoding
@@ -778,15 +778,15 @@ From GDB, we found the `eor.w r3, r3, #1` instruction at `0x10000286` that inver
This is the 32-bit Thumb-2 encoding of `eor.w r3, r3, #1`. The bytes break down as:
```
┌─────────────────────────────────────────────────────────────────┐
eor.w r3, r3, #1 bytes: 83 F0 01 03
│ │
Byte 0: 0x83 ─┐
Byte 1: 0xF0 ─┘ First halfword (opcode + source register)
Byte 2: 0x01 ──── Immediate value (#1) CHANGE THIS
Byte 3: 0x03 ──── Destination register (r3)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| eor.w r3, r3, #1 -> bytes: 83 F0 01 03 |
| |
| Byte 0: 0x83 -?? |
| Byte 1: 0xF0 -+ First halfword (opcode + source register) |
| Byte 2: 0x01 ---- Immediate value (#1) ?? CHANGE THIS |
| Byte 3: 0x03 ---- Destination register (r3) |
| |
+-----------------------------------------------------------------+
```
To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0` (making XOR do nothing):
@@ -800,30 +800,30 @@ To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0`:
3. You should see the byte `01` at this position
4. Change `01` to `00`
> 🔍 **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`.
> ?? **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`.
Now the logic is permanently changed:
- Button released (input = 1): `1 XOR 0 = 1` LED **ON**
- Button pressed (input = 0): `0 XOR 0 = 0` LED **OFF**
- 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**
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
## ? Part 9: Converting and Flashing the Hacked Binary
### Step 22: Convert to UF2 Format
Open a terminal and navigate to your project directory:
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables
cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0014_static-variables
```
Run the conversion command:
@@ -833,7 +833,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
```
**What this command means:**
- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking-main` directory)
- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking` directory)
- `--base 0x10000000` = the XIP base address where code runs from
- `--family 0xe48bff59` = the RP2350 family ID
- `--output build\hacked.uf2` = the output filename
@@ -848,7 +848,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
**Check the serial output:**
```
regular_fav_num: 43 Changed from 42!
regular_fav_num: 43 ?? Changed from 42!
static_fav_num: 42
regular_fav_num: 43
static_fav_num: 43
@@ -859,13 +859,13 @@ static_fav_num: 43
- LED should now be **ON by default** (when button is NOT pressed)
- LED should turn **OFF** when you press the button
🎉 **BOOM! We successfully:**
? **BOOM! We successfully:**
1. Changed the printed value from 42 to 43
2. Inverted the LED/button logic
---
## 📊 Part 10: Summary and Review
## ? Part 10: Summary and Review
### What We Accomplished
@@ -890,46 +890,46 @@ static_fav_num: 43
### 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
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| 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
@@ -943,40 +943,13 @@ static_fav_num: 43
---
## ✅ 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
## ? 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.
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.
@@ -996,7 +969,7 @@ The static variable overflows after 255 iterations. Can you hack it to overflow
---
## 📖 Glossary
## ? Glossary
| Term | Definition |
| --------------------- | ---------------------------------------------------------------- |
@@ -1019,7 +992,7 @@ The static variable overflows after 255 iterations. Can you hack it to overflow
---
## 🔗 Additional Resources
## ? Additional Resources
### GPIO Input Reference
@@ -1061,4 +1034,6 @@ The static variable overflows after 255 iterations. Can you hack it to overflow
**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! 🔧
Happy hacking! ?