diff --git a/README.md b/README.md index d9ffe1e..13b226f 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,16 @@ This chapter covers a comprehensive embedded system analysis reviewing parts of ## Week 4 Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics +### Week 4 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04.md) + +#### Exercise 1: Analyze Variable Storage in Ghidra [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-01.md) + +#### Exercise 2: Patch Binary to Change Variable Value [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-02.md) + +#### Exercise 3: Analyze and Understand GPIO Control [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-03.md) + +#### Exercise 4: Patch GPIO Binary to Change LED Pin [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK04/WEEK04-04.md) + ### Chapter 5: Intro To Variables This chapter covers an introduction to variables as it relates to embedded development on the Pico 2. diff --git a/WEEK04/WEEK04-01.md b/WEEK04/WEEK04-01.md new file mode 100644 index 0000000..af3e6ad --- /dev/null +++ b/WEEK04/WEEK04-01.md @@ -0,0 +1,241 @@ +# 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 1: Analyze Variable Storage in Ghidra + +#### Objective +Import and analyze the `0x0005_intro-to-variables.bin` binary in Ghidra to understand where variables are stored, identify memory sections, and trace how the compiler optimizes variable usage. + +#### Prerequisites +- Ghidra installed and configured +- `0x0005_intro-to-variables.bin` binary available in your build directory +- Understanding of memory sections (`.data`, `.bss`, `.rodata`) from Week 4 Part 2 +- Basic Ghidra navigation skills from Week 3 + +#### Task Description +You will import the binary into Ghidra, configure it for ARM Cortex-M33, analyze the code structure, resolve function names, and locate where the `age` variable is used in the compiled binary. + +#### Step-by-Step Instructions + +##### Step 1: Start Ghidra and Create New Project + +```bash +ghidraRun +``` + +1. Click **File** → **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `week04-ex01-intro-to-variables` +5. Choose a project directory +6. Click **Finish** + +##### Step 2: Import the Binary + +1. Navigate to your file explorer +2. Find `Embedded-Hacking/0x0005_intro-to-variables/build/0x0005_intro-to-variables.bin` +3. **Drag and drop** the `.bin` file into Ghidra's project window + +##### Step 3: Configure Import Settings + +When the import dialog appears: + +1. Click the three dots (**…**) next to **Language** +2. Search for: `Cortex` +3. Select: **ARM Cortex 32 little endian default** +4. Click **OK** + +Now click **Options…** button: +1. Change **Block Name** to: `.text` +2. Change **Base Address** to: `10000000` (XIP flash base) +3. Click **OK** + +Then click **OK** on the main import dialog. + +##### Step 4: Analyze the Binary + +1. Double-click the imported file in the project window +2. When prompted "Analyze now?" click **Yes** +3. Leave all default analysis options selected +4. Click **Analyze** +5. Wait for analysis to complete (watch bottom-right progress bar) + +##### Step 5: Navigate to the Symbol Tree + +Look at the left panel for the **Symbol Tree**. Expand **Functions** to see the auto-detected functions: + +You should see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000234` +- Many more... + +These are auto-generated names because we're analyzing a raw binary without debug symbols. + +##### Step 6: Identify the Main Function + +From Week 3, we know the typical boot sequence: +1. Reset handler copies data +2. `frame_dummy` runs +3. `main()` is called + +Click on `FUN_10000234` - this should be our `main()` function. + +**Look at the Decompile window:** + +```c +void FUN_10000234(void) +{ + FUN_100030cc(); + do { + FUN_10003100("age: %d\r\n", 0x2b); + } while (true); +} +``` + +**Observations:** +- `FUN_100030cc()` is likely `stdio_init_all()` +- `FUN_10003100()` is likely `printf()` +- The magic value `0x2b` appears (what is this?) + +##### Step 7: Convert 0x2b to Decimal + +Let's figure out what `0x2b` means: + +**Manual calculation:** +- `0x2b` in hexadecimal +- `2 × 16 + 11 = 32 + 11 = 43` in decimal + +**In GDB (alternative method):** +```gdb +(gdb) p/d 0x2b +$1 = 43 +``` + +So `0x2b = 43`! This matches our `age = 43` from the source code! + +##### Step 8: Rename Functions for Clarity + +Let's rename the functions to their actual names: + +**Rename FUN_10000234 to main:** +1. Right-click on `FUN_10000234` in the Symbol Tree +2. Select **Rename Function** +3. Enter: `main` +4. Press **Enter** + +**Update main's signature:** +1. In the Decompile window, right-click on `main` +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +**Rename FUN_100030cc to stdio_init_all:** +1. Click on `FUN_100030cc` in the decompile window +2. Right-click → **Edit Function Signature** +3. Change name to: `stdio_init_all` +4. Change signature to: `bool stdio_init_all(void)` +5. Click **OK** + +**Rename FUN_10003100 to printf:** +1. Click on `FUN_10003100` +2. Right-click → **Edit Function Signature** +3. Change name to: `printf` +4. Check the **Varargs** checkbox (printf accepts variable arguments) +5. Click **OK** + +##### Step 9: Examine the Optimized Code + +After renaming, the decompiled main should now look like: + +```c +int main(void) +{ + stdio_init_all(); + do { + printf("age: %d\r\n", 0x2b); + } while (true); +} +``` + +**Critical observation:** Where did our `age` variable go? + +Original source code: +```c +uint8_t age = 42; +age = 43; +``` + +The compiler **optimized it completely away**! + +**Why?** +1. `age = 42` is immediately overwritten +2. The value `42` is never used +3. The compiler replaces `age` with the constant `43` (`0x2b`) +4. No variable allocation in memory is needed! + +##### Step 10: Examine the Assembly Listing + +Click on the **Listing** window (shows assembly code): + +Find the instruction that loads `0x2b`: + +```assembly +10000xxx movs r1, #0x2b +10000xxx ... +10000xxx bl printf +``` + +**What this does:** +- `movs r1, #0x2b` - Moves the immediate value 0x2b (43) into register r1 +- `bl printf` - Branches to printf, which expects format args in r1+ + +##### Step 11: Document Your Findings + +Create a table of your observations: + +| Item | Value/Location | Notes | +| --------------------- | -------------- | ------------------------------- | +| Main function address | `0x10000234` | Entry point of program | +| Age value (hex) | `0x2b` | Optimized constant | +| Age value (decimal) | `43` | Original variable value | +| Variable in memory? | No | Compiler optimized it away | +| Printf address | `0x10003100` | Standard library function | +| Format string | "age: %d\r\n" | Located in .rodata section | + +#### Expected Output + +After completing this exercise, you should be able to: +- Successfully import and configure ARM binaries in Ghidra +- Navigate the Symbol Tree and identify functions +- Understand how compiler optimization removes unnecessary variables +- Convert hexadecimal values to decimal +- Rename functions for better code readability + +#### Questions for Reflection + +###### Question 1: Why did the compiler optimize away the `age` variable? + +###### Question 2: In what memory section would `age` have been stored if it wasn't optimized away? + +###### Question 3: Where is the string "age: %d\r\n" stored, and why can't it be in RAM? + +###### Question 4: What would happen if we had used `age` in a calculation before reassigning it to 43? + +#### Tips and Hints +- Use **CTRL+F** in Ghidra to search for specific values or strings +- The **Data Type Manager** window shows all recognized data types +- If Ghidra's decompiler output looks wrong, try re-analyzing with different options +- Remember: optimized code often looks very different from source code +- The **Display** → **Function Graph** shows control flow visually + +#### Next Steps +- Proceed to Exercise 2 to learn binary patching +- Try analyzing `0x0008_uninitialized-variables.bin` to see how uninitialized variables behave +- Explore the `.rodata` section to find string literals + +#### Additional Challenge +Find the format string "age: %d\r\n" in Ghidra. What address is it stored at? How does the program reference this string in the assembly code? (Hint: Look for an `ldr` instruction that loads the string address into a register.) diff --git a/WEEK04/WEEK04-02.md b/WEEK04/WEEK04-02.md new file mode 100644 index 0000000..53223cd --- /dev/null +++ b/WEEK04/WEEK04-02.md @@ -0,0 +1,220 @@ +# 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 2: Patch Binary to Change Variable Value + +#### Objective +Use Ghidra's patching capabilities to modify the compiled binary, changing the value printed by the program from 43 to a different value of your choice, then convert and flash the modified binary to the Pico 2. + +#### Prerequisites +- Completed Exercise 1 (binary imported and analyzed in Ghidra) +- Python installed for UF2 conversion +- `uf2conv.py` script available in your project directory +- Raspberry Pi Pico 2 connected via USB +- Serial monitor software (PuTTY, minicom, or screen) + +#### Task Description +You will locate the instruction that loads the value 43 into a register, patch it to use a different value (70 in this example), export the modified binary, convert it to UF2 format, and flash it to verify the change. + +#### Step-by-Step Instructions + +##### Step 1: Open Your Ghidra Project + +If you closed Ghidra from Exercise 1: +1. Launch `ghidraRun` +2. Open the project: `week04-ex01-intro-to-variables` +3. Double-click the binary file to open it + +##### Step 2: Navigate to the Value Load Instruction + +In Exercise 1, we found that `main()` calls `printf("age: %d\r\n", 0x2b)`. + +**Find the assembly instruction:** +1. Click on the `main` function in the Symbol Tree +2. Look at the **Listing** window (assembly view) +3. Find the line with `movs r1, #0x2b` + +The instruction should look like: +```assembly +10000xxx 21 2b movs r1, #0x2b +``` + +**What this instruction means:** +- Opcode: `21 2b` (encoded instruction bytes) +- Mnemonic: `movs r1, #0x2b` +- Operation: Move the immediate value 0x2b into register r1 +- Register r1 will be used as the argument to printf + +##### Step 3: Choose Your New Value + +Let's change the value from `43` (0x2b) to `70` (0x46). + +**Convert 70 to hexadecimal:** +- 70 ÷ 16 = 4 remainder 6 +- Therefore: 70 decimal = 0x46 hexadecimal + +You can verify this in Python: +```python +>>> hex(70) +'0x46' +>>> 0x46 +70 +``` + +##### Step 4: Patch the Instruction + +Now we'll modify the binary: + +1. **Right-click** on the instruction `movs r1, #0x2b` +2. Select **Patch Instruction** +3. A dialog appears showing the current instruction +4. Change `#0x2b` to `#0x46` +5. Press **Enter** or click **OK** + +The instruction now reads: +```assembly +10000xxx 21 46 movs r1, #0x46 +``` + +**Visual confirmation:** +- The patched instruction should be highlighted (usually in red or orange) +- The bytes should change from `21 2b` to `21 46` +- The decompiled view should update to show `printf("age: %d\r\n", 0x46);` + +##### Step 5: Verify the Patch in Decompile Window + +Click on the `main` function again and check the **Decompile** window: + +```c +int main(void) +{ + stdio_init_all(); + do { + printf("age: %d\r\n", 0x46); // Changed from 0x2b! + } while (true); +} +``` + +Perfect! The decompiler recognized our patch. + +##### Step 6: Export the Patched Binary + +Now we need to save the modified binary: + +1. Click **File** → **Export Program** +2. Set **Format** to: **Binary** +3. Navigate to your build directory: + - `Embedded-Hacking/0x0005_intro-to-variables/build/` +4. Set **Filename** to: `0x0005_intro-to-variables-h.bin` + - The `-h` suffix means "hacked" +5. Click **OK** + +**Important:** Make sure you're exporting to the correct location! + +##### Step 7: Convert to UF2 Format + +The Pico 2 requires UF2 format. Open a terminal and run: + +**Navigate to the project directory:** +```powershell +cd C:\path\to\Embedded-Hacking\0x0005_intro-to-variables +``` + +**Run the conversion:** +```powershell +python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**Command breakdown:** +- `uf2conv.py` - The conversion script +- `--base 0x10000000` - XIP flash base address (where code runs from) +- `--family 0xe48bff59` - RP2350 family ID +- `--output build\hacked.uf2` - Output filename + +**Expected output:** +``` +Converting to uf2, output size: 57856, start address: 0x10000000 +Wrote 57856 bytes to build\hacked.uf2 +``` + +##### Step 8: Flash the Hacked Binary + +**Enter bootloader mode:** +1. Disconnect your Pico 2 from USB +2. Hold down the **BOOTSEL** button +3. While holding BOOTSEL, plug in the USB cable +4. Release BOOTSEL +5. A drive called **RPI-RP2** appears + +**Flash the binary:** +1. Open the RPI-RP2 drive +2. Drag and drop `build\hacked.uf2` onto the drive +3. The Pico will automatically reboot + +##### Step 9: Verify the Changes + +**Open your serial monitor:** + +For PuTTY: +1. Select **Serial** connection type +2. Set the COM port (check Device Manager) +3. Set speed to **115200** +4. Click **Open** + +For PowerShell: +```powershell +# Find the COM port +Get-PnpDevice -Class Ports | Where-Object {$_.FriendlyName -like "*USB Serial*"} + +# Connect (replace COM3 with your port) +$port = new-Object System.IO.Ports.SerialPort COM3,115200,None,8,one +$port.Open() +while($true) { $port.ReadLine() } +``` + +**Expected output:** +``` +age: 70 +age: 70 +age: 70 +age: 70 +... +``` + +🎉 **Success!** You've successfully patched a binary and changed its behavior! + +#### Expected Output + +After completing this exercise, you should: +- See `age: 70` printing instead of `age: 43` +- Have a patched binary file (`0x0005_intro-to-variables-h.bin`) +- Have a UF2 file (`hacked.uf2`) +- Understand the complete patching workflow + +#### Questions for Reflection + +###### Question 1: Why do we need to convert to UF2 format instead of flashing the raw .bin file? + +###### Question 2: What is the significance of the base address 0x10000000 in the conversion command? + +###### Question 3: What would happen if you patched the wrong instruction by mistake? + +###### Question 4: How can you verify a patch was applied correctly before exporting? + +#### Tips and Hints +- Always make a backup of the original binary before patching +- Use descriptive names like `-h` (hacked) or `-patch` for modified binaries +- Test your patches on hardware to ensure they work as expected +- If something goes wrong, you can always flash the original UF2 file +- Use `File → Export Program → Original File` to revert all patches + +#### Next Steps +- Try patching to different values (100, 255, etc.) +- Proceed to Exercise 3 to learn about GPIO control +- Experiment with patching multiple values in the same binary + +#### Additional Challenge +Instead of changing the value to 70, change it to 255 (0xFF) - the maximum value for a `uint8_t`. What do you observe? Now try changing it to 256 (0x100). What happens and why? (Hint: Think about the size limits of the instruction encoding.) diff --git a/WEEK04/WEEK04-03.md b/WEEK04/WEEK04-03.md new file mode 100644 index 0000000..8938aba --- /dev/null +++ b/WEEK04/WEEK04-03.md @@ -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. diff --git a/WEEK04/WEEK04-04.md b/WEEK04/WEEK04-04.md new file mode 100644 index 0000000..592f118 --- /dev/null +++ b/WEEK04/WEEK04-04.md @@ -0,0 +1,345 @@ +# 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 4: Patch GPIO Binary to Change LED Pin + +#### Objective +Patch the `0x0008_uninitialized-variables.bin` binary to change which LED blinks, modify the printed value, and change the blink timing, then verify all changes work correctly on hardware. + +#### Prerequisites +- Completed Exercise 3 (GPIO binary analyzed in Ghidra) +- Understanding of how GPIO pin numbers are encoded +- Knowledge of hexadecimal-to-decimal conversion +- Pico 2 with ability to test multiple GPIO pins + +#### Task Description +You will locate all instances where GPIO 16 is used, patch them to GPIO 17 (changing from red LED to green LED), modify the printed value from 0 to 66, and adjust the blink timing from 500ms to 100ms for faster blinking. + +#### Step-by-Step Instructions + +##### Step 1: Plan Your Patches + +Before we start patching, let's identify what needs to change: + +| Current Value | New Value | Description | Hex Conversion | +| ------------- | --------- | --------------------- | ------------------- | +| GPIO 16 | GPIO 17 | Change LED pin | 0x10 → 0x11 | +| age = 0 | age = 66 | Change printed value | 0x00 → 0x42 | +| 500ms | 100ms | Change blink timing | 0x1f4 → 0x64 | + +**Verify the hex conversions:** +- 17 decimal = 0x11 hex ✓ +- 66 decimal = 0x42 hex ✓ +- 100 decimal = 0x64 hex ✓ + +##### Step 2: Open the GPIO Project in Ghidra + +1. Launch Ghidra and open `week04-ex03-gpio-analysis` +2. Double-click the binary to open the CodeBrowser +3. Navigate to the `main` function + +**Review the decompiled code:** +```c +int main(void) +{ + stdio_init_all(); + gpio_init(0x10); + gpio_set_dir(0x10, 1); + + while (true) { + printf("age: %d\r\n", 0); + gpio_put(0x10, 1); + sleep_ms(0x1f4); + gpio_put(0x10, 0); + sleep_ms(0x1f4); + } +} +``` + +##### Step 3: Find and Patch gpio_init Parameter + +Look at the **Listing** window (assembly view) for the main function. + +**Find the gpio_init call:** +```assembly +1000023a 10 20 movs r0, #0x10 +1000023c xx xx bl gpio_init +``` + +**Patch instruction:** +1. Right-click on `movs r0, #0x10` +2. Select **Patch Instruction** +3. Change `#0x10` to `#0x11` +4. Press **Enter** + +**Result:** +```assembly +1000023a 11 20 movs r0, #0x11 +``` + +The instruction bytes change from `10 20` to `11 20`. + +##### Step 4: Find and Patch gpio_set_dir Parameter + +Continue down the assembly listing: + +```assembly +10000240 10 23 movs r3, #0x10 +10000242 01 22 movs r2, #1 +10000244 xx xx bl gpio_set_dir +``` + +**Patch the r3 load:** +1. Right-click on `movs r3, #0x10` +2. Select **Patch Instruction** +3. Change to `#0x11` +4. Press **Enter** + +**Why r3 instead of r0?** The SDK might pass GPIO pin as the first parameter differently, or this could be due to register allocation. Trust the analysis! + +##### Step 5: Find All gpio_put Calls + +Inside the while loop, there are two `gpio_put` calls: + +**First gpio_put (LED ON):** +```assembly +10000252 10 24 movs r4, #0x10 +10000254 01 25 movs r5, #1 +10000256 xx xx bl gpio_put +``` + +**Patch:** +1. Right-click on `movs r4, #0x10` +2. Change to `#0x11` + +**Second gpio_put (LED OFF):** +```assembly +1000025e 10 24 movs r4, #0x10 +10000260 00 25 movs r5, #0 +10000262 xx xx bl gpio_put +``` + +**Patch:** +1. Right-click on `movs r4, #0x10` +2. Change to `#0x11` + +**Note:** The exact addresses will vary, but the pattern is consistent. + +##### Step 6: Patch the Printed Value + +Find the printf call in the loop: + +```assembly +1000024a 00 21 movs r1, #0x0 +1000024c xx xx ldr r0, [pc, #offset] +1000024e xx xx bl printf +``` + +**Patch the value:** +1. Right-click on `movs r1, #0x0` +2. Select **Patch Instruction** +3. Change to `#0x42` (66 in decimal) +4. Press **Enter** + +**Result:** +```assembly +1000024a 42 21 movs r1, #0x42 +``` + +##### Step 7: Patch the Sleep Timing (First) + +Find the first `sleep_ms(0x1f4)` call: + +```assembly +10000258 f4 21 movs r1, #0xf4 +1000025a 01 20 movs r0, #1 +1000025c 00 04 lsls r0, r0, #16 +1000025e 08 44 add r0, r1 +10000260 xx xx bl sleep_ms +``` + +**Wait, this looks complex!** The value 0x1f4 (500) is being constructed: +- Load 1 into r0 +- Shift left 16 bits: 1 << 16 = 0x10000 +- Load 0xf4 into r1 +- Add them: 0x10000 + 0xf4 = 0x1f4 + +**Alternative pattern (simpler):** +```assembly +10000xxx f4 20 movs r0, #0xf4 +10000xxx 01 20 movs r1, #0x01 +10000xxx ... +``` + +**For 100ms (0x64):** +Simply find where 0x1f4 is loaded and change it to 0x64. + +**If the instruction is:** +```assembly +movs r0, #0x1f4 +``` + +**Change to:** +```assembly +movs r0, #0x64 +``` + +**Note:** The exact encoding depends on the instruction. For immediate values > 255, Thumb-2 uses different encodings. + +##### Step 8: Handle Large Immediate Values + +If `sleep_ms(500)` uses a multi-instruction sequence to load 0x1f4, you have two options: + +**Option A: Patch both instructions** +If it's loading 0x1f4 as (0x100 + 0xf4): +1. Find where 0xf4 is loaded +2. Change it to 0x64 +3. Find where 0x1 is loaded for the high byte +4. Change it to 0x0 + +**Option B: Simplify to single instruction** +Since 0x64 (100) fits in an 8-bit immediate, you can replace the multi-instruction sequence with: +```assembly +movs r0, #0x64 +nop +nop +``` + +##### Step 9: Verify All Patches + +Check the **Decompile** window to confirm changes: + +```c +int main(void) +{ + stdio_init_all(); + gpio_init(0x11); // Changed from 0x10! + gpio_set_dir(0x11, 1); + + while (true) { + printf("age: %d\r\n", 0x42); // Changed from 0! + gpio_put(0x11, 1); // Changed from 0x10! + sleep_ms(0x64); // Changed from 0x1f4! + gpio_put(0x11, 0); // Changed from 0x10! + sleep_ms(0x64); // Changed from 0x1f4! + } +} +``` + +Perfect! All changes are reflected. + +##### Step 10: Export the Patched Binary + +1. Click **File** → **Export Program** +2. Set Format: **Binary** +3. Navigate to: `Embedded-Hacking/0x0008_uninitialized-variables/build/` +4. Filename: `0x0008_uninitialized-variables-h.bin` +5. Click **OK** + +##### Step 11: Convert to UF2 + +Open PowerShell and navigate to the project: + +```powershell +cd C:\path\to\Embedded-Hacking\0x0008_uninitialized-variables +``` + +**Run conversion:** +```powershell +python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**Expected output:** +``` +Converting to uf2, output size: 61952, start address: 0x10000000 +Wrote 61952 bytes to build\hacked.uf2 +``` + +##### Step 12: Flash and Test + +**Enter bootloader:** +1. Hold **BOOTSEL** button +2. Plug in USB +3. Release BOOTSEL + +**Flash:** +1. Drag `build\hacked.uf2` to RPI-RP2 drive +2. Pico reboots automatically + +**Test with serial monitor:** +``` +age: 66 +age: 66 +age: 66 +... +``` + +**Hardware verification:** +- ✅ GREEN LED (GPIO 17) should be blinking +- ✅ RED LED (GPIO 16) should be off +- ✅ Blink rate should be much faster (10 Hz instead of 1 Hz) +- ✅ Serial output shows 66 instead of 0 + +🎉 **Triple success!** You've patched three different aspects of the program! + +#### Expected Output + +After completing this exercise, you should: +- See `age: 66` printing continuously +- Observe the green LED (GPIO 17) blinking rapidly +- Understand how to find and patch all instances of a value +- Know how to handle different immediate value encoding schemes + +#### Questions for Reflection + +###### Question 1: Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)? + +###### Question 2: What would happen if you forgot to patch one of the gpio_put calls? + +###### Question 3: How does the instruction encoding differ for immediate values less than 256 vs. greater than 255? + +###### Question 4: If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use? + +#### Tips and Hints +- Use Ghidra's **Search** → **For Scalars** to find all instances of a hex value +- Right-click in Listing → **Highlight** → **Highlight Instruction** helps track your patches +- Make notes of addresses you've patched to avoid confusion +- Test incrementally - patch one thing at a time if you're unsure +- Keep the original UF2 to revert if needed + +#### Next Steps +- Try patching to use GPIO 18 (blue LED) instead +- Change the printf format string to display in hexadecimal +- Experiment with different timing patterns (e.g., 200ms on, 800ms off) + +#### Additional Challenge + +**Advanced Multi-LED Pattern:** + +Patch the binary to create an alternating pattern: +- GPIO 16 (red) blinks for 100ms +- GPIO 17 (green) blinks for 100ms +- GPIO 18 (blue) blinks for 100ms +- Repeat + +This requires: +1. Adding new gpio_init and gpio_set_dir calls for GPIO 18 +2. Restructuring the while loop to have three gpio_put sequences +3. Finding space in the binary or replacing existing code + +**Hint:** You might need to NOP out some instructions and carefully insert new ones. This is advanced patching! + +#### Verification Checklist + +Before moving on, confirm: +- [ ] GPIO 17 LED blinks (not GPIO 16) +- [ ] Blink rate is approximately 10 Hz (100ms on/off) +- [ ] Serial output shows "age: 66" +- [ ] You can explain each patch you made +- [ ] You understand why each patch was necessary +- [ ] You successfully converted and flashed the UF2 + +**Congratulations!** You've completed all Week 4 exercises and mastered variable analysis, binary patching, and GPIO manipulation! diff --git a/WEEK04/WEEK04.md b/WEEK04/WEEK04.md new file mode 100644 index 0000000..e73163b --- /dev/null +++ b/WEEK04/WEEK04.md @@ -0,0 +1,958 @@ +# 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 + +### 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand what variables are and how they're stored in memory +- Know the difference between initialized, uninitialized, and constant variables +- Use Ghidra to analyze binaries without debug symbols +- Patch binary files to change program behavior permanently +- Control GPIO pins to blink LEDs on the Pico 2 +- Convert patched binaries to UF2 format for flashing +- Understand the `.data`, `.bss`, and `.rodata` memory sections + +--- + +## 📚 Part 1: Understanding Variables + +### What is a Variable? + +A **variable** is like a labeled box where you can store information. Imagine you have a row of boxes numbered 0 to 9. Each box can hold one item. In programming: + +- The **boxes** are memory locations (addresses in SRAM) +- The **items** are the values you store +- The **labels** are the variable names you choose + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Memory (SRAM) - Like a row of numbered boxes │ +│ │ +│ Box 0 Box 1 Box 2 Box 3 Box 4 ... │ +│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ +│ │ 42 │ │ 17 │ │ 0 │ │255 │ │ 99 │ │ +│ └────┘ └────┘ └────┘ └────┘ └────┘ │ +│ age score count max temp │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Declaration vs Definition + +When working with variables, there are two important concepts: + +| Concept | What It Does | Example | +| ------------------ | ------------------------------------ | -------------------------- | +| **Declaration** | Tells the compiler the name and type | `uint8_t age;` | +| **Definition** | Allocates memory for the variable | (happens with declaration) | +| **Initialization** | Assigns an initial value | `uint8_t age = 42;` | + +**Important Rule:** You must declare a variable BEFORE you use it! + +### Understanding Data Types + +The **data type** tells the compiler how much memory to allocate: + +| Type | Size | Range | Description | +| ---------- | ------- | ------------------------------- | ----------------------- | +| `uint8_t` | 1 byte | 0 to 255 | Unsigned 8-bit integer | +| `int8_t` | 1 byte | -128 to 127 | Signed 8-bit integer | +| `uint16_t` | 2 bytes | 0 to 65,535 | Unsigned 16-bit integer | +| `int16_t` | 2 bytes | -32,768 to 32,767 | Signed 16-bit integer | +| `uint32_t` | 4 bytes | 0 to 4,294,967,295 | Unsigned 32-bit integer | +| `int32_t` | 4 bytes | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer | + +### Anatomy of a Variable Declaration + +Let's break down this line of code: + +```c +uint8_t age = 42; +``` + +| Part | Meaning | +| --------- | ----------------------------------------------------- | +| `uint8_t` | Data type - unsigned 8-bit integer (1 byte) | +| `age` | Variable name - how we refer to this storage location | +| `=` | Assignment operator - puts a value into the variable | +| `42` | The initial value | +| `;` | Semicolon - tells compiler the statement is complete | + +--- + +## 📚 Part 2: Memory Sections - Where Variables Live + +### The Three Main Sections + +When your program is compiled, variables go to different places depending on how they're declared: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ .data Section (Flash → copied to RAM at startup) │ +│ Contains: Initialized global/static variables │ +│ Example: int counter = 42; │ +├─────────────────────────────────────────────────────────────────┤ +│ .bss Section (RAM - zeroed at startup) │ +│ Contains: Uninitialized global/static variables │ +│ Example: int counter; (will be 0) │ +├─────────────────────────────────────────────────────────────────┤ +│ .rodata Section (Flash - read only) │ +│ Contains: Constants, string literals │ +│ Example: const int MAX = 100; │ +│ Example: "hello, world" │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### What Happens to Uninitialized Variables? + +In older C compilers, uninitialized variables could contain "garbage" - random leftover data. But modern compilers (including the Pico SDK) are smarter: + +1. Uninitialized global variables go into the `.bss` section +2. The `.bss` section is **NOT stored in the binary** (saves space!) +3. At boot, the startup code uses `memset` to **zero out** all of `.bss` +4. So uninitialized variables are always `0`! + +This is why in our code: +```c +uint8_t age; // This will be 0, not garbage! +``` + +--- + +## 📚 Part 3: Understanding GPIO (General Purpose Input/Output) + +### What is GPIO? + +**GPIO** stands for **General Purpose Input/Output**. These are pins on the microcontroller that you can control with software. Think of them as tiny switches you can turn on and off. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Raspberry Pi Pico 2 │ +│ │ +│ GPIO 16 ───────► Red LED │ +│ GPIO 17 ───────► Green LED │ +│ GPIO 18 ───────► Blue LED │ +│ ... │ +│ GPIO 25 ───────► Onboard LED │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### GPIO Functions in the Pico SDK + +The Pico SDK provides simple functions to control GPIO pins: + +| Function | Purpose | +| ------------------------------ | ------------------------------- | +| `gpio_init(pin)` | Initialize a GPIO pin for use | +| `gpio_set_dir(pin, direction)` | Set pin as INPUT or OUTPUT | +| `gpio_put(pin, value)` | Set pin HIGH (1) or LOW (0) | +| `sleep_ms(ms)` | Wait for specified milliseconds | + +### Basic LED Blink Code + +```c +#include +#include "pico/stdlib.h" + +#define LED_PIN 16 + +int main(void) { + gpio_init(LED_PIN); // Initialize GPIO 16 + gpio_set_dir(LED_PIN, GPIO_OUT); // Set as output + + while (true) { + gpio_put(LED_PIN, 1); // LED ON + sleep_ms(500); // Wait 500ms + gpio_put(LED_PIN, 0); // LED OFF + sleep_ms(500); // Wait 500ms + } +} +``` + +### What Happens Behind the Scenes? + +Each high-level function calls lower-level code. Let's trace `gpio_init()`: + +``` +gpio_init(LED_PIN) + ↓ +gpio_set_dir(LED_PIN, GPIO_IN) // Initially set as input + ↓ +gpio_put(LED_PIN, 0) // Set output value to 0 + ↓ +gpio_set_function(LED_PIN, GPIO_FUNC_SIO) // Connect to SIO block +``` + +The SIO (Single-cycle I/O) block is a special hardware unit in the RP2350 that provides fast GPIO control! + +--- + +## 📚 Part 4: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. Ghidra installed (for static analysis) +3. Python installed (for UF2 conversion) +4. The sample projects: + - `0x0005_intro-to-variables` + - `0x0008_uninitialized-variables` +5. A serial monitor (PuTTY, minicom, or screen) + +### Project Structure + +``` +Embedded-Hacking/ +├── 0x0005_intro-to-variables/ +│ ├── build/ +│ │ ├── 0x0005_intro-to-variables.uf2 +│ │ └── 0x0005_intro-to-variables.bin +│ └── 0x0005_intro-to-variables.c +├── 0x0008_uninitialized-variables/ +│ ├── build/ +│ │ ├── 0x0008_uninitialized-variables.uf2 +│ │ └── 0x0008_uninitialized-variables.bin +│ └── 0x0008_uninitialized-variables.c +└── uf2conv.py +``` + +--- + +## 🔬 Part 5: Hands-On Tutorial - Analyzing Variables in Ghidra + +### Step 1: Review the Source Code + +First, let's look at the code we'll be analyzing: + +**File: `0x0005_intro-to-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + uint8_t age = 42; + + age = 43; + + stdio_init_all(); + + while (true) + printf("age: %d\r\n", age); +} +``` + +**What this code does:** +1. Declares a variable `age` and initializes it to `42` +2. Changes `age` to `43` +3. Initializes the serial output +4. Prints `age` forever in a loop + +### Step 2: Flash the Binary to Your Pico 2 + +1. Hold the BOOTSEL button on your Pico 2 +2. Plug in the USB cable (while holding BOOTSEL) +3. Release BOOTSEL - a drive called "RPI-RP2" appears +4. Drag and drop `0x0005_intro-to-variables.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Verify It's Working + +Open your serial monitor (PuTTY, minicom, or screen) and you should see: + +``` +age: 43 +age: 43 +age: 43 +... +``` + +The program is printing `43` because that's what we assigned after the initial `42`. + +--- + +## 🔬 Part 6: Setting Up Ghidra for Binary Analysis + +### Step 4: Start Ghidra + +**Open a terminal and type:** + +```bash +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 5: Create a New Project + +1. Click **File** → **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x0005_intro-to-variables` +5. Click **Finish** + +### Step 6: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x0005_intro-to-variables.bin` +4. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 7: Configure the Binary Format + +A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). + +**Click the three dots (…) next to "Language" and:** +1. Search for "Cortex" +2. Select **ARM Cortex 32 little endian default** +3. Click **OK** + +**Click the "Options…" button and:** +1. Change **Block Name** to `.text` +2. Change **Base Address** to `10000000` (the XIP address!) +3. Click **OK** + +### Step 8: Open and Analyze + +1. Double-click on the file in the project window +2. A dialog asks "Analyze now?" - Click **Yes** +3. Use default analysis options and click **Analyze** + +Wait for analysis to complete (watch the progress bar in the bottom right). + +--- + +## 🔬 Part 7: Navigating and Resolving Functions + +### Step 9: Find the Functions + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +You'll see function names like: +- `FUN_1000019a` +- `FUN_10000210` +- `FUN_10000234` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 10: Resolve Known Functions + +From our previous chapters, we know what some of these functions are: + +| Ghidra Name | Actual Name | How We Know | +| -------------- | ------------- | -------------------------- | +| `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | +| `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | +| `FUN_10000234` | `main` | This is where our code is! | + +**To rename `FUN_1000019a` to `data_cpy`:** +1. Click on `FUN_1000019a` in the Symbol Tree +2. In the Decompile window, right-click on the function name +3. Select **Edit Function Signature** +4. Change the name to `data_cpy` +5. Click **OK** + +**Repeat for the other functions:** +- Rename `FUN_10000210` to `frame_dummy` +- Rename `FUN_10000234` to `main` + +### Step 11: Update Main's Signature + +For `main`, let's also fix the return type: + +1. Right-click on `main` in the Decompile window +2. Select **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## 🔬 Part 8: Analyzing the Main Function + +### Step 12: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +void FUN_10000234(void) +{ + FUN_10002f54(); + do { + FUN_100030e4(DAT_10000244,0x2b); + } while( true ); +} +``` + +### Step 13: Resolve stdio_init_all + +1. Click on `FUN_10002f54` +2. Right-click → **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 14: Resolve printf + +1. Click on `FUN_100030e4` +2. Right-click → **Edit Function Signature** +3. Change the name to `printf` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 15: Understand the Optimization + +Look at the decompiled code. This will look different if you resolved your functions however do you notice something interesting? + +```c +void FUN_10000234(void) +{ + FUN_10002f54(); + do { + FUN_100030e4(DAT_10000244,0x2b); + } while( true ); +} +``` + +**Where's `uint8_t age = 42`?** It's gone! + +The compiler **optimized it out**! Here's what happened: + +1. Original code: `age = 42`, then `age = 43` +2. Compiler sees: "The `42` is never used, only `43` matters" +3. Compiler removes the unused `42` and just uses `43` directly + +**What is `0x2b`?** Let's check: +- `0x2b` in hexadecimal = `43` in decimal ✓ + +The compiler replaced our variable with the constant value! + +--- + +## 🔬 Part 9: Patching the Binary - Changing the Value + +### Step 16: Find the Value to Patch + +Look at the **Listing** window (assembly view). Find the instruction that loads `0x2b`: + +```assembly +1000023a 2b 21 movs r1,#0x2b +``` + +This instruction loads the value `0x2b` (43) into register `r1` before calling `printf`. + +### Step 17: Patch the Instruction + +We're going to change `0x2b` (43) to `0x46` (70)! + +1. Click on the instruction `movs r1,#0x2b` +2. Right-click and select **Patch Instruction** +3. Change `0x2b` to `0x46` +4. Press Enter + +The instruction now reads: +```assembly +1000023a 46 21 movs r1,#0x46 +``` + +### Step 18: Export the Patched Binary + +1. Click **File** → **Export Program** +2. Set **Format** to **Raw Bytes** +3. Navigate to your build directory +4. Name the file `0x0005_intro-to-variables-h.bin` +5. Click **OK** + +--- + +## 🔬 Part 10: Converting and Flashing the Hacked Binary + +### Step 19: Convert to UF2 Format + +The Pico 2 expects UF2 files, not raw BIN files. We need to convert it! + +**Open a terminal and navigate to your project directory:** + +```bash +cd Embedded-Hacking/0x0005_intro-to-variables +``` + +**Run the conversion command:** + +```bash +python ../uf2conv.py build/0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2 +``` + +**What this command means:** +- `uf2conv.py` = the conversion script +- `--base 0x10000000` = the XIP base address +- `--family 0xe48bff59` = the RP2350 family ID +- `--output build/hacked.uf2` = the output filename + +### Step 20: Flash the Hacked Binary + +1. Hold BOOTSEL and plug in your Pico 2 +2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive +3. Open your serial monitor + +**You should see:** + +``` +age: 70 +age: 70 +age: 70 +... +``` + +🎉 **BOOM! We hacked it!** The value changed from 43 to 70! + +--- + +## 🔬 Part 11: Uninitialized Variables and GPIO + +Now let's work with a more complex example that includes GPIO control. + +### Step 21: Review the Uninitialized Variables Code + +**File: `0x0008_uninitialized-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +#define LED_PIN 16 + +int main(void) { + uint8_t age; // Uninitialized! + + stdio_init_all(); + + gpio_init(LED_PIN); + gpio_set_dir(LED_PIN, GPIO_OUT); + + while (true) { + printf("age: %d\r\n", age); + + gpio_put(LED_PIN, 1); + sleep_ms(500); + + gpio_put(LED_PIN, 0); + sleep_ms(500); + } +} +``` + +**What this code does:** +1. Declares `age` without initializing it (will be 0 due to BSS zeroing) +2. Initializes GPIO 16 as an output +3. In a loop: prints age, blinks the LED + +### Step 22: Flash and Verify + +1. Flash `0x0008_uninitialized-variables.uf2` to your Pico 2 +2. Open your serial monitor + +**You should see:** + +``` +age: 0 +age: 0 +age: 0 +... +``` + +And the **red LED on GPIO 16 should be blinking**! + +The value is `0` because uninitialized variables in the `.bss` section are zeroed at startup. + +--- + +## 🔬 Part 12: Analyzing GPIO Code in Ghidra + +### Step 23: Set Up Ghidra for the New Binary + +1. Create a new project: `0x0008_uninitialized-variables` +2. Import `0x0008_uninitialized-variables.bin` +3. Set Language to **ARM Cortex 32 little endian** +4. Set Base Address to `10000000` +5. Auto-analyze + +### Step 24: Resolve the Functions + +Find and rename these functions: + +| Ghidra Name | Actual Name | +| -------------- | ---------------- | +| `FUN_10000234` | `main` | +| `FUN_100030cc` | `stdio_init_all` | +| `FUN_100002b4` | `gpio_init` | +| `FUN_1000325c` | `printf` | + +For `gpio_init`, set the signature to: +```c +void gpio_init(uint gpio) +``` + +### Step 25: Examine the Main Function + +The decompiled main should look something like: + +```c +void FUN_10000234(void) +{ + undefined4 extraout_r1; + undefined4 extraout_r2; + undefined4 in_cr0; + undefined4 in_cr4; + + FUN_100030cc(); + FUN_100002b4(0x10); + coprocessor_moveto2(0,4,0x10,1,in_cr4); + do { + FUN_1000325c(DAT_10000274,0); + coprocessor_moveto2(0,4,0x10,1,in_cr0); + FUN_10000d10(500); + coprocessor_moveto2(0,4,0x10,0,in_cr0); + FUN_10000d10(500,extraout_r1,extraout_r2,0); + } while( true ); +} +``` + +--- + +## 🔬 Part 13: Hacking GPIO - Changing the LED Pin + +### Step 26: Find the GPIO Pin Value + +Look in the assembly for instructions that use `0x10` (which is 16 in decimal - our LED pin): + +```assembly +1000023a 10 20 movs r0,#0x10 +``` + +This is where `gpio_init(LED_PIN)` is called with GPIO 16. + +### Step 27: Patch GPIO 16 to GPIO 17 + +We'll change the red LED (GPIO 16) to the green LED (GPIO 17)! + +1. Find the instruction `movs r0,#0x10` +2. Right-click → **Patch Instruction** +3. Change `0x10` to `0x11` (17 in hex) +4. Click **OK** + +### Step 28: Find All GPIO 16 References + +There are more places that use GPIO 16. Look for: + +```assembly +10000244 10 23 movs r3,#0x10 +``` + +This is used in `gpio_set_dir`. Patch this to `0x11` as well. + +```assembly +10000252 10 24 movs r4,#0x10 +``` + +This is inside the loop for `gpio_put`. Patch this to `0x11` as well. + +### Step 29: Bonus - Change the Printed Value + +Let's also change the printed value from `0` to `0x42` (66 in decimal): + +```assembly +1000024a 00 21 movs r1,#0x0 +``` + +1. Right-click → **Patch Instruction** +2. Change `0x0` to `0x42` +3. Click **OK** + +--- + +## 🔬 Part 14: Export and Test the Hacked GPIO + +### Step 30: Export the Patched Binary + +1. Click **File** → **Export Program** +2. Format: **Raw Bytes** +3. Filename: `0x0008_uninitialized-variables-h.bin` +4. Click **OK** + +### Step 31: Convert to UF2 + +```bash +cd Embedded-Hacking/0x0008_uninitialized-variables +python ../uf2conv.py build/0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build/hacked.uf2 +``` + +### Step 32: Flash and Verify + +1. Flash `hacked.uf2` to your Pico 2 +2. Check your serial monitor + +**You should see:** + +``` +age: 66 +age: 66 +age: 66 +... +``` + +And now the **GREEN LED on GPIO 17** should be blinking instead of the red one! + +🎉 **We successfully:** +1. Changed the printed value from 0 to 66 +2. Changed which LED blinks from red (GPIO 16) to green (GPIO 17) + +--- + +## 📚 Part 15: Deep Dive - GPIO at the Assembly Level + +### Understanding the GPIO Coprocessor + +The RP2350 has a special **GPIO coprocessor** that provides fast, single-cycle GPIO control. This is different from the RP2040! + +The coprocessor is accessed using special ARM instructions: + +```assembly +mcrr p0, #4, r4, r5, c0 ; GPIO output control +mcrr p0, #4, r4, r5, c4 ; GPIO direction control +``` + +**What this means:** +- `mcrr` = Move to Coprocessor from two ARM Registers +- `p0` = Coprocessor 0 (the GPIO coprocessor) +- `r4` = Contains the GPIO pin number +- `r5` = Contains the value (0 or 1) +- `c0` = Output value register +- `c4` = Output enable register + +### The Full GPIO Initialization Sequence + +When you call `gpio_init(16)`, here's what actually happens: + +``` +Step 1: Configure pad (address 0x40038044) +┌─────────────────────────────────────────────────────────────────┐ +│ - Clear OD bit (output disable) │ +│ - Set IE bit (input enable) │ +│ - Clear ISO bit (isolation) │ +└─────────────────────────────────────────────────────────────────┘ + +Step 2: Set function (address 0x40028084) +┌─────────────────────────────────────────────────────────────────┐ +│ - Set FUNCSEL to 5 (SIO - Software I/O) │ +└─────────────────────────────────────────────────────────────────┘ + +Step 3: Enable output (via coprocessor) +┌─────────────────────────────────────────────────────────────────┐ +│ - mcrr p0, #4, r4, r5, c4 (where r4=16, r5=1) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Raw Assembly LED Blink + +Here's what a completely hand-written assembly LED blink looks like: + +```assembly +; Initialize GPIO 16 as output +movs r4, #0x10 ; GPIO 16 +movs r5, #0x01 ; Enable +mcrr p0, #4, r4, r5, c4 ; Set as output + +; Configure pad registers +ldr r3, =0x40038044 ; Pad control for GPIO 16 +ldr r2, [r3] ; Load current config +bic r2, r2, #0x80 ; Clear OD (output disable) +orr r2, r2, #0x40 ; Set IE (input enable) +str r2, [r3] ; Store config + +; Set GPIO function to SIO +ldr r3, =0x40028084 ; IO bank control for GPIO 16 +movs r2, #5 ; FUNCSEL = SIO +str r2, [r3] ; Set function + +; Main loop +loop: + ; LED ON + movs r4, #0x10 ; GPIO 16 + movs r5, #0x01 ; High + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 ; ~25 million iterations +delay1: + subs r2, r2, #1 + bne delay1 + + ; LED OFF + movs r4, #0x10 ; GPIO 16 + movs r5, #0x00 ; Low + mcrr p0, #4, r4, r5, c0 + + ; Delay + ldr r2, =0x17D7840 +delay2: + subs r2, r2, #1 + bne delay2 + + b loop ; Repeat forever +``` + +--- + +## 📊 Part 16: Summary and Review + +### What We Accomplished + +1. **Learned about variables** - How they're declared, initialized, and stored +2. **Understood memory sections** - `.data`, `.bss`, and `.rodata` +3. **Analyzed binaries in Ghidra** - Without debug symbols! +4. **Patched binaries** - Changed values directly in the binary +5. **Controlled GPIO** - Made LEDs blink +6. **Changed program behavior** - Different LED, different value + +### The Binary Patching Workflow + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 1. Import .bin file into Ghidra │ +│ - Set language to ARM Cortex │ +│ - Set base address to 0x10000000 │ +├─────────────────────────────────────────────────────────────────┤ +│ 2. Analyze and resolve functions │ +│ - Rename functions to meaningful names │ +│ - Fix function signatures │ +├─────────────────────────────────────────────────────────────────┤ +│ 3. Find the values/instructions to patch │ +│ - Look in the assembly listing │ +│ - Right-click → Patch Instruction │ +├─────────────────────────────────────────────────────────────────┤ +│ 4. Export the patched binary │ +│ - File → Export Program │ +│ - Format: Raw Bytes │ +├─────────────────────────────────────────────────────────────────┤ +│ 5. Convert to UF2 │ +│ - python uf2conv.py file.bin --base 0x10000000 │ +│ --family 0xe48bff59 --output hacked.uf2 │ +├─────────────────────────────────────────────────────────────────┤ +│ 6. Flash and verify │ +│ - Hold BOOTSEL, plug in, drag UF2 │ +│ - Check serial output and LED behavior │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Key Memory Sections + +| Section | Location | Contains | Writable? | +| --------- | -------- | ------------------------------ | --------- | +| `.text` | Flash | Code | No | +| `.rodata` | Flash | Constants, strings | No | +| `.data` | RAM | Initialized globals | Yes | +| `.bss` | RAM | Uninitialized globals (zeroed) | Yes | + +### Important Ghidra Commands + +| Action | How To Do It | +| ----------------- | ------------------------------------- | +| Rename function | Right-click → Edit Function Signature | +| Patch instruction | Right-click → Patch Instruction | +| Export binary | File → Export Program → Raw Bytes | +| Go to address | Press 'G' and enter address | + +--- + +## ✅ Practice Exercises + +### Exercise 1: Change the Delay +The LED blinks every 500ms. Find the `sleep_ms(500)` calls in the binary and change them to `sleep_ms(100)` for faster blinking. + +**Hint:** Look for the value `0x1F4` (500 in hex) being loaded into a register. + +### Exercise 2: Reverse the LED +Instead of GPIO 16 → ON → OFF, make it GPIO 16 → OFF → ON (start with LED on). + +**Hint:** Find and swap the two `gpio_put` calls (the ones with values 0 and 1). + +### Exercise 3: Add a Second LED +Patch the binary so that BOTH GPIO 16 and GPIO 17 blink together. + +**Hint:** You'll need to find space for additional instructions or modify existing ones cleverly. + +### Exercise 4: Change the Format String +The program prints "age: %d\r\n". Can you find this string in Ghidra and figure out where it's stored? + +**Hint:** Look in the `.rodata` section around address `0x10001xxx`. + +--- + +## 🎓 Key Takeaways + +1. **Variables are just memory locations** - The compiler assigns them addresses in SRAM. + +2. **Compilers optimize aggressively** - Unused code and values may be removed entirely. + +3. **Uninitialized doesn't mean random** - Modern compilers zero out the `.bss` section. + +4. **Ghidra works without symbols** - You can analyze any binary, even stripped ones. + +5. **Binary patching is powerful** - You can change behavior without source code. + +6. **UF2 conversion is required** - The Pico 2 needs UF2 format, not raw binaries. + +7. **GPIO is just memory-mapped I/O** - Writing to specific addresses controls hardware. + +--- + +## 📖 Glossary + +| Term | Definition | +| ------------------ | --------------------------------------------------------------------- | +| **BSS** | Block Started by Symbol - section for uninitialized global variables | +| **Declaration** | Telling the compiler a variable's name and type | +| **Definition** | Allocating memory for a variable | +| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | +| **Initialization** | Assigning an initial value to a variable | +| **Linker** | Tool that combines compiled code and assigns memory addresses | +| **Optimization** | Compiler removing or simplifying code for efficiency | +| **Patching** | Modifying bytes directly in a binary file | +| **rodata** | Read-only data section for constants and string literals | +| **SIO** | Single-cycle I/O - fast GPIO control block in RP2350 | +| **UF2** | USB Flashing Format - file format for Pico 2 firmware | +| **Variable** | A named storage location in memory | + +--- + +## 🔗 Additional Resources + +### GPIO Coprocessor Reference + +The RP2350 GPIO coprocessor instructions: + +| Instruction | Description | +| -------------------------- | ---------------------------- | +| `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | +| `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | + +### RP2350 Memory Map Quick Reference + +| Address | Description | +| ------------ | ------------------------ | +| `0x10000000` | XIP Flash (code) | +| `0x20000000` | SRAM (data) | +| `0x40028000` | IO_BANK0 (GPIO control) | +| `0x40038000` | PADS_BANK0 (pad control) | +| `0xd0000000` | SIO (single-cycle I/O) | + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! + +Happy hacking! 🔧