Fixed WEEK06

This commit is contained in:
Kevin Thomas
2026-03-01 20:54:11 -05:00
parent f36217539e
commit d2685b03f2
5 changed files with 299 additions and 146 deletions
+5 -4
View File
@@ -93,17 +93,18 @@ For example, if the initial value is at `0x10004xxx`:
##### Step 6: Patch the Value with a Hex Editor
1. Open `0x0014_static-variables.bin` in HxD
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`
6. Click **File****Save As**`0x0014_static-variables-h.bin` (in the same `build` directory)
##### 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
```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
+9 -7
View File
@@ -48,18 +48,20 @@ arm-none-eabi-gdb build/0x0014_static-variables.elf
##### 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:
From the tutorial, we know `gpio_set_pulls` is called at `0x1000024e` with arguments for GPIO 15:
```gdb
(gdb) x/5i 0x1000024a
(gdb) x/5i 0x10000240
```
You should see:
```
0x1000024a: movs r0, #15 ; GPIO pin 15
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: movs r2, #0 ; down = false
0x10000250: bl 0x100002dc ; gpio_set_pulls
0x1000024e: bl 0x100002d8 ; gpio_set_pulls
```
##### Step 3: Disassemble gpio_set_pulls
@@ -67,7 +69,7 @@ You should see:
Now examine the function itself:
```gdb
(gdb) x/30i 0x100002dc
(gdb) x/30i 0x100002d8
```
Study the disassembly carefully. Look for:
@@ -79,7 +81,7 @@ Study the disassembly carefully. Look for:
##### Step 4: Set a Breakpoint at gpio_set_pulls
```gdb
(gdb) b *0x100002dc
(gdb) b *0x100002d8
(gdb) monitor reset halt
(gdb) c
```
+14 -12
View File
@@ -50,28 +50,29 @@ arm-none-eabi-gdb build/0x0014_static-variables.elf
##### Step 2: Locate the Increment Instruction
From the tutorial, we know the static variable operations are around address `0x10000260`. Disassemble the loop body:
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 0x10000260
(gdb) x/20i 0x10000274
```
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
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
```
Note the exact address of the `adds r3, #0x1` instruction.
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 <address_of_adds>
(gdb) x/2bx 0x1000027c
```
You should see:
@@ -89,7 +90,7 @@ You should see:
Before making a permanent patch, test the change in RAM:
```gdb
(gdb) b *<address_of_adds>
(gdb) b *0x1000027c
(gdb) c
```
@@ -111,19 +112,20 @@ 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
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`
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
```bash
python ../uf2conv.py build/0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2
```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
+30 -25
View File
@@ -7,7 +7,7 @@ Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/
### 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.
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)
@@ -18,7 +18,7 @@ Find the `eor r3, r3, #0x1` instruction that implements the ternary operator's b
- 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.
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
@@ -58,14 +58,14 @@ From the tutorial, the GPIO input/output logic is near address `0x10000274`. Dis
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
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
```
Note the exact address of the `eor r3, r3, #0x1` instruction.
The `eor.w r3, r3, #1` instruction is at address `0x10000286`.
##### Step 3: Understand the Current Logic
@@ -76,14 +76,14 @@ Trace the logic with the pull-up resistor:
| 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.
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` instruction:
Set a breakpoint at the `eor.w` instruction:
```gdb
(gdb) b *<address_of_eor>
(gdb) b *0x10000286
(gdb) c
```
@@ -113,22 +113,26 @@ Or skip the EOR entirely by advancing the PC past it, then observe the LED behav
Look at the raw bytes:
```gdb
(gdb) x/4bx <address_of_eor>
(gdb) x/4bx 0x10000286
0x10000286 <main+82>: 0x83 0xf0 0x01 0x03
```
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.
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. 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`
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)
> 🔍 **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.
> 🔍 **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
@@ -143,8 +147,9 @@ 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
```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
@@ -170,14 +175,14 @@ After completing this exercise, you should be able to:
###### 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 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 r3, r3, #0x1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction
- `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`
+241 -98
View File
@@ -52,8 +52,8 @@ Think of it like this:
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
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?
@@ -227,8 +227,8 @@ When you compile code, the compiler tries to make it faster and smaller. This is
**Example from our code:**
```c
while (true) {
uint8_t regular_fav_num = 42; // Created
regular_fav_num++; // Incremented to 43
uint8_t regular_fav_num = 42; // Created
regular_fav_num++; // Incremented to 43
// But then it's destroyed and recreated as 42 next loop!
}
```
@@ -305,7 +305,7 @@ Embedded-Hacking/
├── 0x0014_static-variables/
│ ├── build/
│ │ ├── 0x0014_static-variables.uf2
│ │ └── 0x0014_static-variables.bin
│ │ └── 0x0014_static-variables.elf
│ └── 0x0014_static-variables.c
└── uf2conv.py
```
@@ -381,6 +381,7 @@ 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
@@ -455,30 +456,50 @@ arm-none-eabi-gdb build/0x0014_static-variables.elf
Let's examine the main function at its entry point. First, disassemble from the start:
```
x/50i 0x10000234
x/38i 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
...
0x10000234 <+0>: push {r4, lr}
0x10000236 <+2>: bl 0x10003014 <stdio_init_all>
0x1000023a <+6>: movs r0, #15
0x1000023c <+8>: bl 0x10000300 <gpio_init>
0x10000240 <+12>: movs r0, #15
0x10000242 <+14>: mov.w r3, #0
0x10000246 <+18>: mcrr 0, 4, r0, r3, cr4
0x1000024a <+22>: movs r2, #0
0x1000024c <+24>: movs r1, #1
0x1000024e <+26>: bl 0x100002d8 <gpio_set_pulls>
0x10000252 <+30>: movs r0, #16
0x10000254 <+32>: bl 0x10000300 <gpio_init>
0x10000258 <+36>: movs r3, #16
0x1000025a <+38>: mov.w r2, #1
0x1000025e <+42>: mcrr 0, 4, r3, r2, cr4
0x10000262 <+46>: ldr r4, [pc, #44] @ (0x10000290 <main+92>)
0x10000264 <+48>: movs r1, #42 @ 0x2a
0x10000266 <+50>: ldr r0, [pc, #44] @ (0x10000294 <main+96>)
0x10000268 <+52>: bl 0x100031a4 <__wrap_printf>
0x1000026c <+56>: ldrb r1, [r4, #0]
0x1000026e <+58>: ldr r0, [pc, #40] @ (0x10000298 <main+100>)
0x10000270 <+60>: bl 0x100031a4 <__wrap_printf>
0x10000274 <+64>: mov.w r1, #3489660928 @ 0xd0000000
0x10000278 <+68>: ldrb r3, [r4, #0]
0x1000027a <+70>: movs r2, #16
0x1000027c <+72>: adds r3, #1
0x1000027e <+74>: strb r3, [r4, #0]
0x10000280 <+76>: ldr r3, [r1, #4]
0x10000282 <+78>: ubfx r3, r3, #15, #1
0x10000286 <+82>: eor.w r3, r3, #1
0x1000028a <+86>: mcrr 0, 4, r2, r3, cr0
0x1000028e <+90>: b.n 0x10000264 <main+48>
0x10000290 <+92>: lsls r0, r5, #22
0x10000292 <+94>: movs r0, #0
0x10000294 <+96>: adds r5, #96 @ 0x60
0x10000296 <+98>: asrs r0, r0, #32
0x10000298 <+100>: adds r5, #120 @ 0x78
0x1000029a <+102>: asrs r0, r0, #32
```
### Step 7: Set a Breakpoint at Main
@@ -490,60 +511,105 @@ c
GDB responds:
```
Breakpoint 1 at 0x10000234
Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c, line 5.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) c
Continuing.
Breakpoint 1, 0x10000234 in ?? ()
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
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.
### Step 8: Examine the Static Variable Location
Static variables live at fixed RAM addresses. Let's find where `static_fav_num` is stored:
Static variables live at fixed RAM addresses. But how do we find that address? Look at the first instruction in the disassembly from Step 6:
```
x/20i 0x10000260
0x10000262: ldr r4, [pc, #44] @ (0x10000290 <main+92>)
```
Look for instructions that load/store to addresses in the `0x20000xxx` range - that's where static variables live in RAM.
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.
> 💡 **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
```
si
Set a breakpoint at the start of the loop and step through:
```gdb
(gdb) b *0x10000264
(gdb) c
```
Use `si` (step instruction) to execute one instruction at a time and watch how the static variable gets loaded, incremented, and stored back.
Now use `si` (step instruction) to execute one instruction at a time:
```gdb
(gdb) si
```
Watch how the static variable gets loaded (`ldrb`), incremented (`adds`), and stored back (`strb`).
### Step 10: Examine Register Values
```
i r
After stepping to `0x10000262` or later, check the registers:
```gdb
(gdb) 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)
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)
### Step 11: Watch the Static Variable Change
Once you identify the static variable address (e.g., `0x200005a8`), you can watch it:
Now that we know the static variable lives at `0x200005a8`, examine it directly:
```
x/1db 0x200005a8
```gdb
(gdb) x/1db 0x200005a8
0x200005a8: 42
```
This displays 1 byte in decimal format. Step through a few loop iterations and re-examine to see it increment.
Step through a full loop iteration (back to `0x10000264`) and re-examine:
```gdb
(gdb) c
(gdb) x/1db 0x200005a8
0x200005a8: 43
```
The value incremented from 42 to 43! Each loop iteration, the `adds r3, #1` at `0x1000027c` bumps it by 1, and `strb r3, [r4, #0]` at `0x1000027e` writes it back to RAM.
### Step 12: Examine GPIO State
Read the GPIO input register to see button state:
Read the GPIO input register to see the button state:
```
x/1wx 0xd0000004
```gdb
(gdb) 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.
The SIO GPIO input register at `0xd0000004` shows the current state of all GPIO pins. Bit 15 corresponds to our button on GPIO 15. To extract just bit 15:
```gdb
(gdb) p/x (*(unsigned int *)0xd0000004 >> 15) & 1
```
- Returns `1` when button is **not pressed** (pull-up holds it HIGH)
- Returns `0` when button is **pressed** (connected to GND)
TRY IT!
---
@@ -553,16 +619,16 @@ 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:
In GDB, examine the code at the start of the loop:
```gdb
(gdb) x/5i 0x1000028c
(gdb) x/5i 0x10000262
```
Look for this instruction:
```
0x1000028e: movs r1, #0x2a
0x10000264: movs r1, #42 @ 0x2a
```
This loads the value `0x2a` (42 in decimal) directly into register `r1` for the first `printf` call.
@@ -571,25 +637,32 @@ This loads the value `0x2a` (42 in decimal) directly into register `r1` for the
### Step 14: Analyze the Static Variable
Examine the static variable operations:
Examine the static variable operations in the second half of the loop body:
```gdb
(gdb) x/10i 0x10000260
(gdb) x/10i 0x10000274
```
Look for references to addresses in the `0x200005xx` range:
Look for the load-increment-store pattern using `r4` (which holds the static variable's RAM address):
```
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
0x10000274 <main+64>: mov.w r1, #3489660928 @ 0xd0000000
0x10000278 <main+68>: ldrb r3, [r4, #0]
0x1000027a <main+70>: movs r2, #16
0x1000027c <main+72>: adds r3, #1
0x1000027e <main+74>: strb r3, [r4, #0]
0x10000280 <main+76>: ldr r3, [r1, #4]
0x10000282 <main+78>: ubfx r3, r3, #15, #1
0x10000286 <main+82>: eor.w r3, r3, #1
0x1000028a <main+86>: mcrr 0, 4, r2, r3, cr0
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`.
**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:
Verify the static variable value which should be `43`:
```gdb
(gdb) x/1db 0x200005a8
@@ -606,32 +679,44 @@ Examine the GPIO input/output code:
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
0x10000274 <main+64>: mov.w r1, #3489660928 @ 0xd0000000
0x10000278 <main+68>: ldrb r3, [r4, #0]
0x1000027a <main+70>: movs r2, #16
0x1000027c <main+72>: adds r3, #1
0x1000027e <main+74>: strb r3, [r4, #0]
0x10000280 <main+76>: ldr r3, [r1, #4]
0x10000282 <main+78>: ubfx r3, r3, #15, #1
0x10000286 <main+82>: eor.w r3, r3, #1
0x1000028a <main+86>: mcrr 0, 4, r2, r3, cr0
0x1000028e <main+90>: b.n 0x10000264 <main+48>
```
**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) |
| Address | Instruction | Purpose |
| -------------- | -------------------------- | ---------------------------------------------------- |
| `0x10000274` | `mov.w r1, #0xd0000000` | Load SIO (Single-cycle I/O) base address into `r1` |
| `0x10000278` | `ldrb r3, [r4, #0]` | Load `static_fav_num` from RAM into `r3` |
| `0x1000027a` | `movs r2, #16` | Load LED pin number (16) into `r2` for later |
| `0x1000027c` | `adds r3, #1` | Increment `static_fav_num` by 1 |
| `0x1000027e` | `strb r3, [r4, #0]` | Store incremented value back to RAM |
| `0x10000280` | `ldr r3, [r1, #4]` | Read GPIO input state (SIO_GPIO_IN at offset `0x04`) |
| `0x10000282` | `ubfx r3, r3, #15, #1` | Extract bit 15 (GPIO 15 = button) |
| `0x10000286` | `eor.w r3, r3, #1` | XOR with 1 to invert (implements `? 0 : 1`) |
| `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.
### Step 16: Find the Infinite Loop
At the end of the function, look for:
The last instruction at `0x1000028e` is already covered in the table above:
```
b 0x10000264
0x1000028e: b.n 0x10000264
```
This is an **unconditional branch** back to the start of the loop — this is the `while (true)` in our code!
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.
---
@@ -639,13 +724,13 @@ This is an **unconditional branch** back to the start of the loop — this is th
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.
> 💡 **Why a hex editor?** GDB lets you modify values **in RAM** at runtime (e.g., `set *(char *)0x200005a8 = 100`), but those changes are **lost when the device reboots** — GDB has no way to write changes back to the `.bin` file on disk. 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 `0x0014_static-variables/build/`
3. Navigate to `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\`
4. Open `0x0014_static-variables.bin`
### Step 18: Calculate the File Offset
@@ -657,12 +742,35 @@ file_offset = address - 0x10000000
```
For example:
- Address `0x1000028e` → file offset `0x28e` (654 in decimal)
- Address `0x10000274` → file offset `0x274` (628 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
From GDB, we know the instruction at `0x1000028e` is:
#### Test in GDB First (Temporary)
Before touching the binary file, test the change in GDB to make sure it works. From your GDB session (with the breakpoint at `0x10000264`):
```gdb
(gdb) set *(char *)0x10000264 = 0x2b
(gdb) x/1i 0x10000264
0x10000264 <main+48>: movs r1, #43 @ 0x2b
(gdb) c
```
Check your serial monitor — you should see `regular_fav_num: 43` instead of `42`. It works!
> ⚠️ **This change is temporary.** GDB modified the value in RAM, but the `.bin` file on disk is unchanged. When you reboot the Pico, it will reload the original binary and go back to `42`. To make the change **permanent**, we need to patch the `.bin` file with a hex editor.
#### Make It Permanent in HxD
Now quit GDB and open the hex editor:
```gdb
(gdb) q
```
From GDB, we know the instruction at `0x10000264` is:
```
movs r1, #0x2a → bytes: 2a 21
@@ -670,32 +778,67 @@ 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)
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 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`.
### 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.
#### Understand the Encoding
In GDB, examine the exact address and bytes:
From GDB, we found the `eor.w r3, r3, #1` instruction at `0x10000286` that inverts the button value. Examine the exact bytes:
```gdb
(gdb) x/2bx <address_of_eor_instruction>
(gdb) x/4bx 0x10000286
0x10000286 <main+82>: 0x83 0xf0 0x01 0x03
```
The `eor` instruction with `#0x1` needs to become `eor` with `#0x0`:
This is the 32-bit Thumb-2 encoding of `eor.w r3, r3, #1`. The bytes break down as:
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`
```
┌─────────────────────────────────────────────────────────────────┐
│ 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) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
Now the logic is:
#### Test in GDB First (Temporary)
```gdb
(gdb) set *(char *)(0x10000286 + 2) = 0x00
(gdb) x/4bx 0x10000286
0x10000286 <main+82>: 0x83 0xf0 0x00 0x03
(gdb) c
```
Now test the button — the LED behavior should be **inverted** (ON when not pressed, OFF when pressed). It works!
> ⚠️ Again, this is **temporary** — reboot and it's gone. Let's make it permanent.
#### Make It Permanent in HxD
The file offset is `0x10000286 - 0x10000000 = 0x286`. The immediate byte is the 3rd byte of the instruction, so: `0x286 + 2 = 0x288`.
To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0`:
1. In HxD, press **Ctrl+G** (Go to offset)
2. Enter offset: `288` (the third byte of the 4-byte instruction)
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`.
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**
@@ -715,21 +858,21 @@ This is the **opposite** of the original behavior!
Open a terminal and navigate to your project directory:
```bash
cd Embedded-Hacking/0x0014_static-variables
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\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
```powershell
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
- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking-main` directory)
- `--base 0x10000000` = the XIP base address where code runs from
- `--family 0xe48bff59` = the RP2350 family ID
- `--output build/hacked.uf2` = the output filename
- `--output build\hacked.uf2` = the output filename
### Step 23: Flash the Hacked Binary