Refactor E and S

This commit is contained in:
Kevin Thomas
2026-03-19 15:01:07 -04:00
parent f524f5b86b
commit 1784a107ae
81 changed files with 2986 additions and 247 deletions
+91
View File
@@ -0,0 +1,91 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Non-Credit Practice Exercise 1 Solution: Change Both LCD Lines
#### Answers
##### String Locations in Flash
| String | Address | File Offset | Length (bytes) | Hex Encoding |
|---------------|--------------|-------------|----------------|---------------------------------------------------|
| "Reverse" | 0x10003ee8 | 0x3EE8 | 8 (7 + null) | 52 65 76 65 72 73 65 00 |
| "Engineering" | 0x10003ef0 | 0x3EF0 | 12 (11 + null) | 45 6E 67 69 6E 65 65 72 69 6E 67 00 |
##### Line 1 Patch: "Reverse" → "Exploit"
| Character | Hex |
|-----------|--------|
| E | 0x45 |
| x | 0x78 |
| p | 0x70 |
| l | 0x6c |
| o | 0x6f |
| i | 0x69 |
| t | 0x74 |
| \0 | 0x00 |
```
Offset 0x3EE8:
Before: 52 65 76 65 72 73 65 00 ("Reverse")
After: 45 78 70 6C 6F 69 74 00 ("Exploit")
```
##### Line 2 Patch: "Engineering" → "Hacking!!!!"
| Character | Hex |
|-----------|--------|
| H | 0x48 |
| a | 0x61 |
| c | 0x63 |
| k | 0x6b |
| i | 0x69 |
| n | 0x6e |
| g | 0x67 |
| ! | 0x21 |
| ! | 0x21 |
| ! | 0x21 |
| ! | 0x21 |
| \0 | 0x00 |
```
Offset 0x3EF0:
Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering")
After: 48 61 63 6B 69 6E 67 21 21 21 21 00 ("Hacking!!!!")
```
##### Conversion and Flash
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### LCD Verification
```
Line 1: Exploit
Line 2: Hacking!!!!
```
#### Reflection Answers
1. **Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?**
Strings are stored consecutively in the `.rodata` section. "Reverse" occupies 8 bytes starting at `0x10003ee8` and "Engineering" starts immediately at `0x10003ef0`. If the replacement string is longer than 8 bytes, the extra bytes would overwrite the beginning of "Engineering" (or whatever data follows). The `.rodata` section has no gaps—it's a packed sequence of constants, format strings, and other read-only data. Corrupting adjacent data could break LCD line 2, crash `printf` format strings, or cause undefined behavior.
2. **The two strings are stored only 8 bytes apart (0x3EE8 to 0x3EF0). "Reverse" is 7 characters + null = 8 bytes. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?**
"Reversal" needs 9 bytes (8 chars + null terminator). The 9th byte (the `0x00` null terminator) would be written to address `0x10003ef0`, which is the first byte of "Engineering" — the letter 'E' (`0x45`). This would overwrite 'E' with `0x00`, turning "Engineering" into an empty string. The LCD would display "Reversal" on line 1 and nothing on line 2, because `lcd_puts` would see a null terminator immediately at the start of the second string.
3. **If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.**
"Hello" = 5 characters, followed by the null terminator and 2 padding null bytes:
```
48 65 6C 6C 6F 00 00 00
H e l l o \0 \0 \0
```
The first `0x00` at position 5 terminates the string. The remaining two `0x00` bytes are padding that fills the original 8-byte allocation. These padding bytes are never read by `lcd_puts` because it stops at the first null terminator.
4. **Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value?**
Yes. Change the first byte at offset `0x3EE8` from `0x52` ('R') to `0x00` (null). This makes the string start with a null terminator, so `lcd_puts` sees an empty string and displays nothing. Only one byte needs to change: the byte at file offset `0x3EE8`, from `0x52` to `0x00`.
+14 -14
View File
@@ -1,10 +1,10 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Exercise 1: Change Both LCD Lines
### Non-Credit Practice Exercise 1: Change Both LCD Lines
#### Objective
Patch the LCD display strings in the `.bin` file to change "Reverse" to "Exploit" on line 1 and "Engineering" to "Hacking!!!!" on line 2, using GDB to locate the string addresses, a hex editor to perform the patches, and the Pico 2 hardware to verify the changes on the physical LCD.
@@ -15,10 +15,10 @@ Patch the LCD display strings in the `.bin` file to change "Reverse" to "Exploit
- 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 1602 LCD connected via I²C
- Raspberry Pi Pico 2 with 1602 LCD connected via I²C
#### Task Description
The LCD currently displays "Reverse" on line 1 and "Engineering" on line 2. You will find both string literals in flash memory using GDB, calculate their file offsets, and patch them to display custom text. The critical constraint is that replacement strings must be the **same length** as the originals (or shorter, padded with null bytes) — otherwise you'll corrupt adjacent data.
The LCD currently displays "Reverse" on line 1 and "Engineering" on line 2. You will find both string literals in flash memory using GDB, calculate their file offsets, and patch them to display custom text. The critical constraint is that replacement strings must be the **same length** as the originals (or shorter, padded with null bytes) — otherwise you'll corrupt adjacent data.
#### Step-by-Step Instructions
@@ -102,8 +102,8 @@ Output:
- "Engineering" = 11 characters + null terminator = 12 bytes
**Replacement strings (MUST be same length or shorter):**
- "Exploit" = 7 characters (same as "Reverse")
- "Hacking!!!!" = 11 characters (same as "Engineering")
- "Exploit" = 7 characters ? (same as "Reverse")
- "Hacking!!!!" = 11 characters ? (same as "Engineering")
Build the ASCII hex for "Exploit":
@@ -141,17 +141,17 @@ Build the ASCII hex for "Hacking!!!!":
file_offset = address - 0x10000000
```
- "Reverse" at `0x10003ee8` file offset `0x3EE8`
- "Engineering" at `0x10003ef0` file offset `0x3EF0`
- "Reverse" at `0x10003ee8` ? file offset `0x3EE8`
- "Engineering" at `0x10003ef0` ? file offset `0x3EF0`
##### Step 6: Patch String 1 — "Reverse" "Exploit"
##### Step 6: Patch String 1 — "Reverse" ? "Exploit"
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin`
2. Press **Ctrl+G** and enter offset: `3EE8`
3. You should see: `52 65 76 65 72 73 65 00` ("Reverse\0")
4. Replace with: `45 78 70 6C 6F 69 74 00` ("Exploit\0")
##### Step 7: Patch String 2 — "Engineering" "Hacking!!!!"
##### Step 7: Patch String 2 — "Engineering" ? "Hacking!!!!"
1. Press **Ctrl+G** and enter offset: `3EF0`
2. You should see: `45 6E 67 69 6E 65 65 72 69 6E 67 00` ("Engineering\0")
@@ -159,7 +159,7 @@ file_offset = address - 0x10000000
##### Step 8: Save and Convert
1. Click **File** **Save As** `0x0017_constants-h.bin`
1. Click **File** ? **Save As** ? `0x0017_constants-h.bin`
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
@@ -187,7 +187,7 @@ After completing this exercise, you should be able to:
###### Question 1: Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?
###### Question 2: The two strings "Reverse" and "Engineering" are stored only 8 bytes apart (`0x3EE8` to `0x3EF0`). "Reverse" is 7 characters + null = 8 bytes — it perfectly fills the gap. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?
###### Question 2: The two strings "Reverse" and "Engineering" are stored only 8 bytes apart (`0x3EE8` to `0x3EF0`). "Reverse" is 7 characters + null = 8 bytes — it perfectly fills the gap. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?
###### Question 3: If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.
@@ -195,9 +195,9 @@ After completing this exercise, you should be able to:
#### Tips and Hints
- Use an ASCII table to convert characters: uppercase A-Z = `0x41`-`0x5A`, lowercase a-z = `0x61`-`0x7A`
- The null terminator `0x00` marks the end of the string — anything after it is ignored by `lcd_puts`
- The null terminator `0x00` marks the end of the string — anything after it is ignored by `lcd_puts`
- If your replacement is shorter, pad with `0x00` bytes to fill the original length
- The 1602 LCD has 16 characters per line — you cannot display more than 16 characters per line regardless of string length
- The 1602 LCD has 16 characters per line — you cannot display more than 16 characters per line regardless of string length
#### Next Steps
- Proceed to Exercise 2 to explore finding all string literals in the binary
+68
View File
@@ -0,0 +1,68 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Non-Credit Practice Exercise 2 Solution: Find All String Literals in the Binary
#### Answers
##### String Catalog
| Address | File Offset | String Content | Length | Purpose |
|---------------|-------------|--------------------------|--------|-----------------------------|
| `0x10003ee8` | `0x3EE8` | "Reverse" | 8 | LCD line 1 text |
| `0x10003ef0` | `0x3EF0` | "Engineering" | 12 | LCD line 2 text |
| `0x10003efc` | `0x3EFC` | "FAV_NUM: %d\r\n" | 16 | printf format string |
| `0x10003f0c` | `0x3F0C` | "OTHER_FAV_NUM: %d\r\n" | 22 | printf format string |
| Various | Various | SDK panic/assert strings | Varies | Pico SDK internal messages |
| Various | Various | Source file paths | Varies | SDK debug/assert references |
##### GDB String Search Commands
```gdb
(gdb) x/s 0x10003ee8
0x10003ee8: "Reverse"
(gdb) x/s 0x10003ef0
0x10003ef0: "Engineering"
(gdb) x/s 0x10003efc
0x10003efc: "FAV_NUM: %d\r\n"
(gdb) x/s 0x10003f0c
0x10003f0c: "OTHER_FAV_NUM: %d\r\n"
```
##### Scanning for Strings
```gdb
(gdb) x/20s 0x10003e00
(gdb) x/50s 0x10003d00
```
##### Literal Pool Reference
From the literal pool at `0x100002a4`:
| Pool Address | Value | String It Points To |
|----------------|---------------|---------------------------|
| `0x100002ac` | `0x10003EE8` | "Reverse" |
| `0x100002b0` | `0x10003EF0` | "Engineering" |
| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" |
| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" |
#### Reflection Answers
1. **How many distinct strings did you find? Were any of them surprising or unexpected?**
At minimum 4 application-level strings: "Reverse", "Engineering", "FAV_NUM: %d\r\n", and "OTHER_FAV_NUM: %d\r\n". Beyond these, the Pico SDK embeds additional strings — panic handler messages, assert failure messages, and source file path strings used for debug output. The SDK strings are surprising because they reveal internal implementation details: file paths expose the build environment directory structure, and error messages reveal which SDK functions have built-in error checking. A reverse engineer can learn the SDK version and build configuration just from these strings.
2. **Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings?**
Strings are high-entropy human-readable data that reveals program behavior without reading assembly. An attacker can learn: what the program displays or communicates (LCD messages, serial output), what libraries it uses (SDK error messages), how it handles errors (panic/assert strings), what data formats it processes (`printf` format strings with `%d`, `%s`, `%f`), network endpoints or credentials (URLs, passwords, API keys), the build environment (file paths), and the overall purpose of the firmware. Strings are often the first thing a reverse engineer examines in an unknown binary.
3. **What technique could a developer use to make strings harder to find in a binary? (Think about what the strings look like in memory.)**
String encryption/obfuscation: encrypt all string literals at compile time using XOR, AES, or a custom cipher, and decrypt them into a RAM buffer only when needed at runtime. This way, scanning the binary with `strings` or a hex editor reveals only ciphertext — random-looking bytes instead of readable text. Other techniques include: splitting strings across multiple locations and assembling them at runtime, using character arrays initialized by code rather than string literals, replacing strings with numeric lookup indices into an encrypted table, or using compile-time obfuscation tools that automatically transform string constants.
4. **The printf format strings contain \r\n. In the binary, these appear as two bytes: 0x0D 0x0A. Why two bytes instead of the four characters \, r, \, n?**
The C compiler processes escape sequences during compilation. In source code, `\r` is written as two characters (backslash + r), but the compiler converts it to a single byte: `0x0D` (carriage return, ASCII 13). Similarly, `\n` becomes `0x0A` (line feed, ASCII 10). These are **control characters** — non-printable ASCII codes that control terminal behavior. The backslash notation is just a human-readable way to represent these bytes in source code. By the time the string reaches the binary, all escape sequences have been resolved to their single-byte equivalents.
+8 -8
View File
@@ -1,10 +1,10 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Exercise 2: Find All String Literals in the Binary
### Non-Credit Practice Exercise 2: Find All String Literals in the Binary
#### Objective
Systematically search through the `0x0017_constants` binary using GDB and a hex editor to locate every human-readable string literal, catalog their addresses, contents, and purposes, and gain experience identifying data structures in compiled binaries.
@@ -16,7 +16,7 @@ Systematically search through the `0x0017_constants` binary using GDB and a hex
- A hex editor (HxD, ImHex, or similar)
#### Task Description
Compiled binaries contain string literals in the `.rodata` section — format strings for `printf`, LCD messages, library strings, and more. You will use two techniques to find them: (1) searching with GDB's `x/s` command to examine suspected string regions, and (2) visually scanning the binary in a hex editor for ASCII sequences. You will document every string you find, its address, and its likely purpose.
Compiled binaries contain string literals in the `.rodata` section — format strings for `printf`, LCD messages, library strings, and more. You will use two techniques to find them: (1) searching with GDB's `x/s` command to examine suspected string regions, and (2) visually scanning the binary in a hex editor for ASCII sequences. You will document every string you find, its address, and its likely purpose.
#### Step-by-Step Instructions
@@ -67,7 +67,7 @@ The program uses `printf("FAV_NUM: %d\r\n", ...)` and `printf("OTHER_FAV_NUM: %d
(gdb) x/10s 0x10003ec0
```
This displays 10 consecutive strings starting from that address. Examine the output — you should find the `printf` format strings. Try different starting addresses if needed:
This displays 10 consecutive strings starting from that address. Examine the output — you should find the `printf` format strings. Try different starting addresses if needed:
```gdb
(gdb) x/20s 0x10003e00
@@ -92,7 +92,7 @@ Many results will be garbage (non-ASCII data interpreted as text), but real stri
2. Switch to the "Text" pane (right side) to see ASCII representation
3. Scroll through the binary and look for readable text sequences
In HxD, printable ASCII characters (0x200x7E) are displayed as text; non-printable bytes appear as dots.
In HxD, printable ASCII characters (0x200x7E) are displayed as text; non-printable bytes appear as dots.
##### Step 6: Use Hex Editor Search
@@ -145,13 +145,13 @@ After completing this exercise, you should be able to:
###### Question 4: The `printf` format strings contain `\r\n`. In the binary, these appear as two bytes: `0x0D 0x0A`. Why two bytes instead of the four characters `\`, `r`, `\`, `n`?
#### Tips and Hints
- In GDB, `x/s` treats any address as the start of a null-terminated string — it will print garbage if the address isn't really a string
- In GDB, `x/s` treats any address as the start of a null-terminated string — it will print garbage if the address isn't really a string
- Use `x/Ns address` where N is a number to print N consecutive strings (useful for scanning regions)
- In HxD, use **Edit** **Select Block** to highlight a region and examine the text pane
- In HxD, use **Edit** ? **Select Block** to highlight a region and examine the text pane
- Real strings are typically 4+ printable ASCII characters followed by a null byte (`0x00`)
- The `.rodata` section is usually located after the `.text` (code) section in the binary
#### Next Steps
- Proceed to Exercise 3 to trace the I²C struct pointer chain
- Proceed to Exercise 3 to trace the I²C struct pointer chain
- Try the `strings` command if available: `strings 0x0017_constants.bin` will extract all printable character sequences
- Consider: if you found a password string in an embedded device binary, what security implications would that have?
+91
View File
@@ -0,0 +1,91 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Non-Credit Practice Exercise 3 Solution: Trace the I²C Struct Pointer Chain
#### Answers
##### Complete Pointer Chain
```
I2C_PORT (source macro: #define I2C_PORT i2c1)
i2c1 (SDK macro: #define i2c1 (&i2c1_inst))
&i2c1_inst = 0x2000062c (SRAM address of i2c_inst_t struct)
i2c1_inst.hw = 0x40098000 (pointer to I²C1 hardware register base)
I²C1 Hardware Registers (memory-mapped I/O silicon)
+-- IC_CON at 0x40098000
+-- IC_TAR at 0x40098004
+-- IC_SAR at 0x40098008
+-- IC_DATA_CMD at 0x40098010
```
##### Literal Pool Load
```gdb
(gdb) x/6wx 0x100002a4
0x100002a4: 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0
0x100002b4: 0x10003efc 0x10003f0c
```
The value `0x2000062c` at pool address `0x100002a8` is loaded into `r0` by a `ldr r0, [pc, #offset]` instruction before the `bl i2c_init` call.
##### i2c1_inst Struct in SRAM
```gdb
(gdb) x/2wx 0x2000062c
0x2000062c <i2c1_inst>: 0x40098000 0x00000000
```
| Offset | Field | Value | Size | Meaning |
|--------|-------------------|-------------|---------|-------------------------------|
| +0x00 | hw | 0x40098000 | 4 bytes | Pointer to I²C1 hardware regs |
| +0x04 | restart_on_next | 0x00000000 | 4 bytes | false (no pending restart) |
Total struct size: 8 bytes (4-byte pointer + 4-byte bool padded to word alignment).
##### Hardware Registers at 0x40098000
```gdb
(gdb) x/8wx 0x40098000
```
| Offset | Register | Address | Description |
|--------|-------------|-------------|---------------------------|
| +0x00 | IC_CON | 0x40098000 | I²C control register |
| +0x04 | IC_TAR | 0x40098004 | Target address register |
| +0x08 | IC_SAR | 0x40098008 | Slave address register |
| +0x10 | IC_DATA_CMD | 0x40098010 | Data command register |
##### I²C0 Comparison
```gdb
(gdb) x/2wx 0x20000628
```
| Controller | Struct Address | hw Pointer | Separation |
|------------|---------------|-------------|-------------|
| I²C0 | 0x20000628 | 0x40090000 | Base |
| I²C1 | 0x2000062c | 0x40098000 | +0x8000 |
Same struct layout, different hardware pointer — demonstrating the SDK's abstraction.
#### Reflection Answers
1. **Why does the SDK use a struct with a pointer to hardware registers instead of accessing 0x40098000 directly? What advantage does this abstraction provide?**
The struct abstraction allows the same code to work for both I²C controllers — I²C0 at `0x40090000` and I²C1 at `0x40098000` — by simply passing a different struct pointer. Functions like `i2c_init(i2c_inst_t *i2c, uint baudrate)` accept a pointer parameter, so one implementation serves both controllers. Without the struct, every I²C function would need either hardcoded addresses (duplicating code for each controller) or `if/else` branches. The abstraction also enables portability: if a future chip moves the hardware registers, only the struct initialization changes — not every function that accesses I²C.
2. **The hw pointer stores 0x40098000. In the binary, this appears as bytes 00 80 09 40. Why is the byte order reversed from how we write the address?**
ARM Cortex-M33 uses **little-endian** byte ordering: the least significant byte (LSB) is stored at the lowest memory address. For the 32-bit value `0x40098000`: byte 0 (lowest address) = `0x00` (LSB), byte 1 = `0x80`, byte 2 = `0x09`, byte 3 = `0x40` (MSB). We write numbers with the MSB first (big-endian notation), but the processor stores them LSB-first. This is a fundamental property of the ARM architecture that affects how you read multi-byte values in hex editors and GDB `x/bx` output.
3. **If you changed the hw pointer at 0x2000062c from 0x40098000 to 0x40090000 using GDB, what I²C controller would the program use? What would happen to the LCD?**
The program would use **I²C0** instead of I²C1, because all subsequent hardware register accesses (via `i2c1_inst.hw->...`) would read/write the I²C0 registers at `0x40090000`. However, the LCD is physically wired to the I²C1 pins (GPIO 14 for SDA, GPIO 15 for SCL), and those GPIOs are configured for the I²C1 peripheral. The I²C0 controller drives different default pins (GPIO 0/1). So the program would send I²C commands through the wrong controller on the wrong pins — the LCD would receive no signals and would stop updating, displaying whatever was last written before the pointer change.
4. **The macro chain has 4 levels of indirection (I2C_PORT → i2c1 → &i2c1_inst → hw → registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?**
Yes, this is typical. STM32 HAL, Nordic nRF5 SDK, ESP-IDF, and most professional embedded SDKs use similar multi-level abstractions. **Benefits:** code reuse across multiple peripheral instances, clean type-safe APIs, portability across chip revisions, and testability (you can mock the struct for unit tests). **Costs:** complexity for reverse engineers (harder to trace from API call to hardware), potential code bloat if not optimized, and a steeper learning curve for SDK users. In practice, modern compilers (with `-O2` or higher) optimize away most indirection — the final binary often inlines the pointer dereferences into direct register accesses, so the runtime overhead is negligible.
+37 -37
View File
@@ -1,13 +1,13 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Exercise 3: Trace the I²C Struct Pointer Chain
### Non-Credit Practice Exercise 3: Trace the I²C Struct Pointer Chain
#### Objective
Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` `i2c1` `&i2c1_inst` `hw` `0x40098000`, and verify each pointer and value in memory.
Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` ? `i2c1` ? `&i2c1_inst` ? `hw` ? `0x40098000`, and verify each pointer and value in memory.
#### Prerequisites
- Completed Week 7 tutorial (Parts 3-4 on structs and the macro chain)
@@ -16,7 +16,7 @@ Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction
- Understanding of C pointers and structs
#### Task Description
The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the I²C hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon.
The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the I²C hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon.
#### Step-by-Step Instructions
@@ -70,13 +70,13 @@ Look for the instruction that loads the `i2c1_inst` pointer into `r0`:
##### Step 3: Follow the PC-Relative Load
The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** — a data area near the code. Examine what's at the literal pool:
The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** — a data area near the code. Examine what's at the literal pool:
```gdb
(gdb) x/4wx 0x100002a8
```
Look for a value in the `0x2000xxxx` range — this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`.
Look for a value in the `0x2000xxxx` range — this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`.
##### Step 4: Examine the i2c1_inst Struct in SRAM
@@ -95,28 +95,28 @@ This maps to the `i2c_inst_t` struct:
| Offset | Field | Value | Meaning |
| ------ | ----------------- | ------------ | -------------------------------- |
| `+0x00` | `hw` | `0x40098000` | Pointer to I²C1 hardware regs |
| `+0x00` | `hw` | `0x40098000` | Pointer to I²C1 hardware regs |
| `+0x04` | `restart_on_next` | `0x00000000` | `false` (no pending restart) |
##### Step 5: Follow the hw Pointer to Hardware Registers
The first member of the struct (`hw`) points to `0x40098000` — the I²C1 hardware register block. Examine it:
The first member of the struct (`hw`) points to `0x40098000` — the I²C1 hardware register block. Examine it:
```gdb
(gdb) x/8wx 0x40098000
```
You should see the I²C1 control and status registers:
You should see the I²C1 control and status registers:
| Offset | Register | Description |
| ------ | -------------- | ------------------------------ |
| `+0x00` | IC_CON | I²C control register |
| `+0x00` | IC_CON | I²C control register |
| `+0x04` | IC_TAR | Target address register |
| `+0x08` | IC_SAR | Slave address register |
| `+0x0C` | (reserved) | — |
| `+0x0C` | (reserved) | — |
| `+0x10` | IC_DATA_CMD | Data command register |
##### Step 6: Verify the I²C Target Address
##### Step 6: Verify the I²C Target Address
After `i2c_init` and `lcd_i2c_init` have run, check the target address register:
@@ -134,7 +134,7 @@ Then examine IC_TAR:
(gdb) x/1wx 0x40098004
```
You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!
You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!
##### Step 7: Document the Complete Chain
@@ -142,38 +142,38 @@ Create a diagram of the complete pointer chain:
```
Your Code: I2C_PORT
(preprocessor macro)
¦
? (preprocessor macro)
i2c1
(macro: #define i2c1 (&i2c1_inst))
¦
? (macro: #define i2c1 (&i2c1_inst))
&i2c1_inst = 0x2000062c (SRAM address)
(struct member access)
¦
? (struct member access)
i2c1_inst.hw = 0x40098000 (hardware register base)
(memory-mapped I/O)
I²C1 Hardware Registers (silicon)
├── IC_CON at 0x40098000
├── IC_TAR at 0x40098004
├── IC_DATA_CMD at 0x40098010
└── ...
¦
? (memory-mapped I/O)
I²C1 Hardware Registers (silicon)
¦
+-- IC_CON at 0x40098000
+-- IC_TAR at 0x40098004
+-- IC_DATA_CMD at 0x40098010
+-- ...
```
##### Step 8: Compare with I²C0
##### Step 8: Compare with I²C0
The Pico 2 has two I²C controllers. Find the `i2c0_inst` struct and compare:
The Pico 2 has two I²C controllers. Find the `i2c0_inst` struct and compare:
```gdb
(gdb) x/2wx 0x20000628
```
If I²C0's struct is at a nearby address, you should see:
- `hw` pointing to `0x40090000` (I²C0 base, different from I²C1's `0x40098000`)
If I²C0's struct is at a nearby address, you should see:
- `hw` pointing to `0x40090000` (I²C0 base, different from I²C1's `0x40098000`)
- `restart_on_next` = 0
This demonstrates how the SDK uses the same struct layout for both I²C controllers, with only the hardware pointer changing.
This demonstrates how the SDK uses the same struct layout for both I²C controllers, with only the hardware pointer changing.
#### Expected Output
@@ -189,18 +189,18 @@ After completing this exercise, you should be able to:
###### Question 2: The `hw` pointer stores `0x40098000`. In the binary, this appears as bytes `00 80 09 40`. Why is the byte order reversed from how we write the address?
###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what I²C controller would the program use? What would happen to the LCD?
###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what I²C controller would the program use? What would happen to the LCD?
###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT i2c1 &i2c1_inst hw registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?
###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT ? i2c1 ? &i2c1_inst ? hw ? registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?
#### Tips and Hints
- Use `x/wx` to examine 32-bit words (pointers are 32 bits on ARM Cortex-M33)
- SRAM addresses start with `0x20xxxxxx`; hardware register addresses start with `0x40xxxxxx`
- The literal pool (where PC-relative loads get their data) is usually right after the function's code
- `i2c_inst_t` is only 8 bytes: 4-byte pointer + 4-byte bool (padded to 4 bytes for alignment)
- I²C0 base = `0x40090000`, I²C1 base = `0x40098000` — they are `0x8000` bytes apart
- I²C0 base = `0x40090000`, I²C1 base = `0x40098000` — they are `0x8000` bytes apart
#### Next Steps
- Proceed to Exercise 4 to patch the LCD to display your own custom message
- Try modifying the `restart_on_next` field in GDB and observe if it changes I²C behavior
- Explore the I²C hardware registers at `0x40098000` — can you read the IC_STATUS register to see if the bus is active?
- Try modifying the `restart_on_next` field in GDB and observe if it changes I²C behavior
- Explore the I²C hardware registers at `0x40098000` — can you read the IC_STATUS register to see if the bus is active?
+96
View File
@@ -0,0 +1,96 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Non-Credit Practice Exercise 4 Solution: Display Your Own Custom Message on the LCD
#### Answers
##### String Constraints
| Line | Original | Address | File Offset | Max Chars | Allocated Bytes |
|------|--------------|-------------|-------------|-----------|-----------------|
| 1 | "Reverse" | 0x10003ee8 | 0x3EE8 | 7 | 8 |
| 2 | "Engineering" | 0x10003ef0 | 0x3EF0 | 11 | 12 |
##### Example Patch: "Hello!!" and "World!!"
**Line 1: "Hello!!" (7 characters — exact fit)**
| Character | Hex |
|-----------|--------|
| H | 0x48 |
| e | 0x65 |
| l | 0x6C |
| l | 0x6C |
| o | 0x6F |
| ! | 0x21 |
| ! | 0x21 |
| \0 | 0x00 |
```
Offset 0x3EE8:
Before: 52 65 76 65 72 73 65 00 ("Reverse")
After: 48 65 6C 6C 6F 21 21 00 ("Hello!!")
```
**Line 2: "World!!" (7 characters — needs 5 bytes of null padding)**
| Character | Hex |
|-----------|--------|
| W | 0x57 |
| o | 0x6F |
| r | 0x72 |
| l | 0x6C |
| d | 0x64 |
| ! | 0x21 |
| ! | 0x21 |
| \0 | 0x00 |
| \0 (pad) | 0x00 |
| \0 (pad) | 0x00 |
| \0 (pad) | 0x00 |
| \0 (pad) | 0x00 |
```
Offset 0x3EF0:
Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering")
After: 57 6F 72 6C 64 21 21 00 00 00 00 00 ("World!!")
```
##### Example Patch: Short String "Hi"
**Line 1: "Hi" (2 characters — needs 5 bytes of null padding)**
```
Offset 0x3EE8:
Before: 52 65 76 65 72 73 65 00 ("Reverse")
After: 48 69 00 00 00 00 00 00 ("Hi")
```
##### Conversion and Flash
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants
python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
#### Reflection Answers
1. **You padded short strings with 0x00 null bytes. Would it also work to pad with 0x20 (space characters)? What would be the difference on the LCD display?**
Both approaches produce valid strings, but the display differs. With `0x00` padding, the string terminates at the first null byte — `lcd_puts` stops reading there, and the remaining bytes are ignored. The LCD shows only your text. With `0x20` (space) padding, the spaces become part of the string — `lcd_puts` sends them to the LCD as visible blank characters. The LCD would show your text followed by trailing spaces. Functionally both work, but `0x00` padding is cleaner because the string length matches your intended text, and the LCD positions after your text remain in whatever state the LCD controller initialized them to (typically blank anyway).
2. **The LCD is a 1602 (16 columns × 2 rows). What would happen if you could somehow put a 20-character string in memory? Would the LCD display all 20, or only the first 16?**
The LCD would display only the first 16 characters in the visible area. The HD44780 controller (used in 1602 LCD modules) has 40 bytes of DDRAM per line, so characters 17-20 would be written to DDRAM but are beyond the visible 16-column window. They would only become visible if you issued a display shift command to scroll the view. The `lcd_puts` function writes all characters to the controller regardless of line length — it has no built-in truncation. The 16-character limit is a physical display constraint, not a software one.
3. **If you wanted to combine the string hacks from Exercise 1 (changing both LCD lines) AND a hypothetical numeric hack (e.g., changing the movs r1, #42 encoding at offset 0x28E), could you do all patches in a single .bin file? What offsets would you need to modify?**
Yes, all patches can be applied to the same `.bin` file since they are at non-overlapping offsets. The three patch locations are:
- **Offset 0x28E**: FAV_NUM — change `movs r1, #42` immediate byte from `0x2A` to desired value (1 byte)
- **Offset 0x3EE8**: LCD line 1 — replace the 8-byte "Reverse" string
- **Offset 0x3EF0**: LCD line 2 — replace the 12-byte "Engineering" string
Each patch modifies a different region of the binary, so they are completely independent. You could also patch the `movw r1, #1337` instruction at offset `0x298` (the imm8 byte of the OTHER_FAV_NUM encoding) for a fourth independent patch.
4. **Besides LCD text, what other strings could you patch in a real-world embedded device to change its behavior? Think about Wi-Fi SSIDs, Bluetooth device names, HTTP headers, etc.**
Real-world embedded devices contain many patchable strings: **Wi-Fi SSIDs** and **passwords** (change what network the device connects to), **Bluetooth device names** (change how it appears during pairing), **HTTP/HTTPS URLs** (redirect API calls to a different server), **MQTT broker addresses** (redirect IoT telemetry), **DNS hostnames**, **firmware version strings** (spoof version for update bypass), **serial number formats**, **command-line interface prompts**, **error and debug messages** (hide forensic evidence), **TLS/SSL certificate fields**, **NTP server addresses** (manipulate time synchronization), and **authentication tokens or API keys**. String patching is one of the most practical firmware modification techniques because it's simple to execute and can dramatically change device behavior.
+1 -1
View File
@@ -4,7 +4,7 @@
## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics
### Exercise 4: Display Your Own Custom Message on the LCD
### Non-Credit Practice Exercise 4: Display Your Own Custom Message on the LCD
#### Objective
Patch both LCD string literals in the binary to display your name (or any custom message) on the 1602 LCD, respecting the character length constraints, converting your text to hex bytes, and verifying the result on hardware.