mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-19 22:38:05 +02:00
271 lines
8.9 KiB
Markdown
271 lines
8.9 KiB
Markdown
# 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.
|