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
+262 -287
View File
@@ -1,12 +1,12 @@
# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
?# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C 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 `#define` macros and `const` variables
- Know how constants are stored differently in memory (compile-time vs runtime)
- Understand the I²C (Inter-Integrated Circuit) communication protocol
- Configure I²C peripherals and communicate with LCD displays
- Understand the I2C (Inter-Integrated Circuit) communication protocol
- Configure I2C peripherals and communicate with LCD displays
- Understand C structs and how the Pico SDK uses them for hardware abstraction
- Use GDB to examine constants, structs, and string literals in memory
- Hack constant values and string literals using a hex editor
@@ -14,7 +14,7 @@ By the end of this tutorial, you will be able to:
---
## 📚 Part 1: Understanding Constants in C
## Part 1: Understanding Constants in C
### Two Types of Constants
@@ -39,20 +39,20 @@ printf("Value: %d", FAV_NUM);
Think of it like a "find and replace" in a text editor. The compiler never sees `FAV_NUM` - it only sees `42`!
```
┌─────────────────────────────────────────────────────────────────┐
Preprocessor Macro Flow
│ │
Source Code Preprocessor Compiler
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
#define Replace Compile
FAV_NUM │ ─────► │ FAV_NUM │ ─────► │ binary
42 with 42 code
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
FAV_NUM doesn't exist in the final binary!
The value 42 is embedded directly in instructions.
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| Preprocessor Macro Flow |
| |
| Source Code Preprocessor Compiler |
| +----------+ +----------+ +----------+ |
| | #define | | Replace | | Compile | |
| | FAV_NUM | -----? | FAV_NUM | -----? | binary | |
| | 42 | | with 42 | | code | |
| +----------+ +----------+ +----------+ |
| |
| FAV_NUM doesn't exist in the final binary! |
| The value 42 is embedded directly in instructions. |
| |
+-----------------------------------------------------------------+
```
### Const Variables
@@ -66,19 +66,19 @@ const int OTHER_FAV_NUM = 1337;
Unlike `#define`, this creates a real memory location in the `.rodata` (read-only data) section of flash:
```
┌─────────────────────────────────────────────────────────────────┐
Const Variable in Memory
│ │
Flash Memory (.rodata section)
│ ┌────────────────────────────────────────────────────────────┐ │
Address: 0x10001234 │ │
Value: 0x00000539 (1337 in hex) │ │
Name: OTHER_FAV_NUM (in debug symbols only) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
The variable EXISTS in memory and can be read at runtime.
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| Const Variable in Memory |
| |
| Flash Memory (.rodata section) |
| +------------------------------------------------------------+ |
| | Address: 0x10001234 | |
| | Value: 0x00000539 (1337 in hex) | |
| | Name: OTHER_FAV_NUM (in debug symbols only) | |
| +------------------------------------------------------------+ |
| |
| The variable EXISTS in memory and can be read at runtime. |
| |
+-----------------------------------------------------------------+
```
### Comparison: #define vs const
@@ -93,35 +93,35 @@ Unlike `#define`, this creates a real memory location in the `.rodata` (read-onl
---
## 📚 Part 2: Understanding I²C Communication
## Part 2: Understanding I2C Communication
### What is I²C?
### What is I2C?
**I²C** (pronounced "I-squared-C" or "I-two-C") stands for **Inter-Integrated Circuit**. It's a way for chips to talk to each other using just TWO wires!
**I2C** (pronounced "I-squared-C" or "I-two-C") stands for **Inter-Integrated Circuit**. It's a way for chips to talk to each other using just TWO wires!
```
┌─────────────────────────────────────────────────────────────────┐
I²C Bus - Two Wires, Many Devices
│ │
3.3V
│ │ │
Pull-up Pull-up
│ │ │ │
SDA ─┼────────────┼───────────────────────────────────────
│ │ │ │
SCL ─┼────────────┼───────────────────────────────────────
│ │ │ │ │ │
│ ┌───┴────┐ ┌──┴───┐ ┌────┴──┐ ┌─────┴───┐ │
Pico LCD Sensor EEPROM
(Master) 0x27 0x48 0x50
│ └────────┘ └──────┘ └───────┘ └─────────┘ │
│ │
Each device has a unique address (0x27, 0x48, 0x50...)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| I2C Bus - Two Wires, Many Devices |
| |
| 3.3V |
| | |
| + Pull-up + Pull-up |
| | | |
| SDA -+------------+--------------------------------------- |
| | | |
| SCL -+------------+--------------------------------------- |
| | | | | |
| +---+----+ +--+---+ +----+--+ +-----+---+ |
| | Pico | | LCD | |Sensor | | EEPROM | |
| |(Master)| | 0x27 | | 0x48 | | 0x50 | |
| +--------+ +------+ +-------+ +---------+ |
| |
| Each device has a unique address (0x27, 0x48, 0x50...) |
| |
+-----------------------------------------------------------------+
```
### The Two I²C Wires
### The Two I2C Wires
| Wire | Name | Purpose |
| ------- | ------------ | ------------------------------------ |
@@ -130,13 +130,13 @@ Unlike `#define`, this creates a real memory location in the `.rodata` (read-onl
### Why Pull-Up Resistors?
I²C uses **open-drain** signals, meaning devices can only pull the line LOW. They can't drive it HIGH! Pull-up resistors are needed to bring the lines back to HIGH when no device is pulling them down.
I2C uses **open-drain** signals, meaning devices can only pull the line LOW. They can't drive it HIGH! Pull-up resistors are needed to bring the lines back to HIGH when no device is pulling them down.
The Pico 2 has internal pull-ups that we can enable with `gpio_pull_up()`.
### I²C Addresses
### I2C Addresses
Every I²C device has a unique **7-bit address**. Common addresses:
Every I2C device has a unique **7-bit address**. Common addresses:
| Device Type | Typical Address |
| --------------------- | ---------------- |
@@ -145,27 +145,27 @@ Every I²C device has a unique **7-bit address**. Common addresses:
| EEPROM | `0x50` |
| Real-time clock | `0x68` |
### I²C Communication Flow
### I2C Communication Flow
```
┌─────────────────────────────────────────────────────────────────┐
I²C Transaction
│ │
1. Master sends START condition
2. Master sends device address (7 bits) + R/W bit
3. Addressed device sends ACK (acknowledge)
4. Data is transferred (8 bits at a time)
5. Receiver sends ACK after each byte
6. Master sends STOP condition
│ │
START ──► Address ──► ACK ──► Data ──► ACK ──► STOP
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| I2C Transaction |
| |
| 1. Master sends START condition |
| 2. Master sends device address (7 bits) + R/W bit |
| 3. Addressed device sends ACK (acknowledge) |
| 4. Data is transferred (8 bits at a time) |
| 5. Receiver sends ACK after each byte |
| 6. Master sends STOP condition |
| |
| START --? Address --? ACK --? Data --? ACK --? STOP |
| |
+-----------------------------------------------------------------+
```
---
## 📚 Part 3: Understanding C Structs
## Part 3: Understanding C Structs
### What is a Struct?
@@ -222,11 +222,11 @@ struct i2c_inst {
---
## 📚 Part 4: Understanding the Pico SDK's I²C Structs
## Part 4: Understanding the Pico SDK's I2C Structs
### The i2c_inst_t Struct
The Pico SDK uses a struct to represent each I²C controller:
The Pico SDK uses a struct to represent each I2C controller:
```c
struct i2c_inst {
@@ -247,24 +247,24 @@ struct i2c_inst {
When you write `I2C_PORT` in your code, here's what happens:
```
┌─────────────────────────────────────────────────────────────────┐
Macro Expansion Chain
│ │
In your code: #define I2C_PORT i2c1
│ │ │
│ ▼ │
In i2c.h: #define i2c1 (&i2c1_inst)
│ │ │
│ ▼ │
In i2c.c: i2c_inst_t i2c1_inst = {i2c1_hw, false};
│ │ │
│ ▼ │
In i2c.h: #define i2c1_hw ((i2c_hw_t *)I2C1_BASE)
│ │ │
│ ▼ │
In addressmap.h: #define I2C1_BASE 0x40098000
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| Macro Expansion Chain |
| |
| In your code: #define I2C_PORT i2c1 |
| | |
| ? |
| In i2c.h: #define i2c1 (&i2c1_inst) |
| | |
| ? |
| In i2c.c: i2c_inst_t i2c1_inst = {i2c1_hw, false}; |
| | |
| ? |
| In i2c.h: #define i2c1_hw ((i2c_hw_t *)I2C1_BASE) |
| | |
| ? |
| In addressmap.h: #define I2C1_BASE 0x40098000 |
| |
+-----------------------------------------------------------------+
```
So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer to hardware registers at address `0x40098000`!
@@ -274,25 +274,25 @@ So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer t
The `i2c_hw_t *hw` member points to the actual silicon:
```
┌─────────────────────────────────────────────────────────────────┐
Memory Map
│ │
Address 0x40098000: I²C1 Hardware Registers
│ ┌────────────────────────────────────────────────────────────┐ │
Offset 0x00: IC_CON (Control register) │ │
Offset 0x04: IC_TAR (Target address register) │ │
Offset 0x10: IC_DATA_CMD (Data command register) │ │
... │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
The i2c_hw_t struct maps directly to these registers!
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| Memory Map |
| |
| Address 0x40098000: I2C1 Hardware Registers |
| +------------------------------------------------------------+ |
| | Offset 0x00: IC_CON (Control register) | |
| | Offset 0x04: IC_TAR (Target address register) | |
| | Offset 0x10: IC_DATA_CMD (Data command register) | |
| | ... | |
| +------------------------------------------------------------+ |
| |
| The i2c_hw_t struct maps directly to these registers! |
| |
+-----------------------------------------------------------------+
```
---
## 📚 Part 5: The ARM Calling Convention (AAPCS)
## Part 5: The ARM Calling Convention (AAPCS)
### How Arguments Are Passed
@@ -322,7 +322,7 @@ bl i2c_init ; Call the function
---
## 📚 Part 6: Setting Up Your Environment
## Part 6: Setting Up Your Environment
### Prerequisites
@@ -333,7 +333,7 @@ Before we start, make sure you have:
4. GDB (`arm-none-eabi-gdb`) installed
5. Python installed (for UF2 conversion)
6. A serial monitor (PuTTY, minicom, or screen)
7. A 1602 LCD display with I²C backpack (PCF8574)
7. A 1602 LCD display with I2C backpack (PCF8574)
8. A hex editor (HxD, ImHex, or similar)
9. The sample project: `0x0017_constants`
@@ -349,43 +349,43 @@ Connect your LCD like this:
| SCL | GPIO 3 |
```
┌─────────────────────────────────────────────────────────────────┐
I²C LCD Wiring
│ │
Pico 2 1602 LCD + I²C Backpack
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ │ │ │ │
GPIO 2 │─────── SDA ─────►│ SDA
(SDA)
│ │ │ │ ┌────────────┐ │ │
GPIO 3 │─────── SCL ─────►│ SCL Reverse
(SCL) Engineering
│ │ │ │ └────────────┘ │ │
3.3V │─────── VCC ─────►│ VCC
│ │ │ │ │ │
GND │─────── GND ─────►│ GND
│ │ │ │ │ │
│ └──────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| I2C LCD Wiring |
| |
| Pico 2 1602 LCD + I2C Backpack |
| +----------+ +----------------------+ |
| | | | | |
| | GPIO 2 |------- SDA -----?| SDA | |
| | (SDA) | | | |
| | | | +------------+ | |
| | GPIO 3 |------- SCL -----?| SCL| Reverse | | |
| | (SCL) | | |Engineering | | |
| | | | +------------+ | |
| | 3.3V |------- VCC -----?| VCC | |
| | | | | |
| | GND |------- GND -----?| GND | |
| | | | | |
| +----------+ +----------------------+ |
| |
+-----------------------------------------------------------------+
```
### Project Structure
```
Embedded-Hacking/
├── 0x0017_constants/
├── build/
├── 0x0017_constants.uf2
└── 0x0017_constants.bin
├── 0x0017_constants.c
└── lcd_1602.h
└── uf2conv.py
+-- 0x0017_constants/
| +-- build/
| | +-- 0x0017_constants.uf2
| | +-- 0x0017_constants.bin
| +-- 0x0017_constants.c
| +-- lcd_1602.h
+-- uf2conv.py
```
---
## 🔬 Part 7: Hands-On Tutorial - Constants and I²C LCD
## ? Part 7: Hands-On Tutorial - Constants and I2C LCD
### Step 1: Review the Source Code
@@ -431,10 +431,10 @@ int main(void) {
**What this code does:**
1. **Lines 7-10:** Define preprocessor macros for constants and I²C configuration
1. **Lines 7-10:** Define preprocessor macros for constants and I2C configuration
2. **Line 12:** Define a `const` variable stored in flash
3. **Line 15:** Initialize UART for serial output
4. **Lines 17-21:** Initialize I²C1 at 100kHz, configure GPIO pins, enable pull-ups
4. **Lines 17-21:** Initialize I2C1 at 100kHz, configure GPIO pins, enable pull-ups
5. **Lines 23-27:** Initialize LCD and display "Reverse" on line 0, "Engineering" on line 1
6. **Lines 29-32:** Infinite loop printing both constant values to serial terminal
@@ -463,9 +463,9 @@ OTHER_FAV_NUM: 1337
---
## 🔬 Part 8: Debugging with GDB (Dynamic Analysis)
## ? Part 8: 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
@@ -473,7 +473,7 @@ OTHER_FAV_NUM: 1337
```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"
@@ -573,17 +573,17 @@ c
GDB responds:
```
Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0017_constants/0x0017_constants.c, line 16.
Breakpoint 1 at 0x10000234: file C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0017_constants/0x0017_constants.c, line 16.
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/0x0017_constants/0x0017_constants.c:16
at C:/Users/assem.KEVINTHOMAS/OneDrive/Documents/Embedded-Hacking/0x0017_constants/0x0017_constants.c:16
16 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 6: Find the #define Constant (FAV_NUM)
@@ -618,13 +618,13 @@ Look for this instruction:
...
```
**Surprise!** The `const` variable is ALSO embedded as an immediate value not loaded from memory! The compiler saw that `OTHER_FAV_NUM` is never address-taken (`&OTHER_FAV_NUM` is never used), so it optimized the `const` the same way as `#define` as a constant embedded directly in the instruction.
**Surprise!** The `const` variable is ALSO embedded as an immediate value - not loaded from memory! The compiler saw that `OTHER_FAV_NUM` is never address-taken (`&OTHER_FAV_NUM` is never used), so it optimized the `const` the same way as `#define` - as a constant embedded directly in the instruction.
The difference is the instruction encoding:
- `FAV_NUM` (42): `movs r1, #0x2a` 16-bit Thumb instruction (values 0-255)
- `OTHER_FAV_NUM` (1337): `movw r1, #0x539` 32-bit Thumb-2 instruction (values 0-65535)
- `FAV_NUM` (42): `movs r1, #0x2a` - 16-bit Thumb instruction (values 0-255)
- `OTHER_FAV_NUM` (1337): `movw r1, #0x539` - 32-bit Thumb-2 instruction (values 0-65535)
> 💡 **Why `movw` instead of `movs`?** The value 1337 doesn't fit in 8 bits (max 255), so the compiler uses `movw` (Move Wide) which can encode any 16-bit immediate (0-65535) in a 32-bit instruction.
> Tip: **Why `movw` instead of `movs`?** The value 1337 doesn't fit in 8 bits (max 255), so the compiler uses `movw` (Move Wide) which can encode any 16-bit immediate (0-65535) in a 32-bit instruction.
### Step 8: Examine the Literal Pool
@@ -647,9 +647,9 @@ These are the values that `ldr rN, [pc, #offset]` instructions load:
| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" format str |
| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" fmt |
> 💡 **Why does the disassembly at `0x100002a4` show `strh r0, [r4, #52]` instead of data?** Same reason as Week 6 GDB's `x/i` tries to decode raw data as instructions. Use `x/wx` to see the actual word values.
> Tip: **Why does the disassembly at `0x100002a4` show `strh r0, [r4, #52]` instead of data?** Same reason as Week 6 - GDB's `x/i` tries to decode raw data as instructions. Use `x/wx` to see the actual word values.
### Step 9: Examine the I²C Struct
### Step 9: Examine the I2C Struct
Find the i2c1_inst struct address loaded into r0 before i2c_init:
@@ -684,9 +684,9 @@ Output:
0x10003ef0: "Engineering"
```
### Step 11: Step Through I²C Initialization
### Step 11: Step Through I2C Initialization
Use `si` to step through instructions and watch the I²C setup:
Use `si` to step through instructions and watch the I2C setup:
```
si
@@ -695,36 +695,36 @@ i r r0 r1
---
## 🔬 Part 9: Understanding the Assembly
## ? Part 9: Understanding the Assembly
Now that we've explored the binary in GDB, let's make sense of the key patterns we found.
### Step 12: Analyze #define vs const in Assembly
From GDB, we discovered something interesting **both constants ended up as instruction immediates!**
From GDB, we discovered something interesting - **both constants ended up as instruction immediates!**
**For FAV_NUM (42) a `#define` macro:**
**For FAV_NUM (42) - a `#define` macro:**
```
0x1000028e: movs r1, #42 @ 0x2a
```
The value 42 is embedded directly in a 16-bit Thumb instruction. This is expected `#define` is text replacement, so the compiler never sees `FAV_NUM`, only `42`.
The value 42 is embedded directly in a 16-bit Thumb instruction. This is expected - `#define` is text replacement, so the compiler never sees `FAV_NUM`, only `42`.
**For OTHER_FAV_NUM (1337) a `const` variable:**
**For OTHER_FAV_NUM (1337) - a `const` variable:**
```
0x10000296: movw r1, #1337 @ 0x539
```
The value 1337 is ALSO embedded directly in an instruction but this time a 32-bit Thumb-2 `movw` because the value doesn't fit in 8 bits.
The value 1337 is ALSO embedded directly in an instruction - but this time a 32-bit Thumb-2 `movw` because the value doesn't fit in 8 bits.
**Why wasn't `const` stored in memory?** In theory, `const int OTHER_FAV_NUM = 1337` creates a variable in the `.rodata` section. But the compiler optimized it away because:
1. We never take the address of `OTHER_FAV_NUM` (no `&OTHER_FAV_NUM`)
2. The value fits in a 16-bit `movw` immediate
3. Loading from an immediate is faster than loading from memory
> 💡 **Key takeaway for reverse engineering:** Don't assume `const` variables will appear as memory loads. Modern compilers aggressively inline constant values. The C keyword `const` is a **source-level** concept the compiler may or may not honor it in the final binary.
> Tip: **Key takeaway for reverse engineering:** Don't assume `const` variables will appear as memory loads. Modern compilers aggressively inline constant values. The C keyword `const` is a **source-level** concept - the compiler may or may not honor it in the final binary.
### Step 13: Analyze the I²C Struct Layout
### Step 13: Analyze the I2C Struct Layout
In GDB, we examined the `i2c1_inst` struct at `0x2000062c`:
@@ -736,19 +736,19 @@ In GDB, we examined the `i2c1_inst` struct at `0x2000062c`:
This maps to the `i2c_inst_t` struct:
```
┌─────────────────────────────────────────────────────────────────┐
i2c_inst_t at 0x2000062c
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
Offset Type Name Value │ │
0x00 i2c_hw_t * hw 0x40098000 │ │
0x04 bool restart_on_next 0x00 (false) │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| i2c_inst_t at 0x2000062c |
| |
| +------------------------------------------------------------+ |
| | Offset Type Name Value | |
| | 0x00 i2c_hw_t * hw 0x40098000 | |
| | 0x04 bool restart_on_next 0x00 (false) | |
| +------------------------------------------------------------+ |
| |
+-----------------------------------------------------------------+
```
The first member (`hw`) points to `0x40098000` the I²C1 hardware register base. This is the end of the macro chain: `I2C_PORT` `i2c1` `&i2c1_inst` `hw` `0x40098000`.
The first member (`hw`) points to `0x40098000` - the I2C1 hardware register base. This is the end of the macro chain: `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> `hw` -> `0x40098000`.
### Step 14: Locate the String Literals
@@ -762,21 +762,21 @@ We found the LCD strings in flash memory:
0x10003ef0: "Engineering"
```
These are stored consecutively in the `.rodata` section. Note the addresses we'll need them for patching.
These are stored consecutively in the `.rodata` section. Note the addresses - we'll need them for patching.
---
## 🔬 Part 10: Hacking the Binary with a Hex Editor
## ? Part 10: 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 and read-only data live. Trying `set *(char *)0x1000028e = 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 and read-only data live. Trying `set *(char *)0x1000028e = 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 15: 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\0x0017_constants\build\`
2. Click **File** -> **Open**
3. Navigate to `C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0017_constants\build\`
4. Open `0x0017_constants.bin`
### Step 16: Calculate the File Offset
@@ -788,22 +788,22 @@ file_offset = address - 0x10000000
```
For example:
- Address `0x1000028e` file offset `0x28E` (654 in decimal)
- Address `0x10003ee8` file offset `0x3EE8` (16104 in decimal)
- Address `0x1000028e` -> file offset `0x28E` (654 in decimal)
- Address `0x10003ee8` -> file offset `0x3EE8` (16104 in decimal)
### Step 17: Understand FAV_NUM Encoding (movs 16-bit Thumb)
### Step 17: Understand FAV_NUM Encoding (movs - 16-bit Thumb)
From our GDB analysis, we know the instruction at `0x1000028e` is:
```
movs r1, #0x2a bytes: 2a 21
movs r1, #0x2a -> bytes: 2a 21
```
In HxD, navigate to file offset `0x28E` and verify you see the byte `2A` followed by `21`.
> 🔍 **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` (42). If you wanted to change this to 43, you'd change `2A` to `2B`.
> ?? **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` (42). If you wanted to change this to 43, you'd change `2A` to `2B`.
### Step 18: Understand OTHER_FAV_NUM Encoding (movw 32-bit Thumb-2)
### Step 18: Understand OTHER_FAV_NUM Encoding (movw - 32-bit Thumb-2)
From GDB, we found the `movw r1, #1337` instruction at `0x10000296`. Examine the exact bytes:
@@ -815,18 +815,18 @@ From GDB, we found the `movw r1, #1337` instruction at `0x10000296`. Examine the
This is the 32-bit Thumb-2 encoding of `movw r1, #0x539` (1337). The bytes break down as:
```
┌─────────────────────────────────────────────────────────────────┐
movw r1, #0x539 bytes: 40 F2 39 51
│ │
Byte 0: 0x40 ─┐
Byte 1: 0xF2 ─┘ First halfword (opcode + upper imm bits)
Byte 2: 0x39 ──── Lower 8 bits of immediate (imm8) CHANGE
Byte 3: 0x51 ──── Destination register (r1) + upper imm bits
│ │
imm16 = 0x0539 = 1337 decimal
imm8 field = 0x39 (lower 8 bits of the value)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| movw r1, #0x539 -> bytes: 40 F2 39 51 |
| |
| Byte 0: 0x40 -?? |
| Byte 1: 0xF2 -+ First halfword (opcode + upper imm bits) |
| Byte 2: 0x39 ---- Lower 8 bits of immediate (imm8) ?? CHANGE |
| Byte 3: 0x51 ---- Destination register (r1) + upper imm bits |
| |
| imm16 = 0x0539 = 1337 decimal |
| imm8 field = 0x39 (lower 8 bits of the value) |
| |
+-----------------------------------------------------------------+
```
The file offset is `0x10000296 - 0x10000000 = 0x296`. The imm8 byte is the 3rd byte of the instruction: `0x296 + 2 = 0x298`.
@@ -838,11 +838,11 @@ To change `movw r1, #1337` to `movw r1, #1344`:
3. You should see the byte `39` at this position
4. Change `39` to `40`
> 🔍 **Why offset `0x298` and not `0x296`?** The lower 8 bits of the immediate (`imm8`) are in the **third byte** of the 4-byte `movw` instruction. The instruction starts at file offset `0x296`, so imm8 is at `0x296 + 2 = 0x298`. Changing `0x39` to `0x40` changes the value from `0x539` (1337) to `0x540` (1344).
> ?? **Why offset `0x298` and not `0x296`?** The lower 8 bits of the immediate (`imm8`) are in the **third byte** of the 4-byte `movw` instruction. The instruction starts at file offset `0x296`, so imm8 is at `0x296 + 2 = 0x298`. Changing `0x39` to `0x40` changes the value from `0x539` (1337) to `0x540` (1344).
### Step 19: Hack Change LCD Text from "Reverse" to "Exploit"
### Step 19: Hack - Change LCD Text from "Reverse" to "Exploit"
**IMPORTANT:** The new string must be the **same length** as the original! "Reverse" and "Exploit" are both 7 characters perfect!
**IMPORTANT:** The new string must be the **same length** as the original! "Reverse" and "Exploit" are both 7 characters - perfect!
From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offset = `0x10003ee8 - 0x10000000 = 0x3EE8`.
@@ -864,20 +864,20 @@ From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offs
### Step 20: Save the Patched Binary
1. Click **File** **Save As**
1. Click **File** -> **Save As**
2. Save as `0x0017_constants-h.bin` in the build directory
3. Close the hex editor
---
## 🔬 Part 11: Converting and Flashing the Hacked Binary
## ? Part 11: Converting and Flashing the Hacked Binary
### Step 21: Convert to UF2 Format
Open a terminal and navigate to your project directory:
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
cd C:\Users\assem.KEVINTHOMAS\OneDrive\Documents\Embedded-Hacking\0x0017_constants
```
Run the conversion command:
@@ -907,18 +907,18 @@ OTHER_FAV_NUM: 1337
...
```
The numbers are unchanged we only patched the LCD string!
The numbers are unchanged - we only patched the LCD string!
🎉 **BOOM! We successfully changed the LCD text from "Reverse" to "Exploit" without access to the source code!**
? **BOOM! We successfully changed the LCD text from "Reverse" to "Exploit" without access to the source code!**
---
## 📊 Part 12: Summary and Review
## ? Part 12: Summary and Review
### What We Accomplished
1. **Learned about constants** - `#define` macros vs `const` variables
2. **Understood I²C communication** - Two-wire protocol for peripheral communication
2. **Understood I2C communication** - Two-wire protocol for peripheral communication
3. **Explored C structs** - How the Pico SDK abstracts hardware
4. **Mastered the macro chain** - From `I2C_PORT` to `0x40098000`
5. **Examined structs in GDB** - Inspected memory layout of `i2c_inst_t`
@@ -928,52 +928,52 @@ The numbers are unchanged — we only patched the LCD string!
### #define vs const Summary
```
┌─────────────────────────────────────────────────────────────────┐
#define FAV_NUM 42
│ ─────────────────── │
Text replacement at compile time
No memory allocated
Cannot take address (&FAV_NUM is invalid)
In binary: value appears as immediate (movs r1, #0x2a)
To hack: patch the instruction operand
├─────────────────────────────────────────────────────────────────┤
const int OTHER_FAV_NUM = 1337
│ ────────────────────────────── │
Theoretically in .rodata, but compiler optimized it away
Value embedded as immediate: movw r1, #0x539 (32-bit instr)
Optimization: compiler saw &OTHER_FAV_NUM is never used
In binary: immediate in instruction, same as #define!
To hack: patch instruction operand (imm8 byte at offset +2)
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| #define FAV_NUM 42 |
| ------------------- |
| - Text replacement at compile time |
| - No memory allocated |
| - Cannot take address (&FAV_NUM is invalid) |
| - In binary: value appears as immediate (movs r1, #0x2a) |
| - To hack: patch the instruction operand |
+-----------------------------------------------------------------+
| const int OTHER_FAV_NUM = 1337 |
| ------------------------------ |
| - Theoretically in .rodata, but compiler optimized it away |
| - Value embedded as immediate: movw r1, #0x539 (32-bit instr) |
| - Optimization: compiler saw &OTHER_FAV_NUM is never used |
| - In binary: immediate in instruction, same as #define! |
| - To hack: patch instruction operand (imm8 byte at offset +2) |
+-----------------------------------------------------------------+
```
### I²C Configuration Summary
### I2C Configuration Summary
```
┌─────────────────────────────────────────────────────────────────┐
I²C Setup Steps
│ │
1. i2c_init(i2c1, 100000) - Initialize at 100kHz
2. gpio_set_function(pin, I2C) - Assign pins to I²C
3. gpio_pull_up(sda_pin) - Enable SDA pull-up
4. gpio_pull_up(scl_pin) - Enable SCL pull-up
5. lcd_i2c_init(...) - Initialize the device
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| I2C Setup Steps |
| |
| 1. i2c_init(i2c1, 100000) - Initialize at 100kHz |
| 2. gpio_set_function(pin, I2C) - Assign pins to I2C |
| 3. gpio_pull_up(sda_pin) - Enable SDA pull-up |
| 4. gpio_pull_up(scl_pin) - Enable SCL pull-up |
| 5. lcd_i2c_init(...) - Initialize the device |
| |
+-----------------------------------------------------------------+
```
### The Struct Chain
```
┌─────────────────────────────────────────────────────────────────┐
I2C_PORT i2c1 &i2c1_inst i2c_inst_t
│ │ │
├── hw i2c_hw_t *
└── 0x40098000
│ │ │
└── restart_on_next (bool)
│ │
└─────────────────────────────────────────────────────────────────┘
+-----------------------------------------------------------------+
| I2C_PORT -> i2c1 -> &i2c1_inst -> i2c_inst_t |
| | |
| +-- hw -> i2c_hw_t * |
| | +-- 0x40098000 |
| | |
| +-- restart_on_next (bool) |
| |
+-----------------------------------------------------------------+
```
### Key Memory Addresses
@@ -984,51 +984,24 @@ The numbers are unchanged — we only patched the LCD string!
| `0x1000028e` | FAV_NUM value in instruction |
| `0x10000296` | OTHER_FAV_NUM value in instruction |
| `0x10003ee8` | "Reverse" string literal (example) |
| `0x40098000` | I²C1 hardware registers base |
| `0x40098000` | I2C1 hardware registers base |
| `0x2000062C` | i2c1_inst struct in SRAM |
---
## ✅ Practice Exercises
### Exercise 1: Change Both LCD Lines
Change "Engineering" to "Hacking!!!" (same number of characters).
**Hint:** Find the second string after "Reverse" in memory.
### Exercise 2: Change the I²C Address
The LCD is at address `0x27`. Find where this is passed to `lcd_i2c_init` and change it.
**Warning:** If you change to an invalid address, the LCD won't work!
### Exercise 3: Find All String Literals
Search the binary for all readable strings. How many can you find? What do they reveal about the program?
**Hint:** In GDB, use `x/s` to search for strings in the binary, or scan through the `.bin` file in your hex editor.
### Exercise 4: Trace the Struct Pointer
Follow the `i2c1_inst` pointer from the code to SRAM. What values are stored in the struct?
**Hint:** The first member should point to `0x40098000`.
### Exercise 5: Add Your Own Message
Can you make the LCD display your name? Remember the character limit!
**Hint:** Line 1 and Line 2 each have 16 characters maximum on a 1602 LCD.
---
## 🎓 Key Takeaways
## ? Key Takeaways
1. **#define is text replacement** - It happens before compilation, no memory used.
2. **const creates real variables** - Stored in .rodata, takes memory, has an address.
3. **I²C uses two wires** - SDA for data, SCL for clock, pull-ups required.
3. **I2C uses two wires** - SDA for data, SCL for clock, pull-ups required.
4. **Structs group related data** - The SDK uses them to abstract hardware.
5. **Macros can chain** - `I2C_PORT` `i2c1` `&i2c1_inst` hardware pointer.
5. **Macros can chain** - `I2C_PORT` -> `i2c1` -> `&i2c1_inst` -> hardware pointer.
6. **ARM passes args in registers** - r0-r3 for first four arguments.
@@ -1042,7 +1015,7 @@ Can you make the LCD display your name? Remember the character limit!
---
## 📖 Glossary
## ? Glossary
| Term | Definition |
| ----------------------- | --------------------------------------------------- |
@@ -1050,22 +1023,22 @@ Can you make the LCD display your name? Remember the character limit!
| **AAPCS** | ARM Architecture Procedure Call Standard |
| **const** | Keyword marking a variable as read-only |
| **Forward Declaration** | Telling compiler a type exists before defining it |
| **I²C** | Inter-Integrated Circuit - two-wire serial protocol |
| **I2C** | Inter-Integrated Circuit - two-wire serial protocol |
| **Immediate Value** | A constant embedded directly in an instruction |
| **Open-Drain** | Output that can only pull low, not drive high |
| **PCF8574** | Common I²C I/O expander chip used in LCD backpacks |
| **PCF8574** | Common I2C I/O expander chip used in LCD backpacks |
| **Preprocessor** | Tool that processes code before compilation |
| **Pull-Up Resistor** | Resistor that holds a line HIGH by default |
| **SCL** | Serial Clock - I²C timing signal |
| **SDA** | Serial Data - I²C data line |
| **SCL** | Serial Clock - I2C timing signal |
| **SDA** | Serial Data - I2C data line |
| **Struct** | User-defined type grouping related variables |
| **typedef** | Creates an alias for a type |
---
## 🔗 Additional Resources
## ? Additional Resources
### I²C Timing Reference
### I2C Timing Reference
| Speed Mode | Maximum Frequency |
| ---------- | ----------------- |
@@ -1073,7 +1046,7 @@ Can you make the LCD display your name? Remember the character limit!
| Fast | 400 kHz |
| Fast Plus | 1 MHz |
### Common I²C Addresses
### Common I2C Addresses
| Device | Address |
| --------------------- | ------------- |
@@ -1091,15 +1064,17 @@ Can you make the LCD display your name? Remember the character limit!
| `ldr rN, [pc, #off]` | Load larger value from literal pool |
| `ldr rN, =value` | Pseudo-instruction for loading any constant |
### RP2350 I²C Memory Map
### RP2350 I2C Memory Map
| Address | Description |
| ------------ | ----------------------- |
| `0x40090000` | I²C0 hardware registers |
| `0x40098000` | I²C1 hardware registers |
| `0x40090000` | I2C0 hardware registers |
| `0x40098000` | I2C1 hardware registers |
---
**Remember:** When you see complex nested structures in a binary, take your time to understand the hierarchy. Use GDB to examine struct layouts in memory and trace pointer chains. And always remember even "constants" can be hacked!
**Remember:** When you see complex nested structures in a binary, take your time to understand the hierarchy. Use GDB to examine struct layouts in memory and trace pointer chains. And always remember - even "constants" can be hacked!
Happy hacking! ?
Happy hacking! 🔧