mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-28 02:02:24 +02:00
Updated WEEK04
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
# 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`.
|
||||
@@ -1,205 +0,0 @@
|
||||
# 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: 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.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 7 tutorial (GDB and hex editor sections)
|
||||
- `0x0017_constants.bin` binary available in your build directory
|
||||
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
|
||||
- A hex editor (HxD, ImHex, or similar)
|
||||
- Python installed (for UF2 conversion)
|
||||
- Raspberry Pi Pico 2 with 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.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Start the Debug Session
|
||||
|
||||
**Terminal 1 - Start OpenOCD:**
|
||||
|
||||
```powershell
|
||||
openocd ^
|
||||
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
|
||||
-f interface/cmsis-dap.cfg ^
|
||||
-f target/rp2350.cfg ^
|
||||
-c "adapter speed 5000"
|
||||
```
|
||||
|
||||
**Terminal 2 - Start GDB:**
|
||||
|
||||
```powershell
|
||||
arm-none-eabi-gdb build\0x0017_constants.elf
|
||||
```
|
||||
|
||||
**Connect to target:**
|
||||
|
||||
```gdb
|
||||
(gdb) target remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Locate the String Literals in Memory
|
||||
|
||||
From the tutorial, we know the strings are in the `.rodata` section:
|
||||
|
||||
```gdb
|
||||
(gdb) x/s 0x10003ee8
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
0x10003ee8: "Reverse"
|
||||
```
|
||||
|
||||
```gdb
|
||||
(gdb) x/s 0x10003ef0
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
0x10003ef0: "Engineering"
|
||||
```
|
||||
|
||||
##### Step 3: Examine the Raw Bytes
|
||||
|
||||
Look at the hex encoding of both strings:
|
||||
|
||||
```gdb
|
||||
(gdb) x/8xb 0x10003ee8
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
0x10003ee8: 0x52 0x65 0x76 0x65 0x72 0x73 0x65 0x00
|
||||
R e v e r s e \0
|
||||
```
|
||||
|
||||
```gdb
|
||||
(gdb) x/12xb 0x10003ef0
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
0x10003ef0: 0x45 0x6e 0x67 0x69 0x6e 0x65 0x65 0x72
|
||||
E n g i n e e r
|
||||
0x10003ef8: 0x69 0x6e 0x67 0x00
|
||||
i n g \0
|
||||
```
|
||||
|
||||
##### Step 4: Plan Your Replacement Strings
|
||||
|
||||
**Original strings and their lengths:**
|
||||
- "Reverse" = 7 characters + null terminator = 8 bytes
|
||||
- "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")
|
||||
|
||||
Build the ASCII hex for "Exploit":
|
||||
|
||||
| Character | Hex |
|
||||
| --------- | ------ |
|
||||
| E | `0x45` |
|
||||
| x | `0x78` |
|
||||
| p | `0x70` |
|
||||
| l | `0x6c` |
|
||||
| o | `0x6f` |
|
||||
| i | `0x69` |
|
||||
| t | `0x74` |
|
||||
| \0 | `0x00` |
|
||||
|
||||
Build the ASCII hex for "Hacking!!!!":
|
||||
|
||||
| Character | Hex |
|
||||
| --------- | ------ |
|
||||
| H | `0x48` |
|
||||
| a | `0x61` |
|
||||
| c | `0x63` |
|
||||
| k | `0x6b` |
|
||||
| i | `0x69` |
|
||||
| n | `0x6e` |
|
||||
| g | `0x67` |
|
||||
| ! | `0x21` |
|
||||
| ! | `0x21` |
|
||||
| ! | `0x21` |
|
||||
| ! | `0x21` |
|
||||
| \0 | `0x00` |
|
||||
|
||||
##### Step 5: Calculate File Offsets
|
||||
|
||||
```
|
||||
file_offset = address - 0x10000000
|
||||
```
|
||||
|
||||
- "Reverse" at `0x10003ee8` ? file offset `0x3EE8`
|
||||
- "Engineering" at `0x10003ef0` ? file offset `0x3EF0`
|
||||
|
||||
##### 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!!!!"
|
||||
|
||||
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")
|
||||
3. Replace with: `48 61 63 6B 69 6E 67 21 21 21 21 00` ("Hacking!!!!\0")
|
||||
|
||||
##### Step 8: Save and Convert
|
||||
|
||||
1. Click **File** ? **Save As** ? `0x0017_constants-h.bin`
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
##### Step 9: Flash and Verify
|
||||
|
||||
1. Hold BOOTSEL and plug in your Pico 2
|
||||
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
|
||||
|
||||
**Check the LCD:**
|
||||
- Line 1 should now show: `Exploit`
|
||||
- Line 2 should now show: `Hacking!!!!`
|
||||
|
||||
#### Expected Output
|
||||
|
||||
After completing this exercise, you should be able to:
|
||||
- Locate string literals in flash memory using GDB
|
||||
- Convert ASCII characters to hex bytes for patching
|
||||
- Patch multiple strings in a single binary
|
||||
- Understand the same-length constraint for string patching
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### 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 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.
|
||||
|
||||
###### Question 4: Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value?
|
||||
|
||||
#### 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`
|
||||
- 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
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 2 to explore finding all string literals in the binary
|
||||
- Try displaying reversed text: can you make it show "gninnignE" and "esreveR"?
|
||||
- Use GDB to verify your patches before flashing: `set {char[8]} 0x10003ee8 = "Exploit"` to test in RAM first
|
||||
@@ -1,68 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,157 +0,0 @@
|
||||
# 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: 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.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 7 tutorial (GDB section)
|
||||
- `0x0017_constants.elf` and `0x0017_constants.bin` available in your build directory
|
||||
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
|
||||
- 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.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Start the Debug Session
|
||||
|
||||
**Terminal 1 - Start OpenOCD:**
|
||||
|
||||
```powershell
|
||||
openocd ^
|
||||
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
|
||||
-f interface/cmsis-dap.cfg ^
|
||||
-f target/rp2350.cfg ^
|
||||
-c "adapter speed 5000"
|
||||
```
|
||||
|
||||
**Terminal 2 - Start GDB:**
|
||||
|
||||
```powershell
|
||||
arm-none-eabi-gdb build\0x0017_constants.elf
|
||||
```
|
||||
|
||||
**Connect to target:**
|
||||
|
||||
```gdb
|
||||
(gdb) target remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Locate the Known Strings
|
||||
|
||||
We already know about these strings from the tutorial:
|
||||
|
||||
```gdb
|
||||
(gdb) x/s 0x10003ee8
|
||||
0x10003ee8: "Reverse"
|
||||
|
||||
(gdb) x/s 0x10003ef0
|
||||
0x10003ef0: "Engineering"
|
||||
```
|
||||
|
||||
These are the LCD display strings. Now let's find more.
|
||||
|
||||
##### Step 3: Find printf Format Strings
|
||||
|
||||
The program uses `printf("FAV_NUM: %d\r\n", ...)` and `printf("OTHER_FAV_NUM: %d\r\n", ...)`. These format strings must be somewhere in flash. Look in the `.rodata` section near the LCD strings:
|
||||
|
||||
```gdb
|
||||
(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:
|
||||
|
||||
```gdb
|
||||
(gdb) x/20s 0x10003e00
|
||||
```
|
||||
|
||||
##### Step 4: Search the Binary Systematically
|
||||
|
||||
Use GDB to scan through flash memory in large chunks, looking for readable strings:
|
||||
|
||||
```gdb
|
||||
(gdb) x/50s 0x10003d00
|
||||
```
|
||||
|
||||
Many results will be garbage (non-ASCII data interpreted as text), but real strings will stand out. Look for:
|
||||
- Format strings containing `%d`, `%s`, `%f`, `\r\n`
|
||||
- Error messages from the Pico SDK
|
||||
- Function names or debug info
|
||||
|
||||
##### Step 5: Open the Binary in a Hex Editor
|
||||
|
||||
1. Open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants\build\0x0017_constants.bin` in HxD
|
||||
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 (0x20–0x7E) are displayed as text; non-printable bytes appear as dots.
|
||||
|
||||
##### Step 6: Use Hex Editor Search
|
||||
|
||||
Most hex editors can search for ASCII text:
|
||||
|
||||
1. Press **Ctrl+F** (Find)
|
||||
2. Switch to "Text" or "String" search mode
|
||||
3. Search for known strings like `Reverse`, `FAV_NUM`, `Engineering`
|
||||
4. For each hit, note the file offset
|
||||
|
||||
##### Step 7: Catalog Your Findings
|
||||
|
||||
Create a table of all strings you find:
|
||||
|
||||
| Address | File Offset | String Content | Purpose |
|
||||
| -------------- | ----------- | ----------------------- | --------------------------- |
|
||||
| `0x10003ee8` | `0x3EE8` | "Reverse" | LCD line 1 text |
|
||||
| `0x10003ef0` | `0x3EF0` | "Engineering" | LCD line 2 text |
|
||||
| `0x10003eXX` | `0x3EXX` | "FAV_NUM: %d\r\n" | printf format string |
|
||||
| `0x10003eXX` | `0x3EXX` | "OTHER_FAV_NUM: %d\r\n" | printf format string |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
Fill in the actual addresses you discover.
|
||||
|
||||
##### Step 8: Identify SDK and Library Strings
|
||||
|
||||
The Pico SDK may include additional strings (error messages, assert messages, etc.). Look for text patterns like:
|
||||
- "panic" or "assert"
|
||||
- File paths (e.g., paths from the SDK source)
|
||||
- Function names
|
||||
|
||||
These strings reveal internal SDK behavior that isn't visible in the source code.
|
||||
|
||||
#### Expected Output
|
||||
|
||||
After completing this exercise, you should be able to:
|
||||
- Systematically enumerate string literals in a compiled binary
|
||||
- Use both GDB (`x/s`) and hex editor text view to find strings
|
||||
- Distinguish between application strings, format strings, and SDK/library strings
|
||||
- Understand that string literals reveal program functionality to a reverse engineer
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 1: How many distinct strings did you find? Were any of them surprising or unexpected?
|
||||
|
||||
###### Question 2: Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings?
|
||||
|
||||
###### Question 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.)
|
||||
|
||||
###### 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
|
||||
- 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
|
||||
- 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
|
||||
- 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?
|
||||
@@ -1,91 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,206 +0,0 @@
|
||||
# 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: 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.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 7 tutorial (Parts 3-4 on structs and the macro chain)
|
||||
- `0x0017_constants.elf` binary available in your build directory
|
||||
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
|
||||
- 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.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Start the Debug Session
|
||||
|
||||
**Terminal 1 - Start OpenOCD:**
|
||||
|
||||
```powershell
|
||||
openocd ^
|
||||
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
|
||||
-f interface/cmsis-dap.cfg ^
|
||||
-f target/rp2350.cfg ^
|
||||
-c "adapter speed 5000"
|
||||
```
|
||||
|
||||
**Terminal 2 - Start GDB:**
|
||||
|
||||
```powershell
|
||||
arm-none-eabi-gdb build\0x0017_constants.elf
|
||||
```
|
||||
|
||||
**Connect to target:**
|
||||
|
||||
```gdb
|
||||
(gdb) target remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Find the i2c_init Call
|
||||
|
||||
Run the program to allow initialization to complete, then halt:
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x10000234
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
Now step through to just before the `i2c_init` call:
|
||||
|
||||
```gdb
|
||||
(gdb) x/10i 0x1000023c
|
||||
```
|
||||
|
||||
Look for the instruction that loads the `i2c1_inst` pointer into `r0`:
|
||||
|
||||
```
|
||||
0x1000023c: ldr r0, [pc, #offset] ; Load &i2c1_inst into r0
|
||||
0x1000023e: ldr r1, =0x186A0 ; 100000 (baud rate)
|
||||
0x10000240: bl i2c_init ; Call i2c_init(i2c1, 100000)
|
||||
```
|
||||
|
||||
##### 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:
|
||||
|
||||
```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`.
|
||||
|
||||
##### Step 4: Examine the i2c1_inst Struct in SRAM
|
||||
|
||||
Now examine the struct at that SRAM address:
|
||||
|
||||
```gdb
|
||||
(gdb) x/2wx 0x2000062c
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
0x2000062c: 0x40098000 0x00000000
|
||||
```
|
||||
|
||||
This maps to the `i2c_inst_t` struct:
|
||||
|
||||
| Offset | Field | Value | Meaning |
|
||||
| ------ | ----------------- | ------------ | -------------------------------- |
|
||||
| `+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:
|
||||
|
||||
```gdb
|
||||
(gdb) x/8wx 0x40098000
|
||||
```
|
||||
|
||||
You should see the I²C1 control and status registers:
|
||||
|
||||
| Offset | Register | Description |
|
||||
| ------ | -------------- | ------------------------------ |
|
||||
| `+0x00` | IC_CON | I²C control register |
|
||||
| `+0x04` | IC_TAR | Target address register |
|
||||
| `+0x08` | IC_SAR | Slave address register |
|
||||
| `+0x0C` | (reserved) | — |
|
||||
| `+0x10` | IC_DATA_CMD | Data command register |
|
||||
|
||||
##### Step 6: Verify the I²C Target Address
|
||||
|
||||
After `i2c_init` and `lcd_i2c_init` have run, check the target address register:
|
||||
|
||||
Let the program run past initialization:
|
||||
|
||||
```gdb
|
||||
(gdb) delete
|
||||
(gdb) b *<address_after_lcd_init>
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
Then examine IC_TAR:
|
||||
|
||||
```gdb
|
||||
(gdb) x/1wx 0x40098004
|
||||
```
|
||||
|
||||
You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!
|
||||
|
||||
##### Step 7: Document the Complete Chain
|
||||
|
||||
Create a diagram of the complete pointer chain:
|
||||
|
||||
```
|
||||
Your Code: I2C_PORT
|
||||
¦
|
||||
? (preprocessor macro)
|
||||
i2c1
|
||||
¦
|
||||
? (macro: #define i2c1 (&i2c1_inst))
|
||||
&i2c1_inst = 0x2000062c (SRAM address)
|
||||
¦
|
||||
? (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
|
||||
+-- ...
|
||||
```
|
||||
|
||||
##### Step 8: Compare with I²C0
|
||||
|
||||
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`)
|
||||
- `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.
|
||||
|
||||
#### Expected Output
|
||||
|
||||
After completing this exercise, you should be able to:
|
||||
- Trace pointer chains from high-level code to hardware registers
|
||||
- Understand how the Pico SDK uses structs to abstract hardware
|
||||
- Read struct members from raw memory using GDB
|
||||
- Navigate from SRAM data structures to memory-mapped I/O registers
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 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?
|
||||
|
||||
###### 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 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
|
||||
|
||||
#### 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?
|
||||
@@ -1,96 +0,0 @@
|
||||
# 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,148 +0,0 @@
|
||||
# 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: 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.
|
||||
|
||||
#### Prerequisites
|
||||
- Completed Week 7 tutorial (hex editor section) and Exercise 1
|
||||
- `0x0017_constants.bin` binary available in your build directory
|
||||
- A hex editor (HxD, ImHex, or similar)
|
||||
- Python installed (for UF2 conversion)
|
||||
- Raspberry Pi Pico 2 with 1602 LCD connected via I²C
|
||||
|
||||
#### Task Description
|
||||
You will choose two custom messages to display on the LCD — one for each line. Line 1 replaces "Reverse" (7 characters max) and line 2 replaces "Engineering" (11 characters max). You must convert your chosen text to ASCII hex, handle the case where your text is shorter than the original (pad with null bytes), patch the binary, and flash it to see your custom message on the physical LCD.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Choose Your Messages
|
||||
|
||||
Plan two messages that fit the constraints:
|
||||
|
||||
| Line | Original | Max Length | Your Message | Length | Valid? |
|
||||
| ---- | ------------- | ---------- | ------------ | ------ | ------ |
|
||||
| 1 | "Reverse" | 7 chars | | | |
|
||||
| 2 | "Engineering" | 11 chars | | | |
|
||||
|
||||
**Examples that work:**
|
||||
- Line 1: "Hello!!" (7 chars) ✅
|
||||
- Line 2: "World!!" (7 chars, pad with 4 null bytes) ✅
|
||||
- Line 1: "Hi" (2 chars, pad with 5 null bytes) ✅
|
||||
- Line 2: "My Name Here" — ❌ (12 chars, too long!)
|
||||
|
||||
> ⚠️ **Remember:** The 1602 LCD can display up to 16 characters per line, but the binary only allocates 8 bytes for "Reverse" and 12 bytes for "Engineering". You cannot exceed these byte allocations.
|
||||
|
||||
##### Step 2: Convert Your Messages to Hex
|
||||
|
||||
Use an ASCII table to convert each character:
|
||||
|
||||
**Common ASCII values:**
|
||||
|
||||
| Character | Hex | Character | Hex | Character | Hex |
|
||||
| --------- | ------ | --------- | ------ | --------- | ------ |
|
||||
| Space | `0x20` | 0-9 | `0x30`-`0x39` | A-Z | `0x41`-`0x5A` |
|
||||
| ! | `0x21` | : | `0x3A` | a-z | `0x61`-`0x7A` |
|
||||
| " | `0x22` | ? | `0x3F` | \0 (null) | `0x00` |
|
||||
|
||||
Write out the hex bytes for each message, including the null terminator and any padding:
|
||||
|
||||
**Line 1 (8 bytes total):**
|
||||
```
|
||||
[char1] [char2] [char3] [char4] [char5] [char6] [char7] [0x00]
|
||||
```
|
||||
|
||||
If your message is shorter than 7 characters, fill the remaining bytes with `0x00`.
|
||||
|
||||
**Line 2 (12 bytes total):**
|
||||
```
|
||||
[char1] [char2] [char3] [char4] [char5] [char6] [char7] [char8] [char9] [char10] [char11] [0x00]
|
||||
```
|
||||
|
||||
If your message is shorter than 11 characters, fill the remaining bytes with `0x00`.
|
||||
|
||||
##### Step 3: Open the Binary and Navigate
|
||||
|
||||
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` (Line 1: "Reverse")
|
||||
3. Verify you see: `52 65 76 65 72 73 65 00` ("Reverse\0")
|
||||
|
||||
##### Step 4: Patch Line 1
|
||||
|
||||
Replace the 8 bytes starting at offset `0x3EE8` with your prepared hex sequence.
|
||||
|
||||
For example, to write "Hello!!" (7 chars + null):
|
||||
```
|
||||
Before: 52 65 76 65 72 73 65 00 (Reverse)
|
||||
After: 48 65 6C 6C 6F 21 21 00 (Hello!!)
|
||||
```
|
||||
|
||||
For a shorter message like "Hi" (2 chars + null + padding):
|
||||
```
|
||||
Before: 52 65 76 65 72 73 65 00 (Reverse)
|
||||
After: 48 69 00 00 00 00 00 00 (Hi\0\0\0\0\0\0)
|
||||
```
|
||||
|
||||
##### Step 5: Patch Line 2
|
||||
|
||||
1. Press **Ctrl+G** and enter offset: `3EF0` (Line 2: "Engineering")
|
||||
2. Verify you see: `45 6E 67 69 6E 65 65 72 69 6E 67 00`
|
||||
3. Replace the 12 bytes with your prepared hex sequence
|
||||
|
||||
##### Step 6: Save the Patched Binary
|
||||
|
||||
1. Click **File** → **Save As** → `0x0017_constants-h.bin`
|
||||
|
||||
##### Step 7: Convert to UF2 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
|
||||
```
|
||||
|
||||
1. Hold BOOTSEL and plug in your Pico 2
|
||||
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
|
||||
|
||||
##### Step 8: Verify on the LCD
|
||||
|
||||
Check the physical LCD display. Your custom messages should appear on lines 1 and 2.
|
||||
|
||||
If the LCD shows garbled text or nothing at all:
|
||||
- Verify your hex conversion was correct
|
||||
- Ensure you included the null terminator (`0x00`)
|
||||
- Confirm you didn't accidentally modify bytes outside the string regions
|
||||
- Re-open the binary and double-check offsets `0x3EE8` and `0x3EF0`
|
||||
|
||||
#### Expected Output
|
||||
|
||||
After completing this exercise, you should be able to:
|
||||
- Convert any ASCII text to hex bytes for binary patching
|
||||
- Handle strings shorter than the allocated space using null padding
|
||||
- Patch string literals in any compiled binary
|
||||
- Verify patches work on real hardware
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 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?
|
||||
|
||||
###### Question 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?
|
||||
|
||||
###### Question 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?
|
||||
|
||||
###### Question 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.
|
||||
|
||||
#### Tips and Hints
|
||||
- HxD shows the ASCII representation of bytes in the right panel — use this to verify your patches look correct
|
||||
- A quick way to compute ASCII: lowercase letter hex = uppercase letter hex + `0x20`
|
||||
- If you make a mistake, close the file WITHOUT saving and start over with the original `.bin`
|
||||
- Take a photo of your custom LCD display for your portfolio!
|
||||
|
||||
#### Next Steps
|
||||
- Review all four WEEK07 exercises and verify you understand string patching, data analysis, struct tracing, and custom message creation
|
||||
- Try patching the `printf` format strings to display different labels in the serial output
|
||||
- Challenge: can you make the LCD display emoji-like characters using the LCD's custom character feature (if supported by the backpack)?
|
||||
+262
-287
@@ -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! 🔧
|
||||
|
||||
Reference in New Issue
Block a user