Added WEEK04

This commit is contained in:
Kevin Thomas
2026-01-31 14:07:15 -05:00
parent 021ca960c6
commit 29073bd383
6 changed files with 2044 additions and 0 deletions
+270
View File
@@ -0,0 +1,270 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 4
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
### Exercise 3: Analyze and Understand GPIO Control
#### Objective
Import the `0x0008_uninitialized-variables.bin` binary, analyze the GPIO initialization and control sequences, understand how `gpio_init()`, `gpio_set_dir()`, and `gpio_put()` work at the assembly level.
#### Prerequisites
- Completed Exercises 1 and 2
- Understanding of GPIO basics from Week 4 Part 3
- Raspberry Pi Pico 2 with an LED connected to GPIO 16
- Basic knowledge of ARM Thumb-2 instruction set
#### Task Description
You will import a new binary that controls GPIO pins, identify the GPIO-related function calls, trace the initialization sequence, and understand how the Pico SDK controls hardware at the low level.
#### Step-by-Step Instructions
##### Step 1: Flash the Original Binary
Before analysis, let's see what the program does:
1. Hold **BOOTSEL** and plug in your Pico 2
2. Flash `0x0008_uninitialized-variables.uf2` to the RPI-RP2 drive
3. Open your serial monitor
**Expected output:**
```
age: 0
age: 0
age: 0
...
```
**Expected behavior:**
- The red LED on GPIO 16 blinks on/off every 500ms
- The value `0` is printed (uninitialized variable)
##### Step 2: Create a New Ghidra Project
1. Launch Ghidra: `ghidraRun`
2. Click **File****New Project**
3. Select **Non-Shared Project**
4. Project Name: `week04-ex03-gpio-analysis`
5. Click **Finish**
##### Step 3: Import the GPIO Binary
1. Drag and drop `0x0008_uninitialized-variables.bin` into Ghidra
2. Set Language: **ARM Cortex 32 little endian default**
3. Click **Options…**
- Block Name: `.text`
- Base Address: `10000000`
4. Click **OK** on all dialogs
5. Double-click the file and click **Yes** to analyze
##### Step 4: Identify the Main Function
Look for the main function (likely `FUN_10000234` or similar):
**In the Symbol Tree:**
1. Expand **Functions**
2. Look for a function that appears to be an entry point
3. Click on potential `main` candidates
**Look for these patterns in the decompile:**
- Call to `stdio_init_all()`
- Call to `gpio_init()`
- Infinite while loop with `gpio_put()` and `sleep_ms()`
##### Step 5: Rename the Main Function
Once you identify `main`:
1. Right-click on the function name
2. Select **Edit Function Signature**
3. Change to: `int main(void)`
4. Click **OK**
**Expected decompiled code structure:**
```c
int main(void)
{
// Some initial value
stdio_init_all();
gpio_init(0x10); // GPIO 16
// ... more GPIO setup
while (true) {
printf(...);
gpio_put(0x10, 1);
sleep_ms(0x1f4);
gpio_put(0x10, 0);
sleep_ms(0x1f4);
}
}
```
##### Step 6: Identify GPIO Function Calls
Look in the decompiled main for function calls. You should see several undefined functions.
**Find and rename these GPIO functions:**
| Auto-Generated Name | Actual Function | How to Identify |
| ------------------- | ----------------- | ------------------------------------------ |
| `FUN_xxxxx` | `gpio_init` | Takes one parameter (pin number) |
| `FUN_xxxxx` | `gpio_set_dir` | Takes two parameters (pin, direction) |
| `FUN_xxxxx` | `gpio_put` | Takes two parameters (pin, value) |
| `FUN_xxxxx` | `sleep_ms` | Takes one parameter (milliseconds) |
| `FUN_xxxxx` | `stdio_init_all` | Takes no parameters, called first |
| `FUN_xxxxx` | `printf` | Takes variable args, has format string |
**Example renaming gpio_init:**
1. Click on the function call in the decompile window
2. Right-click → **Edit Function Signature**
3. Change name to: `gpio_init`
4. Set signature to: `void gpio_init(uint gpio)`
5. Click **OK**
##### Step 7: Analyze GPIO Initialization Sequence
After renaming, your decompiled main should look clearer:
```c
int main(void)
{
stdio_init_all();
gpio_init(0x10); // Initialize GPIO 16
gpio_set_dir(0x10, 1); // Set as output (1 = GPIO_OUT)
while (true) {
printf("age: %d\r\n", 0);
gpio_put(0x10, 1); // LED ON
sleep_ms(0x1f4); // Wait 500ms (0x1f4 = 500)
gpio_put(0x10, 0); // LED OFF
sleep_ms(0x1f4); // Wait 500ms
}
}
```
**Key observations:**
- `0x10` is hexadecimal for 16 (GPIO 16 - red LED)
- `0x1f4` is hexadecimal for 500 (milliseconds)
- `1` means GPIO_OUT (output direction)
- The LED is controlled by toggling between 1 (on) and 0 (off)
##### Step 8: Examine gpio_init Assembly
Double-click on `gpio_init` to jump to its implementation.
**Look for these key operations in the assembly:**
```assembly
; Load GPIO pin number into register
movs r4, r0 ; Save pin number
; Calculate pad register address
; Base address: 0x40038000 (PADS_BANK0)
; Offset: pin * 4
ldr r3, =0x40038000
lsls r5, r4, #2 ; pin * 4
add r3, r5 ; Calculate address
; Configure pad (clear OD bit, set IE bit)
ldr r2, [r3] ; Read current config
bic r2, #0x80 ; Clear output disable
orr r2, #0x40 ; Set input enable
str r2, [r3] ; Write back
; Set GPIO function to SIO (0x05)
ldr r3, =0x40028000 ; IO_BANK0 base
add r3, r5 ; Add offset
movs r2, #5 ; FUNCSEL = SIO
str r2, [r3] ; Set function
```
**What this does:**
1. Configures the GPIO pad registers (physical pin properties)
2. Sets the GPIO function to SIO (Software I/O)
3. Prepares the pin for software control
##### Step 9: Examine gpio_put Assembly
Find the `gpio_put` function and examine its implementation.
**Look for the GPIO coprocessor instruction:**
```assembly
gpio_put:
movs r4, r0 ; GPIO pin number
movs r5, r1 ; Value (0 or 1)
; Use ARM coprocessor to control GPIO
mcrr p0, #4, r4, r5, c0
bx lr ; Return
```
**Critical instruction: `mcrr p0, #4, r4, r5, c0`**
- `mcrr` = Move to Coprocessor from two ARM Registers
- `p0` = Coprocessor 0 (GPIO coprocessor in RP2350)
- `#4` = Operation code
- `r4, r5` = Source registers (pin number, value)
- `c0` = Coprocessor register (GPIO output control)
This is a **single-cycle GPIO operation** - extremely fast!
##### Step 10: Document the GPIO Memory Map
Create a reference table of the addresses you found:
| Address | Register | Purpose |
| ------------ | ------------- | ------------------------------- |
| `0x40028000` | IO_BANK0 | GPIO function selection |
| `0x40038000` | PADS_BANK0 | GPIO pad configuration |
| `0xd0000000` | SIO | Single-cycle I/O (coprocessor) |
**GPIO 16 specific addresses:**
- Pad control: `0x40038000 + (16 * 4) = 0x40038040`
- Function select: `0x40028000 + (16 * 4) = 0x40028040`
##### Step 11: Trace the Blink Timing
Calculate the actual timing:
**sleep_ms(0x1f4):**
- Convert: 0x1f4 = (1 × 256) + (15 × 16) + 4 = 256 + 240 + 4 = 500 decimal
- So the LED is on for 500ms, off for 500ms
- Total cycle time: 1000ms = 1 second
- Blink rate: 1 Hz
#### Expected Output
After completing this exercise, you should understand:
- How GPIO initialization configures hardware registers
- The role of the GPIO coprocessor in the RP2350
- How `gpio_put()` uses a single ARM instruction for fast I/O
- The memory-mapped addresses for GPIO control
- How timing delays are implemented with `sleep_ms()`
#### Questions for Reflection
###### Question 1: Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?
###### Question 2: What is the advantage of using the GPIO coprocessor instruction (`mcrr`) instead of writing to memory-mapped registers?
###### Question 3: If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use?
###### Question 4: What would happen if you called `gpio_put()` on a pin that hasn't been initialized with `gpio_init()` first?
#### Tips and Hints
- Use Ghidra's **References** feature (right-click → Find References) to see where functions are called
- The **Display****Memory Map** shows all memory regions
- Look for bit manipulation instructions (`bic`, `orr`) to understand register configuration
- The ARM Architecture Reference Manual has complete documentation on coprocessor instructions
- Use hex-to-decimal converters online if you're unsure about conversions
#### Next Steps
- Proceed to Exercise 4 to patch the GPIO binary
- Try to identify other SDK functions like `gpio_get()` if they appear
- Explore the full GPIO initialization in the SDK source code
#### Additional Challenge
Find the `gpio_set_dir()` function in Ghidra. Does it also use a GPIO coprocessor instruction? What coprocessor register does it use (c0, c4, or something else)? Compare its implementation to `gpio_put()` and document the differences.