diff --git a/WEEKS/WEEK01/WEEK01-01-S.md b/WEEKS/WEEK01/WEEK01-01-S.md new file mode 100644 index 0000000..d5b37de --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-01-S.md @@ -0,0 +1,49 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 1 Solution: Explore in Ghidra + +#### Answers + +##### Question 1: What does the function return? +`stdio_init_all()` returns `_bool` (displayed as `void` in some Ghidra versions). The function signature shows `_bool stdio_init_all(void)`. + +##### Question 2: What parameters does it take? +**None** - the function signature shows `(void)` in parentheses, meaning zero parameters. + +##### Question 3: What functions does it call? +`stdio_init_all()` calls initialization functions for: +- **USB CDC** initialization (USB serial communication) +- **UART** initialization (serial pin communication) + +These set up the standard I/O subsystem so that `printf()` can output data. + +##### Question 4: What's the purpose? +`stdio_init_all()` initializes **Standard Input/Output** for the Pico 2: +- **std** = Standard +- **io** = Input/Output + +It sets up both USB CDC and UART communication channels, which allows `printf()` to send output through the serial connection. + +##### Expected Output + +``` +stdio_init_all() returns: void (_bool) +It takes 0 parameters +It calls the following functions: USB CDC init, UART init +Based on these calls, I believe it initializes: Standard I/O for USB and UART serial communication +``` + +#### Reflection Answers + +1. **Why would we need to initialize standard I/O before using `printf()`?** + Without initialization, there is no communication channel configured. `printf()` needs a destination (USB or UART) to send its output. Without `stdio_init_all()`, output has nowhere to go. + +2. **Can you find other functions in the Symbol Tree that might be related to I/O?** + Yes - functions like `stdio_usb_init`, `stdio_uart_init`, `__wrap_puts`, and other I/O-related functions appear in the Symbol Tree. + +3. **How does this function support the `printf("hello, world\r\n")` call in main?** + It configures the USB and UART hardware so that when `printf()` (optimized to `__wrap_puts`) executes, the characters are transmitted over the serial connection to the host computer. diff --git a/WEEKS/WEEK01/WEEK01-01.md b/WEEKS/WEEK01/WEEK01-01.md new file mode 100644 index 0000000..05d8efe --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-01.md @@ -0,0 +1,122 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 1: Explore in Ghidra + +#### Objective +Learn how to navigate Ghidra's Symbol Tree to find and analyze functions, specifically examining the `stdio_init_all` function. + +#### Prerequisites +- Ghidra installed and running +- `0x0001_hello-world` project already created and imported in Ghidra +- The `0x0001_hello-world.elf` file already imported and analyzed + +#### Task Description + +Your goal is to explore the `stdio_init_all` function in Ghidra and understand what it does based on: +1. Its decompiled code +2. The functions it calls +3. The variables it accesses + +#### Step-by-Step Instructions + +##### Step 1: Open Your Ghidra Project + +1. Launch **Ghidra** on your computer +2. In the Ghidra Project Manager window, you should see your `0x0001_hello-world` project +3. If you don't see it, create a new project or open an existing one +4. **Double-click** on the project to open it + +##### Step 2: Access the Symbol Tree + +In the CodeBrowser window that opens: +- Look at the left side panel - you should see several tabs +- Find and click on the **Symbol Tree** tab (it might be labeled "Symbol Tree" or showing a tree icon) +- If you don't see it, go to **Window β†’ Symbol Tree** in the menu + +##### Step 3: Expand the Functions List + +1. In the Symbol Tree, look for a folder or section labeled **Functions** +2. **Click the arrow/triangle** next to "Functions" to expand it +3. This will show you a list of all the functions that Ghidra identified in the binary + +##### Step 4: Find the stdio_init_all Function + +1. In the expanded Functions list, scroll through to find `stdio_init_all` +2. **Alternative method**: If the list is long, you can use **Search β†’ For Address or Label** from the menu and type `stdio_init_all` to jump directly to it +3. Once you find it, **click on it** to navigate to that function in the CodeBrowser + +##### Step 5: Examine the Decompiled Code + +Once you've navigated to `stdio_init_all`: +- On the **right side** of the window, you should see the **Decompile** view +- This shows the C-like code that Ghidra has reconstructed from the assembly +- Read through the decompiled code carefully + +##### Step 6: Answer These Questions + +Based on what you see in the decompiled code, answer the following: + +###### Question 1: What does the function return? +Look at the return type at the top of the function. Is it `void`, `int`, `bool`, or something else? + +###### Question 2: What parameters does it take? +Look at the function signature. Does it take any parameters? (Hint: Look for anything inside the parentheses) + +###### Question 3: What functions does it call? +Look for function calls within `stdio_init_all`. What other functions does it call? List them: +- Function 1: ________________ +- Function 2: ________________ +- Function 3: ________________ +(There may be more or fewer) + +###### Question 4: What's the purpose? +Based on the functions it calls and the overall structure, what do you think `stdio_init_all()` is setting up? Think about what "stdio" stands for: +- **std** = Standard +- **io** = Input/Output + +What types of I/O might be getting initialized? + +##### Step 7: Explore Called Functions (Optional Challenge) + +If you want to go deeper: + +1. In the Decompile view, **click on one of the functions** that `stdio_init_all` calls +2. Ghidra will navigate to that function +3. Look at what **that** function does +4. Can you build a picture of what's being initialized? + +#### Expected Output + +You should be able to write a brief summary like: + +``` +stdio_init_all() returns: [your answer] +It takes [number] parameters +It calls the following functions: [list them] +Based on these calls, I believe it initializes: [your analysis] +``` + +#### Questions for Reflection + +1. Why would we need to initialize standard I/O before using `printf()`? +2. Can you find other functions in the Symbol Tree that might be related to I/O? +3. How does this function support the `printf("hello, world\r\n")` call in main? + +#### Tips and Hints + +- If you see a function name you don't recognize, you can right-click on it to see more options +- The Decompile view is your best friend - it shows you what code is doing in an almost-C format +- Don't worry if some variable names are automatic (like `local_4` or `param_1`) - that's normal when symbols aren't available +- You can collapse/expand sections in the Decompile view by clicking the arrows next to braces `{}` + +#### Next Steps + +After completing this exercise, you'll have a better understanding of: +- How to navigate Ghidra's interface +- How to find functions using the Symbol Tree +- How to read decompiled code +- How initialization functions work in embedded systems diff --git a/WEEKS/WEEK01/WEEK01-02-S.md b/WEEKS/WEEK01/WEEK01-02-S.md new file mode 100644 index 0000000..b056d88 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-02-S.md @@ -0,0 +1,56 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 2 Solution: Find Strings in Ghidra + +#### Answers + +##### String Location + +| Item | Answer | +|------|--------| +| **String Address** | `0x100019CC` | +| **Actual String Content** | `"hello, world\r\n"` | +| **String Length** | 14 bytes | +| **Located In** | Flash memory (XIP region, starts with `0x10000...`) | + +##### Question 1: What is the address and is it Flash or RAM? +The string `"hello, world\r\n"` is located at address **`0x100019CC`** in **Flash memory**. We know it is Flash because the address begins with `0x10000...` (the XIP/Execute-In-Place region starts at `0x10000000`). RAM addresses start at `0x20000000`. + +##### Question 2: How many bytes does the string take? +**14 bytes** total: +- 12 printable characters: `h`, `e`, `l`, `l`, `o`, `,`, ` `, `w`, `o`, `r`, `l`, `d` +- 2 special characters: `\r` (carriage return, 0x0D) and `\n` (newline, 0x0A) + +##### Question 3: How many times and which functions reference it? +The string is referenced **1 time**, only in the **`main()`** function. The `ldr` instruction at `0x1000023a` loads the string address into register `r0`, which is then passed to `__wrap_puts`. + +##### Question 4: How is the string encoded? +The string is encoded in **ASCII**. Each character occupies exactly one byte: +- `\r` = `0x0D` (carriage return) +- `\n` = `0x0A` (newline/line feed) + +##### Expected Output + +``` +String Found: "hello, world\r\n" +Address: 0x100019CC +Located in: Flash (XIP region) +Total Size: 14 bytes +Referenced by: main() +Used in: printf() argument (optimized to __wrap_puts) to print the string in an infinite loop +``` + +#### Reflection Answers + +1. **Why is the string stored in Flash instead of RAM?** + String literals are constants that never change. Storing them in Flash (read-only) saves precious RAM for variables and the stack. The XIP feature allows the processor to read directly from Flash. + +2. **What would happen if you tried to modify this string at runtime?** + Flash memory is read-only at runtime. Attempting to write to a Flash address would cause a fault. To modify printed output, you must write your new string to SRAM (`0x20000000+`) and redirect the pointer. + +3. **How does the Listing view help you understand string storage?** + The Listing view shows the raw hex bytes alongside their ASCII interpretation, letting you see exactly how each character maps to memory and where the string boundaries are. diff --git a/WEEKS/WEEK01/WEEK01-02.md b/WEEKS/WEEK01/WEEK01-02.md new file mode 100644 index 0000000..b3ede31 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-02.md @@ -0,0 +1,163 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 2: Find Strings in Ghidra + +#### Objective +Learn how to locate and analyze strings in a binary, understanding where they are stored in memory and how they're used. + +#### Prerequisites +- Ghidra installed with `0x0001_hello-world` project open +- Basic familiarity with Ghidra's interface (from Exercise 1) +- CodeBrowser window open with the binary loaded + +#### Task Description + +In this exercise, you'll find the "hello, world" string in the binary and determine: +1. **Where** it's located in memory (its address) +2. **How** it's used by the program +3. **What** format it's stored in + +#### Step-by-Step Instructions + +##### Step 1: Open the Defined Strings Window + +1. In the CodeBrowser menu, go to **Window** (top menu bar) +2. Look for and click on **Defined Strings** +3. A new window should appear showing all strings Ghidra found in the binary + +##### Step 2: Understand the Strings Window + +The Defined Strings window shows: +- **Address**: The memory location where the string starts +- **String**: The actual text content +- **Length**: How many bytes the string uses +- **Defined**: Whether Ghidra has marked it as data + +##### Step 3: Search for "hello, world" + +1. In the Defined Strings window, look through the list to find `"hello, world"` +2. **Search method**: If the window has a search box at the top, you can type to filter. Otherwise, use **Ctrl+F** to open the search function +3. Once you find it, **click on it** to highlight the entry + +##### Step 4: Record the Address + +When you find `"hello, world"`, note down: + +**String Address**: ________________ + +**Actual String Content**: ________________ + +**String Length**: ________________ bytes + +##### Step 5: Double-Click to Navigate + +1. **Double-click** on the `"hello, world"` entry in the Defined Strings window +2. Ghidra will automatically navigate you to that address in the CodeBrowser +3. You should see the string displayed in the **Listing** view (center panel) + +##### Step 6: Examine the Listing View + +Now that you're at the string's location: + +1. Look at the **Listing view** (center panel) where the string is shown +2. You'll see the string in **hex/ASCII** format +3. Notice how it appears in memory - each character takes one byte +4. Look for the string content: `hello, world\r\n` +5. What comes after the string? (Ghidra may show other data nearby) + +##### Step 7: Look at the Cross-References + +To see where this string is **used**: + +1. In the Listing view where the string is displayed, **right-click** on the string +2. Select **References** ? **Show References to** +3. A dialog should appear showing which functions/instructions reference this string +4. This tells you which parts of the code use this string + +##### Step 8: Answer These Questions + +Based on what you found: + +###### Question 1: Memory Location +- What is the address of the "hello, world" string? __________ +- Is it in Flash memory (starts with `0x100...`) or RAM (starts with `0x200...`)? __________ + +###### Question 2: String Storage +- How many bytes does the string take in memory? __________ +- Can you count the characters? (h-e-l-l-o-,-space-w-o-r-l-d-\r-\n) + +###### Question 3: References +- How many times is this string referenced in the code? __________ +- Which function(s) reference it? (Hint: Look at the cross-references) + +###### Question 4: ASCII Encoding +- How is the string encoded in memory? +- Is each character one byte or more? __________ +- What does `\r` and `\n` represent? (Hint: `\r` = carriage return, `\n` = newline) + +#### Expected Output + +You should be able to fill in a summary like: + +``` +String Found: "hello, world\r\n" +Address: 0x________ +Located in: [Flash / RAM] +Total Size: ________ bytes +Referenced by: [Function names] +Used in: [How the program uses it] +``` + +#### Deeper Exploration (Optional Challenge) + +##### Challenge 1: Follow the String Usage +1. From the cross-references you found, click on the instruction that uses the string +2. You should navigate to the `ldr` (load) instruction that loads the string's address into register `r0` +3. This is how the `printf` function gets the pointer to the string! + +##### Challenge 2: Find Other Strings +1. Go back to the Defined Strings window +2. Look for other strings in the binary +3. Are there any other text strings besides "hello, world"? +4. If yes, where are they and what are they used for? + +##### Challenge 3: Understand Little-Endian +1. When Ghidra shows the string address in the `ldr` instruction, it's showing a number +2. Look at the raw bytes of that address value +3. Notice how the bytes are stored in "backwards" order? That's little-endian! +4. Can you convert the hex bytes to the actual address? + +#### Questions for Reflection + +1. **Why is the string stored in Flash memory?** Why not in RAM? +2. **How does `printf()` know where to find the string?** (Hint: The address is loaded into `r0`) +3. **What would happen if we didn't have the `\r\n` at the end?** How would the output look? +4. **Could we modify this string at runtime?** Why or why not? + +#### Tips and Hints + +- Strings in compiled binaries are often stored in read-only memory (Flash) to save RAM +- The `\r` and `\n` characters are special: they're single bytes (0x0D and 0x0A in hex) +- When you see a string in Ghidra's listing, the ASCII representation is shown on the right side +- You can scroll left/right in the Listing view to see different representations (hex, ASCII, disassembly) + +#### Real-World Application + +Understanding where strings are stored is crucial for: +- **Firmware modification**: Finding text messages to modify +- **Reverse engineering**: Understanding what a program does by finding its strings +- **Vulnerability analysis**: Finding format string bugs or hardcoded credentials +- **Localization**: Finding where text needs to be translated + +#### Summary + +By completing this exercise, you've learned: +1. How to find strings in a binary using Ghidra's Defined Strings window +2. How to determine the memory address of a string +3. How to follow cross-references to see where strings are used +4. How strings are stored in memory and referenced in code +5. The relationship between C code (`printf()`) and assembly (`ldr`) diff --git a/WEEKS/WEEK01/WEEK01-03-S.md b/WEEKS/WEEK01/WEEK01-03-S.md new file mode 100644 index 0000000..70e0262 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-03-S.md @@ -0,0 +1,60 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 3 Solution: Find Cross-References in Ghidra + +#### Answers + +##### Fill-in-the-Blank Items + +| Item | Answer | +|------|--------| +| **Data reference address** | `0x10000244` (DAT_10000244) | +| **Number of references** | 1 | +| **Reference type** | Read (`ldr` instruction) | +| **Function using it** | `main()` | +| **Next instruction after ldr** | `bl __wrap_puts` at `0x100015fc` | + +##### Question 1: What is the address of the data reference? +The data reference is at **`0x10000244`** (labeled `DAT_10000244` in Ghidra). This location stores the pointer value `0x100019CC`, which is the address of the `"hello, world"` string. + +##### Question 2: How many places reference this data? +**1 place** - it is only referenced in the `main()` function via the `ldr` instruction. + +##### Question 3: Is it a read or write operation? Why? +It is a **READ** operation. The `ldr` (Load Register) instruction reads the pointer value from `DAT_10000244` into register `r0`. The program needs to read this pointer to pass the string address as an argument to `__wrap_puts`. + +##### Question 4: What happens next? +After the `ldr r0, [DAT_10000244]` instruction loads the string address into `r0`, the next instruction is **`bl 0x100015fc <__wrap_puts>`** which calls the `puts` function with `r0` as its argument (the string pointer). + +##### Question 5: Complete Data Flow Chain + +``` +String "hello, world\r\n" stored at 0x100019CC (Flash) + | + v +Pointer to string stored at DAT_10000244 (0x10000244) + | + v +main() executes: ldr r0, [DAT_10000244] -> r0 = 0x100019CC + | + v +main() executes: bl __wrap_puts -> prints the string + | + v +main() executes: b.n main+6 -> loops back (infinite loop) +``` + +#### Reflection Answers + +1. **Why does the compiler use a pointer (indirect reference) instead of embedding the string address directly in the instruction?** + ARM Thumb instructions have limited immediate value sizes. The `ldr` instruction uses a PC-relative offset to reach a nearby literal pool entry (`DAT_10000244`) that holds the full 32-bit address. This pattern allows addressing any location in the 4 GB address space. + +2. **What is a literal pool?** + A literal pool is a region of constant data placed near the code that uses it. The compiler stores full 32-bit values here that cannot fit as immediates in Thumb instructions. The `ldr` instruction loads from the literal pool using a small PC-relative offset. + +3. **How does cross-referencing help in reverse engineering?** + Cross-references let you trace data flow through a program. Starting from a known string, you can find which functions use it, how data moves between functions, and understand the program's control flow without having source code. diff --git a/WEEKS/WEEK01/WEEK01-03.md b/WEEKS/WEEK01/WEEK01-03.md new file mode 100644 index 0000000..066676c --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-03.md @@ -0,0 +1,204 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 3: Find Cross-References in Ghidra + +#### Objective +Learn how to use Ghidra's cross-reference feature to trace how data flows through code, understanding where specific data is read, written, or referenced. + +#### Prerequisites +- Ghidra installed with `0x0001_hello-world` project open +- Completed Exercise 2 (Find Strings) - you should know where the "hello, world" string is located +- CodeBrowser window open with the binary loaded + +#### Task Description + +In this exercise, you'll: +1. Navigate to a specific data reference in the `main` function +2. Find where a particular data item (`DAT_...`) is used +3. Trace back to see which functions access this data +4. Understand how data flows from memory to the CPU and then to functions + +#### Background: What are Cross-References? + +A **cross-reference** is a link between different parts of the code: +- **Code ? Data**: An instruction reads or writes data +- **Code ? Code**: A function calls another function +- **Data ? Data**: One data item references another + +In this exercise, we're tracking **code ? data** references to understand where and how the program uses the "hello, world" string. + +#### Step-by-Step Instructions + +##### Step 1: Navigate to the main Function + +1. In Ghidra's CodeBrowser, use **Search ? For Address or Label** (or press **Ctrl+G**) +2. Type `main` and press Enter +3. Ghidra will navigate to the `main` function +4. You should see the disassembly in the Listing view (center panel) + +##### Step 2: Locate the `ldr` Instruction + +In the main function's disassembly, look for an `ldr` (load register) instruction. It should look something like: + +``` +ldr r0, [DAT_10000244] +``` + +or similar. This instruction: +- **`ldr`** = load register (read data from memory) +- **`r0`** = put the data into register `r0` +- **`[DAT_10000244]`** = read from the address stored at location `DAT_10000244` + +##### Step 3: Understand the Notation + +In Ghidra's decompiler notation: +- **`DAT_10000244`** = a data item (not code) at address `0x10000244` +- **`[...]`** = the address of; accessing memory at that location +- The actual value is the address of the "hello, world" string in Flash memory + +##### Step 4: Right-Click on the Data Reference + +1. In the Listing view, find the `ldr` instruction that loads the string address +2. **Right-click** on the `DAT_...` part (the data reference) +3. A context menu should appear + +##### Step 5: Select "References" Option + +In the context menu: +1. Look for an option that says **References** +2. Click on it to see a submenu +3. Select **Show References to** (this shows "where is this data used?") + +##### Step 6: Review the References Window + +A new window should appear showing all the locations where `DAT_10000244` (or whatever the address is) is referenced: + +**Expected output might look like:** +``` +DAT_10000244 (1 xref): + main:10000236 (read) +``` + +This means: +- The data at `DAT_10000244` is used in 1 place +- That place is in the `main` function at instruction `10000236` +- It's a **read** operation (the code is reading this data) + +##### Step 7: Answer These Questions + +###### Question 1: Data Address +- What is the address of the data reference you found? (e.g., `DAT_10000244`) +- __________ + +###### Question 2: Referenced By +- How many places reference this data? +- __________ +- Which function(s) use it? +- __________ + +###### Question 3: Reference Type +- Is it a read or write operation? +- __________ +- Why? (What's the program doing with this data?) +- __________ + +###### Question 4: The Chain +- The `ldr` instruction loads an address into `r0` +- What happens next? (Hint: Look at the next instruction after the `ldr`) +- __________ +- Is there a function call? If so, which one? +- __________ + +###### Question 5: Understanding the Flow +- **`DAT_10000244`** contains the address of the "hello, world" string +- The `ldr` loads that address into `r0` +- Then a function (probably `printf` or `puts`) is called with `r0` as the argument +- Can you trace this complete flow? + +#### Deeper Analysis (Optional Challenge) + +##### Challenge 1: Find the Actual String Address +1. Navigate to the `DAT_10000244` location +2. Look at the value stored there +3. Can you decode the hex bytes and find the actual address of "hello, world"? +4. Hint: The RP2350 uses little-endian encoding, so the bytes are "backwards" + +**Example:** +If you see bytes: `CC 19 00 10` +Read backwards: `10 00 19 CC` = `0x100019CC` + +##### Challenge 2: Understand the Indirection +1. In C, if we want to load an address, we do: `char *ptr = &some_string;` +2. Then to use it: `printf(ptr);` +3. In assembly, this becomes: + - Load the pointer: `ldr r0, [DAT_...]` + - Call the function: `bl printf` +4. Can you see this pattern in the assembly? + +##### Challenge 3: Follow Multiple References +1. Try this with different data items in the binary +2. Find a data reference that has **multiple** cross-references +3. What data is used in more than one place? + +#### Questions for Reflection + +1. **Why does the code need to load an address from memory?** + - Why can't it just use the address directly? + - Hint: Position-independent code and memory protection + +2. **What's the relationship between `DAT_10000244` and the "hello, world" string?** + - They're at different addresses - why? + - Which is in Flash and which points to where it's stored? + +3. **If we wanted to change what gets printed, where would we modify the code?** + - Could we just change the string at address `0x100019CC`? + - Or would we need to change `DAT_10000244`? + - Or both? + +4. **How does this relate to memory layout?** + - Code section (Flash memory starting at `0x10000000`) + - Data section (constants/strings) + - Is everything at different addresses for a reason? + +#### Tips and Hints + +- If you right-click and don't see "References", try right-clicking directly on the instruction address instead +- You can also use **Search ? For Cross References** from the menu for a more advanced search +- In the Decompile view (right side), cross-references may be shown in a different format or with different colors +- Multi-level references: You can right-click on a data item and then follow the chain to another data item + +#### Real-World Applications + +Understanding cross-references is crucial for: +- **Vulnerability hunting**: Finding where user input flows through the code +- **Firmware patching**: Changing constants, strings, or data values +- **Malware analysis**: Tracking command-and-control server addresses or encryption keys +- **Reverse engineering**: Understanding program logic by following data dependencies + +#### Summary + +By completing this exercise, you've learned: +1. How to find and interpret cross-references in Ghidra +2. How to trace data from its definition to where it's used +3. How the `ldr` (load) instruction works to pass data to functions +4. The relationship between high-level C code and assembly-level data flow +5. How addresses are indirectly referenced in position-independent code + +#### Expected Final Understanding + +You should now understand this flow: +``` +String "hello, world" is stored at address 0x100019CC in Flash + ? +A pointer to this address is stored at DAT_10000244 in Flash + ? +The main() function loads this pointer: ldr r0, [DAT_10000244] + ? +main() calls printf with r0 (the string address) as the argument + ? +printf() reads the bytes at that address and prints them +``` diff --git a/WEEKS/WEEK01/WEEK01-04-S.md b/WEEKS/WEEK01/WEEK01-04-S.md new file mode 100644 index 0000000..dfc24e2 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-04-S.md @@ -0,0 +1,76 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 4 Solution: Connect GDB & Basic Exploration + +#### Answers + +##### Question 1: GDB Connection +- **Was GDB able to connect to OpenOCD?** Yes, via `target extended-remote localhost:3333` +- **Did program stop at breakpoint?** Yes, at `Breakpoint 1, main () at ../0x0001_hello-world.c:4` + +##### Question 2: Memory Address of main +- **Address of main's first instruction:** `0x10000234` +- **Flash or RAM?** **Flash memory** - the address starts with `0x10000...` (XIP region starting at `0x10000000`) + +##### Question 3: Stack Pointer Value +- **SP value at main:** `0x20082000` +- **Flash or RAM?** **RAM** - the address starts with `0x20000...` (SRAM starts at `0x20000000`) + +##### Question 4: First Instruction +- **First instruction in main:** `push {r3, lr}` +- **What does it do?** Saves register `r3` and the Link Register (`lr`) onto the stack. This preserves the return address so `main()` can call other functions (like `stdio_init_all()` and `__wrap_puts`) and they can properly use `lr` themselves. + +##### Question 5: Comparison to Ghidra +**Yes, they match.** The GDB disassembly output is identical to what Ghidra shows in the Listing View. Both static analysis (Ghidra) and dynamic analysis (GDB) reveal the same instructions. + +##### Register Values at Breakpoint + +| Register | Value | Description | +|----------|-------|-------------| +| **pc** | `0x10000234` | Program Counter - at start of main (Flash) | +| **sp** | `0x20082000` | Stack Pointer - top of stack (RAM) | +| **lr** | `0x1000018f` | Link Register - return address after main | +| **r0** | `0x0` | General Purpose - will hold function arguments | +| **r1** | `0x10000235` | General Purpose | +| **r2** | `0x80808080` | General Purpose | +| **r3** | `0xe000ed08` | General Purpose | + +##### Full Disassembly of main + +``` +0x10000234 <+0>: push {r3, lr} # Save registers to stack +0x10000236 <+2>: bl 0x1000156c # Initialize I/O +0x1000023a <+6>: ldr r0, [pc, #8] # Load string pointer +0x1000023c <+8>: bl 0x100015fc <__wrap_puts> # Print string +0x10000240 <+12>: b.n 0x1000023a # Infinite loop +0x10000242 <+14>: nop +0x10000244 <+16>: (data: pointer to string) +``` + +##### GDB Connection Sequence + +``` +Terminal 1: openocd -s "..." -f interface/cmsis-dap.cfg -f target/rp2350.cfg +Terminal 2: arm-none-eabi-gdb build/0x0001_hello-world.elf +(gdb) target extended-remote localhost:3333 +(gdb) monitor reset halt +(gdb) b main +(gdb) c +(gdb) disassemble main +(gdb) i r +``` + +#### Reflection Answers + +1. **Why does the stack pointer start at `0x20082000`?** + The initial stack pointer value comes from the first entry in the vector table at `0x10000000`. The linker script sets `__StackTop` to `0x20082000`, which is the top of the SCRATCH_Y region in SRAM. The stack grows downward from this address. + +2. **Why does `push {r3, lr}` save `r3` even though it doesn't seem to be used?** + ARM requires 8-byte stack alignment. Pushing `lr` alone would only move SP by 4 bytes. Including `r3` ensures the stack remains 8-byte aligned, which is required by the ARM Architecture Procedure Call Standard (AAPCS). + +3. **How does the infinite loop work?** + The instruction at `0x10000240` is `b.n 0x1000023a` - an unconditional branch back to `main+6`, which reloads the string pointer and calls `__wrap_puts` again. The function never returns. diff --git a/WEEKS/WEEK01/WEEK01-04.md b/WEEKS/WEEK01/WEEK01-04.md new file mode 100644 index 0000000..89e4f83 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01-04.md @@ -0,0 +1,371 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 1 +Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +### Non-Credit Practice Exercise 4: Connect GDB & Basic Exploration + +#### Objective +Set up GDB (GNU Debugger) to dynamically analyze the "hello, world" program running on your Pico 2, verifying that your debugging setup works correctly. + +#### Prerequisites +- Raspberry Pi Pico 2 with "hello-world" binary already flashed +- OpenOCD installed and working +- GDB (arm-none-eabi-gdb) installed +- Your Pico 2 connected to your computer via USB CMSIS-DAP interface +- CMake build artifacts available (`.elf` file from compilation) + +#### Task Description + +In this exercise, you'll: +1. Start OpenOCD to provide a debug server +2. Connect GDB to the Pico 2 via OpenOCD +3. Set a breakpoint at the main function +4. Examine registers and memory while the program is running +5. Verify that your dynamic debugging setup works + +#### Important Setup Notes + +Before you start, make sure: +- Your Pico 2 is **powered on** and connected to your computer +- You have **OpenOCD** installed for ARM debugging +- You have **GDB** (specifically `arm-none-eabi-gdb`) installed +- Your binary file (`0x0001_hello-world.elf`) is available in the `build/` directory + +#### Step-by-Step Instructions + +##### Step 1: Start OpenOCD in Terminal 1 + +Open a **new terminal window** (PowerShell, Command Prompt, or WSL): + +**On Windows (PowerShell/Command Prompt):** +``` +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" +``` + +**Expected Output:** +``` +Open On-Chip Debugger 0.12.0+dev +... +Info : CMSIS-DAP: SWD detected +Info : RP2350 (dual core) detected +Info : Using JTAG interface +... +Info : accepting 'gdb' connection on tcp/3333 +``` + +##### Step 2: Start GDB in Terminal 2 + +Open a **second terminal window** and navigate to your project directory: + +``` +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +**Expected Output:** +``` +Reading symbols from build\0x0001_hello-world.elf... +(gdb) +``` + +##### Step 3: Connect GDB to OpenOCD + +At the GDB prompt `(gdb)`, type: + +```gdb +target extended-remote localhost:3333 +``` + +**Expected Output:** +``` +Remote debugging using localhost:3333 +(gdb) +``` + +(The warning is normal - you already loaded the .elf file, so it doesn't matter) + +##### Step 4: Reset and Halt the Target + +To reset the Pico 2 and prepare for debugging, type: + +```gdb +monitor reset halt +``` + +**Expected Output:** +``` +(gdb) +``` + +(This resets the processor and halts it, preventing execution until you tell it to run) + +##### Step 5: Set a Breakpoint at main + +To stop execution at the beginning of the `main` function: + +```gdb +b main +``` + +**Expected Output:** +``` +Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 4. +(gdb) +``` + +**What this means:** +- Breakpoint 1 is set at address `0x10000234` +- That's in the file `../0x0001_hello-world.c` at line 4 +- The breakpoint is at the `main` function + +##### Step 6: Continue Execution to the Breakpoint + +Now let the program run until it hits your breakpoint: + +```gdb +c +``` + +**Expected Output:** +``` +Continuing. + +Breakpoint 1, main () at ../0x0001_hello-world.c:4 +4 stdio_init_all(); +(gdb) +``` + +**Great!** Your program is now halted at the beginning of `main()`. + +##### Step 7: Examine the Assembly with `disas` + +To see the assembly language of the current function: + +```gdb +disas +``` + +**Expected Output:** +``` +Dump of assembler code for function main: +=> 0x10000234 <+0>: push {r3, lr} + 0x10000236 <+2>: bl 0x1000156c + 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> + 0x10000240 <+12>: b.n 0x1000023a + 0x10000242 <+14>: nop + 0x10000244 <+16>: adds r4, r1, r7 + 0x10000246 <+18>: asrs r0, r0, #32 +End of assembler dump. +(gdb) +``` + +**Interpretation:** +- The `=>` arrow shows where we're currently stopped (at `0x10000234`) +- We can see the `push`, `bl` (branch and link), `ldr`, and `b.n` (branch) instructions +- This is the exact code you analyzed in the Ghidra exercises! + +##### Step 8: View All Registers with `i r` + +To see the current state of all CPU registers: + +```gdb +i r +``` + +**Expected Output:** +``` +r0 0x0 0 +r1 0x10000235 268436021 +r2 0x80808080 -2139062144 +r3 0xe000ed08 -536810232 +r4 0x100001d0 268435920 +r5 0x88526891 -2007865199 +r6 0x4f54710 83183376 +r7 0x400e0014 1074659348 +r8 0x43280035 1126694965 +r9 0x0 0 +r10 0x10000000 268435456 +r11 0x62707361 1651536737 +r12 0xed07f600 -318245376 +sp 0x20082000 0x20082000 +lr 0x1000018f 268435855 +pc 0x10000234 0x10000234
+xpsr 0x69000000 1761607680 +``` + +**Key Registers to Understand:** +| Register | Value | Meaning | +| -------- | ------------ | ------------------------------------------------- | +| `pc` | `0x10000234` | Program Counter - we're at the start of `main` | +| `sp` | `0x20082000` | Stack Pointer - top of our stack in RAM | +| `lr` | `0x1000018f` | Link Register - where we return from `main` | +| `r0-r3` | Various | Will hold function arguments and return values | + +##### Step 9: Step Into the First Instruction + +To execute one assembly instruction: + +```gdb +si +``` + +**Expected Output:** +``` +0x10000236 in main () at ../0x0001_hello-world.c:5 +5 stdio_init_all(); +(gdb) +``` + +The `pc` should now be at `0x10000236`, which is the next instruction. + +##### Step 10: Answer These Questions + +Based on what you've observed: + +###### Question 1: GDB Connection +- Was GDB able to connect to OpenOCD? (Yes/No) +- Did the program stop at your breakpoint? (Yes/No) +- __________ + +###### Question 2: Breakpoint Address +- What is the memory address of the `main` function's first instruction? +- __________ +- Is this in Flash memory (0x100...) or RAM (0x200...)? +- __________ + +###### Question 3: Stack Pointer +- What is the value of the Stack Pointer (sp) when you're at `main`? +- __________ +- Is this in Flash or RAM? +- __________ + +###### Question 4: First Instruction +- What is the first instruction in `main`? +- __________ +- What does it do? (Hint: `push` = save to stack) +- __________ + +###### Question 5: Disassembly Comparison +- Look at the disassembly from GDB (Step 7) +- Compare it to the disassembly from Ghidra (Exercise 1) +- Are they the same? +- __________ + +#### Deeper Exploration (Optional Challenge) + +##### Challenge 1: Step Through stdio_init_all +1. Continue stepping: `si` (step into) or `ni` (next instruction) +2. Eventually, you'll reach `bl 0x1000156c ` +3. Use `si` to step **into** that function +4. What instructions do you see? +5. What registers are being modified? + +##### Challenge 2: View Specific Registers +Instead of viewing all registers, you can view just a few: +```gdb +i r pc sp lr r0 r1 r2 +``` +This shows only the registers you care about. + +##### Challenge 3: Examine Memory +To examine memory at a specific address (e.g., where the string is): +```gdb +x/16b 0x100019cc +``` +This displays 16 bytes (`b` = byte) starting at address `0x100019cc`. Can you see the "hello, world" string? + +##### Challenge 4: Set a Conditional Breakpoint +Set a breakpoint that only triggers after a certain condition: +```gdb +b *0x1000023a if $r0 != 0 +``` +This is useful when you want to break on a condition rather than every time. + +#### Questions for Reflection + +1. **Why does GDB show both the C source line AND the assembly?** + - This is because the .elf file contains debug symbols + - What would happen if we used a stripped binary? + +2. **How does GDB know the assembly for each instruction?** + - It disassembles the binary on-the-fly based on the architecture + +3. **Why is the Stack Pointer so high (0x20082000)?** + - It's at the top of RAM and grows downward + - Can you calculate how much RAM this Pico 2 has? + +4. **What's the difference between `si` (step into) and `ni` (next instruction)?** + - `si` steps into function calls + - `ni` executes entire functions without stopping inside them + +#### Important GDB Commands Reference + +| Command | Short Form | What It Does | +| ---------------------- | ---------- | ------------------------------------ | +| `target extended-remote localhost:3333` | | Connect to OpenOCD | +| `monitor reset halt` | | Reset and halt the processor | +| `break main` | `b main` | Set a breakpoint at main function | +| `continue` | `c` | Continue until breakpoint | +| `step instruction` | `si` | Step one instruction (into calls) | +| `next instruction` | `ni` | Step one instruction (over calls) | +| `disassemble` | `disas` | Show assembly for current function | +| `info registers` | `i r` | Show all register values | +| `x/Nxy ADDRESS` | `x` | Examine memory (N=count, x=format, y=size) | +| `quit` | `q` | Exit GDB | + +**Examples for `x` command:** +- `x/10i $pc` - examine 10 instructions at program counter +- `x/16b 0x20000000` - examine 16 bytes starting at RAM address +- `x/4w 0x10000000` - examine 4 words (4-byte values) starting at Flash address + +#### Troubleshooting + +##### Problem: "OpenOCD not found" +**Solution:** Make sure OpenOCD is in your PATH or use the full path to the executable + +##### Problem: "Target not responding" +**Solution:** +- Check that your Pico 2 is properly connected +- Make sure OpenOCD is running and shows "accepting 'gdb' connection" +- Restart both OpenOCD and GDB + +##### Problem: "Cannot find breakpoint at main" +**Solution:** +- Make sure you compiled with debug symbols +- The .elf file must include symbol information +- Try breaking at an address instead: `b *0x10000234` + +##### Problem: GDB shows "No source available" +**Solution:** +- This happens with stripped binaries +- You can still see assembly with `disas` +- You can still examine memory and registers + +#### Summary + +By completing this exercise, you've: +1. ? Set up OpenOCD as a debug server +2. ? Connected GDB to a Pico 2 board +3. ? Set a breakpoint and halted execution +4. ? Examined assembly language in a live debugger +5. ? Viewed CPU registers and their values +6. ? Verified your dynamic debugging setup works + +You're now ready for Week 2, where you'll: +- Step through code line by line +- Watch variables and memory change +- Understand program flow in detail +- Use this knowledge to modify running code + +#### Next Steps + +1. **Close GDB**: Type `quit` or `q` to exit +2. **Close OpenOCD**: Type `Ctrl+C` in the OpenOCD terminal +3. **Review**: Go back to the Ghidra exercises and compare static vs. dynamic analysis +4. **Prepare**: Read through Week 2 materials to understand what's coming next diff --git a/WEEKS/WEEK01/WEEK01-SLIDES.pdf b/WEEKS/WEEK01/WEEK01-SLIDES.pdf new file mode 100644 index 0000000..68ae130 Binary files /dev/null and b/WEEKS/WEEK01/WEEK01-SLIDES.pdf differ diff --git a/WEEKS/WEEK01/WEEK01.md b/WEEKS/WEEK01/WEEK01.md new file mode 100644 index 0000000..0baede5 --- /dev/null +++ b/WEEKS/WEEK01/WEEK01.md @@ -0,0 +1,633 @@ +ο»Ώ# Week 1: Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts + +## 🎯 What You'll Learn This Week + +By the end of this week, you will be able to: +- Understand what a microcontroller is and how it works +- Know the basic registers of the ARM Cortex-M33 processor +- Understand memory layout (Flash vs RAM) and why it matters +- Understand how the stack works in embedded systems +- Set up and connect GDB to your Pico 2 for debugging +- Use Ghidra for static analysis of your binary +- Read basic ARM assembly instructions and understand what they do + +--- + +## πŸ“š Part 1: Understanding the Basics + +### What is a Microcontroller? + +Think of a microcontroller as a tiny computer on a single chip. Just like your laptop has a processor, memory, and storage, a microcontroller has all of these packed into one small chip. The **RP2350** is the microcontroller chip that powers the **Raspberry Pi Pico 2**. + +### What is the ARM Cortex-M33? + +The RP2350 has two "brains" inside it - we call these **cores**. It actually contains **four** processor cores: two ARM Cortex-M33 and two RISC-V Hazard3. However, only **two cores of the same type** are active at any given time β€” either both Cortex-M33 or both Hazard3. In this course, we'll focus on the **ARM Cortex-M33** cores because they are more commonly used in the industry. + +> πŸ“– **Datasheet Reference:** Section 3, "Processor subsystem" (p. 36): *"RP2350 is a symmetric dual-core system. Two cores operate simultaneously and independently."* Each core slot has both a Cortex-M33 and a Hazard3 instance, with architecture switching described in Section 3.9 (p. 335). + +### What is Reverse Engineering? + +Reverse engineering is like being a detective for code. Instead of writing code and compiling it, we take compiled code (the 1s and 0s that the computer actually runs) and figure out what it does. This is useful for: +- Understanding how things work +- Finding bugs or security issues +- Learning how software interacts with hardware + +--- + +## πŸ“š Part 2: Understanding Processor Registers + +> πŸ“– **Datasheet Reference:** The Cortex-M33 processor is documented in Section 3.7 (p. 100+). Register descriptions follow the Armv8-M architecture reference manual. The RP2350 instances are configured with Security, DSP, and FPU extensions (Datasheet p. 36). + +### What is a Register? + +A **register** is like a tiny, super-fast storage box inside the processor. The processor uses registers to hold numbers while it's doing calculations. Think of them like the short-term memory your brain uses when doing math in your head. + +### The ARM Cortex-M33 Registers + +The ARM Cortex-M33 has several important registers: + +| Register | Also Called | Purpose | +| ------------ | -------------------- | ------------------------------------------- | +| `r0` - `r12` | General Purpose | Store numbers, pass data between functions | +| `r13` | SP (Stack Pointer) | Keeps track of where we are in the stack | +| `r14` | LR (Link Register) | Remembers where to go back after a function | +| `r15` | PC (Program Counter) | Points to the next instruction to run | + +##### General Purpose Registers (`r0` - `r12`) + +These 13 registers are your "scratch paper." When the processor needs to add two numbers, subtract, or do any calculation, it uses these registers to hold the values. + +**Example:** If you want to add 5 + 3: +1. Put 5 in `r0` +2. Put 3 in `r1` +3. Add them and store the result (8) in `r2` + +##### The Stack Pointer (`r13` / SP) + +The **stack** is a special area of memory that works like a stack of plates: +- When you add something, you put it on top (called a **PUSH**) +- When you remove something, you take it from the top (called a **POP**) + +The Stack Pointer always points to the top of this stack. On ARM systems, the stack **grows downward** in memory. This means when you push something onto the stack, the address number gets smaller! + +``` +Higher Memory Address (0x20082000) +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ β”‚ ← Stack starts here (empty) +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Pushed Item 1 β”‚ ← SP points here after 1 push +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Pushed Item 2 β”‚ ← SP points here after 2 pushes +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +Lower Memory Address (0x20081FF8) +``` + +##### The Link Register (`r14` / LR) + +When you call a function, the processor needs to remember where to come back to. The Link Register stores this "return address." + +**Example:** +``` +main() calls print_hello() + ↓ +LR = address right after the call in main() + ↓ +print_hello() runs + ↓ +print_hello() finishes, looks at LR + ↓ +Jumps back to main() at the address stored in LR +``` + +##### The Program Counter (`r15` / PC) + +The Program Counter always points to the **next instruction** the processor will execute. It's like your finger following along as you read a book - it always points to where you are. + +--- + +## πŸ“š Part 3: Understanding Memory Layout + +### XIP - Execute In Place + +The RP2350 uses something called **XIP (Execute In Place)**. This means the processor can run code directly from the flash memory (where your program is stored) without copying it to RAM first. + +**Key Memory Address:** `0x10000000` + +This is where your program code starts in flash memory. Remember this address - we'll use it a lot! + +> πŸ“– **Datasheet Reference:** The XIP address space starts at `0x10000000` per Section 2.2 "Address Map" (p. 31). XIP caching and flash access are described in Section 4.4 (p. 340+). + +### Memory Map Overview + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Flash Memory (XIP) β”‚ +β”‚ Starts at: 0x10000000 β”‚ +β”‚ Contains: Your program code β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ RAM (SRAM0–7, striped) β”‚ +β”‚ Starts at: 0x20000000 β”‚ +β”‚ Size: 512 KB β”‚ +β”‚ Contains: Stack, Heap, Variables β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ SRAM8/9 (non-striped) β”‚ +β”‚ Starts at: 0x20080000 β”‚ +β”‚ Size: 2 Γ— 4 KB β”‚ +β”‚ Contains: Per-core scratch/stack β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +> πŸ“– **Datasheet Reference:** The full address map is in Section 2.2 (p. 31). SRAM layout: 8 Γ— 64 kB striped banks (SRAM0–7) + 2 Γ— 4 kB non-striped banks (SRAM8–9) = 520 kB total. See Section 2.2.3 (p. 32) and Section 4.2 (p. 338–339). + +### Stack vs Heap + +| Stack | Heap | +| ---------------------------------------- | ---------------------------------- | +| Automatic memory management | Manual memory management | +| Fast | Slower | +| Limited size | More flexible size | +| Used for function calls, local variables | Used for dynamic memory allocation | +| Grows downward | Grows upward | + +--- + +## πŸ“š Part 3.5: Reviewing Our Hello World Code + +Before we start debugging, let's understand the code we'll be working with. Here's our `0x0001_hello-world.c` program: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +### Breaking Down the Code + +##### The Includes + +```c +#include +#include "pico/stdlib.h" +``` + +- **``** - This is the standard input/output library. It gives us access to the `printf()` function that lets us print text. +- **`"pico/stdlib.h"`** - This is the Pico SDK's standard library. It provides essential functions for working with the Raspberry Pi Pico hardware. + +##### The Main Function + +```c +int main(void) { +``` + +Every C program starts running from the `main()` function. The `void` means it takes no arguments, and `int` means it returns an integer (though our program never actually returns). + +##### Initializing Standard I/O + +```c +stdio_init_all(); +``` + +This function initializes all the standard I/O (input/output) for the Pico. It sets up: +- **USB CDC** (so you can see output when connected to a computer via USB) +- **UART** (serial communication pins) + +Without this line, `printf()` wouldn't have anywhere to send its output! + +##### The Infinite Loop + +```c +while (true) + printf("hello, world\r\n"); +``` + +- **`while (true)`** - This creates an infinite loop. The program will keep running forever (or until you reset/power off the Pico). +- **`printf("hello, world\r\n")`** - This prints the text "hello, world" followed by a carriage return (`\r`) and newline (`\n`). + +> πŸ’‘ **Why `\r\n` instead of just `\n`?** +> +> In embedded systems, we often use both carriage return (`\r`) and newline (`\n`) together. The `\r` moves the cursor back to the beginning of the line, and `\n` moves to the next line. This ensures proper display across different terminal programs. + +### What Happens When This Runs? + +1. **Power on** - The Pico boots up and starts executing code from flash memory +2. **`stdio_init_all()`** - Sets up USB and/or UART for communication +3. **Infinite loop begins** - The program enters the `while(true)` loop +4. **Print forever** - "hello, world" is sent over and over as fast as possible + +### Why This Code is Perfect for Learning + +This simple program is ideal for reverse engineering practice because: +- It has a clear, recognizable function call (`printf`) +- It has an infinite loop we can observe +- It's small enough to understand completely +- It demonstrates real hardware interaction (USB/UART output) + +When we debug this code, we'll be able to see how the C code translates to ARM assembly instructions! + +### Compiling and Flashing to the Pico 2 + +Now that we understand the code, let's get it running on our hardware: + +##### Step 1: Compile the Code + +In VS Code, look for the **Compile** button in the status bar at the bottom of the window. This is provided by the Raspberry Pi Pico extension. Click it to compile your project. + +The extension will run CMake and build your code, creating a `.uf2` file that can be loaded onto the Pico 2. + +##### Step 2: Put the Pico 2 in Flash Loading Mode + +To flash new code to your Pico 2, you need to put it into **BOOTSEL mode**: + +1. **Press and hold** the right-most button on your breadboard (the BOOTSEL button) +2. **While holding BOOTSEL**, press the white **Reset** button +3. **Release the Reset button** first +4. **Then release the BOOTSEL button** + +When done correctly, your Pico 2 will appear as a USB mass storage device (like a flash drive) on your computer. This means it's ready to receive new firmware! + +> πŸ’‘ **Tip:** You'll see a drive called "RP2350" appear in your file explorer when the Pico 2 is in flash loading mode. + +##### Step 3: Flash and Run + +Back in VS Code, click the **Run** button in the status bar. The extension will: +1. Copy the compiled `.uf2` file to the Pico 2 +2. The Pico 2 will automatically reboot and start running your code + +Once flashed, your Pico 2 will immediately start executing the hello-world program, printing "hello, world" continuously when we open PuTTY! + +--- + +## πŸ“š Part 4: Dynamic Analysis with GDB + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. GDB (GNU Debugger) installed +3. OpenOCD or another debug probe connection +4. The sample "hello-world" binary loaded on your Pico 2 + +### Connecting to Your Pico 2 with OpenOCD + +Open a terminal and 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" +``` + +### Connecting to Your Pico 2 with GDB + +Open another terminal and start GDB with your binary: + +```powershell +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +Connect to your target: + +```powershell +(gdb) target extended-remote localhost:3333 +(gdb) monitor reset halt +``` + +### Basic GDB Commands: Your First Steps + +Now that we're connected, let's learn three essential GDB commands that you'll use constantly in embedded reverse engineering. + +##### Setting a Breakpoint with `b main` + +A **breakpoint** tells the debugger to pause execution when it reaches a specific point. Let's set one at our `main` function: + +```gdb +(gdb) b main +Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 5. +``` + +**What this tells us:** +- GDB found our `main` function +- It's located at address `0x10000234` in flash memory +- The source file and line number are shown (because we have debug symbols) + +Now let's run to that breakpoint: + +```gdb +(gdb) c +Continuing. + +Breakpoint 1, main () at ../0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +The program has stopped right at the beginning of `main`! + +##### Disassembling with `disas` + +The `disas` (disassemble) command shows us the assembly instructions for the current function: + +```gdb +(gdb) disas +Dump of assembler code for function main: +=> 0x10000234 <+0>: push {r3, lr} + 0x10000236 <+2>: bl 0x1000156c + 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> + 0x10000240 <+12>: b.n 0x1000023a + 0x10000242 <+14>: nop + 0x10000244 <+16>: adds r4, r1, r7 + 0x10000246 <+18>: asrs r0, r0, #32 +End of assembler dump. +``` + +**Understanding the output:** +- The `=>` arrow shows where we're currently stopped +- Each line shows: `address : instruction operands` +- We can see the calls to `stdio_init_all` and `__wrap_puts` (printf was optimized to puts) +- The `b.n 0x1000023a` at the end is our infinite loop - it jumps back to reload the string! + +##### Viewing Registers with `i r` + +The `i r` (info registers) command shows the current state of all CPU registers: + +```gdb +(gdb) i r +r0 0x0 0 +r1 0x10000235 268436021 +r2 0x80808080 -2139062144 +r3 0xe000ed08 -536810232 +r4 0x100001d0 268435920 +r5 0x88526891 -2007865199 +r6 0x4f54710 83183376 +r7 0x400e0014 1074659348 +r8 0x43280035 1126694965 +r9 0x0 0 +r10 0x10000000 268435456 +r11 0x62707361 1651536737 +r12 0xed07f600 -318245376 +sp 0x20082000 0x20082000 +lr 0x1000018f 268435855 +pc 0x10000234 0x10000234
+xpsr 0x69000000 1761607680 +``` + +**Key registers to watch:** +| Register | Value | Meaning | +| -------- | ------------ | ----------------------------------------------- | +| `pc` | `0x10000234` | Program Counter - we're at the start of `main` | +| `sp` | `0x20081fc8` | Stack Pointer - top of our stack in RAM | +| `lr` | `0x100002d5` | Link Register - where we return after `main` | +| `r0-r3` | Various | Will hold function arguments and return values | + +> πŸ’‘ **Tip:** You can also use `i r pc sp lr` to show only specific registers you care about. + +### Quick Reference: Essential GDB Commands + +| Command | Short Form | What It Does | +| --------------------- | ---------- | ------------------------------------ | +| `break main` | `b main` | Set a breakpoint at main | +| `continue` | `c` | Continue execution until breakpoint | +| `disassemble` | `disas` | Show assembly for current function | +| `info registers` | `i r` | Show all register values | +| `stepi` | `si` | Execute one assembly instruction | +| `nexti` | `ni` | Execute one instruction (skip calls) | +| `x/10i $pc` | | Examine 10 instructions at PC | +| `monitor reset halt` | | Reset the target and halt | + +--- + +> πŸ’‘ **What's Next?** In Week 2, we'll put these GDB commands to work with hands-on debugging exercises! We'll step through code, examine the stack, watch registers change, and ultimately use these skills to modify a running program. The commands you learned here are the foundation for everything that follows. + +--- + +## πŸ”¬ Part 5: Static Analysis with Ghidra + +### Setting Up Your First Ghidra Project + +Before we dive into GDB debugging, let's set up Ghidra to analyze our hello-world binary. Ghidra is a powerful reverse engineering tool that will help us visualize the disassembly and decompiled code. + +##### Step 1: Create a New Project + +1. Launch Ghidra +2. A window will appear - select **File β†’ New Project** +3. Choose **Non-Shared Project** and click **Next** +4. Enter the Project Name: `0x0001_hello-world` +5. Click **Finish** + +##### Step 2: Import the Binary + +1. Open your file explorer and navigate to the `Embedded-Hacking` folder +2. **Drag and drop** the `0x0001_hello-world.elf` file into the folder panel within the Ghidra application + +##### Step 3: Understand the Import Dialog + +In the small window that appears, you will see the file identified as an **ELF** (Executable and Linkable Format). + +> πŸ’‘ **What is an ELF file?** +> +> ELF stands for **Executable and Linkable Format**. This format includes **symbols** - human-readable names for functions and variables. These symbols make reverse engineering much easier because you can see function names like `main` and `printf` instead of just memory addresses. +> +> In future weeks, we will work with **stripped binaries** (`.bin` files) that do not contain these symbols. This is more realistic for real-world reverse engineering scenarios where symbols have been removed to make analysis harder. + +3. Click **Ok** to import the file +4. **Double-click** on the file within the project window to open it in the CodeBrowser + +##### Step 4: Auto-Analyze the Binary + +When prompted, click **Yes** to auto-analyze the binary. Accept the default analysis options and click **Analyze**. + +Ghidra will now process the binary, identifying functions, strings, and cross-references. This may take a moment. + +### Reviewing the Main Function in Ghidra + +Once analysis is complete, let's find our `main` function: + +1. In the **Symbol Tree** panel on the left, expand **Functions** +2. Look for `main` in the list (you can also use **Search β†’ For Address or Label** and type "main") +3. Click on `main` to navigate to it + +##### What You'll See + +Ghidra shows you two views of the code: + +**Listing View (Center Panel)** - The disassembled ARM assembly: +``` + ************************************************************* + * FUNCTION + ************************************************************* + int main (void ) + assume LRset = 0x0 + assume TMode = 0x1 + int r0:4 + main XREF[3]: Entry Point (*) , + _reset_handler:1000018c (c) , + .debug_frame::00000018 (*) + 0x0001_hello-world.c:4 (2) + 0x0001_hello-world.c:5 (2) + 10000234 08 b5 push {r3,lr} + 0x0001_hello-world.c:5 (4) + 10000236 01 f0 99 f9 bl stdio_init_all _Bool stdio_init_all(void) + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + 1000023c 01 f0 de f9 bl __wrap_puts int __wrap_puts(char * s) + 0x0001_hello-world.c:7 (8) + 10000240 fb e7 b LAB_1000023a + 10000242 00 ?? 00h + 10000243 bf ?? BFh + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc + +``` + +**Decompile View (Right Panel)** - The reconstructed C code: +```c +int main(void) { + stdio_init_all(); + do { + __wrap_puts("hello, world"); + } while (true); +} +``` + +> 🎯 **Notice how Ghidra reconstructed our original C code!** The decompiler recognized the infinite loop and the `puts` call (the compiler optimized `printf` to `puts` since we're just printing a simple string). + +##### Why We Start with .elf Files + +We're using the `.elf` file because it contains symbols that help us learn: +- Function names are visible (`main`, `stdio_init_all`, `puts`) +- Variable names may be preserved +- The structure of the code is easier to understand + +In future weeks, we'll work with `.bin` files that have been stripped of symbols. This will teach you how to identify functions and understand code when you don't have these helpful hints! + +--- + +## πŸ“Š Part 6: Summary and Review + +### What We Learned + +1. **Registers**: The ARM Cortex-M33 has 13 general-purpose registers (`r0`-`r12`), plus special registers for the stack pointer (`r13`/SP), link register (`r14`/LR), and program counter (`r15`/PC). + +2. **The Stack**: + - Grows downward in memory + - PUSH adds items (SP decreases) + - POP removes items (SP increases) + - Used to save return addresses and register values + +3. **Memory Layout**: + - Code lives in flash memory starting at `0x10000000` (Datasheet Β§2.2, p. 31) + - Stack lives in RAM at the top of SRAM9 (`0x20082000`), growing downward (Datasheet Β§2.2.3, p. 32) + +4. **GDB Basics**: We learned the essential commands for connecting to hardware and examining code: + +| Command | What It Does | +| --------------------- | -------------------------------------- | +| `target remote :3333` | Connect to OpenOCD debug server | +| `monitor reset halt` | Reset and halt the processor | +| `b main` | Set breakpoint at main function | +| `c` | Continue running until breakpoint | +| `disas` | Disassemble current function | +| `i r` | Show all register values | + +5. **Ghidra Static Analysis**: We set up a Ghidra project and analyzed our binary: + - Imported the ELF file with symbols + - Found the `main` function + - Saw the decompiled C code + - Understood how assembly maps to C + +6. **Little-Endian**: The RP2350 stores multi-byte values with the least significant byte at the lowest address, making them appear "backwards" when viewed as a single value. (Datasheet Β§2.1, p. 25: bus fabric is little-endian) + +### The Program Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. push {r3, lr} β”‚ +β”‚ Save registers to stack β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 2. bl stdio_init_all β”‚ +β”‚ Initialize standard I/O β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 3. ldr r0, [pc, #8] ────────────────┐ β”‚ +β”‚ Load address of "hello, world" into r0β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 4. bl __wrap_puts β”‚ β”‚ +β”‚ Print the string β”‚ β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 5. b.n (back to step 3) β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ Infinite loop! β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## βœ… Practice Exercises + +Try these on your own to reinforce what you learned: + +### Exercise 1: Explore in Ghidra +1. Open your `0x0001_hello-world` project in Ghidra +2. Find the `stdio_init_all` function in the Symbol Tree +3. Look at its decompiled code - can you understand what it's setting up? + +### Exercise 2: Find Strings in Ghidra +1. In Ghidra, go to **Window β†’ Defined Strings** +2. Look for `"hello, world"` - what address is it at? +3. Double-click to navigate to it in the listing + +### Exercise 3: Cross-References +1. In Ghidra, navigate to the `main` function +2. Find the `ldr r0, [DAT_...]` instruction that loads the string +3. Right-click on `DAT_10000244` and select **References β†’ Show References to** +4. This shows you where this data is used! + +### Exercise 4: Connect GDB (Preparation for Week 2) +1. Start OpenOCD and connect GDB as shown in Part 4 +2. Set a breakpoint at main: `b main` +3. Continue: `c` +4. Use `disas` to see the assembly +5. Use `i r` to see register values + +> πŸ’‘ **Note:** The detailed hands-on GDB debugging (stepping through code, watching the stack, examining memory) will be covered in Week 2! + +--- + +## πŸŽ“ Key Takeaways + +1. **Reverse engineering combines static and dynamic analysis** - we look at the code (static with Ghidra) and run it to see what happens (dynamic with GDB). + +2. **The stack is fundamental** - understanding how push/pop work is essential for following function calls. + +3. **GDB and Ghidra work together** - Ghidra helps you understand the big picture, GDB lets you watch it happen live. + +4. **Assembly isn't scary** - each instruction does one simple thing. Put them together and you understand the whole program! + +5. **Everything is just numbers** - whether it's code, data, or addresses, it's all stored as numbers in memory. + +--- + +## πŸ“– Glossary + +| Term | Definition | +| ------------------- | --------------------------------------------------------- | +| **Assembly** | Human-readable representation of machine code | +| **Breakpoint** | A marker that tells the debugger to pause execution | +| **GDB** | GNU Debugger - a tool for examining running programs | +| **Hex/Hexadecimal** | Base-16 number system (0-9, A-F) | +| **Little-Endian** | Storing the least significant byte at the lowest address | +| **Microcontroller** | A small computer on a single chip | +| **Program Counter** | Register that points to the next instruction | +| **Register** | Fast storage inside the processor | +| **Stack** | Memory region for temporary storage during function calls | +| **Stack Pointer** | Register that points to the top of the stack | +| **XIP** | Execute In Place - running code directly from flash | diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG00.svg b/WEEKS/WEEK01/slides/WEEK01-IMG00.svg new file mode 100644 index 0000000..e666d03 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 01 + + +Introduction and Overview of +Embedded Reverse Engineering: +Ethics, Scoping, and Basic Concepts + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG01.svg b/WEEKS/WEEK01/slides/WEEK01-IMG01.svg new file mode 100644 index 0000000..58888b0 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG01.svg @@ -0,0 +1,112 @@ +ο»Ώ + + + + +ARM Cortex-M33 Regs +ARM Architecture & Registers + + + + Key Registers + + + + + r0 + Arg 1 / Return + + + + r1 + Arg 2 + + + + r2 + Arg 3 + + + + r3 + Arg 4 + + + + r0-r3 Caller-saved + r4-r11 Callee-saved + + + + SP (r13) + Stack Ptr + + + + LR (r14) + Return Addr + + + + PC (r15) + Next Instr + + + + xPSR + Status Flags + + + + Function Call Flow + + + + + main() + + + + + BL + + + + func() + + + + + BX LR + + + + + + How It Works + + r0 = first argument + puts(r0) passes the + string address in r0 + + + + LR saves return addr + BL: PC+4 stored in LR + BX LR: jump back + + + + All registers: 32 bits wide + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG02.svg b/WEEKS/WEEK01/slides/WEEK01-IMG02.svg new file mode 100644 index 0000000..0e5274a --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG02.svg @@ -0,0 +1,101 @@ +ο»Ώ + + + + +Stack Growth Direction +ARM Stack Mechanics + + + + Before PUSH + + + 0x20082000 + SP here + + + (empty) + + + (empty) + + + (empty) + + + (empty) + + 0x20080000 + + + Grows DOWN + + + + + + After PUSH + + + 0x20082000 + + + (empty) + + + (empty) + + + LR value + + + r3 value + + SP here now + + SP moved down + by 8 bytes + + 0x20080000 + + + + Key Concepts + + + Full Descending + SP points to the + last pushed item + + + + PUSH: SP -= 4 + Each 32-bit val + drops SP by 4 + Two vals = -8 + + + + POP: SP += 4 + Restores values + SP moves back up + + + + Initial SP + Set by vector + table at 0x00 + StackTop=0x20082000 + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG03.svg b/WEEKS/WEEK01/slides/WEEK01-IMG03.svg new file mode 100644 index 0000000..b684520 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG03.svg @@ -0,0 +1,98 @@ +ο»Ώ + + + + +RP2350 Memory Map +RP2350 Address Space + + + + Address Space + + + + + ROM Boot + 0x0000_0000 + + + + XIP Flash + 16MB max + 0x1000_0000 + + + + SRAM + 520KB total + 0x2000_0000 + + + + APB Periph + 0x4000_0000 + + + + AHB Periph + 0x5000_0000 + + + + SIO + 0xD000_0000 + + + + PPB Cortex + 0xE000_0000 + + + addr+ + + + + Key Details + + + + XIP Flash + Code runs directly + from flash via cache + Execute-In-Place + + + + + SRAM Banks + SRAM0-7: 8x64KB + SRAM8-9: 2x4KB + Stack + Heap here + + + + + Peripherals + GPIO, UART, SPI + I2C, PWM, ADC + Memory-mapped I/O + + + + + SIO + PPB + Single-cycle I/O + Debug + NVIC + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG04.svg b/WEEKS/WEEK01/slides/WEEK01-IMG04.svg new file mode 100644 index 0000000..edd55b2 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG04.svg @@ -0,0 +1,89 @@ +ο»Ώ + + + + +Stack vs Heap in RAM +Memory Allocation + + + + SRAM Layout + + + + 0x2008_2000 (top) + + + + STACK + Grows DOWN + SP decrements + + + + + + + + FREE SPACE + + + + + + + + HEAP + Grows UP + malloc expands + + + + .data + .bss + + + 0x2000_0000 (base) + + + + Stack vs Heap + + + + Stack + Local variables + Function args (r0-r3) + Return addresses (LR) + LIFO: last in first out + Auto cleanup on return + + + + + Heap + Dynamic allocation + malloc / free in C + Persists until freed + Risk: memory leaks + + + + + Collision Risk + If stack grows into + heap = crash / corrupt + No MMU on Cortex-M33 + 520KB shared space + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG05.svg b/WEEKS/WEEK01/slides/WEEK01-IMG05.svg new file mode 100644 index 0000000..7941c08 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG05.svg @@ -0,0 +1,86 @@ +ο»Ώ + + + + +Link Register & Return +ARM Function Calls + + + + BL Call Flow + + + + Step 1: caller + + main: BL add + + + + + + + Step 2: hardware saves LR + + LR = return addr + + + + + + + Step 3: run function + + add: ADD r0, r1 + + + + + + + Step 4: return to caller + + BX LR (jump back) + + + + Key Concepts + + + BL instruction + Branch with Link + Saves return addr + in LR (r14) + + + + BX LR + Branch to addr + stored in LR + Returns to caller + + + + Nested Calls + Must PUSH LR first + PUSH {r3, lr} + POP {r3, pc} + POP into PC = return + + + + r14 = LR + Always check LR in GDB + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG06.svg b/WEEKS/WEEK01/slides/WEEK01-IMG06.svg new file mode 100644 index 0000000..e9e2ecf --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG06.svg @@ -0,0 +1,101 @@ +ο»Ώ + + + + +Program Counter Flow +Instruction Execution + + + + PC Execution + + + + ADDR + INSTRUCTION + + + + 0x1000 + MOV r0, #5 + PC + + + + 0x1002 + MOV r1, #3 + + + + 0x1004 + ADD r0, r1 + + + + 0x1006 + BL func + + + + 0x2000 + func: PUSH {lr} + + + + 0x2002 + SUB r0, #1 + + + + 0x2004 + POP {pc} + + + + + PC jumps on BL + then returns via + POP {pc} + + + + Key Concepts + + + r15 = PC + Points to current + instruction + 4 + Prefetch pipeline + + + + Sequential + PC += 2 (Thumb) + PC += 4 (ARM) + Cortex-M33 = Thumb + + + + Branch + B = unconditional + BL = save LR, jump + BX = branch reg + BEQ = branch if Z=1 + + + + GDB tip + stepi = step 1 instr + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG07.svg b/WEEKS/WEEK01/slides/WEEK01-IMG07.svg new file mode 100644 index 0000000..0acc44a --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG07.svg @@ -0,0 +1,110 @@ +ο»Ώ + + + + +Little-Endian Bytes +Byte Ordering + + + + Byte Ordering + + + + 0xDEADBEEF + + + Big-Endian + MSB first + + + DE + + + AD + + + BE + + + EF + + + +0 + +1 + +2 + +3 + + + vs + + + Little-Endian + LSB first + + + EF + + + BE + + + AD + + + DE + + + +0 + +1 + +2 + +3 + + + Bytes are reversed! + Lowest address holds + least significant byte + ARM uses little-endian + + + + Key Concepts + + + ARM = Little-Endian + Cortex-M33 uses LE + by default + Also: x86, RISC-V + + + + Why It Matters + Memory dumps show + raw byte order + Must mentally flip + to get true value + + + + GDB Example + x/4xb 0x2000 + EF BE AD DE + = 0xDEADBEEF + + + + x/xw = word view + GDB auto-corrects + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG08.svg b/WEEKS/WEEK01/slides/WEEK01-IMG08.svg new file mode 100644 index 0000000..e73ae09 --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG08.svg @@ -0,0 +1,89 @@ +ο»Ώ + + + + +XIP Flash Model +Execute-in-Place + + + + Execute-In-Place + + + + + QSPI Flash + External 16MB + + + + + + + + QSPI Controller + + + + + + + + XIP Cache + 16KB cache + + + + + + + + Cortex-M33 CPU + Fetches via PC + + + Mapped at + 0x1000_0000 + + + + Key Details + + + What is XIP? + Code stays in flash + CPU reads it as if + it were normal memory + No copy to SRAM needed + + + + QSPI Interface + 4 data lines + Fast serial flash + CLK, CS, IO0-IO3 + + + + Cache Behavior + Cache hit = fast + Cache miss = slow + Flash read latency + + + + RE Insight + Dump flash via SWD + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG09.svg b/WEEKS/WEEK01/slides/WEEK01-IMG09.svg new file mode 100644 index 0000000..9fae60a --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG09.svg @@ -0,0 +1,88 @@ +ο»Ώ + + + + +ELF File Structure +Binary Format + + + + ELF Layout + + + + + ELF Header + Magic: 7f 45 4c 46 + + + + Program Headers + + + + .text + Machine code + + + + .rodata + + + + .data + + + + .bss + + + + .symtab + + + + Section Headers + + + + Section Details + + + ELF Header + Arch: ARM 32-bit + Entry point addr + Type: executable + + + + .text = code + All instructions + Maps to XIP flash + Disassemble this! + + + + .data / .rodata + .data = initialized + .rodata = constants + .bss = zeroed vars + + + + .symtab = symbols + Function names + Stripped = no names + \ No newline at end of file diff --git a/WEEKS/WEEK01/slides/WEEK01-IMG10.svg b/WEEKS/WEEK01/slides/WEEK01-IMG10.svg new file mode 100644 index 0000000..ecffd4b --- /dev/null +++ b/WEEKS/WEEK01/slides/WEEK01-IMG10.svg @@ -0,0 +1,94 @@ +ο»Ώ + + + + +GDB-OpenOCD Chain +Debug Toolchain + + + + Debug Toolchain + + + + + GDB + Client + + + + + TCP + :3333 + + + + OpenOCD + Server + + + + + USB + + + + CMSIS-DAP + Probe + + + + + SWD + + + + RP2350 + Target + + + + GDB Commands + + + target remote :3333 + Connect to OpenOCD + + monitor reset halt + Reset + stop at entry + + break main + Set breakpoint + + info registers + Dump all regs + + + + SWD Protocol + + + 2 wires only + SWCLK = clock + SWDIO = data + + + + Capabilities + Read/write memory + Read/write regs + Set breakpoints + Full chip control + \ No newline at end of file diff --git a/WEEKS/WEEK02/WEEK02-01-S.md b/WEEKS/WEEK02/WEEK02-01-S.md new file mode 100644 index 0000000..24e86e6 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-01-S.md @@ -0,0 +1,41 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 1 Solution: Change the Message + +#### Answers + +##### Attack Summary +The goal is to write a custom message into SRAM at `0x20000000` and redirect `r0` to print it instead of the original `"hello, world"` string, without changing the source code. + +##### GDB Commands + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +(gdb) b *0x1000023c # Breakpoint before __wrap_puts +(gdb) c # Continue to breakpoint +(gdb) set {char[20]} 0x20000000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'} +(gdb) set $r0 = 0x20000000 # Redirect r0 to injected message +(gdb) c # Resume - serial shows custom message +``` + +##### Verification +```gdb +(gdb) x/s 0x20000000 # Should show your injected message +(gdb) x/s 0x100019cc # Original string still in Flash +``` + +#### Reflection Answers + +1. **Why does the string have to live in SRAM instead of flash during runtime?** + Flash memory is read-only at runtime. The original string at `0x100019cc` cannot be modified. SRAM starting at `0x20000000` is read-write, so that is where we must place our replacement string. + +2. **What would happen if you forgot the null terminator in your injected string?** + `puts()` reads characters until it encounters `\0`. Without it, `puts()` would continue reading past the intended string, printing garbage characters from adjacent memory until a null byte happens to appear. This could crash the program or leak sensitive data. + +3. **How does changing `r0` alter the behavior of `puts()` without touching source code?** + In the ARM calling convention, the first function argument is passed in `r0`. When `bl __wrap_puts` executes at `0x1000023c`, it reads the string address from `r0`. By changing `r0` from `0x100019cc` (original Flash string) to `0x20000000` (our SRAM string), we redirect what `puts()` prints. diff --git a/WEEKS/WEEK02/WEEK02-01.md b/WEEKS/WEEK02/WEEK02-01.md new file mode 100644 index 0000000..6e9d5f0 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-01.md @@ -0,0 +1,104 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 1: Change the Message + +#### Objective +Write your own message into SRAM and redirect `r0` so the running program prints it without changing the source code. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD and `arm-none-eabi-gdb` available in your PATH +- Serial monitor (PuTTY/minicom/screen) set to 115200 baud +- `build\0x0001_hello-world.elf` present and flashed to the board +- Week 2 setup steps (0a–0e) completed: OpenOCD, serial monitor, and GDB ready + +#### Task Description +You will create a custom string in SRAM at `0x20000000`, point `r0` at it just before `puts()` runs, and watch the live output change to your message. + +#### Step-by-Step Instructions + +##### Step 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" +``` + +##### Step 2: Start the Serial Monitor +- Open PuTTY (Serial), choose the correct COM port, set speed to `115200`, then click **Open**. + +##### Step 3: Launch GDB + +```powershell +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +##### Step 4: Connect and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 5: Break Before `puts()` + +```gdb +(gdb) b *0x1000023c +``` + +##### Step 6: Run to the Breakpoint + +```gdb +(gdb) c +``` + +##### Step 7: Inject Your Message into SRAM +Replace the characters with your name as needed. + +```gdb +(gdb) set {char[20]} 0x20000000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'} +``` + +##### Step 8: Point `r0` to Your Message + +```gdb +(gdb) set $r0 = 0x20000000 +``` + +##### Step 9: Resume and Observe + +```gdb +(gdb) c +``` + +Check PuTTY for your custom string replacing "hello, world". + +#### Expected Output +- GDB stops at `0x1000023c` before `__wrap_puts`. +- `x/s 0x20000000` shows your injected message. +- PuTTY displays your custom message after you continue execution. + +#### Questions for Reflection + +###### Question 1: Why does the string have to live in SRAM instead of flash during runtime? + +###### Question 2: What would happen if you forgot the null terminator in your injected string? + +###### Question 3: How does changing `r0` alter the behavior of `puts()` without touching source code? + +#### Tips and Hints +- Keep your string length within the allocated array (`char[20]`). +- If you miss the breakpoint, confirm OpenOCD is running and the address matches `Week 2` disassembly. +- Use `x/s $r0` to confirm the register points to the intended address before continuing. + +#### Next Steps +- Repeat the exercise with different messages to verify repeatability. +- Try smaller or larger buffers (still within SRAM) to see how size affects safety. +- Move on to Exercise 2 to practice using alternate SRAM addresses. diff --git a/WEEKS/WEEK02/WEEK02-02-S.md b/WEEKS/WEEK02/WEEK02-02-S.md new file mode 100644 index 0000000..021ba57 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-02-S.md @@ -0,0 +1,38 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 2 Solution: Use a Different SRAM Address + +#### Answers + +##### Attack Summary +Write the payload to `0x20001000` instead of `0x20000000` to demonstrate that multiple safe SRAM locations can be used for injection. + +##### GDB Commands + +```gdb +(gdb) b *0x1000023c +(gdb) c +(gdb) set {char[14]} 0x20001000 = {'h','a','c','k','e','d','!','!','!','\r','\0'} +(gdb) set $r0 = 0x20001000 +(gdb) c +``` + +##### Verification +```gdb +(gdb) x/s 0x20001000 # Shows "hacked!!!\r" +``` + +#### Reflection Answers + +1. **How can you ensure `0x20001000` does not collide with stack usage?** + The stack pointer was observed at `0x20082000` (top of stack) and grows downward. Since `0x20001000` is far below the stack region, there is substantial separation. Use `info registers sp` in GDB to verify the current stack pointer is well above your injection address. + +2. **What symptoms would indicate you overwrote an active stack frame?** + The program would crash when attempting to return from a function. Symptoms include: unexpected address exceptions, invalid memory access faults, or the program jumping to random addresses. The Link Register return path gets corrupted. + +3. **How would you pick a safe SRAM offset in a larger program with dynamic allocations?** + Start from the bottom of SRAM (`0x20000000`) for small static payloads, working upward. Check the linker script to understand memory regions. In this simple program with no heap allocations, both `0x20000000` and `0x20001000` are safe. In larger programs, examine the `.bss` and `.data` section boundaries. diff --git a/WEEKS/WEEK02/WEEK02-02.md b/WEEKS/WEEK02/WEEK02-02.md new file mode 100644 index 0000000..b79128c --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-02.md @@ -0,0 +1,91 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 2: Use a Different SRAM Address + +#### Objective +Practice writing to an alternate SRAM location and redirecting `r0` so your message prints from `0x20001000` instead of `0x20000000`. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD, `arm-none-eabi-gdb`, and a serial monitor ready (Week 2 steps 0a–0e complete) +- `build\0x0001_hello-world.elf` flashed and running +- Comfortable setting breakpoints at `0x1000023c` + +#### Task Description +You will inject a short string into `0x20001000`, point `r0` there, and verify the live output changes, demonstrating that any safe SRAM slot can host your payload. + +#### Step-by-Step Instructions + +##### Step 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" +``` + +##### Step 2: Start the Serial Monitor +- Open PuTTY (Serial) on the correct COM port at `115200` baud. + +##### Step 3: Launch GDB and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 4: Break Before `puts()` + +```gdb +(gdb) b *0x1000023c +(gdb) c +``` + +##### Step 5: Write a Payload at `0x20001000` + +```gdb +(gdb) set {char[14]} 0x20001000 = {'h','a','c','k','e','d','!','!','!','\r','\0'} +``` + +##### Step 6: Redirect `r0` + +```gdb +(gdb) set $r0 = 0x20001000 +``` + +##### Step 7: Continue and Verify + +```gdb +(gdb) c +``` + +Check PuTTY for the new output sourced from the alternate address. + +#### Expected Output +- `x/s 0x20001000` shows `"hacked!!!\r"` (or your variant). +- PuTTY prints the injected message instead of the original string. +- The program continues looping with your modified output. + +#### Questions for Reflection + +###### Question 1: How can you ensure `0x20001000` does not collide with stack usage? + +###### Question 2: What symptoms would indicate you overwrote an active stack frame? + +###### Question 3: How would you pick a safe SRAM offset in a larger program with dynamic allocations? + +#### Tips and Hints +- Keep payloads short; avoid overrunning the allocated bytes. +- If you see crashes, choose a lower SRAM address away from the stack top (stack grows downward). +- Use `info registers sp` and compare with your chosen address to gauge separation. + +#### Next Steps +- Try other safe addresses (e.g., `0x20002000`) and verify stability. +- Map out stack usage by stepping deeper and watching `sp` move. +- Proceed to Exercise 3 to inspect memory around your payload. diff --git a/WEEKS/WEEK02/WEEK02-03-S.md b/WEEKS/WEEK02/WEEK02-03-S.md new file mode 100644 index 0000000..8661506 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-03-S.md @@ -0,0 +1,54 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 3 Solution: Examine Memory Around Your String + +#### Answers + +##### GDB Commands + +```gdb +(gdb) set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +(gdb) x/20b 0x20000000 +``` + +##### Byte Dump Output + +``` +0x20000000: 0x68 0x61 0x63 0x6b 0x79 0x2c 0x20 0x77 +0x20000008: 0x6f 0x72 0x6c 0x64 0x0d 0x00 0x00 0x00 +0x20000010: 0x00 0x00 0x00 0x00 +``` + +##### ASCII Mapping + +| Offset | Hex Value | Character | +|--------|-----------|-----------| +| 0x00 | `0x68` | h | +| 0x01 | `0x61` | a | +| 0x02 | `0x63` | c | +| 0x03 | `0x6b` | k | +| 0x04 | `0x79` | y | +| 0x05 | `0x2c` | , (comma) | +| 0x06 | `0x20` | (space) | +| 0x07 | `0x77` | w | +| 0x08 | `0x6f` | o | +| 0x09 | `0x72` | r | +| 0x0a | `0x6c` | l | +| 0x0b | `0x64` | d | +| 0x0c | `0x0d` | \r (carriage return) | +| 0x0d | `0x00` | \0 (null terminator) | + +#### Reflection Answers + +1. **Which bytes mark the end of the printable string, and why are they needed?** + The last two meaningful bytes are `0x0d` (carriage return `\r`) and `0x00` (null terminator `\0`). The null terminator signals the end of the string to `puts()`. Without it, `puts()` would read past the intended string and print garbage memory until a null byte is encountered. + +2. **How would misaligned writes show up in the byte view?** + If you write to an incorrect address or use wrong character offsets, the byte dump would show unexpected values at wrong positions. Characters would appear shifted, and adjacent data structures could be corrupted. + +3. **What risks arise if you overwrite bytes immediately after your string?** + Overwriting adjacent bytes could corrupt other data structures in SRAM, such as variables, linked lists, or runtime metadata. This could cause unpredictable crashes or silent data corruption depending on what occupies those memory locations. diff --git a/WEEKS/WEEK02/WEEK02-03.md b/WEEKS/WEEK02/WEEK02-03.md new file mode 100644 index 0000000..18f6ab8 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-03.md @@ -0,0 +1,82 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 3: Examine Memory Around Your String + +#### Objective +Inspect the byte-level layout of your injected string in SRAM and correlate bytes to characters. + +#### Prerequisites +- Pico 2 connected with OpenOCD, GDB, and a serial monitor ready +- `build\0x0001_hello-world.elf` flashed and running +- Ability to break before `__wrap_puts` at `0x1000023c` +- A payload already written to SRAM (e.g., at `0x20000000` from Exercise 1) + +#### Task Description +You will use `x/20b` to view the bytes surrounding your injected string, decode the characters, and confirm the presence of control characters and the null terminator. + +#### Step-by-Step Instructions + +##### Step 1: Connect and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Break Before `puts()` and Run + +```gdb +(gdb) b *0x1000023c +(gdb) c +``` + +##### Step 3: Ensure a String Exists in SRAM +If needed, re-inject a payload: + +```gdb +(gdb) set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +(gdb) set $r0 = 0x20000000 +``` + +##### Step 4: Examine Bytes Around the String + +```gdb +(gdb) x/20b 0x20000000 +``` + +##### Step 5: Decode the Output +- Map each byte to ASCII: e.g., `0x68` ? `h`, `0x0d` ? `\r`, `0x00` ? `\0`. +- Note any bytes before/after the string to ensure you did not overwrite adjacent data. + +##### Step 6: Resume Execution + +```gdb +(gdb) c +``` + +#### Expected Output +- A byte dump where the sequence matches your string (`68 61 63 6b 79 2c 20 77 6f 72 6c 64 0d 00`). +- Confirmation of the carriage return (`0x0d`) and null terminator (`0x00`). +- Stable program output in PuTTY after resuming. + +#### Questions for Reflection + +###### Question 1: Which bytes mark the end of the printable string, and why are they needed? + +###### Question 2: How would misaligned writes show up in the byte view? + +###### Question 3: What risks arise if you overwrite bytes immediately after your string? + +#### Tips and Hints +- Use `x/20bx` if you prefer hex with ASCII side-by-side. +- Keep the dump length modest (20 bytes) to avoid clutter while still seeing context. +- If the bytes look incorrect, re-run the injection command to reset the buffer. + +#### Next Steps +- Try viewing a different address (e.g., `0x20001000`) to compare layouts. +- Experiment with longer strings and observe how the byte dump grows. +- Move on to Exercise 4 to automate the hack workflow. diff --git a/WEEKS/WEEK02/WEEK02-04-S.md b/WEEKS/WEEK02/WEEK02-04-S.md new file mode 100644 index 0000000..3f486b9 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-04-S.md @@ -0,0 +1,58 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 4 Solution: Automate the Hack + +#### Answers + +##### GDB Command Definition + +```gdb +(gdb) define hack +> set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +> set $r0 = 0x20000000 +> c +> end +``` + +##### Usage + +```gdb +(gdb) b *0x1000023c +(gdb) c +(gdb) hack # Executes all three commands at once +``` + +##### Expected Serial Output + +``` +hello, world +hello, world +hello, world +hacky, world <-- HACKED! (after hack command executed) +hacky, world +``` + +#### Reflection Answers + +1. **How could you parameterize the command to accept different strings or addresses?** + Standard GDB `define` blocks do not support function parameters directly. However, you can use GDB convenience variables (`set $myaddr = 0x20000000`) and reference them in the macro, or create multiple specific commands like `hack_addr1`, `hack_addr2`. For advanced parameterization, use GDB Python scripting. + +2. **What happens if you define `hack` before setting the breakpoint - will it still work as expected?** + The `define` command only creates a macro; it does not execute immediately. The breakpoint must be set and hit before invoking `hack`. The sequence matters: set breakpoint -> run/continue to hit breakpoint -> then call `hack`. Defining the macro before or after the breakpoint does not matter as long as you invoke it at the right time. + +3. **How would you adapt this pattern for multi-step routines (e.g., patch, dump, continue)?** + Extend the `define` block with additional commands: + ```gdb + (gdb) define hack_verbose + > set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} + > x/20b 0x20000000 + > set $r0 = 0x20000000 + > info registers r0 + > c + > end + ``` + This dumps memory and registers before continuing, providing verification at each step. diff --git a/WEEKS/WEEK02/WEEK02-04.md b/WEEKS/WEEK02/WEEK02-04.md new file mode 100644 index 0000000..eeac2f0 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02-04.md @@ -0,0 +1,71 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 2 +Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +### Non-Credit Practice Exercise 4: Automate the Hack + +#### Objective +Create a reusable GDB command that injects a string into SRAM, repoints `r0`, and resumes execution with a single call. + +#### Prerequisites +- Pico 2 connected with OpenOCD, GDB, and serial monitor ready +- `build\0x0001_hello-world.elf` available +- Familiarity with breaking at `0x1000023c` and injecting strings from prior exercises + +#### Task Description +You will define a custom GDB command `hack` that writes a payload to `0x20000000`, repoints `r0`, and continues execution automatically. + +#### Step-by-Step Instructions + +##### Step 1: Connect, Halt, and Break + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +(gdb) b *0x1000023c +(gdb) c +``` + +##### Step 2: Define the `hack` Command + +```gdb +(gdb) define hack +> set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +> set $r0 = 0x20000000 +> c +> end +``` + +##### Step 3: Invoke the Command + +```gdb +(gdb) hack +``` + +##### Step 4: Observe Output +- PuTTY should immediately show your injected string after the command runs. +- The breakpoint will be re-hit on the next loop iteration; rerun `hack` if you want to reapply after changes. + +#### Expected Output +- `hack` executes without errors, writes the payload, updates `r0`, and resumes execution. +- Serial output reflects the injected message. + +#### Questions for Reflection + +###### Question 1: How could you parameterize the command to accept different strings or addresses? + +###### Question 2: What happens if you define `hack` before setting the breakpoint—will it still work as expected? + +###### Question 3: How would you adapt this pattern for multi-step routines (e.g., patch, dump, continue)? + +#### Tips and Hints +- Redefine `hack` any time you want a different payload; GDB will overwrite the prior definition. +- Keep the payload length aligned with the buffer size to avoid stray bytes. +- If the target keeps running past the breakpoint, ensure hardware breakpoints are available and set correctly. + +#### Next Steps +- Create additional helper commands (e.g., `dumpstr`, `retarget`) to streamline experiments. +- Explore GDB scripting files (`.gdbinit`) to auto-load your helpers on startup. +- Try combining `hack` with watchpoints to observe memory changes live. diff --git a/WEEKS/WEEK02/WEEK02-SLIDES.pdf b/WEEKS/WEEK02/WEEK02-SLIDES.pdf new file mode 100644 index 0000000..25756f8 Binary files /dev/null and b/WEEKS/WEEK02/WEEK02-SLIDES.pdf differ diff --git a/WEEKS/WEEK02/WEEK02.md b/WEEKS/WEEK02/WEEK02.md new file mode 100644 index 0000000..777d9d2 --- /dev/null +++ b/WEEKS/WEEK02/WEEK02.md @@ -0,0 +1,1730 @@ +ο»Ώ# Week 2: Hello, World - Debugging and Hacking Basics: Debugging and Hacking a Basic Program for the Pico 2 + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Connect to a live embedded system using OpenOCD and GDB +- Step through code instruction by instruction and watch the stack change +- Examine memory, registers, and decode little-endian values +- Set strategic breakpoints to pause execution at key moments +- Understand why direct string assignment fails in bare-metal systems +- Write custom data directly to SRAM memory +- Hijack register values to redirect program behavior +- Modify a running program's output in real-time + +## πŸ”„ Review from Week 1 +This week builds directly on Week 1 concepts. You should already be comfortable with: +- **Registers** (`r0`-`r12`, SP, LR, PC) - We'll watch them change and manipulate `r0` to change program behavior +- **Memory Layout** (Flash at `0x10000000`, RAM at `0x20000000`) - Critical for understanding where we can write +- **The Stack** and how `push`/`pop` work - We'll watch this in action +- **Little-Endian** byte ordering - We'll decode values live +- **GDB basics** from Week 1's dynamic analysis section +- **Ghidra basics** from Week 1's static analysis section + +--- + +## πŸ“š Part 1: Understanding Live Hacking + +#### What is Live Hacking? + +**Live hacking** means modifying a program *while it's running* on real hardware. Instead of changing the source code and recompiling, we intercept the program mid-execution and change what it does on the fly. + +Think of it like this: imagine a train is heading to New York City. Live hacking is like switching the tracks while the train is moving so it goes to Los Angeles instead! + +#### Why is This Important? + +Live hacking techniques are used for: +- **Security Research**: Finding vulnerabilities in embedded systems +- **Penetration Testing**: Testing if systems can be compromised +- **Malware Analysis**: Understanding how malicious code works +- **Debugging**: Fixing bugs in systems that can't be easily reprogrammed + +#### Real-World Application + +> **"With great power comes great responsibility!"** + +Imagine you're a security researcher testing an industrial control system at a power plant. You need to verify that an attacker couldn't: +1. Change the values being displayed to engineers +2. Make dangerous equipment appear safe +3. Hide malicious activity from monitoring systems + +The techniques you'll learn today are *exactly* how this would be done. Understanding these attacks helps us build better defenses! + +--- + +## πŸ“š Part 2: Review - Memory Layout (from Week 1) + +> πŸ”„ **REVIEW:** In Week 1, we learned about the RP2350's memory layout. This knowledge is essential for our hack! + +> πŸ“– **Datasheet Reference:** The address map is in Section 2.2 (p. 31): Flash/XIP at `0x10000000`, SRAM at `0x20000000`. SRAM is 520 KB total across 10 banks (Section 2.2.3, p. 32; Section 4.2, p. 338–339). Flash memory is accessed read-only via XIP during normal execution (Section 4.4, p. 340+). + +Before we hack, let's remember where things live in memory on the RP2350: + +#### The Code We're Hacking + +Remember our `0x0001_hello-world.c` program from Week 1: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +This simple program: +1. Initializes I/O with `stdio_init_all()` +2. Enters an infinite `while(true)` loop +3. Prints `"hello, world\r\n"` forever + +Our goal: **Make it print something else WITHOUT changing the source code!** + +#### Memory Map + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Flash Memory (XIP) - READ ONLY β”‚ +β”‚ Starts at: 0x10000000 β”‚ +β”‚ Contains: Program code, constant strings β”‚ +β”‚ NOTE: We CANNOT write to flash during runtime! β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ SRAM - READ/WRITE β”‚ +β”‚ Starts at: 0x20000000 β”‚ +β”‚ Contains: Stack, Heap, Variables β”‚ +β”‚ NOTE: We CAN write to SRAM during runtime! β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +> πŸ”„ **REVIEW:** In Week 1, we saw the stack pointer (SP) was around `0x20081fc8` - that's in the SRAM region! The stack "grows downward" from the top of SRAM. + +#### Why This Matters for Our Hack + +The string `"hello, world"` is stored in **flash memory** (around `0x100019cc`). Flash memory is **read-only** during normal operation - we can't just overwrite it. + +But SRAM (starting at `0x20000000`) is **read-write**! This is where we'll create our hacked string. + +--- + +## πŸ“š Part 3: The Attack Plan + +Here's our step-by-step attack strategy: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 1: Start the debug server (OpenOCD) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 2: Connect with GDB and halt the program β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 3: Set a breakpoint right before puts() β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 4: When we hit the breakpoint, r0 contains β”‚ +β”‚ the address of "hello, world" β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 5: Create our malicious string in SRAM β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 6: Change r0 to point to OUR string β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ STEP 7: Continue execution - HACKED! β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 4: Setting Up Your Environment + +#### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board with debug probe connected +2. OpenOCD installed and configured +3. GDB (arm-none-eabi-gdb) installed +4. A serial monitor application (like PuTTY, minicom, or screen) +5. The "hello-world" binary loaded on your Pico 2 + +#### What You'll Need Open + +You will need **THREE** terminal windows: +1. **Terminal 1**: Running OpenOCD (the debug server) +2. **Terminal 2**: Running your serial monitor (to see output) +3. **Terminal 3**: Running GDB (where we do the hacking) + +--- + +## πŸ”¬ Part 5: GDB Deep Dive - Exploring the Binary + +Before we start hacking, let's use GDB to thoroughly understand our program. This hands-on tutorial will teach you to examine memory, step through code, and watch the stack in action. + +#### Starting the Debug Environment + +##### Step 0a: Start OpenOCD (Terminal 1) + +OpenOCD is the bridge between your computer and the Pico 2's debug interface. It creates a server that GDB can connect to. + +**Open Terminal 1 and type:** + +```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" +``` + +**What this command means:** +- `openocd` = the OpenOCD program +- `-s ...` = path to OpenOCD scripts folder +- `-f interface/cmsis-dap.cfg` = use the CMSIS-DAP debug probe configuration +- `-f target/rp2350.cfg` = configure for the RP2350 chip +- `-c "adapter speed 5000"` = set the debug speed to 5000 kHz + +**You should see output like:** + +``` +Open On-Chip Debugger 0.12.0 +Licensed under GNU GPL v2 +Info : Listening on port 3333 for gdb connections +Info : CMSIS-DAP: SWD supported +Info : CMSIS-DAP: Interface ready +``` + +**Important:** Leave this terminal running! Don't close it. + +##### Step 0b: Start Your Serial Monitor - PuTTY (Terminal 2) + +PuTTY will show us the output from our Pico 2. When we hack the program, we'll see the results here! + +**To set up PuTTY:** + +1. Open **PuTTY** +2. In the **Session** category: + - **Connection type**: Select **Serial** + - **Serial line**: Enter your COM port (e.g., `COM3` - check Device Manager to find yours) + - **Speed**: Enter `115200` +3. Click **Open** + +> πŸ’‘ **Finding your COM port:** Open Device Manager β†’ Ports (COM & LPT) β†’ Look for "USB Serial Device" or "Pico" - note the COM number. + +**You should see:** + +``` +hello, world +hello, world +hello, world +hello, world +... +``` + +The program is running and printing `"hello, world"` in an infinite loop! + +**Important:** Leave PuTTY running! We'll watch it change when we hack the system. + +##### Step 0c: Start GDB (Terminal 3) + +**Open Terminal 3** and start GDB with your binary: + +```powershell +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +**What this command means:** +- `arm-none-eabi-gdb` = the ARM version of GDB +- `build\0x0001_hello-world.elf` = our compiled program with debug symbols + +**You should see:** + +``` +GNU gdb (Arm GNU Toolchain 13.2) 13.2 +Reading symbols from build\0x0001_hello-world.elf... +(gdb) +``` + +The `(gdb)` prompt means GDB is ready for commands! + +##### Step 0d: Connect GDB to OpenOCD + +Now we need to connect GDB to OpenOCD. OpenOCD is listening on port `3333`. + +**Type this command:** + +```gdb +(gdb) target extended-remote :3333 +``` + +**You should see:** + +``` +Remote debugging using :3333 +main () at 0x0001_hello-world/0x0001_hello-world.c:5. +5 stdio_init_all(); +``` + +We're connected! GDB shows us the program is currently in the `main` function. + +##### Step 0e: Halt the Running Program + +The program is still running (you can see "hello, world" still printing in PuTTY). Let's stop it: + +**Type this command:** + +```gdb +(gdb) monitor reset halt +``` + +**What this command means:** +- `monitor` = send a command to OpenOCD (not GDB) +- `reset` = reset the processor +- `halt` = stop execution immediately + +**You should see:** + +``` +[rp2350.cm0] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +[rp2350.cm1] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +``` + +**Check PuTTY:** The "hello, world" messages should have stopped! The processor is now frozen, waiting for our commands. + +--- + +#### Exploring the Binary + +Now that we're connected and the processor is halted, let's explore! + +##### Step 1: Examine Memory Starting at XIP Base Address + +Our program code starts at address `0x10000000`. Let's look at the first 1000 instructions to find our `main` function. + +**Type this command in GDB:** + +```gdb +(gdb) x/1000i 0x10000000 +``` + +**What this command means:** +- `x` = examine memory +- `/1000i` = show 1000 instructions +- `0x10000000` = starting address + +**What you're looking for:** + +Scroll through the output and look for something like this: + +``` +0x10000234
: push {r3, lr} +``` + +The `
` label tells us we found our main function! The address `0x10000234` is where main starts. + +##### Step 2: Examine the Main Function in Detail + +Now let's look at just the main function. We'll examine 5 instructions starting at the main function address: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 +=> 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Let's understand each instruction:** + +| Address | Instruction | What It Does | +| ------------ | -------------------------------- | --------------------------------------------------- | +| `0x10000234` | `push {r3, lr}` | Save `r3` and the return address onto the stack | +| `0x10000236` | `bl 0x1000156c ` | Call the `stdio_init_all` function | +| `0x1000023a` | `ldr r0, [pc, #8]` | Load the address of our string into `r0` | +| `0x1000023c` | `bl 0x100015fc <__wrap_puts>` | Call `puts` to print our string | +| `0x10000240` | `b.n 0x1000023a` | Jump back to the `ldr` instruction (infinite loop!) | + +##### Step 3: Set a Breakpoint at Main + +A **breakpoint** is like a stop sign for your program. When the processor reaches that address, it will pause and let us examine things. + +**Type this command:** + +```gdb +(gdb) b *0x10000234 +``` + +**You should see:** + +``` +Breakpoint 1 at 0x10000234: file C:/Users/.../0x0001_hello-world.c, line 5. +Note: automatically using hardware breakpoints for read-only addresses. +``` + +**What this means:** +- `b` = set breakpoint +- `*0x10000234` = at this exact memory address +- GDB confirms the breakpoint is set and even tells us which line of C code this corresponds to! + +##### Step 4: Continue Execution Until Breakpoint + +Now let's run the program until it hits our breakpoint: + +**Type this command:** + +```gdb +(gdb) c +``` + +**You should see:** + +``` +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 1, main () + at C:/Users/.../0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +**What happened:** +- The processor ran until it reached address `0x10000234` +- It stopped right before executing the instruction at that address +- GDB shows us we're at line 5 of our C source code + +##### Step 5: Examine Instructions with Arrow + +Let's look at our instructions again: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 +=> 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Notice the arrow `=>`!** This arrow shows which instruction we're about to execute. We haven't executed it yet - we're paused right before it. + +--- + +#### Understanding the Stack in Action + +##### Step 6: Examine the Stack Before Push + +Before we execute the `push` instruction, let's see what's on the stack: + +**Type this command:** + +```gdb +(gdb) x/10x $sp +``` + +**What this command means:** +- `x` = examine memory +- `/10x` = show 10 values in hexadecimal +- `$sp` = starting at the stack pointer address + +**You should see:** + +``` +0x20082000: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082010: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082020: 0x00000000 0x00000000 +``` + +**What this shows:** +- The stack pointer is at address `0x20082000` +- The stack is empty (all zeros) +- This is the "top" of our stack in RAM + +##### Step 7: Execute One Instruction (Step Into) + +Now let's execute just ONE assembly instruction: + +**Type this command:** + +```gdb +(gdb) si +``` + +**What this command means:** +- `si` = step instruction (execute one assembly instruction) + +**You should see:** + +``` +0x10000236 5 stdio_init_all(); +``` + +Let's verify where we are: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +``` +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} +=> 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Notice:** The arrow `=>` has moved! We've executed the `push` instruction and are now about to execute the `bl` (branch with link) instruction. + +##### Step 8: Examine the Stack After Push + +Now let's see what the push instruction did to our stack: + +**Type this command:** + +```gdb +(gdb) x/10x $sp +``` + +**You should see:** + +``` +0x20081ff8: 0xe000ed08 0x1000018f 0x00000000 0x00000000 +0x20082008: 0x00000000 0x00000000 0x00000000 0x00000000 +0x20082018: 0x00000000 0x00000000 +``` + +**What changed:** +- The stack pointer moved from `0x20082000` to `0x20081ff8` +- That's 8 bytes lower (2 Γ— 4-byte values) +- Two new values appeared: `0xe000ed08` and `0x1000018f` + +##### Step 9: Verify What Was Pushed + +Let's prove that these values came from `r3` and `lr`: + +**Check `r3`:** + +```gdb +(gdb) x/x $r3 +``` + +**You should see:** + +``` +0xe000ed08: Cannot access memory at address 0xe000ed08 +``` + +This error is expected! The value `0xe000ed08` is in `r3`, and when we try to examine it as an address, that memory location isn't accessible. But we can see the value matches what's on the stack! + +**Check `lr` (Link Register):** + +```gdb +(gdb) x/x $lr +``` + +**You should see:** + +``` +0x1000018f : 0x00478849 +``` + +The value `0x1000018f` is in `lr` - this is the return address! This matches the second value on our stack. + +##### Step 10: Verify Stack Layout + +Let's look at each pushed value individually: + +**First pushed value (`r3`):** + +```gdb +(gdb) x/x $sp +``` + +**You should see:** + +``` +0x20081ff8: 0xe000ed08 +``` + +This is the value from `r3`, pushed first. + +**Second pushed value (`lr`):** + +```gdb +(gdb) x/x $sp+4 +``` + +**You should see:** + +``` +0x20081ffc: 0x1000018f +``` + +This is the value from `lr` (the return address), pushed second. + +### Understanding the Stack Diagram + +``` +Before push {r3, lr}: After push {r3, lr}: + +Address Value Address Value +───────────────────── ───────────────────── +0x20082000 (empty) ← SP 0x20082000 (old SP location) + 0x20081ffc 0x1000018f (lr) + 0x20081ff8 0xe000ed08 (r3) ← SP +``` + +**Key Points:** +1. The stack grows DOWNWARD (addresses get smaller) +2. The SP always points to the last item pushed +3. `r3` was pushed first, then `lr` was pushed on top of it + +--- + +#### Continuing Through the Program + +##### Step 11: Step Over the stdio_init_all Function + +We don't need to examine every instruction inside `stdio_init_all` - it's just setup code. Let's "step over" it: + +**First, verify where we are:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} +=> 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +**Now step over the function call:** + +```gdb +(gdb) n +``` + +**What this command means:** +- `n` = next (step over function calls, don't go inside them) + +**You should see:** + +``` +8 printf("hello, world\r\n"); +``` + +**Verify where we are now:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c +=> 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +The arrow has moved past the function call! + +#### Understanding the LDR Instruction + +We're now at: +``` +ldr r0, [pc, #8] @ (0x10000244 ) +``` + +**What does this instruction do?** + +1. Take the current Program Counter (PC) value +2. Add 8 to it +3. Go to that memory address (`0x10000244`) +4. Load the value stored there into `r0` + +This is loading a **pointer** - the address of our "hello, world" string! + +##### Step 13: Execute the LDR and Examine `r0` + +**Execute one instruction:** + +```gdb +(gdb) si +``` + +**You should see:** + +``` +0x1000023c 8 printf("hello, world\r\n"); +``` + +**Now examine what's in `r0`:** + +```gdb +(gdb) x/x $r0 +``` + +**You should see:** + +``` +0x100019cc: 0x6c6c6568 +``` + +##### Step 14: Decoding the Mystery Value + +The value `0x6c6c6568` looks strange, but it's actually ASCII characters! Let's decode it: + +**ASCII Table Reference:** +| Hex | Character | +| ------ | --------- | +| `0x68` | h | +| `0x65` | e | +| `0x6c` | l | +| `0x6c` | l | + +So `0x6c6c6568` = "lleh" backwards! + +**Why is it backwards?** + +This is called **little-endian** byte order. The RP2350 stores bytes in reverse order in memory. When we read them as a 32-bit value, they appear reversed. + +##### Step 15: View the Full String + +Let's tell GDB to show this as a string instead of a hex number: + +```gdb +(gdb) x/s $r0 +``` + +**What this command means:** +- `x` = examine memory +- `/s` = show as a string +- `$r0` = at the address stored in `r0` + +**You should see:** + +``` +0x100019cc: "hello, world\r" +``` + +There's our string! The `\r` is a carriage return character (part of `\r\n`). + +> 🎯 **Key Discovery:** The string `"hello, world"` is stored at address `0x100019cc` in flash memory. This is the value that gets loaded into `r0` before calling `puts()`. We'll use this knowledge in our hack! + +--- + +## πŸ”¬ Part 6: Starting the Debug Session for the Hack + +##### Step 1: Start OpenOCD (Debug Server) + +OpenOCD is the bridge between your computer and the Pico 2's debug interface. It creates a server that GDB can connect to. + +**Open Terminal 1 and type:** + +```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" +``` + +**What this command means:** +- `openocd` = the OpenOCD program +- `-f interface/cmsis-dap.cfg` = use the CMSIS-DAP debug probe configuration +- `-f target/rp2350.cfg` = configure for the RP2350 chip +- `-c "adapter speed 5000"` = set the debug speed to 5000 kHz + +**You should see output like:** + +``` +Open On-Chip Debugger 0.12.0 +Licensed under GNU GPL v2 +Info : Listening on port 3333 for gdb connections +Info : CMSIS-DAP: SWD supported +Info : CMSIS-DAP: Interface ready +``` + +**Important:** Leave this terminal running! Don't close it. + +##### Step 2: Start Your Serial Monitor + +**Open PuTTY** and start your serial monitor to watch the Pico 2's output. + +**Example using screen (macOS/Linux):** + +```bash +screen /dev/tty.usbmodem* 115200 +``` + +**Example using minicom:** + +```bash +minicom -D /dev/ttyACM0 -b 115200 +``` + +**You should see:** + +``` +hello, world +hello, world +hello, world +hello, world +... +``` + +The program is running and printing `"hello, world"` in an infinite loop! + +**Important:** Leave this terminal running! We'll watch it change when we hack the system. + +##### Step 3: Start GDB and Load the Binary + +**Open Terminal 3** and start GDB with your binary: + +```powershell +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +**What this command means:** +- `arm-none-eabi-gdb` = the ARM version of GDB +- `build\0x0001_hello-world.elf` = our compiled program with debug symbols + +**You should see:** + +``` +GNU gdb (Arm GNU Toolchain 13.2) 13.2 +Reading symbols from build\0x0001_hello-world.elf... +(gdb) +``` + +The `(gdb)` prompt means GDB is ready for commands! + +##### Step 4: Connect to the Remote Debug Server + +Now we need to connect GDB to OpenOCD. OpenOCD is listening on port `3333`. + +**Type this command:** + +```gdb +(gdb) target extended-remote :3333 +``` + +**What this command means:** +- `target remote` = connect to a remote debug server +- `:3333` = on localhost, port 3333 (where OpenOCD is listening) + +**You should see:** + +``` +Remote debugging using :3333 +warning: multi-threaded target stopped without sending a thread-id, using first non-exited thread +0x1000023c in main () at 0x0001_hello-world.c:8 +8 printf("hello, world\r\n"); +``` + +We're connected! GDB shows us the program is currently in the `main` function. + +##### Step 5: Halt the Running Program + +The program is still running (you can see "hello, world" still printing in your serial monitor). Let's stop it: + +**Type this command:** + +```gdb +(gdb) monitor reset halt +``` + +**What this command means:** +- `monitor` = send a command to OpenOCD (not GDB) +- `reset` = reset the processor +- `halt` = stop execution immediately + +**You should see:** + +```gdb +(gdb) monitor reset halt +[rp2350.cm0] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +[rp2350.cm1] halted due to debug-request, current mode: Thread +xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 +``` + +**Check your serial monitor (Terminal 2):** The "hello, world" messages should have stopped! The processor is now frozen, waiting for our commands. + +--- + +## πŸ”¬ Part 7: Analyzing the Target + +> πŸ”„ **REVIEW:** We're using the same GDB commands we learned earlier. The `x` command examines memory, and `/5i` shows 5 instructions. + +##### Step 6: Examine the Main Function + +Let's look at the main function to understand what we're dealing with: + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000234 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/5i` = show 5 instructions +- `0x10000234` = the address of main (we found this in Week 1!) + +**You should see:** + +```gdb +(gdb) x/5i 0x10000234 + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x1000156c + 0x1000023a : ldr r0, [pc, #8] @ (0x10000244 ) + 0x1000023c : bl 0x100015fc <__wrap_puts> + 0x10000240 : b.n 0x1000023a +``` + +> πŸ”„ **REVIEW:** This is the same disassembly we analyzed in Week 1! Remember: +> - `push {r3, lr}` saves registers to the stack +> - `bl` is "branch with link" - it calls a function and saves the return address in LR +> - `b.n` is the infinite loop that jumps back to the `ldr` instruction + +#### Understanding the Code Flow + +> πŸ”„ **REVIEW:** In Week 1, we learned that `r0`-`r3` are used to pass arguments to functions. The first argument always goes in `r0`! + +Let's break down what happens each time through the loop: + +| Address | Instruction | What Happens | +| ------------ | ------------------ | ------------------------------------------------------ | +| `0x1000023a` | `ldr r0, [pc, #8]` | Load the address of `"hello, world"` into `r0` | +| `0x1000023c` | `bl __wrap_puts` | Call `puts()` - it reads the string address from `r0`! | +| `0x10000240` | `b.n 0x1000023a` | Jump back to the `ldr` instruction (loop forever) | + +#### How This Maps to Our C Code + +```c +while (true) + printf("hello, world\r\n"); // The compiler optimized this to puts() +``` + +The compiler: +1. Loads the string address into `r0` (first argument) +2. Calls `puts()` (optimized from `printf()` since we're just printing a string) +3. Loops back forever with `b.n` + +**The Key Insight:** Right before the `bl __wrap_puts` instruction (at `0x1000023c`), the register `r0` contains the address of the string to print! + +If we can change what `r0` points to, we can make it print **anything we want**! + +--- + +## πŸ”¬ Part 8: Setting the Trap + +> πŸ”„ **REVIEW:** In Week 1, we used `b main` and `b *0x10000234` to set breakpoints. Now we'll use the same technique at a more strategic location! + +##### Step 7: Set a Strategic Breakpoint + +We want to stop the program RIGHT BEFORE it calls `puts()`. That's at address `0x1000023c`. + +**Type this command:** + +```gdb +(gdb) b *0x1000023c +``` + +**What this command means:** +- `b` = set a breakpoint (same as Week 1!) +- `*0x1000023c` = at this exact memory address (the asterisk means "address") + +**You should see:** + +``` +Breakpoint 1 at 0x1000023c: file 0x0001_hello-world.c, line 8. +Note: automatically using hardware breakpoints for read-only addresses. +``` + +**What does "hardware breakpoints" mean?** + +Because our code is in flash memory (read-only), GDB can't insert a software breakpoint by modifying the code. Instead, it uses a special feature of the ARM processor called a **hardware breakpoint**. The processor has a limited number of these (usually 4-8), but they work on any memory type. + +##### Step 8: Continue Execution and Hit the Breakpoint + +Now let's run the program until it hits our breakpoint: + +**Type this command:** + +```gdb +(gdb) c +``` + +**What this command means:** +- `c` = continue (run until something stops us) + +**You should see:** + +```gdb +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 1, 0x1000023c in main () + at 0x0001_hello-world.c:8 +8 printf("hello, world\r\n"); +``` + +The program has stopped RIGHT BEFORE calling `puts()`! The string address is loaded into `r0`, but the function hasn't been called yet. + +##### Step 9: Verify Our Position with Disassembly + +Let's double-check where we are using the `disas` command: + +**Type this command:** + +```gdb +(gdb) disas +``` + +**What this command means:** +- `disas` = disassemble the current function + +**You should see:** + +```gdb +(gdb) disas +Dump of assembler code for function main: + 0x10000234 <+0>: push {r3, lr} + 0x10000236 <+2>: bl 0x1000156c + 0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 ) +=> 0x1000023c <+8>: bl 0x100015fc <__wrap_puts> + 0x10000240 <+12>: b.n 0x1000023a + 0x10000242 <+14>: nop + 0x10000244 <+16>: adds r4, r1, r7 + 0x10000246 <+18>: asrs r0, r0, #32 +``` + +**Notice the arrow `=>`** pointing to `0x1000023c`! This confirms we're about to execute the `bl __wrap_puts` instruction. Perfect! + +--- + +## πŸ”¬ Part 9: Examining the Current State + +> πŸ”„ **REVIEW:** In Week 1, we used `x/s $r0` to view the "hello, world" string. We also learned about **little-endian** byte ordering - remember how `0x6c6c6568` spelled "lleh" backwards? + +##### Step 10: Examine What's in r0 + +Let's see what string `r0` is currently pointing to: + +**Type this command:** + +```gdb +(gdb) x/s $r0 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/s` = display as a string +- `$r0` = the address stored in register `r0` + +**You should see:** + +```gdb +0x100019cc: "hello, world\r" +``` + +There it is! The register `r0` contains `0x100019cc`, which is the address of our `"hello, world"` string in flash memory. + +> πŸ”„ **REVIEW:** This is the same address `0x100019cc` we discovered in Week 1, Step 15 when we used `x/s $r0` after executing the `ldr` instruction! + +--- + +## πŸ”¬ Part 10: The Failed Hack Attempt (Learning Why) + +##### Step 11: Try to Directly Change the String (This Will Fail!) + +Your first instinct might be to just assign a new string to `r0`. Let's try it and see what happens: + +**Type this command:** + +```gdb +(gdb) set $r0 = "hacky, world\r" +``` + +**You should see an error:** + +``` +evaluation of this expression requires the program to have a function "malloc". +``` + +**Oh no! It didn't work!** + +#### Why Did This Fail? + +This is a very important lesson! Here's what happened: + +1. When you type `"hacky, world\r"` in GDB, GDB interprets this as: "Create a new string and give me its address" + +2. To create a new string at runtime, GDB would need to allocate memory using `malloc()` + +3. But our embedded system has **no operating system** and **no C runtime library loaded**! There's no `malloc()` function available. + +4. GDB can't create the string because there's nowhere to put it! + +**Let's verify nothing changed:** + +```gdb +(gdb) x/s $r0 +``` + +**You should see:** + +``` +0x100019cc: "hello, world\r" +``` + +The original string is still there. Our hack attempt failed... but we're not giving up! + +--- + +## πŸ”¬ Part 11: The Real Hack - Writing to SRAM + +##### Step 12: Understanding the Solution + +Since we can't use `malloc()`, we need to manually create our string somewhere in memory. Remember our memory map? + +- Flash (`0x10000000`): **Read-only** - can't write here +- SRAM (`0x20000000`): **Read-write** - we CAN write here! + +> πŸ”„ **REVIEW:** In Week 1, we saw the stack pointer was at `0x20081fc8`. The stack lives at the TOP of SRAM and grows downward. We'll write our string at the BOTTOM of SRAM (`0x20000000`) to avoid conflicts! + +We'll write our malicious string directly to SRAM, then point `r0` to it. + +##### Step 13: Create Our Malicious String in SRAM + +We need to write 14 bytes (13 characters + null terminator) to SRAM: + +| Character | ASCII Hex | +| --------- | --------- | +| h | - | +| a | - | +| c | - | +| k | - | +| y | - | +| , | - | +| (space) | - | +| w | - | +| o | - | +| r | - | +| l | - | +| d | - | +| \r | - | +| \0 | - | + +**Type this command:** + +```gdb +(gdb) set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +``` + +**What this command means:** +- `set` = modify memory +- `{char[14]}` = treat the target as an array of 14 characters +- `0x20000000` = the address where we're writing (start of SRAM) +- `= {...}` = the characters to write + +**No output means success!** + +##### Step 14: Verify Our String Was Written + +Let's confirm our malicious string is in SRAM: + +**Type this command:** + +```gdb +(gdb) x/s 0x20000000 +``` + +**You should see:** + +```gdb +0x20000000 : "hacky, world\r" +``` + +**OUR STRING IS IN MEMORY!** + +GDB shows it's at the `ram_vector_table` location - that's just a label from the linker script. The important thing is our string is there and ready to use. + +--- + +## πŸ”¬ Part 12: Hijacking the Register + +> πŸ”„ **REVIEW:** In Week 1, we learned that `r0` holds the first argument to a function. When `puts()` is called, it expects `r0` to contain a pointer to the string it should print. By changing `r0`, we change what gets printed! + +##### Step 15: Change r0 to Point to Our String + +Now for the magic moment! We'll change `r0` from pointing to the original string to pointing to OUR string: + +**Type this command:** + +```gdb +(gdb) set $r0 = 0x20000000 +``` + +**What this command means:** +- `set` = modify a value +- `$r0` = the `r0` register +- `= 0x20000000` = change it to this address (where our string is) + +**No output means success!** + +##### Step 16: Verify the Register Was Changed + +Let's confirm `r0` now points to our malicious string: + +**First, check the raw value:** + +```gdb +(gdb) x/x $r0 +``` + +**You should see:** + +``` +0x20000000 : 0x68 +``` + +The value `0x68` is the ASCII code for 'h' - the first character of "hacky"! + +**Now check it as a string:** + +```gdb +(gdb) x/s $r0 +``` + +**You should see:** + +``` +0x20000000 : "hacky, world\r" +``` + +**THE HIJACK IS COMPLETE!** When `puts()` runs, it will read the string address from `r0` - which now points to our malicious string! + +--- + +## πŸ”¬ Part 13: Executing the Hack + +##### Step 17: Continue Execution + +This is the moment of truth! Let's continue the program and watch our hack take effect: + +**Type this command:** + +```gdb +(gdb) c +``` + +**You should see:** + +```gdb +Continuing. + +Thread 1 "rp2350.cm0" hit Breakpoint 1, 0x1000023c in main () + at 0x0001_hello-world.c:8 +8 printf("hello, world\r\n"); +``` + +The program ran through one loop iteration and hit our breakpoint again. + +##### Step 18: Check Your Serial Monitor! + +**Look at Terminal 2 (your serial monitor)!** + +**You should see:** + +``` +hello, world +hello, world +hello, world +hacky, world <-- OUR HACK! +``` + +πŸŽ‰ **BOOM! WE DID IT!** πŸŽ‰ + +You just modified a running program on real hardware! The processor executed code that was supposed to print "hello, world" but instead printed "hacky, world" because we hijacked the data it was using! + +--- + +## πŸ”¬ Part 14: Static Analysis with Ghidra - Understanding the Hack + +Now that we've performed the hack dynamically with GDB, let's use Ghidra to understand the same concepts through static analysis. This shows how you could plan such an attack without even connecting to the hardware! + +#### Opening the Project in Ghidra + +If you haven't already set up the Ghidra project from Week 1: + +1. Launch Ghidra +2. Select **File β†’ New Project** β†’ **Non-Shared Project** +3. Name it `0x0001_hello-world` +4. Drag and drop `0x0001_hello-world.elf` into the project +5. Double-click to open in CodeBrowser +6. Click **Yes** to auto-analyze + +##### Step 1: Navigate to Main + +1. In the **Symbol Tree** panel (left side), expand **Functions** +2. Find and click on `main` + +**What you'll see in the Listing View:** + +``` + ************************************************************* + * FUNCTION + ************************************************************* + int main (void ) + assume LRset = 0x0 + assume TMode = 0x1 + int r0:4 + main XREF[3]: Entry Point (*) , + _reset_handler:1000018c (c) , + .debug_frame::00000018 (*) + 0x0001_hello-world.c:4 (2) + 0x0001_hello-world.c:5 (2) + 10000234 08 b5 push {r3,lr} + 0x0001_hello-world.c:5 (4) + 10000236 01 f0 99 f9 bl stdio_init_all _Bool stdio_init_all(void) + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + 1000023c 01 f0 de f9 bl __wrap_puts int __wrap_puts(char * s) + 0x0001_hello-world.c:7 (8) + 10000240 fb e7 b LAB_1000023a + 10000242 00 ?? 00h + 10000243 bf ?? BFh + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc + +``` + +**What you'll see in the Decompile View:** + +```c +int main(void) { + stdio_init_all(); + do { + __wrap_puts("hello, world"); + } while (true); +} +``` + +##### Step 2: Identify the Attack Point + +In our GDB hack, we set a breakpoint at `0x1000023c` - right before `bl __wrap_puts`. Let's understand why this was the perfect attack point: + +**Click on address `0x1000023c` in the Listing view.** + +Notice: +- The instruction is `bl __wrap_puts` - a function call +- The previous instruction at `0x1000023a` loaded `r0` with the string address +- Ghidra shows `= "hello, world\r"` right in the listing! + +> 🎯 **Key Insight:** Ghidra already tells us the string value! In the Listing, you can see `= "hello, world\r"` and `= 100019CCh`. This is the exact address we discovered through GDB! + +##### Step 3: Find the String in Memory + +Let's trace where the string actually lives: + +1. In the Listing view, look at address `0x1000023a`: + ``` + LAB_1000023a XREF[1]: 10000240 (j) + 0x0001_hello-world.c:7 (6) + 0x0001_hello-world.c:8 (6) + 1000023a 02 48 ldr r0=>__EH_FRAME_BEGIN__ ,[DAT_10000244 ] = "hello, world\r" + = 100019CCh + + ``` + +2. **Double-click on `DAT_10000244`** to go to the data reference + +3. You'll see: + ``` + DAT_10000244 XREF[1]: main:1000023a (R) + 10000244 cc 19 00 10 undefine 100019CCh ? -> 100019cc + + ``` + +4. **Double-click on `100019CCh`** to navigate to the actual string + +**You'll arrive at the string data:** + +``` + // + // .rodata + // SHT_PROGBITS [0x100019cc - 0x10001b17] + // ram:100019cc-ram:10001b17 + // + __init_array_end XREF[5]: Entry Point (*) , + __boot2_start__ frame_dummy:10000218 (*) , + __boot2_end__ main:1000023a (*) , + __EH_FRAME_BEGIN__ runtime_init:1000138a (R) , + _elfSectionHeaders::0000005c (*) + 100019cc 68 65 6c ds "hello, world\r" + 6c 6f 2c + 20 77 6f + +``` + +##### Step 4: Understand Why We Needed SRAM + +Look at the string address: `0x100019cc` + +This starts with `0x10...` which means it's in **Flash memory (XIP region)**! + +| Address Range | Memory Type | Writable? | +| ------------- | ----------- | --------- | +| `0x10000000`+ | Flash (XIP) | **NO** - Read Only | +| `0x20000000`+ | SRAM | **YES** - Read/Write | + +> 🎯 **This is why our direct string modification failed in GDB!** The string lives in flash memory, which is read-only at runtime. We had to create our malicious string in SRAM (`0x20000000`) instead. + +##### Step 5: Examine Cross-References + +Ghidra's cross-reference feature shows everywhere a value is used: + +1. Navigate back to `main` (press **G**, type `main`, press Enter) +2. Click on `__wrap_puts` at address `0x1000023c` +3. Right-click and select **References β†’ Show References to __wrap_puts** + +This shows every place that calls `puts()`. In a larger program, you could find ALL the print statements and potentially modify any of them! + +##### Step 6: Use the Decompiler to Plan Attacks + +The Decompile view makes attack planning easy: + +```c +int main(void) { + stdio_init_all(); + do { + __wrap_puts("hello, world"); // <-- Attack target identified! + } while (true); +} +``` + +From this view, you can immediately see: +- The program loops forever (`do { } while (true)`) +- It calls `__wrap_puts()` with a string argument +- To change the output, you need to change what's passed to `puts()` + +##### Step 7: Viewing the String in the .rodata Section + +When you navigate to the string address `0x100019cc`, you'll see the string stored in the `.rodata` (read-only data) section: + +``` + // + // .rodata + // SHT_PROGBITS [0x100019cc - 0x10001b17] + // ram:100019cc-ram:10001b17 + // + __init_array_end XREF[5]: Entry Point (*) , + __boot2_start__ frame_dummy:10000218 (*) , + __boot2_end__ main:1000023a (*) , + __EH_FRAME_BEGIN__ runtime_init:1000138a (R) , + _elfSectionHeaders::0000005c (*) + 100019cc 68 65 6c ds "hello, world\r" + 6c 6f 2c + 20 77 6f + +``` + +This shows the raw bytes of our string: `68 65 6c 6c 6f 2c 20 77 6f...` which spell out "hello, world\r" in ASCII. + +##### Step 8: Patching Data in Ghidra (Preview) + +Ghidra allows you to modify data directly in the binary! Here's how to patch the string: + +1. **Navigate to the string** at address `0x100019cc` +2. **Right-click** on the string `"hello, world\r"` in the Listing view +3. **Select** **Patch Data** from the context menu +4. **Type** your new string: `"hacky, world\r"` +5. **Press Enter** to apply the patch + +> ⚠️ **Important:** The new string must be the **same length or shorter** than the original! If your new string is longer, it will overwrite adjacent data and likely crash the program. + +| Original String | Patched String | Result | +| --------------- | -------------- | ------ | +| `hello, world\r` (14 bytes) | `hacky, world\r` (14 bytes) | βœ… Works perfectly | +| `hello, world\r` (14 bytes) | `PWNED!\r` (7 bytes) | βœ… Works (shorter is OK) | +| `hello, world\r` (14 bytes) | `this is a much longer string\r` | ❌ Overwrites other data! | + +After patching, you'll see the change reflected in the Listing view: + +``` + 100019cc 68 61 63 ds "hacky, world\r" + 6b 79 2c + 20 77 6f +``` + +Notice how the bytes changed: `68 65 6c 6c 6f` ("hello") became `68 61 63 6b 79` ("hacky")! + +#### Looking Ahead: Persistent Binary Patching + +> πŸ” **Coming in Future Lessons:** What we've done in Ghidra so far is just a **preview** of the patch - it modifies the data in Ghidra's view, but doesn't save it back to the actual binary file. + +In future lessons, we will learn how to: + +1. **Export the patched binary** from Ghidra to create a modified `.elf` or `.bin` file +2. **Flash the patched binary** to the Pico 2, making the hack **persistent** +3. **Understand patch verification** - how defenders detect modified binaries +4. **Bypass integrity checks** that try to prevent patching + +The key difference: + +| Technique | Persistence | When It's Useful | +| --------- | ----------- | ---------------- | +| **GDB Live Hacking** (this week) | Temporary - lost on reset | Testing, debugging, quick exploitation | +| **Ghidra Patch Preview** (this step) | None - just visualization | Planning and verifying patches | +| **Binary Patching** (future lessons) | **Permanent** - survives reboot | Persistent backdoors, firmware mods | + +This step helps you understand the mechanics of modifying binary data. Once you're comfortable with this concept, you'll be ready to create truly persistent modifications! + +#### Comparing GDB and Ghidra Approaches + +| Task | GDB (Dynamic) | Ghidra (Static) | +| ---- | ------------- | --------------- | +| Find main address | `x/1000i 0x10000000` + search | Symbol Tree β†’ Functions β†’ main | +| Find string address | Step through `ldr`, examine `$r0` | Click on `ldr` - shows `= 100019CCh` | +| See string content | `x/s $r0` | Double-click address β†’ see `ds "hello, world"` | +| Identify attack point | Set breakpoints, step, observe | Read decompiled code, find function calls | +| Verify memory type | Know address ranges | Check address prefix (`0x10...` vs `0x20...`) | + +#### Why Use Both Tools? + +- **Ghidra** helps you **plan** the attack by understanding code structure +- **GDB** lets you **execute** the attack and modify live values +- Together, they form a complete reverse engineering workflow! + +#### Ghidra Tips for Attack Planning + +1. **Use the Decompiler** - It shows you the high-level logic without decoding assembly +2. **Follow Cross-References** - Find all places a function or variable is used +3. **Check Address Ranges** - Quickly identify Flash vs SRAM locations +4. **Add Comments** - Press `;` to annotate what you discover for later +5. **Rename Variables** - Right-click β†’ Rename to give meaningful names + +--- + +## πŸ“Š Part 15: Summary and Review + +#### What We Accomplished + +We successfully performed a **live memory injection attack**: + +1. **Connected** to a running embedded system using OpenOCD and GDB +2. **Analyzed** the program flow to find the perfect attack point +3. **Set a breakpoint** right before the critical function call +4. **Discovered** that direct string assignment doesn't work without `malloc()` +5. **Wrote** our malicious data directly to SRAM +6. **Hijacked** the `r0` register to point to our data +7. **Executed** the hack and watched the output change! + +#### Week 1 Concepts We Applied + +| Week 1 Concept | How We Used It This Week | +| -------------- | ------------------------ | +| Memory Layout (Flash vs RAM) | We knew flash is read-only, so we wrote to SRAM | +| Registers (`r0`) | We hijacked `r0` to point to our malicious string | +| GDB `x` command | We examined memory and verified our injected string | +| GDB breakpoints (`b`) | We set a strategic breakpoint before `puts()` | +| Disassembly (`disas`) | We found the exact instruction to target | +| Little-endian | We understood how our string bytes are stored | + +#### The Attack Flow Diagram + +``` +BEFORE OUR HACK: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ r0 = 0x100019ccβ”‚ ───> β”‚ Flash: "hello, world\r" β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + puts() prints "hello, world" + + +AFTER OUR HACK: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ r0 = 0x20000000β”‚ ───> β”‚ SRAM: "hacky, world\r" β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + puts() prints "hacky, world" +``` + +#### New GDB Commands We Learned + +| Command | What It Does | New/Review | +| ---------------------------- | ----------------------------------- | ---------- | +| `target remote :3333` | Connect to OpenOCD debug server | **New** | +| `monitor reset halt` | Reset and halt the processor | **New** | +| `disas` | Disassemble the current function | Review | +| `x/Ni ADDRESS` | Examine N instructions at ADDRESS | Review | +| `x/s ADDRESS` | Examine memory as a string | Review | +| `b *ADDRESS` | Set breakpoint at exact address | Review | +| `c` | Continue execution | Review | +| `set $r0 = VALUE` | Change a register's value | **New** | +| `set {char[N]} ADDR = {...}` | Write characters directly to memory | **New** | + +#### Key Memory Addresses + +| Address | What's There | Read/Write? | Datasheet Reference | +| ------------ | -------------------------------- | ----------- | ------------------- | +| `0x10000234` | Start of `main()` function | Read-only | (binary-specific) | +| `0x1000023c` | The `bl __wrap_puts` call | Read-only | (binary-specific) | +| `0x100019cc` | Original `"hello, world"` string | Read-only | XIP region, Β§2.2 (p. 31) | +| `0x20000000` | Start of SRAM (our hack target) | Read-Write | Β§2.2.3 SRAM (p. 32) | + +--- + +## βœ… Practice Exercises + +#### Exercise 1: Change the Message +Try creating a different message! Write your name to SRAM and make the program print it: + +```gdb +(gdb) set {char[20]} 0x20000000 = {'Y','o','u','r',' ','N','a','m','e','!','\r','\0'} +(gdb) set $r0 = 0x20000000 +(gdb) c +``` + +#### Exercise 2: Use a Different SRAM Address +The SRAM region is large. Try writing your string to a different address: + +```gdb +(gdb) set {char[14]} 0x20001000 = {'h','a','c','k','e','d','!','!','!','\r','\0'} +(gdb) set $r0 = 0x20001000 +(gdb) c +``` + +#### Exercise 3: Examine Memory Around Your String +Look at the bytes around your injected string: + +```gdb +(gdb) x/20b 0x20000000 +``` + +What do you see? Can you identify each character? + +#### Exercise 4: Automate the Hack +Create a GDB command sequence that does the full hack. You can use GDB's command feature: + +```gdb +(gdb) define hack +> set {char[14]} 0x20000000 = {'h','a','c','k','y',',',' ','w','o','r','l','d','\r','\0'} +> set $r0 = 0x20000000 +> c +> end +(gdb) hack +``` + +Now you can just type `hack` each time! + +--- + +## πŸŽ“ Key Takeaways + +#### Building on Week 1 + +1. **Memory layout knowledge is power** - Understanding that flash is read-only and SRAM is read-write was essential for our hack. This directly built on Week 1's memory map lesson. + +2. **Registers control everything** - In Week 1, we watched registers change during execution. This week, we CHANGED them ourselves to alter program behavior. + +3. **GDB is a hacking tool** - The same commands we used for learning (`x`, `b`, `c`, `disas`) are the same commands used for exploitation. + +#### New Concepts + +4. **Flash is Read-Only at Runtime** - You can't modify code or constant strings in flash memory while the program runs. You must use SRAM. + +5. **Bare-Metal Means No Runtime** - Without an operating system, there's no `malloc()`, no dynamic memory allocation. You have to manage memory manually. + +6. **Registers Are the Key** - Function arguments are passed in registers (`r0`, `r1`, etc.). By changing these registers at the right moment, you can change what functions do. + +7. **Timing is Everything** - We had to set our breakpoint at exactly the right instruction. One instruction earlier, and `r0` wouldn't be loaded yet. One instruction later, and `puts()` would already have the wrong address. + +8. **This is Real Hacking** - The techniques you learned today are used by security researchers, penetration testers, and yes, attackers. Understanding these attacks helps us build more secure systems. + +--- + +## πŸ” Security Implications + +#### How Would This Work in the Real World? + +Imagine an attacker with physical access to an industrial control system: + +| Scenario | Attack | +| ---------------------- | ----------------------------------------------------------------- | +| **Nuclear Centrifuge** | Change the displayed RPM from dangerous (15,000) to safe (10,000) | +| **Medical Device** | Modify dosage readings to hide an overdose | +| **Vehicle ECU** | Alter speedometer reading while car actually speeds | +| **Smart Lock** | Change the "locked" status to "unlocked" | + +#### How Do We Defend Against This? + +1. **Disable Debug Ports** - Production devices should have JTAG/SWD disabled +2. **Secure Boot** - Verify firmware hasn't been tampered with +3. **Memory Protection** - Use ARM's MPU to restrict memory access +4. **Tamper Detection** - Hardware that detects physical intrusion +5. **Encryption** - Keep sensitive data encrypted in memory + +--- + +## πŸ“– Glossary + +#### New Terms This Week + +| Term | Definition | +| ------------------------- | ---------------------------------------------------------------------- | +| **Bare-Metal** | Programming directly on hardware without an operating system | +| **CMSIS-DAP** | A standard debug interface protocol for ARM processors | +| **Hijack** | Taking control of a value or flow that was intended for something else | +| **Hardware Breakpoint** | A breakpoint implemented in CPU hardware, works on any memory | +| **Memory Injection** | Writing attacker-controlled data into a program's memory space | +| **OpenOCD** | Open On-Chip Debugger - software that interfaces with debug hardware | +| **Register Manipulation** | Changing the values stored in CPU registers | +| **SRAM** | Static Random Access Memory - fast, volatile, read-write memory | +| **Software Breakpoint** | A breakpoint implemented by modifying code (requires writable memory) | + +#### Review Terms from Week 1 + +| Term | Definition | Where We Used It | +| ------------------- | --------------------------------------------------------- | ---------------- | +| **Breakpoint** | A marker that pauses program execution at a specific location | Part 7 - Setting the trap | +| **Register** | Fast storage inside the processor | Part 11 - Hijacking `r0` | +| **Stack Pointer** | Register that points to the top of the stack | Part 2 - Memory layout | +| **XIP** | Execute In Place - running code directly from flash | Part 2 - Why we can't write to flash | +| **Little-Endian** | Storing the least significant byte at the lowest address | Part 10 - String storage | diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG00.svg b/WEEKS/WEEK02/slides/WEEK02-IMG00.svg new file mode 100644 index 0000000..44adb6f --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 02 + + +Hello, World - Debugging and +Hacking Basics: Debugging and Hacking +a Basic Program for the Pico 2 + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG01.svg b/WEEKS/WEEK02/slides/WEEK02-IMG01.svg new file mode 100644 index 0000000..c48c43a --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG01.svg @@ -0,0 +1,73 @@ +ο»Ώ + + + + +Live Hacking Overview +Introduction to Live Hacking + + + + What Is Live Hacking? + + + Modify a program + WHILE it is running + on real hardware + + + + The Train Analogy + Train heading to NYC + Switch the tracks + while it moves + Now it goes to LA! + + + + Why It Matters + Security research + Penetration testing + Malware analysis + Hardware debugging + + No recompile needed! + + + + This Week's Goal + + + Target Program + hello-world.c + Prints "hello, world" + in infinite loop + + + + Our Mission + Make it print + something ELSE + without changing + the source code + + + + Tools Used + GDB = live debug + OpenOCD = HW bridge + Ghidra = analysis + + Hack the running binary + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG02.svg b/WEEKS/WEEK02/slides/WEEK02-IMG02.svg new file mode 100644 index 0000000..e0a46ae --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG02.svg @@ -0,0 +1,80 @@ +ο»Ώ + + + + +GDB Debug Session +GDB Fundamentals + + + + Setup Steps + + + + Step 1: Start OpenOCD + + openocd -f rp2350.cfg + + + Step 2: Launch GDB + + gdb-multiarch hello.elf + + + Step 3: Connect to target + + target remote :3333 + + + Step 4: Reset + halt + + monitor reset halt + + + Step 5: Set breakpoint + + break main + + Then: continue (c) + + + + What Each Does + + + openocd + Bridges USB probe + to GDB via TCP + Listens on port 3333 + + + + gdb-multiarch + ARM-aware debugger + Loads ELF symbols + + + + target remote + GDB connects to + OpenOCD server + + + + monitor reset halt + Reset chip + stop + at very first instr + Clean starting state + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG03.svg b/WEEKS/WEEK02/slides/WEEK02-IMG03.svg new file mode 100644 index 0000000..37fffca --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG03.svg @@ -0,0 +1,88 @@ +ο»Ώ + + + + +Breakpoints +GDB Breakpoint Types + + + + How They Work + + + + Normal Execution + + + MOV r0, #5 + + + MOV r1, #3 + + + BL printf + + + With Breakpoint + + + MOV r0, #5 + + + MOV r1, #3 + STOP + + + BL printf + paused + + CPU halts BEFORE + executing breakpoint + instruction + + Now you can inspect + + + + GDB Breakpoints + + + break main + Stop at function + By symbol name + + + + break *0x10000340 + Stop at exact addr + By hex address + + + + info break + List all active + breakpoints + + + + continue (c) + Resume running + until next break + + + + delete 1 + Remove breakpoint #1 + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG04.svg b/WEEKS/WEEK02/slides/WEEK02-IMG04.svg new file mode 100644 index 0000000..39e93e5 --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG04.svg @@ -0,0 +1,101 @@ +ο»Ώ + + + + +Stack in Action +Runtime Stack Analysis + + + + Before Call + + + SP = 0x20082000 + + + (empty) + + + (empty) + + + (empty) + + + (empty) + + Stack is clean + SP at top of + SRAM space + + Ready to call + main() + + + + After PUSH + + + PUSH {r4, lr} + + + (empty) + + + (empty) + + + LR saved + + + r4 saved + + SP moved down + by 8 bytes + 2 regs x 4 bytes + + GDB: info regs + Watch SP change! + + + + Key Points + + + PUSH saves + Preserves regs + before function + body runs + + + + POP restores + Puts values + back when func + returns + + + + Watch in GDB + x/4xw $sp + See stack data + + + + stepi + Step 1 instr + watch stack + change live + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG05.svg b/WEEKS/WEEK02/slides/WEEK02-IMG05.svg new file mode 100644 index 0000000..83ce46a --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG05.svg @@ -0,0 +1,79 @@ +ο»Ώ + + + + +LDR Instruction +ARM Load Instructions + + + + How LDR Works + + + + Instruction: + + LDR r0, [pc, #12] + + + Step 1: Calculate addr + + addr = PC + 12 + + + Step 2: Read memory + + value = *(addr) + + + Step 3: Load into reg + + r0 = value + + + r0 now holds the + address of our + "hello, world" string + + + + Why It Matters + + + String Loading + printf needs addr + of string in r0 + r0 = first argument + + + + PC-Relative + Address computed + relative to current + PC position + Works from any addr + + + + The Attack Point + If we change r0 + AFTER the LDR + printf prints OUR + string instead! + + + + This is the hack! + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG06.svg b/WEEKS/WEEK02/slides/WEEK02-IMG06.svg new file mode 100644 index 0000000..52029b8 --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG06.svg @@ -0,0 +1,93 @@ +ο»Ώ + + + + +The Attack Plan +Exploit Strategy + + + + Attack Flow (4 Steps) + + + + + 1. Break at + printf call + + + + + + + + 2. Write new + string to SRAM + + + + + + + + 3. Set r0 to + SRAM addr + + + + + + + + 4. Continue + execution + + printf reads r0, prints "hacky, world"! + + + + Normal Flow + + + + LDR r0, ="hello" + + + BL printf + + Output: + "hello, world" + + Prints original string + + + + Hacked Flow + + + + LDR r0, ="hello" + + + r0 = 0x20040000 + + + BL printf + + Output: + "hacky, world" + + Prints our string + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG07.svg b/WEEKS/WEEK02/slides/WEEK02-IMG07.svg new file mode 100644 index 0000000..984e441 --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG07.svg @@ -0,0 +1,80 @@ +ο»Ώ + + + + +Failed vs Real Hack +Attack Methodology + + + + Failed Attempt + + + The Bad Idea + Set r0 to point + at a string literal + like "hacky" + + + + Why It Fails + r0 only holds a + 32-bit number + Not a string itself! + + + + set $r0 = "HACK" + GDB interprets this + as an address value + pointing to garbage + + + + Result: CRASH + or prints garbage + + + + Real Hack + + + The Right Way + 1. Write string + bytes to SRAM + 2. Point r0 to + that SRAM addr + + + + GDB Commands + + + set {char[13]}0x20040000 + + + = "hacky, world" + + + set $r0 = 0x20040000 + + + + String exists in + writable SRAM + r0 points to it + + "hacky, world" printed! + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG08.svg b/WEEKS/WEEK02/slides/WEEK02-IMG08.svg new file mode 100644 index 0000000..f9626eb --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG08.svg @@ -0,0 +1,83 @@ +ο»Ώ + + + + +Writing to SRAM +Memory Manipulation + + + + SRAM at 0x20040000 + + + + Before (empty) + + + 00 00 00 00 00 00 00 00 + + + 00 00 00 00 00 00 00 00 + + + After writing + + + 68 61 63 6b 79 2c 20 77 + + h a c k y , w + + + GDB Command: + + + set {char[13]} + 0x20040000 = "hacky, world" + + + Verify with: + + x/s 0x20040000 + + + + Why SRAM? + + + SRAM = writable + RAM at 0x20000000 + We can write any + data here via GDB + + + + Flash = read-only + XIP at 0x10000000 + Cannot write to it + during execution + That's why we use RAM + + + + Choosing Address + 0x20040000 is safe + Far from stack + and heap regions + + + + Null terminator + \0 ends the string + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG09.svg b/WEEKS/WEEK02/slides/WEEK02-IMG09.svg new file mode 100644 index 0000000..4cf346e --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG09.svg @@ -0,0 +1,77 @@ +ο»Ώ + + + + +Register Hijack +Control Flow Attack + + + + Before Hijack + + + r0 loaded by LDR: + + + r0 = 0x10001234 + + Points to flash: + + + "hello, world\r\n" + + printf will read r0 + and print that string + + + + The Hijack Command + + + set $r0 = 0x20040000 + + Now r0 points to + OUR string in SRAM + instead of flash + + + + After Hijack + + + r0 now contains: + + + r0 = 0x20040000 + + Points to SRAM: + + + "hacky, world" + + + + Then: continue + printf reads r0 + Follows pointer + to 0x20040000 + Finds "hacky, world" + Prints it! + + + + Output changed + without touching code + \ No newline at end of file diff --git a/WEEKS/WEEK02/slides/WEEK02-IMG10.svg b/WEEKS/WEEK02/slides/WEEK02-IMG10.svg new file mode 100644 index 0000000..3582678 --- /dev/null +++ b/WEEKS/WEEK02/slides/WEEK02-IMG10.svg @@ -0,0 +1,75 @@ +ο»Ώ + + + + +GDB vs Ghidra +Static vs Dynamic Analysis + + + + GDB (Dynamic) + + + Live analysis + Program is running + on real hardware + + + + Capabilities + Set breakpoints + Read/write memory + Modify registers + Step instructions + Watch values change + + + + Best For + Live modification + Runtime behavior + Testing exploits + Verifying attacks + + Needs running target + + + + Ghidra (Static) + + + Offline analysis + Just the binary file + No hardware needed + + + + Capabilities + Disassembly view + Decompile to C + Find functions + Cross-references + String search + + + + Best For + Planning attacks + Understanding code + Finding targets + Mapping functions + + Works with just ELF + \ No newline at end of file diff --git a/WEEKS/WEEK03/WEEK03-01-S.md b/WEEKS/WEEK03/WEEK03-01-S.md new file mode 100644 index 0000000..9a5c66b --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-01-S.md @@ -0,0 +1,47 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 1 Solution: Trace a Reset + +#### Answers + +##### Instruction Trace (First 10 Instructions from 0x1000015c) + +| # | Address | Instruction | What It Does | Key Register Change | +|---|-------------|----------------------------------------|------------------------------------------------------|---------------------| +| 1 | 0x1000015c | `mov.w r0, #0xd0000000` | Loads SIO base address into r0 | r0 = 0xd0000000 | +| 2 | 0x10000160 | `ldr r0, [r0, #0]` | Reads CPUID register at SIO base + 0 | r0 = 0 (Core 0) | +| 3 | 0x10000162 | `cbz r0, 0x1000016a` | If CPUID == 0 (Core 0), branch to data copy section | PC = 0x1000016a | +| 4 | 0x1000016a | `ldr r0, [pc, #...]` | Loads source address for data copy (flash) | r0 = flash addr | +| 5 | 0x1000016c | `ldr r1, [pc, #...]` | Loads destination address for data copy (RAM) | r1 = RAM addr | +| 6 | 0x1000016e | `ldr r2, [pc, #...]` | Loads end address for data copy | r2 = end addr | +| 7 | 0x10000170 | `cmp r1, r2` | Checks if all data has been copied | Flags updated | +| 8 | 0x10000172 | `bhs 0x10000178` | If done (dest >= end), skip to BSS clear | PC conditional | +| 9 | 0x10000174 | `ldm r0!, {r3}` | Loads 4 bytes from flash source, auto-increments r0 | r3 = data, r0 += 4 | +| 10| 0x10000176 | `stm r1!, {r3}` | Stores 4 bytes to RAM destination, auto-increments r1| r1 += 4 | + +##### GDB Session + +```gdb +(gdb) b *0x1000015c +(gdb) monitor reset halt +(gdb) c +Breakpoint 1, 0x1000015c in _reset_handler () +(gdb) si +(gdb) disas $pc,+2 +(gdb) info registers r0 +``` + +#### Reflection Answers + +1. **Why does the reset handler check the CPUID before doing anything else?** + The RP2350 has two Cortex-M33 cores that share the same reset handler entry point. The CPUID check at SIO base `0xd0000000` returns 0 for Core 0 and 1 for Core 1. Only Core 0 should perform the one-time initialization (data copy, BSS clear, calling `main()`). Without this check, both cores would simultaneously try to initialize RAM, causing data corruption and race conditions. + +2. **What would happen if Core 1 tried to run the same initialization code as Core 0?** + Both cores would simultaneously write to the same RAM locations during the data copy and BSS clear phases. This would cause data corruption due to race conditionsβ€”values could be partially written or overwritten unpredictably. The `cbz` instruction at `0x10000162` prevents this: if CPUID != 0, the code branches to `hold_non_core0_in_bootrom`, which sends Core 1 back to the bootrom to wait until Core 0 explicitly launches it later. + +3. **Which registers are used in the first 10 instructions, and why those specific ones?** + The first 10 instructions use `r0`, `r1`, `r2`, and `r3`. These are the ARM "caller-saved" scratch registers (r0–r3) that don't need to be preserved across function calls. Since the reset handler is the very first code to run (no caller to preserve state for), using these low registers is both efficient (16-bit Thumb encodings) and safe. `r0` handles CPUID check and source pointer, `r1` is the destination pointer, `r2` is the end marker, and `r3` is the data transfer register. diff --git a/WEEKS/WEEK03/WEEK03-01.md b/WEEKS/WEEK03/WEEK03-01.md new file mode 100644 index 0000000..0a04ff6 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-01.md @@ -0,0 +1,130 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 1: Trace a Reset + +#### Objective +Single-step through the first 10 instructions of the reset handler to understand exactly what happens when the RP2350 powers on or resets. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD and `arm-none-eabi-gdb` available in your PATH +- `build\0x0001_hello-world.elf` present and flashed to the board +- Week 3 environment setup completed (OpenOCD running, GDB connected) + +#### Task Description +You will set a breakpoint at the reset handler (`0x1000015c`), trigger a reset, and step through each instruction one at a time while documenting what each instruction does. + +#### Step-by-Step Instructions + +##### Step 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" +``` + +##### Step 2: Launch GDB + +```powershell +arm-none-eabi-gdb build\0x0001_hello-world.elf +``` + +##### Step 3: Connect to Target + +```gdb +(gdb) target extended-remote :3333 +``` + +##### Step 4: Set Breakpoint at Reset Handler + +```gdb +(gdb) b *0x1000015c +``` + +**What this does:** Places a breakpoint at the very first instruction of the reset handler (the entry point after bootrom). + +##### Step 5: Reset and Break + +```gdb +(gdb) monitor reset halt +(gdb) c +``` + +**What this does:** +- `monitor reset halt` resets the chip and immediately halts it +- `c` continues execution until the breakpoint at the reset handler is hit + +##### Step 6: Single-Step Through Instructions + +Now step through the first 10 instructions, one at a time: + +```gdb +(gdb) si +(gdb) disas $pc,+2 +(gdb) info registers r0 +``` + +Repeat `si` nine more times, examining each instruction. + +**Example of what you'll see:** + +**Instruction 1:** +``` +0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000 +``` +**What it does:** Loads the SIO base address (0xd0000000) into r0 + +**Instruction 2:** +``` +0x10000160 <_reset_handler+4>: ldr r0, [r0, #0] +``` +**What it does:** Reads the CPUID register to determine which core is running + +**Instruction 3:** +``` +0x10000162 <_reset_handler+6>: cbz r0, 0x1000016a +``` +**What it does:** If CPUID is 0 (Core 0), branch ahead to continue boot; otherwise handle Core 1 + +##### Step 7: Document Your Observations + +For each of the 10 instructions: +1. Write down the address +2. Write down the assembly instruction +3. Explain what it does +4. Note any register changes using `info registers` + +#### Expected Output +- You should see the reset handler check which core is running +- If you're on Core 0, you'll see it jump to the data copy section +- Register `r0` will contain CPUID value (should be 0) +- PC (program counter) advances with each `si` command + +#### Questions for Reflection + +###### Question 1: Why does the reset handler check the CPUID before doing anything else? + +###### Question 2: What would happen if Core 1 tried to run the same initialization code as Core 0? + +###### Question 3: Which registers are used in the first 10 instructions, and why those specific ones? + +#### Tips and Hints +- Use `disas $pc,+20` to see upcoming instructions without stepping through them +- Use `info registers` to see all register values at any point +- If you step past where you wanted to stop, just `monitor reset halt` and start over +- Keep notes as you go—this is detective work! + +#### Next Steps +- Try stepping all the way through to the data copy loop +- Set a breakpoint at `0x1000016c` (the data copy loop) and continue there directly +- Move on to Exercise 2 to calculate the stack size from the vector table + +#### Additional Challenge +Set a breakpoint at `0x10000178` (the BSS clear phase) and continue execution to see how the reset handler transitions from data copying to BSS clearing. diff --git a/WEEKS/WEEK03/WEEK03-02-S.md b/WEEKS/WEEK03/WEEK03-02-S.md new file mode 100644 index 0000000..4bf4e4e --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-02-S.md @@ -0,0 +1,76 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 2 Solution: Find the Stack Size + +#### Answers + +##### Initial Stack Pointer + +```gdb +(gdb) x/x 0x10000000 +0x10000000 <__vectors>: 0x20082000 +``` + +The initial stack pointer is **0x20082000**, stored as the first entry in the vector table. + +##### Stack Limit + +The stack limit is **0x20078000**, defined by the linker script. + +```gdb +(gdb) info symbol __StackLimit +__StackLimit in section .stack +``` + +##### Stack Size Calculation + +| Value | Hex | Decimal | +|-----------------|-------------|---------------| +| Stack Top | 0x20082000 | 537,108,480 | +| Stack Limit | 0x20078000 | 537,067,520 | +| **Stack Size** | 0x0000A000 | **40,960 bytes** | + +``` +Stack Size = 0x20082000 - 0x20078000 = 0xA000 = 40,960 bytes +40,960 Γ· 1,024 = 40 KB +``` + +##### Memory Region Verification + +| Region | Start | End | Size | +|-----------|-------------|-------------|--------| +| RAM | 0x20000000 | 0x20080000 | 512 KB | +| SCRATCH_X | 0x20080000 | 0x20081000 | 4 KB | +| SCRATCH_Y | 0x20081000 | 0x20082000 | 4 KB | +| **Stack** | 0x20078000 | 0x20082000 | **40 KB** | + +The stack spans from the upper portion of main RAM through SCRATCH_X and into SCRATCH_Y. + +##### Runtime Stack Usage at main() + +```gdb +(gdb) b main +(gdb) c +(gdb) info registers sp +sp 0x20081fc8 0x20081fc8 +``` + +Stack used at `main()` entry: `0x20082000 - 0x20081fc8 = 0x38 = 56 bytes` + +#### Reflection Answers + +1. **Why is the stack 40 KB instead of just fitting in the 4 KB SCRATCH_Y region?** + The stack pointer is initialized at the top of SCRATCH_Y (`0x20082000`), but the stack grows **downward**. As functions are called and local variables are allocated, the stack pointer decreases past the SCRATCH_Y boundary (`0x20081000`), through SCRATCH_X, and into the upper portion of main RAM. The linker sets the stack limit at `0x20078000` to give the stack 40 KB of room, which is sufficient for typical embedded applications with nested function calls. + +2. **What happens if the stack grows beyond 0x20078000?** + A **stack overflow** occurs. The stack would collide with the heap or global data stored in the lower portion of RAM. This can corrupt variables, overwrite heap metadata, or cause a HardFault if memory protection is enabled. On the RP2350 with Cortex-M33 MPU support, a MemManage fault could be triggered if stack guard regions are configured. + +3. **How would you detect a stack overflow during runtime?** + Several methods exist: (a) Write a known "canary" pattern (e.g., `0xDEADBEEF`) at the stack limit and periodically check if it's been overwritten. (b) Use the Cortex-M33 MPU to set a guard region at `0x20078000` that triggers a MemManage fault on access. (c) In GDB, use a watchpoint: `watch *(int*)0x20078000` to break if the stack reaches the limit. (d) Use the `stackusage` GDB macro from the exercise to monitor usage. + +4. **Why does the stack grow downward instead of upward?** + This is an ARM architecture convention inherited from early processor designs. Growing downward allows the stack and heap to grow toward each other from opposite ends of RAM, maximizing memory utilization without needing to know each region's size in advance. The stack starts at the highest address and grows down, while the heap starts at a lower address and grows up. If they meet, memory is exhaustedβ€”but this layout gives both regions the maximum possible space. diff --git a/WEEKS/WEEK03/WEEK03-02.md b/WEEKS/WEEK03/WEEK03-02.md new file mode 100644 index 0000000..e2e837e --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-02.md @@ -0,0 +1,167 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 2: Find the Stack Size + +#### Objective +Calculate the size of the stack by examining the vector table, understanding the linker script's memory layout, and performing manual calculations. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD and `arm-none-eabi-gdb` available +- `build\0x0001_hello-world.elf` flashed to the board +- Understanding of memory regions from Week 3 Part 5 (Linker Script) + +#### Task Description +You will examine the initial stack pointer value from the vector table, identify the stack limit, calculate the total stack size in bytes and kilobytes, and verify your calculations. + +#### Background Information + +From the Week 3 lesson, we learned: +- The initial stack pointer is stored at `0x10000000` (first entry in vector table) +- The linker script defines: `SCRATCH_Y: ORIGIN = 0x20081000, LENGTH = 4k` +- Stack top is calculated as: `ORIGIN + LENGTH = 0x20082000` +- The stack grows downward from high addresses to low addresses + +#### Step-by-Step Instructions + +##### Step 1: Connect and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Examine the Initial Stack Pointer + +```gdb +(gdb) x/x 0x10000000 +``` + +**Expected output:** +``` +0x10000000 <__vectors>: 0x20082000 +``` + +This is the **top of the stack** (where the stack starts before growing downward). + +##### Step 3: Find the Stack Limit + +The stack limit is defined in the linker script and can be found by examining stack-related symbols or calculating from memory regions. + +From the Week 3 lesson, the stack limit is `0x20078000`. + +You can verify this in GDB: + +```gdb +(gdb) info symbol __StackLimit +``` + +or check registers after boot: + +```gdb +(gdb) info registers +``` + +Look for stack limit values or calculate: The main RAM starts at `0x20000000`, and SCRATCH_Y starts at `0x20081000`. + +##### Step 4: Calculate Stack Size in Bytes + +**Formula:** +``` +Stack Size = Stack Top - Stack Limit +Stack Size = 0x20082000 - 0x20078000 +``` + +Let's convert to decimal: +- `0x20082000` = 537,108,480 decimal +- `0x20078000` = 537,067,520 decimal +- Difference = 40,960 bytes + +**Alternative hex calculation:** +``` +0x20082000 +- 0x20078000 +----------- +0x0000A000 = 40,960 bytes +``` + +##### Step 5: Convert to Kilobytes + +``` +Bytes to KB = 40,960 χ 1,024 = 40 KB +``` + +So the stack is **40 KB** in size. + +##### Step 6: Verify Using Memory Regions + +Cross-check with the memory layout: +- **RAM**: `0x20000000` - `0x20080000` (512 KB) +- **SCRATCH_X**: `0x20080000` - `0x20081000` (4 KB) +- **SCRATCH_Y**: `0x20081000` - `0x20082000` (4 KB) ? Stack lives here +- **Stack range**: `0x20078000` - `0x20082000` (40 KB) + +The stack extends from SCRATCH_Y down into the upper portion of main RAM. + +##### Step 7: Examine Stack Usage at Runtime + +You can see the current stack pointer value: + +```gdb +(gdb) b main +(gdb) c +(gdb) info registers sp +``` + +**Expected output:** +``` +sp 0x20081fc8 0x20081fc8 +``` + +This shows the stack has used: +``` +0x20082000 - 0x20081fc8 = 0x38 = 56 bytes +``` + +#### Expected Output +- Initial stack pointer: `0x20082000` +- Stack limit: `0x20078000` +- Stack size: **40,960 bytes** or **40 KB** +- Current stack usage (at main): approximately 56 bytes + +#### Questions for Reflection + +###### Question 1: Why is the stack 40 KB instead of just fitting in the 4 KB SCRATCH_Y region? + +###### Question 2: What happens if the stack grows beyond 0x20078000? + +###### Question 3: How would you detect a stack overflow during runtime? + +###### Question 4: Why does the stack grow downward instead of upward? + +#### Tips and Hints +- Use Windows Calculator in Programmer mode to convert hex to decimal +- Remember: 1 KB = 1,024 bytes (not 1,000) +- The stack pointer (SP) decreases as the stack grows (push operations) +- Use `bt` (backtrace) in GDB to see how much stack is currently in use + +#### Next Steps +- Monitor the stack pointer as you step through functions to see it change +- Calculate stack usage for specific function calls +- Move on to Exercise 3 to examine all vector table entries + +#### Additional Challenge +Write a GDB command to automatically calculate and display stack usage: + +```gdb +(gdb) define stackusage +> set $used = 0x20082000 - $sp +> printf "Stack used: %d bytes (%d KB)\n", $used, $used/1024 +> end + +(gdb) stackusage +``` diff --git a/WEEKS/WEEK03/WEEK03-03-S.md b/WEEKS/WEEK03/WEEK03-03-S.md new file mode 100644 index 0000000..3b496c2 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-03-S.md @@ -0,0 +1,78 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 3 Solution: Examine All Vectors + +#### Answers + +##### Vector Table Dump + +```gdb +(gdb) x/16x 0x10000000 +0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d +0x10000010 <__vectors+16>: 0x1000011f 0x10000121 0x10000123 0x00000000 +0x10000020 <__vectors+32>: 0x00000000 0x00000000 0x00000000 0x10000125 +0x10000030 <__vectors+48>: 0x00000000 0x00000000 0x10000127 0x10000129 +``` + +##### Complete Vector Table Map + +| Offset | Vector # | Raw Value | Address Type | Actual Addr | Handler Name | +|--------|----------|-------------|---------------|-------------|------------------| +| 0x00 | β€” | 0x20082000 | Stack Pointer | N/A | __StackTop | +| 0x04 | 1 | 0x1000015d | Code (Thumb) | 0x1000015c | _reset_handler | +| 0x08 | 2 | 0x1000011b | Code (Thumb) | 0x1000011a | isr_nmi | +| 0x0C | 3 | 0x1000011d | Code (Thumb) | 0x1000011c | isr_hardfault | +| 0x10 | 4 | 0x1000011f | Code (Thumb) | 0x1000011e | isr_memmanage | +| 0x14 | 5 | 0x10000121 | Code (Thumb) | 0x10000120 | isr_busfault | +| 0x18 | 6 | 0x10000123 | Code (Thumb) | 0x10000122 | isr_usagefault | +| 0x1C | 7 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x20 | 8 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x24 | 9 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x28 | 10 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x2C | 11 | 0x10000125 | Code (Thumb) | 0x10000124 | isr_svcall | +| 0x30 | 12 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x34 | 13 | 0x00000000 | Reserved | N/A | (Reserved) | +| 0x38 | 14 | 0x10000127 | Code (Thumb) | 0x10000126 | isr_pendsv | +| 0x3C | 15 | 0x10000129 | Code (Thumb) | 0x10000128 | isr_systick | + +##### Handler Verification + +```gdb +(gdb) info symbol 0x1000015c +_reset_handler in section .text +(gdb) info symbol 0x1000011a +isr_nmi in section .text +(gdb) x/3i 0x1000011a +0x1000011a : bkpt 0x0000 +0x1000011c : bkpt 0x0000 +0x1000011e : bkpt 0x0000 +``` + +Most default handlers are single `bkpt` instructionsβ€”they halt the processor if triggered unexpectedly. + +##### Summary Statistics + +| Category | Count | +|---------------------|-------| +| Stack Pointer entry | 1 | +| Valid code entries | 10 | +| Reserved (0x0) | 5 | +| **Total entries** | **16** | + +#### Reflection Answers + +1. **Why do all the code addresses end in odd numbers (LSB = 1)?** + ARM Cortex-M processors exclusively execute in Thumb mode. The least significant bit (LSB) of vector table entries indicates the instruction set: LSB = 1 means Thumb mode. Since Cortex-M33 only supports Thumb/Thumb-2, all code addresses must have bit 0 set to 1. The actual instruction address is the value with bit 0 cleared (e.g., `0x1000015d` β†’ instruction at `0x1000015c`). This is a hardware requirementβ€”loading an even address into the PC on Cortex-M would cause a HardFault. + +2. **What happens if an exception occurs for a reserved/null vector entry?** + If the processor attempts to invoke a handler through a vector entry containing `0x00000000`, it tries to fetch instructions from address `0x00000000` (in the bootrom region). This would either execute bootrom code unexpectedly or trigger a HardFault because the address lacks the Thumb bit (LSB = 0). In practice, a HardFault would occur, and if the HardFault handler itself is invalid, the processor enters a lockup state. + +3. **Why do most exception handlers just contain `bkpt` instructions?** + The Pico SDK provides these as **default weak handlers**. They are intentionally minimalβ€”a `bkpt` (breakpoint) instruction halts the processor immediately so a debugger can catch the event. This is a safety mechanism: rather than letting an unhandled exception corrupt state or silently continue, the default handlers stop execution so the developer can diagnose the problem. Application code can override any handler by defining a function with the matching name (the weak linkage gets replaced). + +4. **How would you replace a default handler with your own custom handler?** + Define a C function with the exact handler name (e.g., `void isr_hardfault(void)`) in your application code. Because the SDK declares these handlers as `__attribute__((weak))`, the linker will use your strong definition instead of the default `bkpt` stub. The new function's address (with Thumb bit set) will automatically appear in the vector table at the correct offset. No linker script modification is needed. diff --git a/WEEKS/WEEK03/WEEK03-03.md b/WEEKS/WEEK03/WEEK03-03.md new file mode 100644 index 0000000..c47c460 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-03.md @@ -0,0 +1,209 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 3: Examine All Vectors + +#### Objective +Examine the first 16 entries of the vector table to understand the exception handler layout, identify valid code addresses, and recognize the Thumb mode addressing convention. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD and `arm-none-eabi-gdb` available +- `build\0x0001_hello-world.elf` loaded +- Understanding of the vector table from Week 3 Part 4 +- Knowledge of Thumb mode addressing (LSB = 1 indicates Thumb code) + +#### Task Description +You will examine 16 consecutive 32-bit values from the vector table, decode each entry, determine if it's a valid code address, and identify which exception handler it points to. + +#### Background Information + +The ARM Cortex-M vector table structure: + +| Offset | Vector # | Handler Name | Purpose | +|--------|----------|---------------------|---------| +| 0x00 | - | Initial SP | Stack pointer initialization | +| 0x04 | 1 | Reset | Power-on/reset entry point | +| 0x08 | 2 | NMI | Non-Maskable Interrupt | +| 0x0C | 3 | HardFault | Serious errors | +| 0x10 | 4 | MemManage | Memory protection fault | +| 0x14 | 5 | BusFault | Bus error | +| 0x18 | 6 | UsageFault | Undefined instruction, etc. | +| 0x1C-0x28 | 7-10 | Reserved | Not used | +| 0x2C | 11 | SVCall | Supervisor call | +| 0x30 | 12 | Debug Monitor | Debug events | +| 0x34 | 13 | Reserved | Not used | +| 0x38 | 14 | PendSV | Pendable service call | +| 0x3C | 15 | SysTick | System tick timer | + +#### Step-by-Step Instructions + +##### Step 1: Connect and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Examine 16 Vector Table Entries + +```gdb +(gdb) x/16x 0x10000000 +``` + +**Expected output (example):** +``` +0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d +0x10000010 <__vectors+16>: 0x1000011f 0x10000121 0x10000123 0x00000000 +0x10000020 <__vectors+32>: 0x00000000 0x00000000 0x00000000 0x10000125 +0x10000030 <__vectors+48>: 0x00000000 0x00000000 0x10000127 0x10000129 +``` + +##### Step 3: Analyze Each Entry + +Create a table documenting each entry: + +**Entry 1 (Offset 0x00):** +``` +Address: 0x10000000 +Value: 0x20082000 +Valid Code Address? NO - This is the stack pointer (in RAM region 0x2xxxxxxx) +Handler: Initial Stack Pointer +``` + +**Entry 2 (Offset 0x04):** +``` +Address: 0x10000004 +Value: 0x1000015d +Valid Code Address? YES (starts with 0x1000...) +Thumb Mode? YES (LSB = 1, so actual address is 0x1000015c) +Handler: Reset Handler (_reset_handler) +``` + +**Entry 3 (Offset 0x08):** +``` +Address: 0x10000008 +Value: 0x1000011b +Valid Code Address? YES +Thumb Mode? YES (actual address: 0x1000011a) +Handler: NMI Handler (isr_nmi) +``` + +Continue this analysis for all 16 entries... + +##### Step 4: Verify Handlers with Symbols + +For each code address, check what function it points to: + +```gdb +(gdb) info symbol 0x1000015c +``` + +**Expected output:** +``` +_reset_handler in section .text +``` + +Repeat for other addresses: + +```gdb +(gdb) info symbol 0x1000011a +(gdb) info symbol 0x1000011c +(gdb) info symbol 0x1000011e +``` + +##### Step 5: Examine Handler Code + +Look at the actual code at each handler: + +```gdb +(gdb) x/3i 0x1000011a +``` + +**Expected output for NMI handler:** +``` +0x1000011a : bkpt 0x0000 +0x1000011c : bkpt 0x0000 +0x1000011e : bkpt 0x0000 +``` + +##### Step 6: Identify Reserved Entries + +Note any entries with value `0x00000000`: + +``` +0x00000000 = Reserved/Unused vector +``` + +These slots are reserved by ARM and not used on Cortex-M33. + +##### Step 7: Create a Complete Map + +Document all 16 entries in this format: + +| Offset | Value | Address Type | Actual Addr | Handler Name | +|--------|------------|--------------|-------------|------------------| +| 0x00 | 0x20082000 | Stack Ptr | N/A | __StackTop | +| 0x04 | 0x1000015d | Code (Thumb) | 0x1000015c | _reset_handler | +| 0x08 | 0x1000011b | Code (Thumb) | 0x1000011a | isr_nmi | +| 0x0C | 0x1000011d | Code (Thumb) | 0x1000011c | isr_hardfault | +| ... | ... | ... | ... | ... | + +#### Expected Output +- First entry is the stack pointer in RAM (0x2xxxxxxx range) +- Entries 2-16 are mostly code addresses in flash (0x1000xxxx range) +- Code addresses have LSB = 1 (Thumb mode indicator) +- Reserved entries show 0x00000000 +- Most handlers point to simple `bkpt` instructions (default handlers) + +#### Questions for Reflection + +###### Question 1: Why do all the code addresses end in odd numbers (LSB = 1)? + +###### Question 2: What happens if an exception occurs for a reserved/null vector entry? + +###### Question 3: Why do most exception handlers just contain `bkpt` instructions? + +###### Question 4: How would you replace a default handler with your own custom handler? + +#### Tips and Hints +- Use `x/32x 0x10000000` to see even more vectors (up to 48) +- Remember to subtract 1 from addresses before disassembling (remove Thumb bit) +- Use `info functions` to see all available handler symbols +- Compare GDB output with Ghidra's vector table view + +#### Next Steps +- Set breakpoints at different exception handlers to see if they're ever called +- Trigger a fault intentionally to see which handler executes +- Move on to Exercise 4 to analyze your main function + +#### Additional Challenge +Write a GDB script to automatically decode and display all vector table entries: + +```gdb +(gdb) define vectors +> set $i = 0 +> while $i < 16 +> set $addr = 0x10000000 + ($i * 4) +> set $val = *(int*)$addr +> printf "[%2d] 0x%08x: 0x%08x", $i, $addr, $val +> if $i == 0 +> printf " (Stack Pointer)\n" +> else +> if $val != 0 +> if ($val & 0x10000000) == 0x10000000 +> printf " -> 0x%08x\n", $val & 0xFFFFFFFE +> else +> printf " (Invalid/Reserved)\n" +> end +> else +> printf " (Reserved)\n" +> end +> end +> set $i = $i + 1 +> end +> end +``` diff --git a/WEEKS/WEEK03/WEEK03-04-S.md b/WEEKS/WEEK03/WEEK03-04-S.md new file mode 100644 index 0000000..8645a21 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-04-S.md @@ -0,0 +1,94 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 4 Solution: Find Your Main Function and Trace Back + +#### Answers + +##### Main Function Location + +```gdb +(gdb) info functions main +0x10000234 int main(void); +``` + +`main()` is at address **0x10000234**. + +##### Disassembly of main() + +```gdb +(gdb) x/10i 0x10000234 +0x10000234
: push {r7, lr} +0x10000236 : sub sp, #8 +0x10000238 : add r7, sp, #0 +0x1000023a : bl 0x100012c4 +0x1000023e : movw r0, #404 @ 0x194 +0x10000242 : movt r0, #4096 @ 0x1000 +0x10000246 : bl 0x1000023c <__wrap_puts> +0x1000024a : b.n 0x1000023e +``` + +##### First Function Call + +The first function call is at offset +6: +``` +0x1000023a : bl 0x100012c4 +``` + +`stdio_init_all()` initializes all standard I/O systems (USB CDC, UART) so `printf()` and `puts()` can output to the serial console. + +##### Link Register (Caller Identification) + +```gdb +(gdb) b main +(gdb) c +(gdb) info registers lr +lr 0x1000018b 268435851 +``` + +LR = **0x1000018b** (Thumb address), actual return address = **0x1000018a**. + +##### Caller Disassembly + +```gdb +(gdb) x/10i 0x10000186 +0x10000186 : ldr r1, [pc, #80] +0x10000188 : blx r1 β†’ calls runtime_init() +0x1000018a : ldr r1, [pc, #80] β†’ LR points here (return from main) +0x1000018c : blx r1 β†’ THIS called main() +0x1000018e : ldr r1, [pc, #80] +0x10000190 : blx r1 β†’ calls exit() +0x10000192 : bkpt 0x0000 +``` + +##### Complete Boot Chain + +``` +Power On + β†’ Bootrom (0x00000000) + β†’ Vector Table (0x10000000) + β†’ _reset_handler (0x1000015c) + β†’ Data Copy & BSS Clear + β†’ platform_entry (0x10000186) + β†’ runtime_init() (first blx) + β†’ main() (second blx) ← 0x10000234 + β†’ stdio_init_all() (first call in main) + β†’ puts() loop (infinite) +``` + +#### Reflection Answers + +1. **Why does the link register point 4 bytes after the `blx` instruction that called main?** + The LR stores the **return address**β€”the instruction to execute after `main()` returns. The `blx r1` instruction at `0x10000188` (which calls `runtime_init`) is 2 bytes, and the `blx r1` at `0x1000018c` (which calls `main`) is also 2 bytes. The LR is set to the instruction immediately following the `blx` that called `main()`, which is `0x1000018a` (the `ldr` after `runtime_init`'s call). Actually, `platform_entry+4` at `0x1000018a` is where execution resumes, and the actual `blx` that calls main is at `+6` (`0x1000018c`), so LR = `0x1000018e` + Thumb bit. The key point: LR always points to the next instruction after the branch, so the caller can resume where it left off. + +2. **What would happen if `main()` tried to return (instead of looping forever)?** + Execution would return to `platform_entry` at the address stored in LR. Looking at the disassembly, `platform_entry` would proceed to execute the third `blx r1` at offset +10, which calls `exit()`. The `exit()` function would perform cleanup and ultimately halt the processor. After `exit()`, there's a `bkpt` instruction as a safety net. In practice on bare-metal embedded systems, returning from `main()` is generally avoided because there's no OS to return to. + +3. **How can you tell from the disassembly that main contains an infinite loop?** + The instruction at `0x1000024a` is `b.n 0x1000023e `, which is an **unconditional branch** back to the `movw r0, #404` instruction that loads the string address. This is a `b` (branch) with no condition code, meaning it always jumps backward. There is no path that reaches the end of the function or a `pop {r7, pc}` (return). The `push {r7, lr}` at the start saves the return address but it's never restoredβ€”the function loops forever. + +4. **Why is `stdio_init_all()` called before the printf loop?** + `stdio_init_all()` configures the hardware interfaces (USB CDC and/or UART) that `printf()`/`puts()` uses for output. Without this initialization, the serial output drivers are not set upβ€”writes to stdout would go nowhere or cause a fault. It must be called exactly once before any I/O operation. Calling it inside the loop would repeatedly reinitialize the hardware, wasting time and potentially disrupting active connections. diff --git a/WEEKS/WEEK03/WEEK03-04.md b/WEEKS/WEEK03/WEEK03-04.md new file mode 100644 index 0000000..4d0d8d6 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03-04.md @@ -0,0 +1,238 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 3 +Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +### Non-Credit Practice Exercise 4: Find Your Main Function and Trace Back + +#### Objective +Locate the `main()` function, examine its first instructions, identify the first function call, and trace backward to discover where `main()` was called from. + +#### Prerequisites +- Raspberry Pi Pico 2 with debug probe connected +- OpenOCD and `arm-none-eabi-gdb` available +- `build\0x0001_hello-world.elf` loaded +- Understanding of function calls and the link register (LR) from previous weeks + +#### Task Description +You will use GDB to find `main()`, examine its disassembly, identify the initial function call (`stdio_init_all`), and use the link register to trace backward through the boot sequence. + +#### Background Information + +Key concepts: +- **Link Register (LR)**: Stores the return address when a function is called +- **Program Counter (PC)**: Points to the currently executing instruction +- **Function prologue**: The setup code at the start of every function +- **bl instruction**: "Branch with Link" - calls a function and stores return address in LR + +#### Step-by-Step Instructions + +##### Step 1: Connect and Halt + +```gdb +(gdb) target extended-remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the Main Function + +```gdb +(gdb) info functions main +``` + +**Expected output:** +``` +All functions matching regular expression "main": + +File 0x0001_hello-world.c: +0x10000234 int main(void); + +Non-debugging symbols: +0x10000186 platform_entry_arm_a +... +``` + +Note the address of `main`: **`0x10000234`** + +##### Step 3: Examine Instructions at Main + +```gdb +(gdb) x/10i 0x10000234 +``` + +**Expected output:** +``` +0x10000234
: push {r7, lr} +0x10000236 : sub sp, #8 +0x10000238 : add r7, sp, #0 +0x1000023a : bl 0x100012c4 +0x1000023e : movw r0, #404 @ 0x194 +0x10000242 : movt r0, #4096 @ 0x1000 +0x10000246 : bl 0x1000023c <__wrap_puts> +0x1000024a : b.n 0x1000023e +0x1000024c : push {r3, r4, r5, r6, r7, lr} +``` + +##### Step 4: Identify the First Function Call + +The first function call in `main()` is: +``` +0x1000023a : bl 0x100012c4 +``` + +**What does this function do?** + +```gdb +(gdb) info functions stdio_init_all +``` + +**Answer:** `stdio_init_all()` initializes all standard I/O systems (USB, UART, etc.) so `printf()` works. + +##### Step 5: Set a Breakpoint at Main + +```gdb +(gdb) b main +(gdb) c +``` + +**Expected output:** +``` +Breakpoint 1, main () at 0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +##### Step 6: Examine the Link Register + +When stopped at `main()`, check what's in the link register: + +```gdb +(gdb) info registers lr +``` + +**Expected output:** +``` +lr 0x1000018b 268435851 +``` + +The LR contains the return address - where execution will go when `main()` returns. + +##### Step 7: Disassemble the Caller + +Subtract 1 to remove the Thumb bit and disassemble: + +```gdb +(gdb) x/10i 0x1000018a +``` + +**Expected output:** +``` +0x10000186 : ldr r1, [pc, #80] +0x10000188 : blx r1 +0x1000018a : ldr r1, [pc, #80] ? LR points here +0x1000018c : blx r1 ? This called main +0x1000018e : ldr r1, [pc, #80] +0x10000190 : blx r1 +0x10000192 : bkpt 0x0000 +``` + +##### Step 8: Understand the Call Chain + +Working backward from `main()`: + +``` +platform_entry (0x10000186) + ? calls (blx at +2) +runtime_init() (0x1000024c) + ? calls (blx at +6) +main() (0x10000234) ? We are here + ? will call (blx at +6) +stdio_init_all() (0x100012c4) +``` + +##### Step 9: Verify Platform Entry Calls Main + +Look at what `platform_entry` loads before the `blx`: + +```gdb +(gdb) x/x 0x100001dc +``` + +This is the address loaded into r1 before calling `blx`. It should point to `main()`. + +**Expected output:** +``` +0x100001dc : 0x10000235 +``` + +Note: `0x10000235` = `0x10000234` + 1 (Thumb bit), which is the address of `main()`! + +##### Step 10: Complete the Boot Trace + +You've now traced the complete path: + +``` +1. Reset (Power-on) + ? +2. Bootrom (0x00000000) + ? +3. Vector Table (0x10000000) + ? +4. _reset_handler (0x1000015c) + ? +5. Data Copy & BSS Clear + ? +6. platform_entry (0x10000186) + ? +7. runtime_init() (first call) + ? +8. main() (second call) ? Exercise focus + ? +9. stdio_init_all() (first line of main) +``` + +#### Expected Output +- `main()` is at address `0x10000234` +- First function call is `stdio_init_all()` at offset +6 +- Link register points to `platform_entry+4` (0x1000018a) +- `platform_entry` makes three function calls: runtime_init, main, and exit + +#### Questions for Reflection + +###### Question 1: Why does the link register point 4 bytes after the `blx` instruction that called main? + +###### Question 2: What would happen if `main()` tried to return (instead of looping forever)? + +###### Question 3: How can you tell from the disassembly that main contains an infinite loop? + +###### Question 4: Why is `stdio_init_all()` called before the printf loop? + +#### Tips and Hints +- Use `bt` (backtrace) to see the call stack +- Remember to account for Thumb mode when reading addresses from LR +- Use `info frame` to see detailed information about the current stack frame +- The `push {r7, lr}` at the start of main saves the return address + +#### Next Steps +- Set a breakpoint at `stdio_init_all()` and step through its initialization +- Examine what happens after `main()` by looking at `exit()` function +- Try Exercise 5 in Ghidra for static analysis of the boot sequence + +#### Additional Challenge + +Create a GDB command to automatically trace the call chain: + +```gdb +(gdb) define calltrace +> set $depth = 0 +> set $addr = $pc +> while $depth < 10 +> printf "%d: ", $depth +> info symbol $addr +> set $addr = *(int*)($lr - 4) +> set $depth = $depth + 1 +> end +> end +``` + +Then try stepping through functions and running `calltrace` at each level to build a complete call graph. diff --git a/WEEKS/WEEK03/WEEK03-SLIDES.pdf b/WEEKS/WEEK03/WEEK03-SLIDES.pdf new file mode 100644 index 0000000..9ad8b53 Binary files /dev/null and b/WEEKS/WEEK03/WEEK03-SLIDES.pdf differ diff --git a/WEEKS/WEEK03/WEEK03.md b/WEEKS/WEEK03/WEEK03.md new file mode 100644 index 0000000..a760a49 --- /dev/null +++ b/WEEKS/WEEK03/WEEK03.md @@ -0,0 +1,1403 @@ +ο»Ώ# Week 3: Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand how the RP2350 boots from the on-chip bootrom +- Know what the vector table is and why it's important +- Trace the complete boot sequence from power-on to `main()` +- Understand XIP (Execute In Place) and how code runs from flash +- Read and analyze the startup assembly code (`crt0.S`) +- Use GDB to examine the boot process step by step +- Use Ghidra to statically analyze the boot sequence +- Understand the difference between Thumb mode addressing and actual addresses + +## πŸ”„ Review from Weeks 1-2 +This week builds on your GDB and Ghidra skills from previous weeks: +- **GDB Commands** (`x`, `b`, `c`, `si`, `disas`, `i r`) - We'll use all of these to trace the boot process +- **Memory Layout** (Flash at `0x10000000`, RAM at `0x20000000`) - Understanding where code and data live +- **Registers** (`r0`-`r12`, SP, LR, PC) - We'll watch how they're initialized during boot +- **Ghidra Analysis** - Decompiling and understanding assembly in a visual tool +- **Thumb Mode** - Remember addresses with LSB=1 indicate Thumb code + +--- + +## πŸ“š The Code We're Analyzing + +Throughout this week, we'll continue working with our `0x0001_hello-world.c` program: + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + while (true) + printf("hello, world\r\n"); +} +``` + +But this week, we're going **deeper** - we'll understand everything that happens BEFORE `main()` even runs! How does the chip know where `main()` is? How does the stack get initialized? Let's find out! + +--- + +## πŸ“š Part 1: Understanding the Boot Process + +### What Happens When You Power On? + +When you plug in your Raspberry Pi Pico 2, a lot happens before your `main()` function runs! Think of it like waking up in the morning: + +1. **First, your alarm goes off** (Power is applied to the chip) +2. **You open your eyes** (The bootrom starts running) +3. **You check your phone** (The bootrom looks for valid code in flash) +4. **You get out of bed** (The bootrom jumps to your program) +5. **You brush your teeth, get dressed** (Startup code initializes everything) +6. **Finally, you start your day** (Your `main()` function runs!) + +Each of these steps has a corresponding piece of code. Let's explore them all! + +### The RP2350 Boot Sequence Overview + +> πŸ“– **Datasheet Reference:** The full boot sequence is described in Chapter 5, Section 5.2 "Processor-controlled boot sequence" (Datasheet p. 375). The address map is in Section 2.2 (Datasheet p. 31). + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 1: Power On β”‚ +β”‚ - The Cortex-M33 core wakes up β”‚ +β”‚ - Execution begins at address 0x00000000 (Bootrom) β”‚ +β”‚ (Datasheet Β§2.2.1, p. 31: ROM at address zero) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 2: Bootrom Executes (32KB on-chip ROM) β”‚ +β”‚ - This code is burned into the chip - can't be changed! β”‚ +β”‚ - It looks for valid firmware in flash memory β”‚ +β”‚ - It scans the first 4 kB of the image for a valid IMAGE_DEF β”‚ +β”‚ (Datasheet Β§4.1, p. 338: 32KB ROM; Β§5.9.5, p. 429: IMAGE_DEF) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 3: Flash XIP Setup (bootrom-managed) β”‚ +β”‚ - The bootrom configures the flash interface automatically β”‚ +β”‚ - Sets up XIP (Execute In Place) mode β”‚ +β”‚ - NOTE: Unlike RP2040, there is NO separate boot2 in flash! β”‚ +β”‚ (Datasheet Β§5.2, p. 375: "removal of a boot2 in the first β”‚ +β”‚ 256 bytes of the image") β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 4: Vector Table & Reset Handler β”‚ +β”‚ - Bootrom reads the vector table at 0x10000000 β”‚ +β”‚ - Gets the initial stack pointer from offset 0x00 β”‚ +β”‚ - Gets the reset handler address from offset 0x04 β”‚ +β”‚ - Jumps to the reset handler! β”‚ +β”‚ (Datasheet Β§5.9.3.3, p. 425: "the Arm vector table is assumed β”‚ +β”‚ to be at the start of the image"; Β§5.9.5, p. 429) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ STEP 5: C Runtime Startup (crt0.S) β”‚ +β”‚ - Copies initialized data from flash to RAM β”‚ +β”‚ - Zeros out the BSS section β”‚ +β”‚ - Calls runtime_init() β”‚ +β”‚ - Finally calls main()! β”‚ +β”‚ (SDK source: pico_crt0/crt0.S, platform_entry label) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 2: The Bootrom - Where It All Begins + +> πŸ“– **Datasheet Reference:** ROM is described in Section 4.1 (p. 338). The boot sequence is Chapter 5 (p. 355+). The address map showing ROM at 0x00000000 is in Section 2.2 (p. 31). + +### What is the Bootrom? + +The **bootrom** is a 32KB piece of code that is permanently burned into the RP2350 chip at the factory. You cannot change it - it's "mask ROM" (Read Only Memory). + +Think of the bootrom like the BIOS in your computer - it's the first thing that runs and is responsible for finding and loading your actual program. + +### Key Bootrom Facts + +| Property | Value | Description | Datasheet Reference | +| ----------- | ------------- | ---------------------------------- | ------------------- | +| Size | 32 KB | Small but powerful | Β§2.1 Bus Fabric diagram (p. 25) | +| Location | `0x00000000` | The very first address in memory | Β§2.2 Address Map (p. 31) | +| Modifiable? | **NO** | Burned into silicon at the factory | Β§4.1 ROM (p. 338) | +| Purpose | Boot the chip | Find and load your firmware | Β§5.2 Boot Sequence (p. 375) | + +### What Does the Bootrom Do? + +1. **Initialize Hardware**: Sets up clocks, resets peripherals +2. **Check Boot Sources**: Looks for valid firmware in flash +3. **Validate Firmware**: Checks for magic markers (IMAGE_DEF) +4. **Configure Flash**: Sets up the XIP interface +5. **Jump to Your Code**: Reads the vector table and jumps to your reset handler + +### The IMAGE_DEF Structure + +The bootrom looks for a special marker in your firmware called **IMAGE_DEF**. This tells the bootrom "Hey, there's valid code here!" + +Here's what it looks like in the Pico SDK: + +```assembly +.section .picobin_block, "a" // placed in flash +.word 0xffffded3 // PICOBIN_BLOCK_MARKER_START ← ROM looks for this! +.byte 0x42 // PICOBIN_BLOCK_ITEM_1BS_IMAGE_TYPE +.byte 0x1 // item is 1 word in size +.hword 0b0001000000100001 // SECURE mode (0x1021) +.byte 0xff // PICOBIN_BLOCK_ITEM_2BS_LAST +.hword 0x0001 // item is 1 word in size +.byte 0x0 // pad +.word 0x0 // relative pointer to next block (0 = loop to self) +.word 0xab123579 // PICOBIN_BLOCK_MARKER_END +``` + +**The magic numbers:** +- `0xffffded3` = Start marker ("I'm a valid Pico binary!") +- `0xab123579` = End marker ("End of the header block") + +> πŸ“– **Datasheet Reference:** Block markers are defined in Section 5.1.5.1 "Blocks" (p. 357): *"it must begin with the 4 byte magic header, PICOBIN_BLOCK_MARKER_START (0xffffded3)"* and *"it must end with the 4 byte magic footer, PICOBIN_BLOCK_MARKER_END (0xab123579)"*. The minimum Arm IMAGE_DEF is detailed in Section 5.9.5.1 (p. 429). + +> ⚠️ **RP2350 vs RP2040 Key Difference:** Unlike the RP2040, the RP2350 does **not** require a checksummed "boot2" function at flash address 0. The RP2350 bootrom performs its own flash XIP setup. Instead, the RP2350 requires a valid IMAGE_DEF block **anywhere within the first 4 kB** of the image (Datasheet Β§5.9.5, p. 429: *"This must appear within the first 4 kB of a flash image"*; Β§5.2, p. 375: *"the main differences being the removal of a boot2 in the first 256 bytes of the image"*). + +--- + +## πŸ“š Part 3: Understanding XIP (Execute In Place) + +> οΏ½ **Datasheet Reference:** The XIP address space is defined in Section 2.2 "Address Map" (p. 31): XIP base = `0x10000000`. The XIP cache architecture is described in Section 4.4 (p. 340+). + +> οΏ½πŸ”„ **REVIEW:** In Week 1, we learned that our code lives at `0x10000000` in flash memory. We used `x/1000i 0x10000000` to find our `main` function. Now we'll understand WHY code is at this address! + +### What is XIP? + +**XIP (Execute In Place)** means the processor can run code directly from flash memory without copying it to RAM first. + +Think of it like reading a book: +- **Without XIP**: You photocopy every page into a notebook, then read from the notebook +- **With XIP**: You just read directly from the book! + +### Why Use XIP? + +| Advantage | Explanation | +| ----------- | ------------------------------------------- | +| Saves RAM | Code stays in flash, RAM is free for data | +| Faster Boot | No need to copy entire program to RAM first | +| Simpler | Less memory management needed | + +### XIP Memory Address + +The XIP flash region starts at address `0x10000000`. This is where your compiled code lives! + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Address: 0x10000000 (XIP Base) β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ Vector Table (first thing here!) β”‚β”‚ +β”‚ β”‚ - Stack Pointer at offset 0x00 β”‚β”‚ +β”‚ β”‚ - Reset Handler at offset 0x04 β”‚β”‚ +β”‚ β”‚ - Other exception handlers... β”‚β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚ +β”‚ β”‚ Your Code β”‚β”‚ +β”‚ β”‚ - Reset handler β”‚β”‚ +β”‚ β”‚ - main() function β”‚β”‚ +β”‚ β”‚ - Other functions β”‚β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚ +β”‚ β”‚ Read-Only Data β”‚β”‚ +β”‚ β”‚ - Strings like "hello, world" β”‚β”‚ +β”‚ β”‚ - Constant values β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 4: The Vector Table - The CPU's Instruction Manual + +> πŸ“– **Datasheet Reference:** The VTOR (Vector Table Offset Register) is defined in Section 3.7 (p. 182–183). The bootrom's use of the vector table to enter the image is described in Section 5.9.5.1 (p. 429): *"the bootrom will assume the binary starts with a Cortex-M vector table, and enter via the reset handler and initial stack pointer specified in that table"*. The optional VECTOR_TABLE item is in Section 5.9.3.3 (p. 425): *"If there is no ENTRY_POINT or VECTOR_TABLE Item, then the Arm vector table is assumed to be at the start of the image."* + +### What is the Vector Table? + +The **vector table** is a list of addresses at the very beginning of your program. It tells the CPU: +1. Where to set the stack pointer +2. Where to start executing code (reset handler) +3. Where to go when errors or interrupts happen + +Think of it like the table of contents in a book - it tells you where to find everything! + +### Vector Table Layout + +The vector table lives at `0x10000000` (the start of the flash image) and looks like this: + +| Offset | Address | Content | Description | +| ------ | ------------ | ------------ | --------------------------- | +| `0x00` | `0x10000000` | `0x20082000` | Initial Stack Pointer (SP) | +| `0x04` | `0x10000004` | `0x1000015d` | Reset Handler (entry point) | +| `0x08` | `0x10000008` | `0x1000011b` | NMI Handler | +| `0x0C` | `0x1000000C` | `0x1000011d` | HardFault Handler | + +### Understanding Thumb Mode Addressing + +**Important Concept Alert!** + +Look at the reset handler address: `0x1000015d`. Notice it ends in `d` (an odd number)? + +On ARM Cortex-M processors, all code runs in **Thumb mode**. The processor uses the **least significant bit (LSB)** of an address to indicate this: + +| LSB | Mode | Meaning | +| ---------- | ----- | ----------------------------------------- | +| `1` (odd) | Thumb | "This is Thumb code" | +| `0` (even) | ARM | "This is ARM code" (not used on Cortex-M) | + +So `0x1000015d` means: +- The actual code is at `0x1000015c` (even address) +- The `+1` tells the processor "use Thumb mode" + +**GDB vs Ghidra:** +- GDB shows `0x1000015d` (with Thumb bit) +- Ghidra shows `0x1000015c` (actual instruction address) +- Both are correct! They're just displaying it differently. + +--- + +## πŸ“š Part 5: The Linker Script - Memory Mapping + +> πŸ“– **Datasheet Reference:** The SRAM address map is in Section 2.2.3 (p. 32) and Section 4.2 (p. 338–339). The hardware defines 10 SRAM banks: SRAM0–7 (8 Γ— 64 kB, striped) and SRAM8–9 (2 Γ— 4 kB, non-striped). See the note below about SCRATCH_X/Y naming. + +### What is a Linker Script? + +The **linker script** tells the compiler where to put different parts of your program in memory. It's like an architect's blueprint for memory! + +### Finding the Linker Script + +On Windows with the Pico SDK 2.2.0, you'll find it at: +``` +C:\Users\\.pico-sdk\sdk\2.2.0\src\rp2_common\pico_crt0\rp2350\memmap_default.ld +``` + +### Key Parts of the Linker Script + +```ld +MEMORY +{ + INCLUDE "pico_flash_region.ld" + RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k + SCRATCH_X(rwx) : ORIGIN = 0x20080000, LENGTH = 4k + SCRATCH_Y(rwx) : ORIGIN = 0x20081000, LENGTH = 4k +} +``` + +**What this means:** + +| Region | Start Address | Size | Purpose | Datasheet Reference | +| --------- | ------------- | -------- | --------------------- | ------------------- | +| Flash | `0x10000000` | (varies) | Your code (XIP) | Β§2.2 Address Map (p. 31) | +| RAM | `0x20000000` | 512 KB | Main RAM (SRAM0–7, striped) | Β§2.2.3 SRAM (p. 32) | +| SCRATCH_X | `0x20080000` | 4 KB | Core 0 scratch memory | Β§2.2.3 β€” Hardware name: **SRAM8** (p. 32) | +| SCRATCH_Y | `0x20081000` | 4 KB | Core 0 stack | Β§2.2.3 β€” Hardware name: **SRAM9** (p. 32) | + +> ⚠️ **RP2350 vs RP2040 Naming Note:** The names `SCRATCH_X` and `SCRATCH_Y` are **SDK linker script conventions** carried over from the RP2040 for software compatibility. On the RP2350 hardware, these 4 kB banks are called **SRAM8** (at `0x20080000`) and **SRAM9** (at `0x20081000`). The datasheet states (Β§2.2.3, p. 32): *"SRAM 8-9 are always non-striped"* with `SRAM8_BASE = 0x20080000`, `SRAM9_BASE = 0x20081000`, `SRAM_END = 0x20082000`. The older RP2040 had 264 KB of SRAM terminating in dedicated SCRATCH_X/SCRATCH_Y blocks; the RP2350 redesigned the memory into 10 independently-arbitrated SRAM banks (SRAM0–9) totaling 520 KB. The datasheet notes (Β§4.2, p. 339): *"Software may choose to use these for per-core purposes (e.g. stack and frequently-executed code), guaranteeing that the processors never stall on these accesses."* + +### Where Does the Stack Come From? + +The linker script calculates the initial stack pointer: + +```ld +__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); +``` + +Let's do the math: +- `ORIGIN(SCRATCH_Y)` = `0x20081000` +- `LENGTH(SCRATCH_Y)` = `0x1000` (4 KB) +- `__StackTop` = `0x20081000` + `0x1000` = **`0x20082000`** + +This value (`0x20082000`) is what we see at offset `0x00` in the vector table! + +--- + +## πŸ“š Part 6: Setting Up Your Environment (GDB - Dynamic Analysis) + +> πŸ”„ **REVIEW:** This setup is identical to Weeks 1-2. If you need a refresher on OpenOCD and GDB connection, refer back to Week 1 Part 4 or Week 2 Part 5. + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board with debug probe connected +2. OpenOCD installed and configured +3. GDB (`arm-none-eabi-gdb`) installed +4. The "hello-world" binary loaded on your Pico 2 +5. Access to the Pico SDK source files (for reference) + +### Starting 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\0x0001_hello-world.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +--- + +## πŸ”¬ Part 7: Hands-On GDB Tutorial - Examining the Vector Table + +> πŸ”„ **REVIEW:** We're using the same `x` (examine) command from Week 1. Remember: `x/Nx` shows N hex values, `x/Ni` shows N instructions, `x/s` shows strings. + +### Step 1: Examine the Vector Table + +Let's look at the first 4 entries of the vector table at `0x10000000`: + +**Type this command:** + +```gdb +(gdb) x/4x 0x10000000 +``` + +**What this command means:** +- `x` = examine memory (Week 1 review!) +- `/4x` = show 4 values in hexadecimal +- `0x10000000` = the address of the vector table + +**You should see:** + +``` +0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d +``` + +### Step 2: Understanding What We See + +> πŸ”„ **REVIEW:** In Week 1, we saw `sp = 0x20081fc8` when stopped at `main`. That's *after* some stack was used during boot. Here we see the *initial* stack pointer before any code runs! + +Let's decode each value: + +| Address | Value | Meaning | +| ------------ | ------------ | ---------------------------------------- | +| `0x10000000` | `0x20082000` | Initial Stack Pointer β€” top of SRAM9 (SDK: SCRATCH_Y) | +| `0x10000004` | `0x1000015d` | Reset Handler + 1 (Thumb bit) | +| `0x10000008` | `0x1000011b` | NMI Handler + 1 (Thumb bit) | +| `0x1000000C` | `0x1000011d` | HardFault Handler + 1 (Thumb bit) | + +**Key Insight:** The stack pointer (`0x20082000`) is exactly what the linker script calculated! And all the handler addresses have their LSB set to `1` for Thumb mode. + +> πŸ“– **Datasheet Reference:** The vector table is at the start of the image (`0x10000000`) because the Pico SDK does not include an explicit VECTOR_TABLE item in the IMAGE_DEF. Per Section 5.9.3.3 (p. 425): *"If there is no ENTRY_POINT or VECTOR_TABLE Item, then the Arm vector table is assumed to be at the start of the image."* The bootrom enters *"via the reset handler and initial stack pointer specified in that table (offsets +4 and +0 bytes into the table)"* (Β§5.9.5.1, p. 429). + +### Step 3: Verify the Stack Pointer Calculation + +Let's confirm our math by examining what's at `0x10000000`: + +**Type this command:** + +```gdb +(gdb) x/x 0x10000000 +``` + +**You should see:** + +``` +0x10000000 <__vectors>: 0x20082000 +``` + +This matches (Datasheet Β§2.2.3, p. 32: `SRAM_END = 0x20082000`): +- SRAM9 (SDK name: `SCRATCH_Y`) starts at `0x20081000` +- SRAM9 is 4 KB (`0x1000` bytes) +- `0x20081000` + `0x1000` = `0x20082000` βœ“ + +--- + +## πŸ”¬ Part 8: Examining the Reset Handler + +> πŸ”„ **REVIEW:** We used `x/5i` extensively in Weeks 1-2 to examine our `main` function. Now we'll use the same technique to examine the code that runs BEFORE `main`! + +### Step 4: Disassemble the Reset Handler + +The reset handler is where execution begins after the bootrom hands off control. Let's look at it: + +**Type this command:** + +```gdb +(gdb) x/3i 0x1000015c +``` + +**Note:** We use `0x1000015c` (even) not `0x1000015d` (odd) because we want to see the actual instructions! + +**You should see:** + +``` + 0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000 + 0x10000160 <_reset_handler+4>: ldr r0, [r0, #0] + 0x10000162 <_reset_handler+6>: + cbz r0, 0x1000016a +``` + +### Step 5: Understanding the Reset Handler + +Let's break down what these first three instructions do: + +**Instruction 1: `mov.w r0, #0xd0000000`** + +This loads the address `0xd0000000` into register `r0`. But what's at that address? + +That's the **SIO (Single-cycle I/O) base address**! The SIO block contains a special register called **CPUID** that tells us which core we're running on. (Datasheet Β§3.1, p. 38: SIO_BASE = `0xd0000000`; Β§3.1.2, p. 39: *"The CPUID SIO register returns a value of 0 when read by core 0, and 1 when read by core 1."*) + +**Instruction 2: `ldr r0, [r0, #0]`** + +This reads the value at address `0xd0000000` (the CPUID register) into `r0`. + +| Core | CPUID Value | +| ------ | ----------- | +| Core 0 | `0` | +| Core 1 | `1` | + +**Instruction 3: `cbz r0, 0x1000016a`** + +This is "Compare and Branch if Zero". If `r0` is `0` (meaning we're on Core 0), branch to `0x1000016a` to continue with startup. Otherwise, we're on Core 1 and need to handle that differently. + +### Why Check Which Core We're On? + +The RP2350 has **two cores**, but only **Core 0** should run the startup code! If both cores tried to initialize the same memory and peripherals, chaos would ensue. + +So the reset handler checks: +- **Core 0?** β†’ Continue with startup +- **Core 1?** β†’ Go back to the bootrom and wait + +--- + +## πŸ”¬ Part 9: The Complete Reset Handler Flow + +### Step 6: Examine More of the Reset Handler + +Let's look at more instructions to see the full picture: + +**Type this command:** + +```gdb +(gdb) x/20i 0x1000015c +``` + +**You should see something like:** + +``` +0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000 +0x10000160 <_reset_handler+4>: ldr r0, [r0, #0] +0x10000162 <_reset_handler+6>: +cbz r0, 0x1000016a +0x10000164 : mov.w r0, #0 +0x10000168 : +b.n 0x10000150 <_enter_vtable_in_r0> +0x1000016a : +add r4, pc, #52 @ (adr r4, 0x100001a0 ) +0x1000016c : ldmia r4!, {r1, r2, r3} +0x1000016e : cmp r1, #0 +0x10000170 : +beq.n 0x10000178 +0x10000172 : +bl 0x1000019a +0x10000176 : +b.n 0x1000016c +0x10000178 : +ldr r1, [pc, #84] @ (0x100001d0 ) +0x1000017a : +ldr r2, [pc, #88] @ (0x100001d4 ) +0x1000017c : movs r0, #0 +0x1000017e : +b.n 0x10000182 +0x10000180 : stmia r1!, {r0} +0x10000182 : cmp r1, r2 +0x10000184 : bne.n 0x10000180 +0x10000186 : +ldr r1, [pc, #80] @ (0x100001d8 ) +0x10000188 : blx r1 +``` + +> πŸ’‘ **About GDB Label Offsets:** You may notice GDB shows instructions like `` for code that is clearly part of the data copy loop, not the core-parking function. This is **not** a GDB bug or "symbol snapping" β€” `hold_non_core0_in_bootrom` is a **real label** defined in the SDK's `crt0.S` (line 454). The `+8` offset simply means "8 bytes past that label." GDB always displays addresses relative to the nearest preceding symbol. The data copy code falls through from the `cbz` branch at the core check, landing right after the `hold_non_core0_in_bootrom` label, hence the offset. + +### Step 7: Understanding the Startup Phases + +The reset handler performs several phases: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 1: Core Check (0x1000015c - 0x10000168) β”‚ +β”‚ - Check CPUID to see which core we're on β”‚ +β”‚ - If not Core 0, go back to bootrom β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 2: Data Copy (0x1000016a - 0x10000176) β”‚ +β”‚ - Copy initialized variables from flash to RAM β”‚ +β”‚ - Uses data_cpy_table for source/destination info β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 3: BSS Clear (0x10000178 - 0x10000184) β”‚ +β”‚ - Zero out all uninitialized global variables β”‚ +β”‚ - C standard requires BSS to start at zero β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PHASE 4: Runtime Init & Main (0x10000186+) β”‚ +β”‚ - Call runtime_init() for SDK setup β”‚ +β”‚ - Call __libc_init_array() for C++ constructors β”‚ +β”‚ - Finally call main()! β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ”¬ Part 10: Understanding the Data Copy Phase + +### What is the Data Copy Phase? + +> πŸ”„ **REVIEW:** In Week 2, we learned that flash is read-only and SRAM is read-write. That's why the startup code must COPY initialized variables from flash to RAM - they can't be modified in flash! + +When you write C code like this: + +```c +int my_counter = 42; // Initialized global variable +``` + +The value `42` is stored in flash memory (because flash is non-volatile). But variables need to live in RAM to be modified! So the startup code **copies** these initial values from flash to RAM. + +### Step 8: Find the Data Copy Table + +The data copy table contains entries that describe what to copy where. Let's examine it: + +**Type this command:** + +```gdb +(gdb) x/12x 0x100001a0 +``` + +**You should see something like:** + +``` +0x100001a0 : 0x10001b4c 0x20000110 0x200002ac 0x10001ce8 +... +``` + +The data_cpy_table contains multiple entries. Each entry has three values: +1. **Source address** (in flash) +2. **Destination address** (in RAM) +3. **End address** (where to stop copying) + +In the output above, we see: +- **First entry**: `0x10001b4c` (source), `0x20000110` (dest), `0x200002ac` (end) +- **Second entry starts**: `0x10001ce8` (source of next entry), ... + +The table ends with an entry where the source address is `0x00000000` (which signals "no more entries"). + +### Step 9: Watch the Data Copy Loop + +The data copy loop works like this: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. Load source, dest, end from table β”‚ +β”‚ 2. If source == 0, we're done β”‚ +β”‚ 3. Otherwise, copy word by word β”‚ +β”‚ 4. Go back to step 1 for next entry β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +The actual code (starting at **`0x1000016c`** in the reset handler): + +```assembly +0x1000016c : ldmia r4!, {r1, r2, r3} +0x1000016e : cmp r1, #0 +0x10000170 : +beq.n 0x10000178 +0x10000172 : +bl 0x1000019a +0x10000176 : +b.n 0x1000016c +``` + +> πŸ’‘ **Note:** You can see this code in **Step 6** earlier where we examined the reset handler with `x/20i 0x1000015c`. + +--- + +## πŸ”¬ Part 11: Understanding the BSS Clear Phase + +### What is BSS? + +**BSS** stands for "Block Started by Symbol" (historical name). It's the section of memory for **uninitialized global variables**. + +When you write: + +```c +int my_counter; // Uninitialized - will be in BSS +``` + +The C standard says this variable **must start at zero**. The BSS clear phase zeros out this entire region. + +### Step 10: Examine the BSS Clear Loop + +**Type this command:** + +```gdb +(gdb) x/5i 0x10000178 +``` + +**You should see:** + +``` +0x10000178 : +ldr r1, [pc, #84] @ (0x100001d0 ) +0x1000017a : +ldr r2, [pc, #88] @ (0x100001d4 ) +0x1000017c : movs r0, #0 +0x1000017e : +b.n 0x10000182 +0x10000180 : stmia r1!, {r0} +``` + +### Understanding the Loop + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ r1 = start of BSS section β”‚ +β”‚ r2 = end of BSS section β”‚ +β”‚ r0 = 0 β”‚ +β”‚ β”‚ +β”‚ LOOP: β”‚ +β”‚ Store 0 at address r1 β”‚ +β”‚ Increment r1 by 4 bytes β”‚ +β”‚ If r1 != r2, repeat β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ”¬ Part 12: Examining Exception Handlers + +### Step 11: Look at the Default Exception Handlers + +What happens if an exception occurs (like a HardFault)? Let's look: + +**Type this command:** + +```gdb +(gdb) x/10i 0x10000110 +``` + +**You should see:** + +``` +0x10000110 : mrs r0, IPSR +0x10000114 : subs r0, #16 +0x10000116 : bkpt 0x0000 +0x10000118 : bkpt 0x0000 +0x1000011a : bkpt 0x0000 +0x1000011c : bkpt 0x0000 +0x1000011e : bkpt 0x0000 +0x10000120 : bkpt 0x0000 +0x10000122 : bkpt 0x0000 +0x10000124 <__default_isrs_end>: + @ instruction: 0xebf27188 +``` + +### What is `bkpt`? + +The `bkpt` instruction is a **breakpoint**. When executed, it stops the processor and triggers the debugger! + +These are the **default** exception handlers - they just stop the program so you can debug. In your own code, you can override these with real handlers. + +### Why So Many Handlers? + +Each type of exception has its own handler: + +| Handler | Purpose | +| --------------- | ------------------------------------------ | +| `isr_nmi` | Non-Maskable Interrupt (can't be disabled) | +| `isr_hardfault` | Serious error (bad memory access, etc.) | +| `isr_svcall` | Supervisor Call (used by RTOSes) | +| `isr_pendsv` | Pendable Supervisor (also for RTOSes) | +| `isr_systick` | System Timer tick interrupt | + +--- + +## πŸ”¬ Part 13: Finding Where Main is Called + +### Step 12: Look at Platform Entry + +After all the setup, the code finally calls `main()`. Let's find it: + +**Type this command:** + +```gdb +(gdb) x/10i 0x10000186 +``` + +**You should see:** + +``` +0x10000186 : +ldr r1, [pc, #80] @ (0x100001d8 ) +0x10000188 : blx r1 +0x1000018a : +ldr r1, [pc, #80] @ (0x100001dc ) +0x1000018c : blx r1 +0x1000018e : +ldr r1, [pc, #80] @ (0x100001e0 ) +0x10000190 : blx r1 +0x10000192 : bkpt 0x0000 +0x10000194 : +b.n 0x10000192 +0x10000196 : ldmia r1!, {r0} +0x10000198 : stmia r2!, {r0} +``` + +### Understanding Platform Entry + +The platform entry code makes **three function calls** using `ldr` + `blx`: + +1. **First call**: `runtime_init()` - SDK initialization +2. **Second call**: `main()` - YOUR CODE! +3. **Third call**: `exit()` - Called when main returns + +After `main()` returns, `exit()` is called to handle cleanup. The `bkpt` instruction after `exit()` should never be reached - it's there to catch errors if `exit()` somehow returns. + +### Step 13: Set a Breakpoint at Main + +> πŸ”„ **REVIEW:** We've used `b main` and `b *ADDRESS` many times in Weeks 1-2. This is the same technique! + +Let's verify we understand the boot process by setting a breakpoint at main: + +**Type this command:** + +```gdb +(gdb) b main +``` + +**You should see:** + +``` +Breakpoint 1 at 0x10000234: file 0x0001_hello-world.c, line 5. +``` + +**Now continue:** + +```gdb +(gdb) c +``` + +**You should see:** + +``` +Continuing. + +Breakpoint 1, main () at 0x0001_hello-world.c:5 +5 stdio_init_all(); +``` + +πŸŽ‰ We've traced the entire boot process from power-on to `main()`! + +--- + +## πŸ”¬ Part 14: Understanding the Binary Info Header + +### Step 14: Examine the Binary Info Header + +Between the default ISRs and the reset handler, there's a special data structure called the **binary info header**. Let's look at it: + +**Type this command:** + +```gdb +(gdb) x/5x 0x10000138 +``` + +**You should see:** + +``` +0x10000138 <__binary_info_header_end>: 0xffffded3 0x10210142 0x000001ff 0x00001bb0 +0x10000148 <__binary_info_header_end+16>: 0xab123579 +``` + +### Decoding the Binary Info Header + +| Address | Value | Meaning | +| ------------ | ------------ | ----------------------------------------- | +| `0x10000138` | `0xffffded3` | Start marker (PICOBIN_BLOCK_MARKER_START) | +| `0x1000013c` | `0x10212142` | Image type descriptor | +| `0x10000140` | `0x000001ff` | Item header/size field | +| `0x10000144` | `0x00001bb0` | Link to next block or data | +| `0x10000148` | `0xab123579` | End marker (PICOBIN_BLOCK_MARKER_END) | + +**Why does GDB show this as instructions?** + +GDB doesn't know this is data, not code! It tries to disassemble it as Thumb instructions, which results in nonsense. This is why you'll see things like: + +```gdb +(gdb) x/i 0x10000138 +``` + +``` +0x10000138 <__binary_info_header_end>: udf #211 @ 0xd3 +``` + +That's not real code - it's the magic number `0xffffded3` being misinterpreted! + +--- + +## πŸ”¬ Part 15: Static Analysis with Ghidra - Examining the Boot Sequence + +> πŸ”„ **REVIEW:** In Week 1, we set up a Ghidra project and analyzed our hello-world binary. Now we'll use Ghidra to understand the boot sequence from a static analysis perspective! + +### Why Use Ghidra for Boot Analysis? + +While GDB is excellent for dynamic analysis (watching code execute), Ghidra excels at: +- **Seeing the big picture** - Understanding code flow without running it +- **Cross-references** - Finding all places that call a function +- **Decompilation** - Seeing C-like code even for assembly routines +- **Annotation** - Adding notes and renaming functions for clarity + +### Step 15: Open Your Project in Ghidra + +> πŸ”„ **REVIEW:** If you haven't created the project yet, refer back to Week 1 Part 5 for setup instructions. + +1. Launch Ghidra and open your `0x0001_hello-world` project +2. Double-click on the `.elf` file to open it in the CodeBrowser +3. If prompted to auto-analyze, click **Yes** + +### Step 16: Navigate to the Vector Table + +1. In the **Navigation** menu, select **Go To...** +2. Type `0x10000000` and press Enter +3. You should see the vector table data + +**What you'll see in the Listing view:** + +``` + // + // .text + // SHT_PROGBITS [0x10000000 - 0x100019cb] + // ram:10000000-ram:100019cb + // + assume spsr = 0x0 (Default) + __vectors XREF[4]: Entry Point (*) , + __flash_binary_start runtime_init_install_ram_vector_ + __VECTOR_TABLE _elfProgramHeaders::00000028 (*) , + __logical_binary_start _elfSectionHeaders::00000034 (*) + 10000000 00 undefine 00h + 10000001 20 ?? 20h + 10000002 08 ?? 08h + 10000003 20 ?? 20h + 10000004 5d ?? 5Dh ] ? -> 1000015d + 10000005 01 ?? 01h + 10000006 00 ?? 00h + 10000007 10 ?? 10h + 10000008 1b ?? 1Bh ? -> 1000011b + 10000009 01 ?? 01h + 1000000a 00 ?? 00h + 1000000b 10 ?? 10h + 1000000c 1d ?? 1Dh ? -> 1000011d + 1000000d 01 ?? 01h + 1000000e 00 ?? 00h + 1000000f 10 ?? 10h +``` + +> πŸ’‘ **Notice:** Ghidra shows the vector table data as individual bytes by default. You can see it has labeled the start as `__vectors`, `__flash_binary_start`, `__VECTOR_TABLE`, and `__logical_binary_start`. The arrows (like `? -> 1000015d`) show that Ghidra recognizes these bytes as pointers to code addresses! To see the data formatted as 32-bit addresses instead of bytes, you can right-click and retype the data. + +### Step 17: Navigate to the Reset Handler + +1. In the Symbol Tree panel (left side), expand **Functions** +2. Find and click on `_reset_handler` (or search for it) +3. Alternatively, double-click on `_reset_handler` in the vector table listing + +**What you'll see in the Decompile view (right panel):** + +Ghidra will show you a decompiled version of the reset handler. While it won't be perfect C code (since this is hand-written assembly), it helps visualize the flow: + +```c +void _reset_handler(void) + +{ + bool bVar1; + undefined4 uVar2; + int iVar3; + undefined4 *puVar4; + int *piVar5; + int *piVar6; + int *piVar7; + + if (_DAT_d0000000 != 0) { + _DAT_e000ed08 = 0; + bVar1 = (bool)isCurrentModePrivileged(); + if (bVar1) { + setMainStackPointer(_gpio_set_function_masked64); + } + /* WARNING: Could not recover jumptable at 0x1000015a. Too many branches */ + /* WARNING: Treating indirect jump as call */ + (*pcRam00000004)(8,_gpio_set_function_masked64); + return; + } + piVar5 = &data_cpy_table; + uVar2 = 0; + while( true ) { + iVar3 = *piVar5; + piVar6 = piVar5 + 1; + piVar7 = piVar5 + 2; + piVar5 = piVar5 + 3; + if (iVar3 == 0) break; + uVar2 = data_cpy(uVar2,iVar3,*piVar6,*piVar7); + } + for (puVar4 = (undefined4 *)&__TMC_END__; puVar4 != (undefined4 *)&end; puVar4 = puVar4 + 1) { + *puVar4 = 0; + } + runtime_init(); + iVar3 = main(); + /* WARNING: Subroutine does not return */ + exit(iVar3); +} +``` + +### Step 18: Trace the Path to Main + +Let's find how the boot code eventually calls `main()`: + +1. In the Symbol Tree, find the `main` function +2. Right-click on `main` and select **References β†’ Show References to main** +3. This shows everywhere `main` is called from! + +**You should see:** + +| Location | Type | Label | +| ------------------------- | ---- | ------------------ | +| `platform_entry+6` | CALL | `blx r1` (to main) | + +4. Double-click on the reference to jump to `platform_entry` + +### Step 19: Examine Platform Entry + +In Ghidra, look at `platform_entry`: + +**Listing View:** +``` + platform_entry + crt0.S:512 (2) + 10000186 14 49 ldr r1,[DAT_100001d8 ] = 1000137Dh + crt0.S:513 (2) + 10000188 88 47 blx r1=>runtime_init void runtime_init(void) + crt0.S:514 (2) + 1000018a 14 49 ldr r1,[DAT_100001dc ] = 10000235h + crt0.S:515 (2) + 1000018c 88 47 blx r1=>main int main(void) + crt0.S:516 (2) + 1000018e 14 49 ldr r1,[DAT_100001e0 ] = 10001375h + crt0.S:517 (2) + 10000190 88 47 blx r1=>exit void exit(int status) + LAB_10000192 XREF[1]: 10000194 (j) + crt0.S:521 (2) + 10000192 00 be bkpt 0x0 + crt0.S:522 (2) + 10000194 fd e7 b LAB_10000192 +``` + +> 🎯 **Key Insight:** Ghidra's decompiler makes the boot sequence crystal clear! You can see exactly what functions are called before `main()`. + +### Step 20: Create a Boot Sequence Graph + +Ghidra can visualize the call flow: + +1. With `_reset_handler` selected, go to **Window β†’ Function Call Graph** +2. This shows a visual graph of all function calls from the reset handler +3. You can see the path: `_reset_handler` β†’ `platform_entry` β†’ `main` + +### Comparing GDB and Ghidra for Boot Analysis + +| Aspect | GDB (Dynamic) | Ghidra (Static) | +| ------ | ------------- | --------------- | +| **Sees runtime values** | βœ… Yes - register contents, memory | ❌ No - must infer from code | +| **Needs hardware** | βœ… Yes - Pico 2 must be connected | ❌ No - works offline | +| **Shows code flow** | Step-by-step execution | Full graph visualization | +| **Best for** | Watching what happens | Understanding structure | +| **Thumb bit handling** | Shows with +1 (0x1000015d) | Shows actual addr (0x1000015c) | + +### Ghidra Tips for Boot Analysis + +1. **Rename functions** - Right-click and rename unclear labels for future reference +2. **Add comments** - Press `;` to add inline comments explaining code +3. **Set data types** - Help Ghidra understand structures like the vector table +4. **Use bookmarks** - Mark important locations with **Ctrl+D** + +--- + +## πŸ“Š Part 16: Summary and Review + +### The Complete Boot Sequence + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. POWER ON β”‚ +β”‚ Cortex-M33 begins at 0x00000000 (bootrom) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 2. BOOTROM β”‚ +β”‚ - Initializes hardware β”‚ +β”‚ - Configures flash XIP (no separate boot2 on RP2350) β”‚ +β”‚ - Finds IMAGE_DEF within first 4 kB of flash image β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 3. VECTOR TABLE (0x10000000) β”‚ +β”‚ - Reads SP from offset 0x00 β†’ 0x20082000 β”‚ +β”‚ - Reads Reset Handler from offset 0x04 β†’ 0x1000015d β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 4. RESET HANDLER (0x1000015c) β”‚ +β”‚ - Checks CPUID (Core 0 continues, Core 1 waits) β”‚ +β”‚ - Copies .data from flash to RAM β”‚ +β”‚ - Zeros .bss section β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 5. PLATFORM ENTRY (0x10000186) β”‚ +β”‚ - Calls runtime_init() β”‚ +β”‚ - Calls main() β”‚ +β”‚ - Calls exit() when main returns β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 6. YOUR CODE RUNS! β”‚ +β”‚ main() at 0x10000234 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Addresses to Remember + +| Address | What's There | Datasheet Reference | +| ------------ | ---------------------------------------- | ------------------- | +| `0x00000000` | Bootrom (32KB, read-only) | Β§2.2.1 ROM (p. 31), Β§4.1 (p. 338) | +| `0x10000000` | Vector table / XIP flash start | Β§2.2 Address Map (p. 31), Β§5.9.5.1 (p. 429) | +| `0x1000015c` | Reset handler (`_reset_handler`) | SDK crt0.S | +| `0x10000234` | Your `main()` function | (binary-specific) | +| `0x20000000` | Start of RAM | Β§2.2.3 SRAM (p. 32) | +| `0x20082000` | Initial SP (top of SRAM9 / SDK: SCRATCH_Y) | Β§2.2.3 (p. 32): SRAM_END | +| `0xd0000000` | SIO base (CPUID register) | Β§3.1 SIO (p. 38), Β§3.1.2 CPUID (p. 39) | + +### Weeks 1-2 Concepts We Applied + +| Previous Concept | How We Used It This Week | +| ---------------- | ------------------------ | +| Memory Layout (Flash/RAM) | Understood why data must be copied from flash to RAM | +| GDB `x` command | Examined vector table, reset handler, and boot code | +| Breakpoints (`b`) | Set breakpoints to trace the boot sequence | +| Thumb Mode Addresses | Recognized LSB=1 means Thumb code in vector table | +| Stack Pointer | Saw how SP is initialized from the vector table | +| Ghidra Analysis | Used decompiler to understand boot flow | + +### GDB Commands Reference + +| Command | What It Does | New/Review | +| ---------------- | --------------------------------- | ---------- | +| `x/Nx ADDRESS` | Examine N hex values at ADDRESS | Review | +| `x/Ni ADDRESS` | Examine N instructions at ADDRESS | Review | +| `b main` | Set breakpoint at main function | Review | +| `b *ADDRESS` | Set breakpoint at exact address | Review | +| `si` | Step one instruction | Review | +| `c` | Continue execution | Review | +| `info registers` | Show all register values | Review | +| `monitor reset halt` | Reset and halt the target | Review | + +### Key Concepts + +| Concept | Definition | +| ---------------- | ----------------------------------------------------- | +| **Bootrom** | 32KB factory-programmed ROM that initializes the chip | +| **Vector Table** | List of addresses for SP and exception handlers | +| **XIP** | Execute In Place - running code directly from flash | +| **Thumb Mode** | ARM's compact instruction set (LSB=1 in addresses) | +| **BSS** | Section for uninitialized globals (must be zeroed) | +| **crt0.S** | C Runtime startup assembly file | +| **Reset Handler**| First function called after power-on/reset | +| **CPUID** | Register identifying which CPU core is executing | + +### Ghidra Actions We Used + +| Action | How to Access | Purpose | +| ------ | ------------- | ------- | +| Go To Address | Navigation β†’ Go To... | Jump to specific memory address | +| Show References | Right-click β†’ References β†’ Show References to | Find all callers of a function | +| Function Call Graph | Window β†’ Function Call Graph | Visualize call flow | +| Add Comment | Press `;` | Document your analysis | +| Rename Symbol | Right-click β†’ Rename | Give meaningful names to functions | + +--- + +## βœ… Practice Exercises + +### Exercise 1: Trace a Reset + +1. Set a breakpoint at the reset handler: `b *0x1000015c` +2. Type `monitor reset halt` then `c` +3. Single-step through the first 10 instructions with `si` +4. For each instruction, explain what it does + +### Exercise 2: Find the Stack Size + +1. The stack starts at `0x20082000` +2. The stack limit is `0x20078000` (from register assignments) +3. Calculate: How many bytes is the stack? +4. How many kilobytes is that? + +### Exercise 3: Examine All Vectors + +1. Use `x/16x 0x10000000` to see the first 16 vector table entries +2. For each entry, determine: + - Is it a valid code address (starts with `0x1000...`)? + - What handler does it point to? + +### Exercise 4: Find Your Main Function + +1. Use `info functions main` to find main +2. Examine 10 instructions at that address +3. Identify the first function call in main +4. What does that function do? + +### Exercise 5: Trace Back from Main + +1. When stopped at main, examine `$lr` (link register) +2. What address is stored there? +3. Disassemble that address - what function is it? +4. This shows you where main was called from! + +### Exercise 6: Ghidra Boot Analysis + +1. In Ghidra, navigate to `_reset_handler` +2. Use **Window β†’ Function Call Graph** to visualize the call tree +3. Identify the path from `_reset_handler` to `main` +4. How many functions are called before `main` starts? +5. Add a comment in Ghidra explaining what each function does + +--- + +## πŸŽ“ Key Takeaways + +### Building on Weeks 1-2 + +1. **GDB skills compound** - The `x`, `b`, `si`, and `disas` commands you learned in Weeks 1-2 are essential for understanding the boot process. Each week adds new applications for the same core skills. + +2. **Memory layout is fundamental** - Understanding flash vs RAM from Week 2 explains why startup code must copy data and zero BSS. + +3. **Ghidra complements GDB** - Dynamic analysis (GDB) shows what happens at runtime; static analysis (Ghidra) reveals the overall structure. Use both together! + +### New Concepts This Week + +4. **The boot process is deterministic** - Every RP2350 boots the same way, and understanding this helps you debug startup problems. + +5. **The bootrom can't be changed** - It's burned into silicon. Security features depend on this immutability. + +6. **The vector table is critical** - It tells the CPU where to start and how to handle errors. + +7. **Thumb mode uses the LSB** - Address `0x1000015d` means "run Thumb code at `0x1000015c`". + +8. **Startup code does essential work** - Copying data, zeroing BSS, and initializing the runtime all happen before `main()`. + +9. **Only Core 0 runs startup** - Core 1 waits in the bootrom until explicitly started. + +--- + +## πŸ” Security Implications + +### How Boot Sequence Knowledge Applies to Security + +Understanding the boot process is critical for both attackers and defenders. Knowledge of how the RP2350 boots reveals potential attack vectors and defense strategies. + +#### Attack Scenarios + +| Scenario | Attack | Boot Process Knowledge Required | +| -------- | ------ | ------------------------------- | +| **Firmware Replacement** | Replace the entire flash image with malicious firmware | Understanding IMAGE_DEF structure and how bootrom validates firmware | +| **Vector Table Hijacking** | Modify the reset handler address to point to malicious code | Knowing the vector table location at `0x10000000` | +| **Bootrom Exploitation** | Find bugs in the immutable bootrom to bypass security | Understanding bootrom behavior and sequence | +| **Debug Port Attack** | Use SWD/JTAG to dump firmware or inject code | Knowledge of how to halt and examine the boot process | +| **Startup Code Modification** | Change how data is copied or BSS is cleared | Understanding crt0 and runtime_init sequences | + +#### Real-World Applications + +**Industrial Control Systems:** +- An attacker with physical access could replace firmware to hide malicious behavior +- Understanding the boot sequence helps identify the earliest point where security checks can be added + +**IoT Devices:** +- Compromised boot code could establish backdoors before the main application runs +- Secure boot implementations verify the vector table and reset handler integrity + +**Medical Devices:** +- Boot-time attacks could modify critical safety parameters before device operation +- Understanding initialization helps implement tamper detection + +### Defense Strategies + +#### 1. Secure Boot Implementation + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SECURE BOOT FLOW β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Bootrom (immutable) β”‚ +β”‚ ↓ β”‚ +β”‚ Verify IMAGE_DEF signature β”‚ +β”‚ ↓ β”‚ +β”‚ Verify application image signature β”‚ +β”‚ ↓ β”‚ +β”‚ If all valid: Jump to reset handler β”‚ +β”‚ If any invalid: Refuse to boot β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Implementation:** Use cryptographic signatures to verify each boot stage before execution. + +#### 2. Debug Port Protection + +- **Production devices:** Permanently disable SWD/JTAG in final products +- **Debug authentication:** Require cryptographic challenge-response before allowing debug access +- **Fuses:** Blow hardware fuses to disable debug ports permanently + +#### 3. Flash Protection + +- **Read protection:** Enable flash read protection to prevent dumping firmware +- **Write protection:** Make critical boot sectors write-protected after initial programming +- **Encrypted storage:** Store firmware encrypted in flash + +#### 4. Memory Protection Unit (MPU) + +Configure the Cortex-M33's MPU to: +- Mark code regions as execute-only (no reading code as data) +- Separate privileged and unprivileged memory regions +- Prevent execution from RAM regions (defend against code injection) + +#### 5. Boot-Time Integrity Checks + +```c +// Early in reset handler or runtime_init +void verify_boot_integrity(void) { + // Check vector table hasn't been modified + uint32_t vector_table_checksum = calculate_checksum(0x10000000, VECTOR_TABLE_SIZE); + if (vector_table_checksum != EXPECTED_CHECKSUM) { + // Vector table tampered - refuse to boot + secure_halt(); + } + + // Check critical data structures + // Verify stack pointer is in valid range + // etc. +} +``` + +#### 6. Anti-Tampering Hardware + +- **Tamper detection:** Sensors that detect case opening or voltage glitching +- **Response actions:** Erase sensitive keys, refuse to boot, or alert monitoring systems +- **Secure elements:** Store cryptographic keys in separate tamper-resistant chips + +### Lessons for Defenders + +1. **The bootrom is your trust anchor** - Its immutability makes it the foundation of security. RP2350's secure boot features leverage this. + +2. **Early is critical** - Security checks in the reset handler or runtime_init run before any application code, making them harder to bypass. + +3. **Defense in depth** - Multiple layers (hardware fuses, encrypted storage, secure boot, MPU) make attacks much harder. + +4. **Physical access = game over** - If an attacker can connect a debug probe, they can potentially compromise the device. Physical security matters! + +5. **Know your boot sequence** - Understanding exactly what runs when helps you identify where to add security checks and what assets need protection. + +### Security Research Value + +For security researchers and penetration testers, boot sequence analysis helps: + +- **Find vulnerabilities:** Many security bugs exist in startup code that runs before normal security checks +- **Develop exploits:** Understanding memory layout and initialization is essential for exploit development +- **Assess attack surface:** Knowing what's accessible at boot time reveals potential attack vectors +- **Build better defenses:** You can't defend what you don't understand + +> **"To know your enemy, you must become your enemy."** - Sun Tzu + +Understanding how an attacker would analyze and exploit the boot sequence is essential for building robust defenses. + +--- + +## πŸ“– Glossary + +### New Terms This Week + +| Term | Definition | +| ----------------- | ----------------------------------------------------------------------- | +| **Bootrom** | Factory-programmed ROM containing first-stage bootloader | +| **BSS** | Block Started by Symbol - section for uninitialized global variables | +| **CPUID** | Register that identifies which CPU core is executing | +| **crt0** | C Runtime Zero - the startup code that runs before main | +| **IMAGE_DEF** | Structure that marks valid firmware for the bootrom | +| **Linker Script** | File that defines memory layout for the compiled program | +| **Reset Handler** | First function called after reset/power-on | +| **Thumb Mode** | Compact instruction encoding used by Cortex-M | +| **Vector Table** | Array of addresses for stack pointer and exception handlers | +| **VTOR** | Vector Table Offset Register - tells CPU where to find the vector table | +| **XIP** | Execute In Place - running code directly from flash memory | + +### Review Terms from Weeks 1-2 + +| Term | Definition | How We Used It | +| ---- | ---------- | -------------- | +| **Breakpoint** | Marker that pauses program execution | Set at reset handler and main | +| **Register** | Fast storage inside the processor | Watched SP, LR, PC during boot | +| **Stack Pointer** | Register pointing to top of stack | Saw initial value in vector table | +| **Flash Memory** | Read-only storage for code | Contains vector table and boot code | +| **SRAM** | Read-write memory for data | Where stack and variables live | + +--- + +## πŸ“š Additional Resources + +### RP2350 Datasheet + +For more details on the boot process, see the RP2350 Datasheet: +https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf + +Key sections referenced in this tutorial: + +| Section | Page | Topic | +| ------- | ---- | ----- | +| Β§2.1 | 25 | Bus fabric overview (SRAM0–9, ROM 32KB) | +| Β§2.2 | 31 | Address map (ROM, XIP, SRAM, SIO, PPB) | +| Β§2.2.3 | 32 | SRAM layout (SRAM0–7 striped, SRAM8–9 non-striped, SRAM_END = 0x20082000) | +| Β§3.1.2 | 39 | CPUID register (core identification) | +| Β§3.7 | 182 | VTOR β€” Vector Table Offset Register | +| Β§4.1 | 338 | ROM (32KB bootrom) | +| Β§4.2 | 338–339 | SRAM bank mapping and striping | +| Β§5.1.4 | 356 | Image definitions (IMAGE_DEF concept) | +| Β§5.1.5 | 357 | Blocks and block loops (markers 0xffffded3 / 0xab123579) | +| Β§5.2 | 375 | Boot sequence ("removal of a boot2 in the first 256 bytes") | +| Β§5.9.3.3 | 425 | VECTOR_TABLE item ("assumed to be at the start of the image") | +| Β§5.9.5 | 429 | Minimum viable image metadata (IMAGE_DEF must be in first 4 kB) | +| Β§5.9.5.1 | 429 | Minimum Arm IMAGE_DEF (20-byte sequence, entry via vector table) | + +### Pico SDK Source Code + +The startup code lives in: +- `crt0.S` - Main startup assembly (vector table at `.section .vectors`, reset handler, data copy, BSS clear, platform_entry) +- `memmap_default.ld` - Default linker script (section ordering: `.vectors` β†’ `.binary_info_header` β†’ `.embedded_block` β†’ `.reset`) +- `embedded_start_block.inc.S` - IMAGE_DEF block (replaces RP2040's `boot2_generic_03h.S`) + +> ⚠️ **Note:** The RP2040 used a `boot2_generic_03h.S` second-stage bootloader occupying the first 256 bytes of flash. The RP2350 eliminated this; the bootrom handles flash XIP setup directly. The SDK still includes a `boot2` mechanism for compatibility, but it is **not** placed at flash address 0 β€” it is embedded in the data copy table and executed from the stack during startup. + +### Bootrom Source + +The bootrom source is available at: +https://github.com/raspberrypi/pico-bootrom-rp2350 + +--- + +**Remember:** Understanding the boot process is fundamental to embedded systems work. Whether you're debugging a system that won't start, reverse engineering firmware, or building secure boot chains, this knowledge is essential! + +Happy exploring! πŸ” diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG00.svg b/WEEKS/WEEK03/slides/WEEK03-IMG00.svg new file mode 100644 index 0000000..d40fbfd --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 03 + + +Embedded System Analysis: +Understanding the RP2350 Architecture +w/ Comprehensive Firmware Analysis + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG01.svg b/WEEKS/WEEK03/slides/WEEK03-IMG01.svg new file mode 100644 index 0000000..00427ba --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG01.svg @@ -0,0 +1,70 @@ +ο»Ώ + + + + +RP2350 Boot Sequence +Power-On to main() β€” 5 Steps + + + +STEP 1 +Power On +Cortex-M33 wakes, execution at 0x00000000 (Bootrom) + + +β–Ό + + + +STEP 2 +Bootrom Executes +32KB on-chip ROM β€” finds IMAGE_DEF at 0x10000000 + + +β–Ό + + + +STEP 3 +Flash XIP Setup (bootrom-managed) +Bootrom configures flash interface & XIP (no boot2 on RP2350) + + +β–Ό + + + +STEP 4 +Vector Table & Reset Handler +Reads SP from offset 0x00 -> 0x20082000 +Reads Reset Handler from 0x04 -> 0x1000015d + + +β–Ό + + + +STEP 5 +C Runtime Startup (crt0.S) +Copy .data from flash -> RAM +Zero .bss section +Call runtime_init() -> main() + + + +Key Insight +Your main() is the LAST thing to run. +All 5 steps must complete first! + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG02.svg b/WEEKS/WEEK03/slides/WEEK03-IMG02.svg new file mode 100644 index 0000000..b7c0025 --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG02.svg @@ -0,0 +1,84 @@ + + + + + +The Bootrom +32KB Factory-Programmed ROM β€” Where It All Begins + + + +Bootrom Properties + + +Size +32 KB + + +Location +0x00000000 + + +Modifiable? +NO β€” mask ROM + + +Purpose +Boot the chip + +Burned into silicon at factory +Like BIOS in your computer + + + +What It Does + + +1. +Initialize hardware + + +2. +Check boot sources + + +3. +Validate IMAGE_DEF + + +4. +Configure flash + + +5. +Jump to your code + + + +IMAGE_DEF β€” Magic Markers +Bootrom looks for these to validate firmware + + +Start Marker +0xFFFFDED3 +"I'm a valid Pico binary!" + + +End Marker +0xAB123579 +"End of header block" + +Bootrom reads flash at 0x10000000, +finds these markers, then boots. + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG03.svg b/WEEKS/WEEK03/slides/WEEK03-IMG03.svg new file mode 100644 index 0000000..4db555b --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG03.svg @@ -0,0 +1,74 @@ + + + + + +XIP β€” Execute In Place +Run Code Directly from Flash β€” No Copy Needed + + + +Book Analogy + + +Without XIP +Photocopy every page, read copy + + +With XIP +Read directly from the book! + + + +Why Use XIP? + + +Saves RAM +Code stays in flash + + +Faster Boot +No bulk copy needed + + +Simpler +Less memory mgmt + + + +XIP Flash Region at 0x10000000 + + + +Vector Table +SP at offset 0x00 | Reset Handler at offset 0x04 | Exception handlers... + + + +Your Code +_reset_handler | main() | other functions + + + +Read-Only Data +Strings like "hello, world" | constant values + + +0x10000000 +0x100001xx +0x10001xxx + +CPU fetches instructions directly +from flash via XIP cache. + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG04.svg b/WEEKS/WEEK03/slides/WEEK03-IMG04.svg new file mode 100644 index 0000000..f843576 --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG04.svg @@ -0,0 +1,80 @@ +ο»Ώ + + + + +The Vector Table +CPU's Instruction Manual at 0x10000000 + + + +Vector Table Layout + + + +Offset +Address +Value +Meaning + + + +0x00 +0x10000000 +0x20082000 +Initial SP + + + +0x04 +0x10000004 +0x1000015D +Reset Handler + + + +0x08 +0x10000008 +0x1000011B +NMI Handler + + + +0x0C +0x1000000C +0x1000011D +HardFault Handler + + + +GDB: +x/4x 0x10000000 + + + +On Power-On +1. CPU reads SP from 0x00 +2. Sets SP = 0x20082000 +3. Reads Reset from 0x04 +4. Jumps to 0x1000015C + + + +Default Handlers +NMI, HardFault, SVCall, +PendSV, SysTick all use: + +bkpt 0x0000 +<- stops debugger + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG05.svg b/WEEKS/WEEK03/slides/WEEK03-IMG05.svg new file mode 100644 index 0000000..c3d03a0 --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG05.svg @@ -0,0 +1,70 @@ + + + + + +Thumb Mode Addressing +Why Addresses End in Odd Numbers + + + +The LSB Rule +ARM Cortex-M uses the Least Significant +Bit (LSB) to indicate instruction mode: + + + +LSB = 1 (odd) +Thumb mode + + + +LSB = 0 (even) +ARM mode + + + +Reset Handler Example + +Vector table stores: +0x1000015D + +Actual code address: +0x1000015C + +The +1 means: +"Use Thumb mode" + + + +GDB Shows + +0x1000015D +with Thumb bit + + +Vector table raw value + + + +Ghidra Shows + +0x1000015C +actual address + + +Real instruction location + +Both are correct β€” just displayed differently! + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG06.svg b/WEEKS/WEEK03/slides/WEEK03-IMG06.svg new file mode 100644 index 0000000..f7d8b17 --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG06.svg @@ -0,0 +1,69 @@ +ο»Ώ + + + + +Linker Script Memory Map +memmap_default.ld β€” Where Everything Lives + + + +Memory Regions + + + +Flash (XIP) +0x10000000 +varies +Your code (read-only) + + + +RAM +0x20000000 +512 KB +Main RAM (r/w) + + + +SCRATCH_X +0x20080000 +4 KB +Core 0 scratch (HW: SRAM8) + + + +SCRATCH_Y +0x20081000 +4 KB +Core 0 stack! (HW: SRAM9) + + + +Stack Pointer Calculation + +__StackTop = ORIGIN(SCRATCH_Y) + + LENGTH(SCRATCH_Y) + + +ORIGIN +0x20081000 + ++ LENGTH +0x1000 +(4 KB) + += __StackTop = 0x20082000 +<- matches vector table! + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG07.svg b/WEEKS/WEEK03/slides/WEEK03-IMG07.svg new file mode 100644 index 0000000..eea62be --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG07.svg @@ -0,0 +1,87 @@ +ο»Ώ + + + + +Reset Handler β€” 4 Phases +_reset_handler at 0x1000015C + + + +Phase 1: Core Check +0x1000015C β€” 0x10000168 + +mov r0, #0xD0000000 +Read CPUID -> Core 0 continues + + + +Phase 2: Data Copy +0x1000016A β€” 0x10000176 + +ldmia r4!, {r1,r2,r3} +Copy .data from flash -> RAM + + + +Phase 3: BSS Clear +0x10000178 β€” 0x10000184 + +stmia r1!, {r0} +r0 = 0 +Zero all uninitialized globals + + + +Phase 4: Platform Entry +0x10000186+ + +blx r1 +-> main() +runtime_init -> main -> exit + + + +Execution Flow + + + +Core Check +CPUID == 0? + +-> + + +Data Copy +flash -> RAM + +-> + + +BSS Clear +zero globals + +-> + + +Platform Entry +-> main()! + + + +Why check cores? +RP2350 has 2 cores. +Only Core 0 runs startup. +Core 1 returns to bootrom and waits. + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG08.svg b/WEEKS/WEEK03/slides/WEEK03-IMG08.svg new file mode 100644 index 0000000..b85e7ce --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG08.svg @@ -0,0 +1,93 @@ +ο»Ώ + + + + +Data Copy & BSS Clear +Initializing RAM Before main() Can Run + + + +Phase 2: Data Copy +Copy initialized variables flash -> RAM + +C code: + +int counter = 42; + +Value 42 stored in flash +but variables live in RAM! + + + +Flash + +-> + + +RAM + + +data_cpy_table has entries: + +src: 0x10001B4C (flash) +dst: 0x20000110 (RAM) + + + +Phase 3: BSS Clear +Zero uninitialized global variables + +C code: + +int my_counter; + +C standard requires +this to start at zero. + + + +r1 = BSS start +r2 = BSS end +r0 = 0 + +Loop: store 0, advance r1 +Until r1 == r2 -> done! + + + +Key Assembly Instructions + + + +ldmia r4!, {r1,r2,r3} + +Load source, dest, end from table + + +bl data_cpy + +Copy word-by-word until done + + + +movs r0, #0 + +Load zero into r0 + + +stmia r1!, {r0} + +Store zero, advance pointer + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG09.svg b/WEEKS/WEEK03/slides/WEEK03-IMG09.svg new file mode 100644 index 0000000..d2f8f1f --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG09.svg @@ -0,0 +1,82 @@ +ο»Ώ + + + + +Platform Entry -> main() +The Final Step β€” 3 Function Calls at 0x10000186 + + + +platform_entry Assembly + + +0x10000186 +ldr r1, [DAT] +-> load runtime_init addr + + +0x10000188 +blx r1 +-> call runtime_init() + + +0x1000018C +blx r1 +-> call main() + + +0x10000190 +blx r1 +-> call exit() + + + +Call Sequence + + +runtime_init() +SDK setup + +-> + + +main() +YOUR CODE + +-> + + +exit() +cleanup + + + +runtime_init() +Initializes SDK systems: +β€’ Clock configuration +β€’ GPIO setup +β€’ C++ constructor calls +β€’ Peripheral initialization + + +After main() Returns +exit() handles cleanup. +Then: + +bkpt 0x0000 +<- infinite halt + +Should never be reached! + \ No newline at end of file diff --git a/WEEKS/WEEK03/slides/WEEK03-IMG10.svg b/WEEKS/WEEK03/slides/WEEK03-IMG10.svg new file mode 100644 index 0000000..7dd1bcb --- /dev/null +++ b/WEEKS/WEEK03/slides/WEEK03-IMG10.svg @@ -0,0 +1,92 @@ +ο»Ώ + + + + +Secure Boot & Attack Vectors +Why Boot Sequence Knowledge Matters for Security + + + +Attack Scenarios + + +Firmware Replacement +Replace flash with malicious code + + +Vector Table Hijack +Modify reset handler address + + +Debug Port Attack +SWD/JTAG to dump or inject code + + +Startup Code Modification +Change crt0 data copy / BSS init + +Physical access = game over + + + +Defense Strategies + + +1. Secure Boot + + +2. Debug Port Lock + + +3. Flash Read Protect + + +4. MPU Configuration + + +5. Integrity Checks + +Defense in depth! + + + +Secure Boot Chain + + +Bootrom +immutable + +-> + + +Verify Sig +IMAGE_DEF + +-> + + +Verify App +signature + +-> + + +Boot! +or refuse + +Each stage cryptographically verifies +the next before handing off control. +Bootrom = trust anchor (can't be changed) + \ No newline at end of file diff --git a/WEEKS/WEEK04/WEEK04-01-S.md b/WEEKS/WEEK04/WEEK04-01-S.md new file mode 100644 index 0000000..9241b72 --- /dev/null +++ b/WEEKS/WEEK04/WEEK04-01-S.md @@ -0,0 +1,63 @@ +# 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 + +### Non-Credit Practice Exercise 1 Solution: Analyze Variable Storage in Ghidra + +#### Answers + +##### Main Function Analysis + +| 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 | +| stdio_init_all addr | 0x100030cc | I/O initialization | +| Format string | "age: %d\r\n" | Located in .rodata section | + +##### Decompiled main() After Renaming + +```c +int main(void) +{ + stdio_init_all(); + do { + printf("age: %d\r\n", 0x2b); + } while (true); +} +``` + +##### Hex-to-Decimal Conversion + +``` +0x2b = (2 Γ— 16) + 11 = 32 + 11 = 43 +``` + +The compiler replaced both `age = 42` and `age = 43` with the final constant value `0x2b` (43) as an immediate operand in `movs r1, #0x2b`. + +##### Assembly Listing + +```assembly +movs r1, #0x2b ; Load 43 directly into r1 (printf argument) +ldr r0, [pc, #...] ; Load format string address +bl printf ; Call printf +``` + +#### Reflection Answers + +1. **Why did the compiler optimize away the `age` variable?** + The compiler performs **dead store elimination** and **constant propagation**. The initial assignment `age = 42` is immediately overwritten by `age = 43` with no intervening reads of the value 42. Since the only value ever observed is 43, the compiler replaces all references to `age` with the constant `0x2b` (43) as an immediate operand. No memory allocation is neededβ€”the value lives entirely in the instruction encoding (`movs r1, #0x2b`). + +2. **In what memory section would `age` have been stored if it wasn't optimized away?** + As a local variable declared inside `main()`, `age` would have been stored on the **stack** (in RAM at `0x2000xxxx`). The compiler would allocate space by subtracting from SP, store the value with a `str` instruction, and load it back with `ldr` before passing it to `printf`. If `age` were declared as a global initialized variable, it would be placed in the **`.data`** section (RAM, initialized from flash at boot). If declared as `static` or global but uninitialized, it would go in the **`.bss`** section (RAM, zeroed at boot). + +3. **Where is the string "age: %d\r\n" stored, and why can't it be in RAM?** + The format string is stored in the **`.rodata`** (read-only data) section in flash memory at `0x1000xxxx`. It cannot be in RAM because: (a) string literals are constants that never change, so storing them in limited RAM would waste space; (b) flash is non-volatileβ€”the string persists across power cycles without needing to be copied from anywhere; (c) the XIP (Execute In Place) mechanism allows the CPU to read directly from flash, so `.rodata` access is efficient. + +4. **What would happen if we had used `age` in a calculation before reassigning it to 43?** + The compiler would be forced to preserve the value 42 because it's actually read before being overwritten. For example, `printf("before: %d\n", age); age = 43;` would require the compiler to generate both `movs r1, #0x2a` (42) for the first print and `movs r1, #0x2b` (43) for subsequent uses. Alternatively, a calculation like `age = age + 1` would allow the compiler to constant-fold `42 + 1 = 43` at compile time and still emit just `#0x2b`. Only if the value depends on runtime input would the variable require actual memory allocation. diff --git a/WEEKS/WEEK04/WEEK04-01.md b/WEEKS/WEEK04/WEEK04-01.md new file mode 100644 index 0000000..3bb7c46 --- /dev/null +++ b/WEEKS/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 + +### Non-Credit Practice 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 + +```powershell +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/WEEKS/WEEK04/WEEK04-02-S.md b/WEEKS/WEEK04/WEEK04-02-S.md new file mode 100644 index 0000000..8b15db9 --- /dev/null +++ b/WEEKS/WEEK04/WEEK04-02-S.md @@ -0,0 +1,53 @@ +# 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 + +### Non-Credit Practice Exercise 2 Solution: Patch Binary to Change Variable Value + +#### Answers + +##### Patch Details + +| Item | Original | Patched | +|--------------------|-------------- |---------------| +| Instruction | movs r1, #0x2b | movs r1, #0x46 | +| Hex bytes | 21 2b | 21 46 | +| Decimal value | 43 | 70 | +| Output | age: 43 | age: 70 | + +##### Patching Steps + +1. Located `movs r1, #0x2b` in Ghidra Listing view +2. Right-click β†’ **Patch Instruction** β†’ changed `#0x2b` to `#0x46` +3. Verified in Decompile window: `printf("age: %d\r\n", 0x46)` +4. Exported: **File β†’ Export Program β†’ Binary** β†’ `0x0005_intro-to-variables-h.bin` +5. Converted to UF2: + ```powershell + python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 + ``` +6. Flashed via BOOTSEL β†’ RPI-RP2 drive + +##### Serial Output + +``` +age: 70 +age: 70 +age: 70 +... +``` + +#### Reflection Answers + +1. **Why do we need to convert to UF2 format instead of flashing the raw .bin file?** + The RP2350 bootloader (accessed via BOOTSEL) expects **UF2 (USB Flashing Format)** files. UF2 is a container format that includes metadata: the target flash address for each 256-byte block, a family ID (`0xe48bff59` for RP2350), and checksums. A raw `.bin` file contains only code bytes with no addressing informationβ€”the bootloader wouldn't know where in flash to write the data. UF2 also supports partial updates and is self-describing, making it safer for USB mass storage flashing. + +2. **What is the significance of the base address 0x10000000 in the conversion command?** + `0x10000000` is the **XIP (Execute In Place) flash base address** on the RP2350. This tells the UF2 converter that byte 0 of the binary should be written to flash address `0x10000000`. The CPU fetches instructions directly from this address range via the XIP controller. If the base address were wrong (e.g., `0x20000000` for RAM), the code would be written to the wrong location and the processor would fail to boot because the vector table wouldn't be found at the expected address. + +3. **What would happen if you patched the wrong instruction by mistake?** + The consequences depend on what was changed: (a) Patching a different `movs` might corrupt an unrelated function parameter, causing incorrect behavior or a crash. (b) Patching opcode bytes (not just the immediate) could create an invalid instruction, triggering a HardFault or UsageFault. (c) Patching inside a multi-byte instruction could split it into two unintended instructions, corrupting the entire subsequent instruction stream. The program would likely crash, output garbage, or hangβ€”requiring reflashing with the original UF2 to recover. + +4. **How can you verify a patch was applied correctly before exporting?** + Multiple verification methods: (a) Check the **Decompile window**β€”it should reflect the new value (e.g., `printf("age: %d\r\n", 0x46)`). (b) Inspect the **Listing window** bytesβ€”confirm the instruction bytes changed from `21 2b` to `21 46`. (c) Use **right-click β†’ Highlight β†’ Highlight Difference** to see patched bytes highlighted. (d) Compare the patched instruction against the ARM Thumb encoding reference to verify the encoding is valid. (e) Check surrounding instructions are unchangedβ€”patches should not accidentally modify adjacent code. diff --git a/WEEKS/WEEK04/WEEK04-02.md b/WEEKS/WEEK04/WEEK04-02.md new file mode 100644 index 0000000..a820d55 --- /dev/null +++ b/WEEKS/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 + +### Non-Credit Practice 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/WEEKS/WEEK04/WEEK04-03-S.md b/WEEKS/WEEK04/WEEK04-03-S.md new file mode 100644 index 0000000..1315c78 --- /dev/null +++ b/WEEKS/WEEK04/WEEK04-03-S.md @@ -0,0 +1,97 @@ +# 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 + +### Non-Credit Practice Exercise 3 Solution: Analyze and Understand GPIO Control + +#### Answers + +##### Main Function Decompiled (After Renaming) + +```c +int main(void) +{ + stdio_init_all(); + gpio_init(0x10); // Initialize GPIO 16 + gpio_set_dir(0x10, 1); // Set as output (GPIO_OUT = 1) + + while (true) { + printf("age: %d\r\n", 0); // Uninitialized variable = 0 + gpio_put(0x10, 1); // LED ON + sleep_ms(0x1f4); // Wait 500ms + gpio_put(0x10, 0); // LED OFF + sleep_ms(0x1f4); // Wait 500ms + } +} +``` + +##### GPIO Function Identification Table + +| Auto-Generated Name | Actual Function | Parameters | Identification Method | +|---------------------|-----------------|--------------------------------|--------------------------------------| +| FUN_100030cc | stdio_init_all | (void) | Called first, no parameters | +| FUN_xxxxxxxx | gpio_init | (uint gpio) | Single parameter = pin number (0x10) | +| FUN_xxxxxxxx | gpio_set_dir | (uint gpio, bool out) | Two params: pin + direction (1=out) | +| FUN_xxxxxxxx | gpio_put | (uint gpio, bool value) | Two params: pin + value (0 or 1) | +| FUN_xxxxxxxx | sleep_ms | (uint32_t ms) | Single param = delay (0x1f4 = 500) | +| FUN_10003100 | printf | (const char *fmt, ...) | Format string + varargs | + +##### Key Value Conversions + +| Hex | Decimal | Binary | Purpose | +|--------|---------|-------------|------------------------| +| 0x10 | 16 | 0000 1000 | GPIO pin number (red LED) | +| 0x1f4 | 500 | β€” | Sleep duration (ms) | +| 0x01 | 1 | 0000 0001 | GPIO_OUT / LED ON | +| 0x00 | 0 | 0000 0000 | GPIO_IN / LED OFF | + +``` +0x10 = (1 Γ— 16) + 0 = 16 +0x1f4 = (1 Γ— 256) + (15 Γ— 16) + 4 = 256 + 240 + 4 = 500 +``` + +##### GPIO Memory Map + +| Address | Register | Purpose | +|-------------|-------------|------------------------------| +| 0x40028000 | IO_BANK0 | GPIO function selection | +| 0x40038000 | PADS_BANK0 | GPIO pad configuration | +| 0xd0000000 | SIO | Single-cycle I/O | + +GPIO 16 specific: +- Pad control: `0x40038000 + (16 Γ— 4) = 0x40038040` +- Function select: `0x40028000 + (16 Γ— 4) = 0x40028040` + +##### gpio_put Coprocessor Instruction + +```assembly +mcrr p0, #4, r4, r5, c0 +``` + +- `mcrr` = Move to Coprocessor from two ARM Registers +- `p0` = Coprocessor 0 (GPIO coprocessor on RP2350) +- `r4` = GPIO pin number, `r5` = value (0 or 1) +- Single-cycle GPIO operation + +##### Blink Timing Analysis + +- ON duration: `sleep_ms(0x1f4)` = 500ms +- OFF duration: `sleep_ms(0x1f4)` = 500ms +- Total cycle: 1000ms = 1 second +- Blink rate: **1 Hz** + +#### Reflection Answers + +1. **Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?** + These registers control different aspects of GPIO operation. **PADS_BANK0** (`0x40038000`) configures the physical pad properties: input enable (IE), output disable (OD), pull-up/pull-down resistors, drive strength, and slew rate. **IO_BANK0** (`0x40028000`) configures the function multiplexer (FUNCSEL), selecting which internal peripheral drives the pinβ€”SIO (function 5) for software control, UART, SPI, I2C, PWM, etc. Both must be configured: PADS sets the electrical characteristics of the physical pin, while IO_BANK0 routes the correct internal signal to it. + +2. **What is the advantage of using the GPIO coprocessor instruction (`mcrr`) instead of writing to memory-mapped registers?** + The `mcrr` coprocessor instruction completes in a **single CPU cycle**, whereas writing to memory-mapped GPIO registers requires multiple cycles: an address load, a data load, and a store instruction (plus potential bus wait states). On the RP2350, the SIO coprocessor provides deterministic, single-cycle access to GPIO outputs, which is critical for bit-banging protocols (like SPI or custom serial) where precise timing is required. The coprocessor path bypasses the AHB/APB bus entirely. + +3. **If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use?** + At 10 Hz, each full on/off cycle is 100ms. Since the loop has two `sleep_ms()` calls (one for ON, one for OFF), each should be `100 / 2 = 50ms`. In hex: `50 = 0x32`. So both `sleep_ms()` calls should use `sleep_ms(0x32)`. + +4. **What would happen if you called `gpio_put()` on a pin that hasn't been initialized with `gpio_init()` first?** + The GPIO pin's function would still be set to its reset default (typically NULL/function 0), not SIO (function 5). The `gpio_put()` coprocessor instruction would update the SIO output register internally, but since the pin's function multiplexer isn't routing SIO to the physical pad, the electrical state of the pin wouldn't change. The LED would remain off. Additionally, without pad configuration, input enable and output disable bits may not be set correctly, further preventing any observable output. diff --git a/WEEKS/WEEK04/WEEK04-03.md b/WEEKS/WEEK04/WEEK04-03.md new file mode 100644 index 0000000..3ec9408 --- /dev/null +++ b/WEEKS/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 + +### Non-Credit Practice 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/WEEKS/WEEK04/WEEK04-04-S.md b/WEEKS/WEEK04/WEEK04-04-S.md new file mode 100644 index 0000000..818e3bd --- /dev/null +++ b/WEEKS/WEEK04/WEEK04-04-S.md @@ -0,0 +1,102 @@ +# 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 + +### Non-Credit Practice Exercise 4 Solution: Patch GPIO Binary to Change LED Pin + +#### Answers + +##### Patch Summary + +| Item | Original | Patched | Hex Change | +|---------------|-----------------|-----------------|---------------| +| LED pin | GPIO 16 | GPIO 17 | 0x10 β†’ 0x11 | +| Printed value | 0 (uninitialized)| 66 | 0x00 β†’ 0x42 | +| Blink timing | 500ms | 100ms | 0x1f4 β†’ 0x64 | + +##### Detailed Patch Locations + +**1. gpio_init parameter:** +```assembly +; Before: movs r0, #0x10 (bytes: 10 20) +; After: movs r0, #0x11 (bytes: 11 20) +``` + +**2. gpio_set_dir parameter:** +```assembly +; Before: movs r3, #0x10 (bytes: 10 23) +; After: movs r3, #0x11 (bytes: 11 23) +``` + +**3. gpio_put (LED ON) parameter:** +```assembly +; Before: movs r4, #0x10 (bytes: 10 24) +; After: movs r4, #0x11 (bytes: 11 24) +``` + +**4. gpio_put (LED OFF) parameter:** +```assembly +; Before: movs r4, #0x10 (bytes: 10 24) +; After: movs r4, #0x11 (bytes: 11 24) +``` + +**5. printf value:** +```assembly +; Before: movs r1, #0x00 (bytes: 00 21) +; After: movs r1, #0x42 (bytes: 42 21) +``` + +**6. sleep_ms (both calls):** +```assembly +; Before: loads 0x1f4 (500ms) +; After: movs r0, #0x64 (100ms) +``` + +##### Hex Conversions + +``` +GPIO 17: 17 = 0x11 = 0001 0001 binary +Value 66: 66 = 0x42 = 0100 0010 binary +100ms: 100 = 0x64 = 0110 0100 binary +``` + +##### Decompiled Result After All Patches + +```c +int main(void) +{ + stdio_init_all(); + gpio_init(0x11); // GPIO 17 (green LED) + gpio_set_dir(0x11, 1); // Output + + while (true) { + printf("age: %d\r\n", 0x42); // Prints 66 + gpio_put(0x11, 1); // Green LED ON + sleep_ms(0x64); // 100ms + gpio_put(0x11, 0); // Green LED OFF + sleep_ms(0x64); // 100ms + } +} +``` + +##### Hardware Verification + +- GREEN LED (GPIO 17) blinks at 10 Hz (100ms on, 100ms off) +- RED LED (GPIO 16) remains off +- Serial output: `age: 66` repeating + +#### Reflection Answers + +1. **Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)?** + Each function takes the GPIO pin number as a separate parameter. `gpio_init(16)` configures the pad and function mux for pin 16. `gpio_set_dir(16, 1)` sets pin 16's direction to output. `gpio_put(16, value)` toggles pin 16's output state. These are independent function calls with independent immediate values in the instruction streamβ€”the compiler doesn't share or reuse a single "pin number variable." Each `movs rN, #0x10` loads the pin number fresh for its respective function call. Missing any one patch would result in a mismatch: e.g., initializing pin 17 but toggling pin 16. + +2. **What would happen if you forgot to patch one of the gpio_put calls?** + You would get asymmetric behavior. For example, if you patched the "LED ON" `gpio_put` to pin 17 but left the "LED OFF" at pin 16: GPIO 17 (green) would turn on but never turn off (staying permanently lit), while GPIO 16 (red) would receive the "off" command for a pin that was never initializedβ€”which would have no visible effect. The result: green LED stuck on, no blinking. + +3. **How does the instruction encoding differ for immediate values less than 256 vs. greater than 255?** + In 16-bit Thumb encoding, `movs Rd, #imm8` can only encode immediate values 0–255 in a single 2-byte instruction. For values > 255 (like 500 = 0x1f4), the compiler must use either: (a) a 32-bit Thumb-2 `movw Rd, #imm16` instruction (4 bytes, can encode 0–65535), (b) a multi-instruction sequence that constructs the value (e.g., `movs` + `lsls` + `add`), or (c) an `ldr Rd, [pc, #offset]` that loads the constant from a literal pool in flash. This is why patching `sleep_ms(500)` may be more complex than patching `gpio_put(16, 1)`. + +4. **If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?** + At 5 Hz, each complete cycle is `1000 / 5 = 200ms`. With two `sleep_ms()` calls per cycle (ON and OFF), each call should be `200 / 2 = 100ms`. In hex: `100 = 0x64`. So `sleep_ms(0x64)` for each callβ€”which is exactly the value used in this exercise's patch. For a different duty cycle (e.g., 150ms on, 50ms off), you'd use different values for each call while keeping the total at 200ms. diff --git a/WEEKS/WEEK04/WEEK04-04.md b/WEEKS/WEEK04/WEEK04-04.md new file mode 100644 index 0000000..296c680 --- /dev/null +++ b/WEEKS/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 + +### Non-Credit Practice 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/WEEKS/WEEK04/WEEK04-SLIDES.pdf b/WEEKS/WEEK04/WEEK04-SLIDES.pdf new file mode 100644 index 0000000..f1a6bdd Binary files /dev/null and b/WEEKS/WEEK04/WEEK04-SLIDES.pdf differ diff --git a/WEEKS/WEEK04/WEEK04.md b/WEEKS/WEEK04/WEEK04.md new file mode 100644 index 0000000..b404c96 --- /dev/null +++ b/WEEKS/WEEK04/WEEK04.md @@ -0,0 +1,964 @@ +ο»Ώ# 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: + +> πŸ“– **Datasheet Reference:** SRAM starts at `0x20000000` and XIP flash at `0x10000000` (Section 2.2, p. 31). The `.data` and `.bss` sections live in SRAM; `.text` and `.rodata` live in flash and are accessed via Execute-In-Place (Section 4.4, p. 340+). + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ .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! + +> πŸ“– **Datasheet Reference:** IO_BANK0 is at `0x40028000` and PADS_BANK0 is at `0x40038000` (Section 2.2, p. 33). FUNCSEL value 5 selects the SIO function for a GPIO pin (Section 9.6.1, p. 612). The SIO block base address is `0xd0000000` (Section 2.2, p. 31). + +--- + +## πŸ“š 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:** + +```powershell +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. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **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:** + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0005_intro-to-variables +``` + +**Run the conversion command:** + +```powershell +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 + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\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! + +> πŸ“– **Datasheet Reference:** The GPIO coprocessor (GPIOC) is documented in Section 3.7.5 (p. 101–104). It provides low-overhead Cortex-M33 coprocessor access to SIO GPIO registers via `mcrr`/`mrrc` instructions, allowing single-cycle GPIO operations. + +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) | + +> πŸ“– **Datasheet Reference:** Full address map in Section 2.2 (p. 31–33). IO_BANK0 at `0x40028000` (p. 605), PADS_BANK0 at `0x40038000` (p. 786), SIO at `0xd0000000`. + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG00.svg b/WEEKS/WEEK04/slides/WEEK04-IMG00.svg new file mode 100644 index 0000000..2b2aae1 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 04 + + +Variables in Embedded Systems: +Debugging and Hacking Variables +w/ GPIO Output Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG01.svg b/WEEKS/WEEK04/slides/WEEK04-IMG01.svg new file mode 100644 index 0000000..5331adf --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG01.svg @@ -0,0 +1,96 @@ + + + + + +What is a Variable? +Labeled Boxes in Memory (SRAM) + + + +Memory β€” A Row of Numbered Boxes + + + +42 +age +Box 0 + + + +17 +score +Box 1 + + + +0 +count +Box 2 + + + +255 +max +Box 3 + + + +99 +temp +Box 4 + + + +Anatomy of a Declaration + + +uint8_t age = 42; + +uint8_t +Data type (1 byte) + +age +Variable name (label) + += 42 +Initial value + +; +End of statement + + + +Key Concepts + + +Declaration +name + type + + +Definition +allocates memory + + +Initialization +assigns value + + + +Important Rule +You MUST declare a +variable BEFORE you +use it! +Compiler needs to know the type + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG02.svg b/WEEKS/WEEK04/slides/WEEK04-IMG02.svg new file mode 100644 index 0000000..8933406 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG02.svg @@ -0,0 +1,86 @@ + + + + + +Data Types & Sizes +How Much Memory Each Type Uses + + + + + + +Type +Size +Range +Description + + + +uint8_t +1 byte +0 β€” 255 +Unsigned 8-bit + + + +int8_t +1 byte +-128 β€” 127 +Signed 8-bit + + + +uint16_t +2 bytes +0 β€” 65,535 +Unsigned 16-bit + + + +int16_t +2 bytes +-32,768 β€” 32,767 +Signed 16-bit + + + +uint32_t +4 bytes +0 β€” 4,294,967,295 +Unsigned 32-bit + + + +int32_t +4 bytes +-2.1B β€” 2.1B +Signed 32-bit + + +Size Comparison + + +1B +uint8_t + + +2B +uint16_t + + +4 Bytes +uint32_t + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG03.svg b/WEEKS/WEEK04/slides/WEEK04-IMG03.svg new file mode 100644 index 0000000..0cbcf5a --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG03.svg @@ -0,0 +1,63 @@ +ο»Ώ + + + + +Memory Sections +Where Variables Live After Compilation + + + + +.data +Flash -> copied to RAM at startup +Contains: Initialized global/static variables +int counter = 42; +Initial value stored in flash, copied to SRAM by data_cpy + + + + +.bss +RAM β€” zeroed at startup +Contains: Uninitialized global/static variables +int counter; +NOT stored in binary (saves space!) β€” memset to 0 at boot + + + + +.rodata +Flash β€” read only +Contains: Constants and string literals +const int MAX = 100; +Lives in flash permanently β€” cannot be modified at runtime + + + +.data +RAM +Writable +Initialized globals + +.bss +RAM +Writable +Uninitialized globals (zeroed) + +.rodata +Flash +Read-only +Constants & strings + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG04.svg b/WEEKS/WEEK04/slides/WEEK04-IMG04.svg new file mode 100644 index 0000000..82437d2 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG04.svg @@ -0,0 +1,79 @@ + + + + + +GPIO Basics +General Purpose Input/Output on RP2350 + + + +Pico 2 GPIO Pins + +GPIO 16 + +Red LED + +GPIO 17 + +Green LED + +GPIO 18 + +Blue LED + +GPIO 25 + +Onboard LED + +Software-controlled switches + + + +Pico SDK Functions + + +gpio_init(pin) +Init pin + + +gpio_set_dir(pin,d) +I/O dir + + +gpio_put(pin,val) +Set H/L + + +sleep_ms(ms) +Delay + + + +Basic LED Blink Code + + +#define LED_PIN 16 +int main(void) { +gpio_init(LED_PIN); +gpio_set_dir(LED_PIN, GPIO_OUT); +while (true) { +gpio_put(LED_PIN, 1); +// ON +sleep_ms(500); +gpio_put(LED_PIN, 0); +// OFF +sleep_ms(500); +}} + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG05.svg b/WEEKS/WEEK04/slides/WEEK04-IMG05.svg new file mode 100644 index 0000000..c6a0ef6 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG05.svg @@ -0,0 +1,79 @@ +ο»Ώ + + + + +Ghidra Binary Analysis +Analyzing a Raw .bin Without Symbols + + + +1. Import + +File -> Import +Language: +ARM Cortex 32 LE +Block: +.text +Base: +10000000 +XIP address for RP2350 + + + +2. Analyze + +Auto-Analyze: Yes +Ghidra finds: +FUN_1000019a +FUN_10000210 +FUN_10000234 +Auto-generated names + + + +3. Resolve + +Edit Function Sig +Rename to: +data_cpy +frame_dummy +main +Fix signatures + + + +Decompiled main() in Ghidra + + +Before Resolving: +void FUN_10000234(void){ +FUN_10002f54(); +do { +FUN_100030e4( +DAT_10000244,0x2b); +} while(true); +} + + +After Resolving: +int main(void) { +stdio_init_all(); +do { +printf( +"age: %d\r\n" +, 0x2b); +} while(true); +} + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG06.svg b/WEEKS/WEEK04/slides/WEEK04-IMG06.svg new file mode 100644 index 0000000..a2c4295 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG06.svg @@ -0,0 +1,77 @@ +ο»Ώ + + + + +Compiler Optimization +Why Your Variable Disappeared + + + +Source Code + + +int main(void) { +uint8_t age = 42; +age = 43; +stdio_init_all(); +while (true) +printf("age: %d", age); + + + +Compiler Thinks... + + +age = 42 is NEVER read + + +Dead store -> REMOVED + + +age = 43 -> constant fold + +Replaces variable with literal + + + +Resulting Assembly + + +1000023a +2b 21 +movs r1, #0x2b +; 0x2b = 43 +No age=42 instruction β€” compiler removed it + + + +Key Takeaway + + +Source Code +age = 42 +age = 43 + +-> + + +Binary +movs r1, #0x2b + + +Compiler +Optimizes dead +stores away! + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG07.svg b/WEEKS/WEEK04/slides/WEEK04-IMG07.svg new file mode 100644 index 0000000..d211d60 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG07.svg @@ -0,0 +1,86 @@ + + + + + +Binary Patching +Changing Values in the Binary + + + +Before Patch + + +1000023a +2b 21 +movs r1,#0x2b + +0x2b = 43 decimal +Output: +age: 43 +Compiler-optimized constant + + + +After Patch + + +1000023a +46 21 +movs r1,#0x46 + +0x46 = 70 decimal +Output: +age: 70 +Changed program behavior! + + + +How to Patch in Ghidra + + +1. Find Instr + +-> + + +2. Rt-Click + +-> + + +3. Patch Val + +-> + + +Done! + +Patch Instruction: change operand + + + +Export Patched Binary + + +File: Export + + +Format: Raw Bytes + + +Save as *-h.bin + +Exported binary has your patches + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG08.svg b/WEEKS/WEEK04/slides/WEEK04-IMG08.svg new file mode 100644 index 0000000..b28da76 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG08.svg @@ -0,0 +1,99 @@ + + + + + +GPIO Hacking +Patching GPIO 16 to GPIO 17 + + + +Original: GPIO 16 +Red LED on pin 16 + + +1000023a +10 20 +movs r0,#0x10 + + +10000244 +10 23 +movs r3,#0x10 + + +10000252 +10 24 +movs r4,#0x10 + +0x10 = 16, three locations + + + +Patched: GPIO 17 +Green LED on pin 17 + + +1000023a +11 20 +movs r0,#0x11 + + +10000244 +11 23 +movs r3,#0x11 + + +10000252 +11 24 +movs r4,#0x11 + +0x11 = 17, all patched! + + + +What Each Patch Controls + + +gpio_init +r0 + + +gpio_set_dir +r3 + + +gpio_put +r4 + +ALL pin refs must be patched + + + +Bonus: Change Print Value + + +00 21 +movs r1,#0x0 +age: 0 + +-> + + +42 21 +movs r1,#0x42 +age: 66 + +Changed value: 0 to 66 (0x42) + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG09.svg b/WEEKS/WEEK04/slides/WEEK04-IMG09.svg new file mode 100644 index 0000000..6743432 --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG09.svg @@ -0,0 +1,78 @@ + + + + + +GPIO Coprocessor +RP2350 Single-Cycle I/O via mcrr + + + +mcrr Instruction Breakdown + + +mcrr p0, #4, r4, r5, c0 + +mcrr +Move to Coprocessor (2 regs) +p0 +Coprocessor 0 (GPIO) +r4 +GPIO pin number +r5 +Value (0=LOW, 1=HIGH) + + + +Output Value (c0) + + +mcrr p0,#4,r4,r5,c0 + +r4 = pin number +r5 = 0 or 1 +Controls GPIO output state + + +Output Enable (c4) + + +mcrr p0,#4,r4,r5,c4 + +r4 = pin number +r5 = 1 (enable output) +Sets pin direction to OUTPUT + + + +gpio_init(16) Sequence + + +Step 1: Config Pad +addr 0x40038044 + + +Step 2: Set Func +FUNCSEL = 5 (SIO) + + +Step 3: Enable Out +mcrr p0,#4,r4,r5,c4 + + + + +Pad: clear OD, set IE, clear ISO +SIO = fast single-cycle GPIO access + \ No newline at end of file diff --git a/WEEKS/WEEK04/slides/WEEK04-IMG10.svg b/WEEKS/WEEK04/slides/WEEK04-IMG10.svg new file mode 100644 index 0000000..23741fe --- /dev/null +++ b/WEEKS/WEEK04/slides/WEEK04-IMG10.svg @@ -0,0 +1,119 @@ + + + + + +Full Patching Pipeline +End-to-End Binary Hacking Workflow + + + + +1 +Import .bin +Ghidra: Import +ARM Cortex 32 LE +Base: 0x10000000 + + + + + + + +2 +Analyze +Auto-analyze +Rename functions +Fix signatures + + + + + + + +3 +Find Target +Listing window +Find movs rN,#val +Identify bytes to change + + + + +4 +Patch +Right-click: +Patch Instruction +Change operand value + + + + + + + +5 +Export +File: Export +Format: Raw Bytes +Save as *-h.bin + + + + + + + +6 +Convert UF2 +uf2conv.py +--family 0xe48bff59 +RP2350 family ID + + + +UF2 Command + +python uf2conv.py file.bin --base 0x10000000 -o hacked.uf2 + + + +Flash to Pico 2 +1. Hold BOOTSEL + USB +2. Drop hacked.uf2 +3. Pico reboots hacked +RPI-RP2 drive in BOOTSEL + + + +Key Sections + +.text +Flash +Code + +.rodata +Flash +Constants + +.data +RAM +Init globals + +.bss +RAM +Zeroed globals + \ No newline at end of file diff --git a/WEEKS/WEEK05/WEEK05-01-S.md b/WEEKS/WEEK05/WEEK05-01-S.md new file mode 100644 index 0000000..59eb48f --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-01-S.md @@ -0,0 +1,63 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 1 Solution: Analyze the Float Binary in Ghidra + +#### Answers + +##### Main Function Analysis + +| Item | Value | Notes | +|--------------------------|------------------------|------------------------------------| +| Main function address | 0x10000234 | Entry point of program | +| Float value (original) | 42.5 | Declared as `float` | +| Double hex encoding | 0x4045400000000000 | Promoted to double for printf | +| r3 (high word) | 0x40454000 | Sign + exponent + top mantissa | +| r2 (low word) | 0x00000000 | All zeros (clean fractional part) | +| Exponent (stored) | 1028 | Biased value | +| Exponent (real) | 5 | After subtracting bias 1023 | +| Format string | "fav_num: %f\r\n" | Located at 0x100034a8 | +| stdio_init_all address | 0x10002f5c | I/O initialization | +| printf address | 0x100030ec | Standard library function | + +##### IEEE 754 Decoding of 0x4045400000000000 + +``` +r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 +r2 = 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 + +Sign bit (bit 63): 0 β†’ Positive +Exponent (bits 62-52): 10000000100 = 1028 β†’ 1028 - 1023 = 5 +Mantissa (bits 51-0): 0101010000...0 β†’ 1.010101 (with implied 1) + +Value = 1.010101β‚‚ Γ— 2⁡ = 101010.1β‚‚ = 32 + 8 + 2 + 0.5 = 42.5 βœ“ +``` + +##### Decompiled main() After Renaming + +```c +int main(void) +{ + stdio_init_all(); + do { + __wrap_printf("fav_num: %f\r\n", /* r2:r3 = 0x4045400000000000 = 42.5 */); + } while (true); +} +``` + +#### Reflection Answers + +1. **Why does the compiler promote a `float` to a `double` when passing it to `printf`?** + The C standard (Β§6.5.2.2) specifies **default argument promotions** for variadic functions like `printf`. When a `float` is passed to a variadic parameter (the `...` part), it is automatically promoted to `double`. This is because historically, floating-point hardware and calling conventions operated more efficiently with double precision. The `printf` function with `%f` always expects a 64-bit `double` on the stack or in the register pair `r2:r3`, never a 32-bit `float`. + +2. **The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`?** + It means the fractional part of 42.5 can be represented exactly with very few mantissa bits. The value 0.5 is exactly 2⁻¹ in binaryβ€”a single bit. After normalization, the mantissa is `010101000...` which only needs 6 significant bits. All remaining 46 bits (including the entire low 32-bit word) are zero. Values like 0.5, 0.25, 0.125 (negative powers of 2) and their sums always produce clean low words, while values like 0.1 or 0.3 produce repeating binary fractions that fill both words. + +3. **What is the purpose of the exponent bias (1023) in IEEE 754 double-precision?** + The bias allows the exponent field to represent both positive and negative exponents using only unsigned integers. The 11-bit exponent field stores values 0–2047. By subtracting the bias (1023), the actual exponent range is βˆ’1022 to +1023. This avoids needing a separate sign bit for the exponent and simplifies hardware comparisonβ€”doubles can be compared as unsigned integers (for positive values) because larger exponents produce larger bit patterns. The bias value 1023 = 2¹⁰ βˆ’ 1 is chosen to center the range symmetrically. + +4. **If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent?** + The value would be **βˆ’42.5**. The sign bit in IEEE 754 is independent of all other fields: flipping bit 63 from 0 to 1 simply negates the value. The hex encoding would change from `0x4045400000000000` to `0xC045400000000000`β€”only the most significant nibble changes from `4` (`0100`) to `C` (`1100`), with bit 31 of r3 changing from 0 to 1. diff --git a/WEEKS/WEEK05/WEEK05-01.md b/WEEKS/WEEK05/WEEK05-01.md new file mode 100644 index 0000000..1122c79 --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-01.md @@ -0,0 +1,271 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 1: Analyze the Float Binary in Ghidra + +#### Objective +Import and analyze the `0x000e_floating-point-data-type.bin` binary in Ghidra to understand how the compiler handles floating-point variables, discover float-to-double promotion, and decode the IEEE 754 double-precision encoding of `42.5` from two 32-bit registers. + +#### Prerequisites +- Ghidra installed and configured +- `0x000e_floating-point-data-type.bin` binary available in your build directory +- Understanding of IEEE 754 encoding from Week 5 Part 2 +- Basic Ghidra navigation skills from Weeks 3 and 4 + +#### Task Description +You will import the float binary into Ghidra, configure it for ARM Cortex-M33, resolve function names, discover that the compiler promotes `float` to `double` when passing to `printf`, and manually decode the 64-bit double `0x4045400000000000` field by field to confirm it represents `42.5`. + +#### Step-by-Step Instructions + +##### Step 1: Start Ghidra and Create New Project + +```powershell +ghidraRun +``` + +1. Click **File** ? **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `week05-ex01-floating-point` +5. Choose a project directory +6. Click **Finish** + +##### Step 2: Import the Binary + +1. Navigate to your file explorer +2. Find `Embedded-Hacking\0x000e_floating-point-data-type\build\0x000e_floating-point-data-type.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: Locate and Rename the Main Function + +Look at the **Symbol Tree** panel on the left. Expand **Functions**. + +From previous weeks, we know the boot sequence leads to `main()`: + +1. Click on `FUN_10000234` +2. Right-click ? **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +##### Step 6: Resolve stdio_init_all and printf + +**Rename stdio_init_all:** +1. Click on `FUN_10002f5c` in the decompile window +2. Right-click ? **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +**Rename printf:** +1. Click on `FUN_100030ec` +2. Right-click ? **Edit Function Signature** +3. Change to: `int __wrap_printf(char *format,...)` +4. Check the **Varargs** checkbox +5. Click **OK** + +##### Step 7: Observe the Decompiled Code + +After resolving, the decompiled `main` should look like: + +```c +int main(void) +{ + undefined4 uVar1; + undefined4 extraout_r1; + undefined4 uVar2; + undefined4 extraout_r1_00; + + stdio_init_all(); + uVar1 = DAT_1000024c; + uVar2 = extraout_r1; + do { + __wrap_printf(DAT_10000250,uVar2,0,uVar1); + uVar2 = extraout_r1_00; + } while( true ); +} +``` + +**Critical observation:** Where is `float fav_num = 42.5`? The compiler optimized it into constants! + +##### Step 8: Identify the Register Pair + +Look at the **Listing** window (assembly view) for `main`: + +```assembly +1000023a 00 24 movs r4, #0x0 +1000023c 03 4d ldr r5, [DAT_1000024c] = 40454000h +``` + +Two values are being passed to `printf`: +- `r2 = 0x00000000` (low 32 bits) +- `r3 = 0x40454000` (high 32 bits) + +Together they form a 64-bit double: `0x4045400000000000` + +**Why a double?** The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. So even though our variable is declared as `float fav_num = 42.5`, `printf` always receives a 64-bit double. + +##### Step 9: Write Out the Binary Layout + +Convert both registers to binary: + +``` +r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 +r2 (low 32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 +``` + +Map the 64-bit IEEE 754 fields: + +``` +Bit 63 (sign): 0 +Bits 62–52 (exponent): 10000000100 +Bits 51–0 (mantissa): 0101010000000000...0000 +``` + +##### Step 10: Decode the Sign Bit + +Bit 63 of the double = bit 31 of r3: + +``` +r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 + ^ + bit 31 = 0 ? Positive number +``` + +IEEE 754 sign rule: `0` = Positive, `1` = Negative. + +##### Step 11: Decode the Exponent + +Extract bits 30–20 from r3: + +``` +0x40454000: 0 10000000100 01010100000000000000 + ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + sign exponent mantissa (top 20) +``` + +Exponent bits: `10000000100` = 2Ή° + 2² = 1024 + 4 = **1028** + +Subtract the double-precision bias (1023): + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +##### Step 12: Decode the Mantissa + +High 20 bits of mantissa (from r3 bits 19–0): +``` +0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +Low 32 bits of mantissa (from r2): +``` +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +With the implied leading `1`: +``` +1.010101 00000... +``` + +##### Step 13: Reconstruct the Final Value + +$$1.010101_2 \times 2^5 = 101010.1_2$$ + +Convert to decimal: + +| Bit | Power | Value | +|-----|-------|-------| +| 1 | 25 | 32 | +| 0 | 24 | 0 | +| 1 | 2³ | 8 | +| 0 | 2² | 0 | +| 1 | 2Ή | 2 | +| 0 | 2° | 0 | +| 1 | 2?Ή | 0.5 | + +$$32 + 8 + 2 + 0.5 = \mathbf{42.5} ?$$ + +##### Step 14: Find the Format String + +In the Listing view, click on the data reference to locate: + +``` +s_fav_num:_%f_100034a8 ds "fav_num: %f\r\n" +``` + +Note that the format specifier is `%f`, confirming this is a floating-point print call. + +##### Step 15: Document Your Findings + +Create a table of your observations: + +| Item | Value | Notes | +| ------------------------- | ------------------ | ---------------------------------- | +| Main function address | `0x10000234` | Entry point of program | +| Float value (original) | `42.5` | Declared as `float` | +| Double hex encoding | `0x4045400000000000` | Promoted to double for printf | +| r3 (high word) | `0x40454000` | Contains sign + exponent + mantissa top bits | +| r2 (low word) | `0x00000000` | All zeros — clean fractional part | +| Exponent (stored) | 1028 | Biased value | +| Exponent (real) | 5 | After subtracting bias 1023 | +| Format string | `"fav_num: %f\r\n"` | Located at `0x100034a8` | +| Float-to-double promotion | Yes | C standard for variadic functions | + +#### Expected Output + +After completing this exercise, you should be able to: +- Import and configure ARM binaries in Ghidra for float analysis +- Explain why `printf` receives a `double` even when the variable is a `float` +- Identify the register pair `r2:r3` that holds a 64-bit double +- Manually decode an IEEE 754 double from hex to decimal +- Locate format strings in the binary + +#### Questions for Reflection + +###### Question 1: Why does the compiler promote a `float` to a `double` when passing it to `printf`? + +###### Question 2: The low word (`r2`) is `0x00000000`. What does this tell you about the fractional part of `42.5`? + +###### Question 3: What is the purpose of the exponent bias (1023) in IEEE 754 double-precision? + +###### Question 4: If the sign bit (bit 63) were `1` instead of `0`, what value would the double represent? + +#### Tips and Hints +- The **Listing** window shows raw assembly; the **Decompile** window shows reconstructed C +- Double-click on a `DAT_` reference to jump to the data constant +- Use Python to verify: `import struct; struct.pack('>d', 42.5).hex()` gives `4045400000000000` +- Remember: r3 = high 32 bits (sign + exponent + top mantissa), r2 = low 32 bits (bottom mantissa) +- The bias for doubles is always 1023; for floats it's 127 + +#### Next Steps +- Proceed to Exercise 2 to patch this float value in Ghidra +- Try computing the IEEE 754 encoding of other values like `3.14` or `100.0` by hand +- Compare the 32-bit float encoding `0x422A0000` with the 64-bit double encoding `0x4045400000000000` — both represent `42.5` + +#### Additional Challenge +Find the data constant `DAT_1000024c` in the Listing view. What raw bytes are stored there? Remember that ARM is little-endian — the bytes in memory are in reverse order. Write out the byte order as it appears in memory vs. as a 32-bit value. diff --git a/WEEKS/WEEK05/WEEK05-02-S.md b/WEEKS/WEEK05/WEEK05-02-S.md new file mode 100644 index 0000000..deafe4b --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-02-S.md @@ -0,0 +1,73 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 2 Solution: Patch the Float Binary β€” Changing 42.5 to 99.0 + +#### Answers + +##### IEEE 754 Encoding of 99.0 + +``` +Integer: 99 = 1100011β‚‚ +Fractional: .0 = .0β‚‚ +Combined: 1100011.0β‚‚ +Normalized: 1.100011β‚‚ Γ— 2⁢ + +Sign: 0 (positive) +Exponent: 6 + 1023 = 1029 = 10000000101β‚‚ +Mantissa: 100011 followed by 46 zeros + +Full double: 0x4058C00000000000 +``` + +##### Patch Summary + +| Register | Old Value (42.5) | New Value (99.0) | Changed? | +|----------|-----------------|------------------|----------| +| r2 | 0x00000000 | 0x00000000 | No | +| r3 | 0x40454000 | 0x4058C000 | **Yes** | + +##### Ghidra Patch + +``` +DAT_1000024c: + Before (little-endian): 00 40 45 40 β†’ 0x40454000 + After (little-endian): 00 C0 58 40 β†’ 0x4058C000 +``` + +##### Serial Output + +``` +fav_num: 99.000000 +fav_num: 99.000000 +fav_num: 99.000000 +... +``` + +#### Reflection Answers + +1. **Why did we only need to patch r3 (the high word) and not r2 (the low word)?** + Both 42.5 and 99.0 have "clean" fractional parts that can be exactly represented with few mantissa bits. For 42.5, the mantissa is `010101000...0`; for 99.0, it's `100011000...0`. In both cases, all significant mantissa bits fit within the top 20 bits (stored in r3 bits 19–0), leaving the bottom 32 bits (r2) as all zeros. Only values with complex or repeating binary fractions (like 42.52525 or 99.99) need non-zero low words. + +2. **What would the high word be if we wanted to patch the value to `-99.0` instead?** + Flip bit 31 of r3 (the sign bit). The current r3 = `0x4058C000` = `0100 0000 0101 1000 1100...`. Setting bit 31 to 1: `0xC058C000` = `1100 0000 0101 1000 1100...`. The full double encoding of βˆ’99.0 is `0xC058C00000000000`. Only the most significant nibble changes from `4` to `C`. + +3. **Walk through the encoding of `100.0` as a double. What are the high and low words?** + ``` + 100 = 1100100β‚‚ + 100.0 = 1100100.0β‚‚ = 1.1001β‚‚ Γ— 2⁢ + Sign: 0 + Exponent: 6 + 1023 = 1029 = 10000000101β‚‚ + Mantissa: 1001 followed by 48 zeros + + Full 64-bit: 0 10000000101 1001000000...0 + High word (r3): 0x40590000 + Low word (r2): 0x00000000 + ``` + Verification: `struct.pack('>d', 100.0).hex()` β†’ `4059000000000000` βœ“ + +4. **Why do we need the `--family 0xe48bff59` flag when converting to UF2?** + The `--family` flag specifies the target chip family in the UF2 file header. `0xe48bff59` is the registered family ID for the RP2350. The bootloader reads this field to verify the firmware is intended for the correct chip before flashing. If the family ID doesn't match (e.g., using the RP2040 ID `0xe48bff56`), the bootloader may reject the firmware or write it incorrectly. This prevents accidentally flashing RP2040 firmware onto an RP2350 (or vice versa), which could cause undefined behavior since the chips have different architectures (Cortex-M0+ vs Cortex-M33). diff --git a/WEEKS/WEEK05/WEEK05-02.md b/WEEKS/WEEK05/WEEK05-02.md new file mode 100644 index 0000000..83868cf --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-02.md @@ -0,0 +1,173 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 2: Patch the Float Binary β€” Changing 42.5 to 99.0 + +#### Objective +Calculate the IEEE 754 double-precision encoding of `99.0`, patch the float binary in Ghidra to change the printed value from `42.5` to `99.0`, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change. + +#### Prerequisites +- Completed Exercise 1 (float binary imported and analyzed in Ghidra) +- Understanding of IEEE 754 double-precision encoding from Week 5 Part 2.7 +- 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 convert `99.0` to its IEEE 754 double-precision encoding by hand (integer-to-binary conversion, normalization, field extraction), determine which register words need to change, patch the data constant in Ghidra, and verify on hardware that the serial output now prints `99.000000`. + +#### Step-by-Step Instructions + +##### Step 1: Convert 99 to Binary + +Use repeated division by 2: + +| Division | Quotient | Remainder | +|----------|----------|-----------| +| 99 Γ· 2 | 49 | **1** | +| 49 Γ· 2 | 24 | **1** | +| 24 Γ· 2 | 12 | **0** | +| 12 Γ· 2 | 6 | **0** | +| 6 Γ· 2 | 3 | **0** | +| 3 Γ· 2 | 1 | **1** | +| 1 Γ· 2 | 0 | **1** | + +Read remainders bottom-to-top: $99_{10} = 1100011_2$ + +##### Step 2: Handle the Fractional Part + +The fractional part of `99.0` is `.0` β€” exactly zero. There are no fractional bits. + +$$99.0_{10} = 1100011.0_2$$ + +##### Step 3: Normalize to IEEE 754 Form + +Move the binary point so there is exactly one `1` before it: + +$$1100011.0_2 = 1.100011_2 \times 2^6$$ + +We shifted the binary point 6 places left, so the exponent is **6**. + +##### Step 4: Extract the IEEE 754 Fields + +1. **Sign:** `0` (positive) +2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ +3. **Mantissa:** `100011` followed by 46 zeros (everything after the `1.`, padded to 52 bits) +4. **Full double:** `0x4058C00000000000` + +Split into register words: +- **r3 (high word):** `0x4058C000` +- **r2 (low word):** `0x00000000` + +##### Step 5: Determine What to Patch + +Compare old vs. new: + +| Register | Old Value | New Value | Changed? | +| -------- | ------------ | ------------ | -------- | +| `r2` | `0x00000000` | `0x00000000` | No | +| `r3` | `0x40454000` | `0x4058C000` | **Yes** | + +Since `r2` stays all zeros, we only need to patch the high word in `r3`. + +##### Step 6: Locate the Data Constant in Ghidra + +Open your Ghidra project from Exercise 1. In the Listing view, find the data constant that loads into `r3`: + +``` + DAT_1000024c +10000248 00 40 45 40 undefined4 40454000h +``` + +This is the 32-bit constant `0x40454000` β€” the high word of the double `42.5`. + +##### Step 7: Patch the Constant + +1. Click on **Window** β†’ **Bytes** to open the Bytes Editor +2. Click the **Pencil Icon** to enable editing +3. Navigate to the data at `DAT_1000024c` +4. The bytes in memory (little-endian) read: `00 40 45 40` +5. Change them to: `00 C0 58 40` (which is `0x4058C000` in little-endian) +6. Press **Enter** + +**Verify** in the Listing view β€” the data should now show `4058C000h`. + +##### Step 8: Verify the Patch in the Decompile Window + +The decompiled code should now reference the new constant. The value loaded into `r3` should be `0x4058C000`. + +##### Step 9: 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: `0x000e_floating-point-data-type-h.bin` +5. Click **OK** + +##### Step 10: Convert to UF2 Format + +Open a terminal and navigate to your project directory: + +```powershell +cd Embedded-Hacking-main\0x000e_floating-point-data-type +``` + +Run the conversion: + +```powershell +python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 11: Flash and Verify + +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 + +**Expected output:** + +``` +fav_num: 99.000000 +fav_num: 99.000000 +fav_num: 99.000000 +... +``` + +πŸŽ‰ **Success!** The value changed from `42.5` to `99.0`! + +#### Expected Output + +After completing this exercise, you should: +- See `fav_num: 99.000000` printing instead of `fav_num: 42.500000` +- Have a patched binary file (`0x000e_floating-point-data-type-h.bin`) +- Have a UF2 file (`hacked.uf2`) +- Understand the complete IEEE 754 encoding and patching workflow for floats + +#### Questions for Reflection + +###### Question 1: Why did we only need to patch r3 (the high word) and not r2 (the low word)? + +###### Question 2: What would the high word be if we wanted to patch the value to `-99.0` instead? (Hint: which bit controls the sign?) + +###### Question 3: Walk through the encoding of `100.0` as a double. What are the high and low words? + +###### Question 4: Why do we need the `--family 0xe48bff59` flag when converting to UF2? + +#### Tips and Hints +- Always verify your encoding with Python: `import struct; struct.pack('>d', 99.0).hex()` +- Little-endian means the bytes in memory are reversed: `0x4058C000` is stored as `00 C0 58 40` +- If you patch the wrong bytes, use **File** β†’ **Undo** in Ghidra (or re-import the original binary) +- The bias for double-precision is always 1023; for single-precision it's 127 +- A clean fractional part (like `.0` or `.5`) means the low word (`r2`) is all zeros + +#### Next Steps +- Proceed to Exercise 3 to analyze the double-precision binary +- Try patching to different values: `3.14`, `100.0`, `255.0` +- Compare how many register words change for clean vs. messy fractions + +#### Additional Challenge +Patch the float to `3.14` instead of `99.0`. Since `3.14` has a repeating binary fraction, the low word will **not** be zero. This means you need to modify the assembly to load a non-zero value into `r2` as well. Can you figure out where `r2` gets its zero value (`movs r4, #0x0`) and change it? diff --git a/WEEKS/WEEK05/WEEK05-03-S.md b/WEEKS/WEEK05/WEEK05-03-S.md new file mode 100644 index 0000000..aa3fd6a --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-03-S.md @@ -0,0 +1,70 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 3 Solution: Analyze the Double Binary in Ghidra + +#### Answers + +##### Register Pair for 42.52525 + +| Register | Value | Role | +|----------|-------------|---------------| +| r2 | 0x645A1CAC | Low 32 bits | +| r3 | 0x4045433B | High 32 bits | + +Full double: **0x4045433B645A1CAC** + +##### IEEE 754 Decoding + +``` +r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 +r2 = 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100 + +Sign bit (bit 63): 0 β†’ Positive +Exponent (bits 62-52): 10000000100 = 1028 β†’ 1028 - 1023 = 5 +Mantissa (bits 51-0): 0101010000110011101101100100010110100001110010101100 + +Value = 1.0101010000110011...β‚‚ Γ— 2⁡ = 101010.10000110011...β‚‚ +Integer part: 101010β‚‚ = 32 + 8 + 2 = 42 +Fractional part: .10000110011... β‰ˆ 0.52525 +Result: 42.52525 βœ“ +``` + +##### Float vs Double Comparison + +| Item | Float (42.5) | Double (42.52525) | +|--------------------------|---------------------|------------------------| +| r2 (low word) | 0x00000000 | 0x645A1CAC | +| r3 (high word) | 0x40454000 | 0x4045433B | +| Low word is zero? | Yes | **No** | +| Words to patch | 1 (r3 only) | **2 (both r2 and r3)** | +| Format specifier | %f | %lf | +| Assembly load instruction| movs + ldr | ldrd (load double) | + +##### Key Assembly + +```assembly +10000238 push {r3,r4,r5,lr} +1000023a adr r5, [0x10000254] +1000023c ldrd r4, r5, [r5, #0x0] ; r4 = 0x645A1CAC, r5 = 0x4045433B +10000240 bl stdio_init_all +10000244 mov r2, r4 ; r2 = low word +10000246 mov r3, r5 ; r3 = high word +``` + +#### Reflection Answers + +1. **Why does `42.5` have a zero low word but `42.52525` does not?** + The fractional part determines whether the low word is zero. 0.5 in binary is exactly 2⁻¹ = `0.1β‚‚`β€”a single bit. After normalization, the mantissa for 42.5 is `010101000...0`, needing only 6 significant bits, all fitting in the top 20 mantissa bits within r3. In contrast, 0.52525 is a **repeating binary fraction** that cannot be represented exactlyβ€”it requires all 52 mantissa bits to approximate as closely as possible. The lower 32 bits in r2 (`0x645A1CAC`) carry the additional precision needed for this approximation. + +2. **The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage?** + `ldrd` (Load Register Double) loads two consecutive 32-bit words from memory in a single instruction, completing in one memory access cycle (or two back-to-back aligned accesses on the bus). Using two separate `ldr` instructions would require two instruction fetches, two decode cycles, and two memory accesses. `ldrd` reduces code size by 4 bytes (one 4-byte instruction vs. two) and improves performance by allowing the memory controller to pipeline both loads. For 64-bit doubles that are always loaded in pairs, `ldrd` is the optimal choice. + +3. **Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why?** + Both 42.5 and 42.52525 fall in the same range: between 32 (2⁡) and 64 (2⁢). Normalization produces `1.xxx Γ— 2⁡` for both values. The exponent is determined solely by the magnitude (which power of 2 the number falls between), not by the fractional precision. Any number from 32.0 to 63.999... would have real exponent 5 (stored as 1028). The mantissa captures the differencesβ€”42.5 has mantissa `010101000...` while 42.52525 has `0101010000110011...`. + +4. **If you were patching this double, how many data constants would you need to modify compared to the float exercise?** + **Two** data constantsβ€”both `DAT_10000254` (low word, r2) and `DAT_10000258` (high word, r3). In the float exercise (42.5), only one constant needed patching because r2 was zero and stayed zero when patching to 99.0. For the double 42.52525, since the low word is already non-zero (`0x645A1CAC`), any new value with a different repeating fraction will require changing both words. The only exception would be patching to a value whose low word happens to also be `0x645A1CAC` (virtually impossible for an arbitrary target value). diff --git a/WEEKS/WEEK05/WEEK05-03.md b/WEEKS/WEEK05/WEEK05-03.md new file mode 100644 index 0000000..4b99d37 --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-03.md @@ -0,0 +1,277 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 3: Analyze the Double Binary in Ghidra + +#### Objective +Import and analyze the `0x0011_double-floating-point-data-type.bin` binary in Ghidra to understand how doubles differ from floats at the binary level, observe that **both** register words carry non-zero data when the fractional part is complex, and decode the IEEE 754 double-precision encoding of `42.52525` from two 32-bit registers. + +#### Prerequisites +- Completed Exercises 1 and 2 (float analysis and patching) +- Understanding of IEEE 754 double-precision format from Week 5 Part 3 +- Understanding of register pairs (`r2:r3`) from Exercise 1 +- Basic Ghidra navigation skills + +#### Task Description +You will import the double binary into Ghidra, resolve function names, identify that `42.52525` requires non-zero data in **both** r2 and r3 (unlike `42.5` which had r2 = 0), and decode the full 64-bit double `0x4045433B645A1CAC` field by field to confirm it represents `42.52525`. + +#### Step-by-Step Instructions + +##### Step 1: Flash the Original Binary + +Before analysis, verify the program works: + +1. Hold **BOOTSEL** and plug in your Pico 2 +2. Flash `0x0011_double-floating-point-data-type.uf2` to the RPI-RP2 drive +3. Open your serial monitor + +**Expected output:** +``` +fav_num: 42.525250 +fav_num: 42.525250 +fav_num: 42.525250 +... +``` + +##### Step 2: Create a New Ghidra Project + +1. Launch Ghidra: `ghidraRun` +2. Click **File** β†’ **New Project** +3. Select **Non-Shared Project** +4. Project Name: `week05-ex03-double-analysis` +5. Click **Finish** + +##### Step 3: Import and Configure the Binary + +1. Drag and drop `0x0011_double-floating-point-data-type.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: Locate and Rename Functions + +Identify the main function and standard library calls: + +**Rename main:** +1. Click on `FUN_10000238` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +**Rename stdio_init_all:** +1. Click on `FUN_10002f64` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +**Rename printf:** +1. Click on `FUN_100030f4` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int printf(char *format,...)` +4. Check the **Varargs** checkbox +5. Click **OK** + +##### Step 5: Observe the Decompiled Code + +After renaming, the decompiled `main` should look like: + +```c +int main(void) +{ + undefined4 uVar1; + undefined4 uVar2; + undefined4 extraout_r1; + undefined4 uVar3; + undefined4 extraout_r1_00; + + uVar2 = DAT_10000258; + uVar1 = DAT_10000254; + stdio_init_all(); + uVar3 = extraout_r1; + do { + printf(DAT_10000250,uVar3,uVar1,uVar2); + uVar3 = extraout_r1_00; + } while( true ); +} +``` + +**Critical observation:** There are now **two** non-zero data constants β€” `DAT_10000254` and `DAT_10000258`. This is the key difference from the float exercise! + +##### Step 6: Examine the Assembly Listing + +Click on the **Listing** window and find the main function assembly: + +```assembly +10000238 push {r3,r4,r5,lr} +1000023a adr r5, [0x10000254] +1000023c ldrd r4, r5, [r5, #0x0] = 645A1CACh / 4045433Bh +10000240 bl stdio_init_all +``` + +**Key instruction: `ldrd r4, r5, [r5, #0x0]`** + +This is a **load register double** instruction β€” it loads two consecutive 32-bit words in a single instruction: +- `r4` gets the low word: `0x645A1CAC` +- `r5` gets the high word: `0x4045433B` + +Later in the loop: +```assembly +10000244 mov r2, r4 ; r2 = 0x645A1CAC (low) +10000246 mov r3, r5 ; r3 = 0x4045433B (high) +``` + +##### Step 7: Identify the Register Pair + +| Register | Value | Role | +| -------- | ------------ | ------------ | +| `r2` | `0x645A1CAC` | Low 32 bits | +| `r3` | `0x4045433B` | High 32 bits | + +Together: `0x4045433B645A1CAC` + +**Compare to the float exercise:** +- Float `42.5`: r2 = `0x00000000`, r3 = `0x40454000` β€” low word is all zeros +- Double `42.52525`: r2 = `0x645A1CAC`, r3 = `0x4045433B` β€” **both words are non-zero!** + +This happens because `42.52525` has a repeating binary fraction that needs all 52 mantissa bits. + +##### Step 8: Decode the Sign Bit + +Convert r3 to binary and check bit 31: + +``` +r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 + ^ + bit 31 = 0 β†’ Positive number βœ“ +``` + +##### Step 9: Decode the Exponent + +Extract bits 30–20 from r3: + +``` +0x4045433B: 0 10000000100 01010100001100111011 + ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ + sign exponent mantissa (top 20) +``` + +Exponent bits: `10000000100` = 2¹⁰ + 2Β² = 1024 + 4 = **1028** + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +##### Step 10: Decode the Mantissa + +**High 20 bits** (r3 bits 19–0): +``` +0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 +``` + +**Low 32 bits** (all of r2 = `0x645A1CAC`): +``` +0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 +``` + +With the implied leading `1`: +``` +1.0101010000110011101101100100010110100001110010101100 +``` + +##### Step 11: Reconstruct the Integer Part + +$$1.0101010000110011..._2 \times 2^5$$ + +Shift the binary point 5 places right β†’ `101010.1000011001110110...` + +Integer part `101010`: + +| Bit | Power | Value | +|-----|-------|-------| +| 1 | 2⁡ | 32 | +| 0 | 2⁴ | 0 | +| 1 | 2Β³ | 8 | +| 0 | 2Β² | 0 | +| 1 | 2ΒΉ | 2 | +| 0 | 2⁰ | 0 | + +$$32 + 8 + 2 = \mathbf{42}$$ + +##### Step 12: Approximate the Fractional Part + +The first few fractional bits `.10000110011...`: + +| Bit | Power | Decimal | +|------|--------|---------------| +| 1 | 2⁻¹ | 0.5 | +| 0 | 2⁻² | 0 | +| 0 | 2⁻³ | 0 | +| 0 | 2⁻⁴ | 0 | +| 0 | 2⁻⁡ | 0 | +| 1 | 2⁻⁢ | 0.015625 | +| 1 | 2⁻⁷ | 0.0078125 | +| 0 | 2⁻⁸ | 0 | +| 0 | 2⁻⁹ | 0 | +| 1 | 2⁻¹⁰ | 0.0009765625 | +| 1 | 2⁻¹¹ | 0.00048828125 | + +First 11 bits sum β‰ˆ 0.5249. The remaining 36 fractional bits refine this to β‰ˆ 0.52525. + +$$42 + 0.52525 = \mathbf{42.52525} βœ“$$ + +##### Step 13: Find the Format String + +In the Listing view, locate: + +``` +s_fav_num:_%lf_100034b0 ds "fav_num: %lf\r\n" +``` + +Note the `%lf` format specifier β€” this program explicitly uses `double`, unlike the float program which used `%f`. + +##### Step 14: Document Your Findings + +| Item | Float (`42.5`) | Double (`42.52525`) | +| ------------------------- | -------------------- | ------------------------ | +| r2 (low word) | `0x00000000` | `0x645A1CAC` | +| r3 (high word) | `0x40454000` | `0x4045433B` | +| Low word is zero? | Yes | **No** | +| Words to patch | 1 (r3 only) | **2 (both r2 and r3)** | +| Format specifier | `%f` | `%lf` | +| Assembly load instruction | `movs` + `ldr` | `ldrd` (load double) | + +#### Expected Output + +After completing this exercise, you should understand: +- How the compiler loads a 64-bit double using `ldrd` (load register double) +- Why `42.52525` requires non-zero data in both registers while `42.5` does not +- How to decode a complex IEEE 754 double with a repeating binary fraction +- The differences between float and double handling at the assembly level + +#### Questions for Reflection + +###### Question 1: Why does `42.5` have a zero low word but `42.52525` does not? + +###### Question 2: The assembly uses `ldrd r4, r5, [r5, #0x0]` instead of two separate `ldr` instructions. What is the advantage? + +###### Question 3: Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why? + +###### Question 4: If you were patching this double, how many data constants would you need to modify compared to the float exercise? + +#### Tips and Hints +- Use Python to verify: `import struct; struct.pack('>d', 42.52525).hex()` gives `4045433b645a1cac` +- The `ldrd` instruction always loads the lower-addressed word into the first register +- A repeating binary fraction (like `0.52525`) can never be represented exactly β€” double precision uses the closest 52-bit approximation +- Compare data addresses: the float binary has one `DAT_` constant; the double binary has two consecutive ones + +#### Next Steps +- Proceed to Exercise 4 to patch both register words +- Compare the mantissa of `42.5` (clean: `010101 000...`) vs. `42.52525` (complex: `0101010000110011...`) +- Think about what values would have a zero low word (hint: powers of 2, halves, quarters) + +#### Additional Challenge +Using the 52-bit mantissa `0101010000110011101101100100010110100001110010101100`, manually sum the first 20 fractional bits to see how close you get to `0.52525`. How many bits of precision does it take to get within 0.001 of the true value? diff --git a/WEEKS/WEEK05/WEEK05-04-S.md b/WEEKS/WEEK05/WEEK05-04-S.md new file mode 100644 index 0000000..df3491f --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-04-S.md @@ -0,0 +1,70 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 4 Solution: Patch the Double Binary β€” Changing 42.52525 to 99.99 + +#### Answers + +##### IEEE 754 Encoding of 99.99 + +``` +Integer: 99 = 1100011β‚‚ +Fractional: .99 β‰ˆ .111111010111...β‚‚ (repeating) +Combined: 1100011.111111010111...β‚‚ +Normalized: 1.100011111111010111...β‚‚ Γ— 2⁢ + +Sign: 0 (positive) +Exponent: 6 + 1023 = 1029 = 10000000101β‚‚ +Mantissa: 1000111111110101 11000010100011110101 11000010100011110...β‚‚ (52 bits) + +Full double: 0x4058FF5C28F5C28F +``` + +Python verification: `struct.pack('>d', 99.99).hex()` β†’ `4058ff5c28f5c28f` βœ“ + +##### Patch Summary + +| Register | Old Value (42.52525) | New Value (99.99) | Changed? | +|----------|---------------------|-------------------|----------| +| r2 | 0x645A1CAC | 0x28F5C28F | **Yes** | +| r3 | 0x4045433B | 0x4058FF5C | **Yes** | + +##### Ghidra Patches + +**Low word (DAT_10000254):** +``` +Before (little-endian): AC 1C 5A 64 β†’ 0x645A1CAC +After (little-endian): 8F C2 F5 28 β†’ 0x28F5C28F +``` + +**High word (DAT_10000258):** +``` +Before (little-endian): 3B 43 45 40 β†’ 0x4045433B +After (little-endian): 5C FF 58 40 β†’ 0x4058FF5C +``` + +##### Serial Output + +``` +fav_num: 99.990000 +fav_num: 99.990000 +fav_num: 99.990000 +... +``` + +#### Reflection Answers + +1. **Why did both r2 and r3 change when patching 42.52525 β†’ 99.99, but only r3 changed when patching 42.5 β†’ 99.0?** + Both 42.5 and 99.0 have "clean" fractional parts (0.5 and 0.0 respectively) that are exact in binaryβ€”they need very few mantissa bits, all fitting in the top 20 bits of r3. The low word (r2) remains `0x00000000` for both. In contrast, 42.52525 and 99.99 both have repeating binary fractions (0.52525 and 0.99 respectively) that require all 52 mantissa bits to approximate. Since the low 32 bits of the mantissa live in r2, changing from one repeating fraction to another necessarily changes both r2 and r3. + +2. **The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value?** + It means 99.99 **cannot** be represented exactly as an IEEE 754 double. The binary fraction 0.111111010111... repeats indefinitely, but the mantissa only has 52 bits. The stored value is the closest 52-bit approximation, which is 99.98999999999999... (off by approximately 10⁻¹⁴). This is a fundamental limitation of binary floating-point: decimal fractions that aren't sums of negative powers of 2 always produce repeating binary expansions. The `printf` output rounds to `99.990000` because the default `%lf` precision (6 decimal places) hides the tiny error. + +3. **If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change?** + **Both** would need to changeβ€”but for the opposite reason. Currently r2 = `0x645A1CAC` (non-zero). For 100.0: `struct.pack('>d', 100.0).hex()` = `4059000000000000`, so r3 = `0x40590000` and r2 = `0x00000000`. The r2 constant must be patched from `0x645A1CAC` to `0x00000000`, and r3 from `0x4045433B` to `0x40590000`. Even though the low word becomes zero, you still need to patch it because it was previously non-zero. + +4. **Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently?** + The float binary uses separate instructions: `movs r4, #0x0` (loads zero into r4 for the low word) and `ldr r5, [DAT_1000024c]` (loads the high word from a literal pool). The double binary uses a single `ldrd r4, r5, [r5, #0x0]` instruction that loads both words from consecutive memory addresses in one operation. The `ldrd` approach is more efficient (fewer instructions, single memory transaction) and is preferred when both words carry meaningful data. The float's approach works fine because one word is a trivially loaded zero. diff --git a/WEEKS/WEEK05/WEEK05-04.md b/WEEKS/WEEK05/WEEK05-04.md new file mode 100644 index 0000000..462b882 --- /dev/null +++ b/WEEKS/WEEK05/WEEK05-04.md @@ -0,0 +1,217 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 5 +Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +### Non-Credit Practice Exercise 4: Patch the Double Binary β€” Changing 42.52525 to 99.99 + +#### Objective +Calculate the IEEE 754 double-precision encoding of `99.99`, patch **both** register words in the double binary in Ghidra, export the patched binary, convert it to UF2 format, and flash it to the Pico 2 to verify the change. + +#### Prerequisites +- Completed Exercise 3 (double binary imported and analyzed in Ghidra) +- Understanding of IEEE 754 double-precision encoding from Week 5 Parts 2.7 and 3.7 +- Knowledge of integer-to-binary conversion and the multiply-by-2 method for fractions +- Python installed for UF2 conversion and verification +- Raspberry Pi Pico 2 connected via USB + +#### Task Description +You will derive the IEEE 754 encoding of `99.99` step by step (integer part, fractional part, normalization, field extraction), patch both the low word and high word data constants in Ghidra, and verify on hardware that the serial output now prints `99.990000`. Unlike Exercise 2 where only one word changed, this exercise requires patching **both** registers. + +#### Step-by-Step Instructions + +##### Step 1: Convert the Integer Part (99) to Binary + +Use repeated division by 2: + +| Division | Quotient | Remainder | +|----------|----------|-----------| +| 99 Γ· 2 | 49 | **1** | +| 49 Γ· 2 | 24 | **1** | +| 24 Γ· 2 | 12 | **0** | +| 12 Γ· 2 | 6 | **0** | +| 6 Γ· 2 | 3 | **0** | +| 3 Γ· 2 | 1 | **1** | +| 1 Γ· 2 | 0 | **1** | + +Read remainders bottom-to-top: $99_{10} = 1100011_2$ + +##### Step 2: Convert the Fractional Part (.99) to Binary + +Use the multiply-by-2 method: + +| Multiply | Result | Integer part | Remaining fraction | +|----------------|--------|--------------|-------------------| +| 0.99 Γ— 2 | 1.98 | **1** | 0.98 | +| 0.98 Γ— 2 | 1.96 | **1** | 0.96 | +| 0.96 Γ— 2 | 1.92 | **1** | 0.92 | +| 0.92 Γ— 2 | 1.84 | **1** | 0.84 | +| 0.84 Γ— 2 | 1.68 | **1** | 0.68 | +| 0.68 Γ— 2 | 1.36 | **1** | 0.36 | +| 0.36 Γ— 2 | 0.72 | **0** | 0.72 | +| 0.72 Γ— 2 | 1.44 | **1** | 0.44 | +| 0.44 Γ— 2 | 0.88 | **0** | 0.88 | +| 0.88 Γ— 2 | 1.76 | **1** | 0.76 | +| 0.76 Γ— 2 | 1.52 | **1** | 0.52 | +| 0.52 Γ— 2 | 1.04 | **1** | 0.04 | +| ... | ... | ... | *(continues β€” repeating fraction)* | + +Reading the integer parts top-to-bottom: $0.99_{10} \approx 0.111111010111..._2$ + +This is a **repeating fraction** β€” it never terminates in binary. + +##### Step 3: Combine Integer and Fractional Parts + +$$99.99_{10} = 1100011.111111010111..._2$$ + +##### Step 4: Normalize to IEEE 754 Form + +Move the binary point so there is exactly one `1` before it: + +$$1100011.111111010111..._2 = 1.100011111111010111..._2 \times 2^6$$ + +We shifted the binary point 6 places left, so the exponent is **6**. + +##### Step 5: Extract the IEEE 754 Fields + +1. **Sign:** `0` (positive) +2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ +3. **Mantissa:** `1000111111110101110000101000111101011100001010001111...` (52 bits after the `1.`) +4. **Full double:** `0x4058FF5C28F5C28F` + +**Verify with Python:** +```python +>>> import struct +>>> struct.pack('>d', 99.99).hex() +'4058ff5c28f5c28f' +``` + +##### Step 6: Split into Register Words + +| Register | Old Value | New Value | Changed? | +| -------- | ------------ | ------------ | -------- | +| `r2` | `0x645A1CAC` | `0x28F5C28F` | **Yes** | +| `r3` | `0x4045433B` | `0x4058FF5C` | **Yes** | + +**Both registers change!** This is the key difference from Exercise 2 where only r3 changed. + +##### Step 7: Locate the Data Constants in Ghidra + +Open your Ghidra project from Exercise 3. In the Listing view, find the two data constants: + +**Low word (loaded into r2):** +``` + DAT_10000254 +10000254 ac 1c 5a 64 undefined4 645A1CACh +``` + +**High word (loaded into r3):** +``` + DAT_10000258 +10000258 3b 43 45 40 undefined4 4045433Bh +``` + +##### Step 8: Patch the Low Word + +1. Click on **Window** β†’ **Bytes** to open the Bytes Editor +2. Click the **Pencil Icon** to enable editing +3. Navigate to address `10000254` +4. The bytes read: `AC 1C 5A 64` (little-endian for `0x645A1CAC`) +5. Change to: `8F C2 F5 28` (little-endian for `0x28F5C28F`) +6. Press **Enter** + +##### Step 9: Patch the High Word + +1. Navigate to address `10000258` +2. The bytes read: `3B 43 45 40` (little-endian for `0x4045433B`) +3. Change to: `5C FF 58 40` (little-endian for `0x4058FF5C`) +4. Press **Enter** + +**Verify** in the Listing view: +- `DAT_10000254` should show `28F5C28Fh` +- `DAT_10000258` should show `4058FF5Ch` + +Together: `0x4058FF5C28F5C28F` = `99.99` as a double βœ“ + +##### Step 10: 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: `0x0011_double-floating-point-data-type-h.bin` +5. Click **OK** + +##### Step 11: Convert to UF2 Format + +Open a terminal: + +```powershell +cd Embedded-Hacking-main\0x0011_double-floating-point-data-type +``` + +```powershell +python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 12: Flash and Verify + +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 + +**Expected output:** + +``` +fav_num: 99.990000 +fav_num: 99.990000 +fav_num: 99.990000 +... +``` + +πŸŽ‰ **Success!** The value changed from `42.52525` to `99.99`! + +#### Expected Output + +After completing this exercise, you should: +- See `fav_num: 99.990000` printing instead of `fav_num: 42.525250` +- Have a patched binary file (`0x0011_double-floating-point-data-type-h.bin`) +- Have a UF2 file (`hacked.uf2`) +- Understand that patching doubles with repeating fractions requires modifying **both** register words + +#### Questions for Reflection + +###### Question 1: Why did both r2 and r3 change when patching 42.52525 β†’ 99.99, but only r3 changed when patching 42.5 β†’ 99.0? + +###### Question 2: The multiply-by-2 method for 0.99 produces a repeating pattern. What does this mean for the precision of the stored value? + +###### Question 3: If you wanted to patch the double to `100.0` instead of `99.99`, how many data constants would need to change? + +###### Question 4: Compare the Ghidra Listing for the float binary (Exercise 1) and the double binary (Exercise 3). How does the compiler load the double differently? + +#### Tips and Hints +- Always verify your encoding with Python before patching +- Little-endian byte order: `0x28F5C28F` is stored as `8F C2 F5 28` in memory +- Use Ghidra's **Bytes** window (Window β†’ Bytes) for precise hex editing +- If `r2` was zero before and needs to be non-zero after, you need to patch the data constant β€” not the `movs r4, #0x0` instruction +- The `ldrd` instruction loads r4 and r5 from two consecutive memory addresses β€” both must be correct + +#### Next Steps +- Review the complete patching workflow diagram in Week 5 Part 3.95 +- Try patching to `100.0` β€” since it has a zero low word, you'll need to change `r2` from non-zero to zero +- Attempt the practice exercises at the end of Week 5 + +#### Additional Challenge +Patch the double to `3.14159265358979` (pi). This requires extreme precision in all 52 mantissa bits. Use Python to get the exact encoding, then patch both words. Verify the output prints at least 6 correct decimal places. What happens to the precision if you only patch the high word and leave the low word as `0x645A1CAC`? + +#### Verification Checklist + +Before moving on, confirm: +- [ ] Serial output shows `fav_num: 99.990000` +- [ ] Both data constants were patched (low word and high word) +- [ ] You can derive the IEEE 754 encoding of `99.99` from scratch +- [ ] You understand why messy fractions require patching both register words +- [ ] You can explain the difference between the float and double patching workflows +- [ ] You successfully converted and flashed the UF2 + +**Congratulations!** You've completed all Week 5 exercises and mastered floating-point analysis, IEEE 754 decoding, and double-precision binary patching! diff --git a/WEEKS/WEEK05/WEEK05-SLIDES.pdf b/WEEKS/WEEK05/WEEK05-SLIDES.pdf new file mode 100644 index 0000000..78b8021 Binary files /dev/null and b/WEEKS/WEEK05/WEEK05-SLIDES.pdf differ diff --git a/WEEKS/WEEK05/WEEK05.md b/WEEKS/WEEK05/WEEK05.md new file mode 100644 index 0000000..61f7abe --- /dev/null +++ b/WEEKS/WEEK05/WEEK05.md @@ -0,0 +1,1415 @@ +ο»Ώ# Week 5: Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand how integers and floating-point numbers are stored in memory +- Know the difference between signed and unsigned integers (`uint8_t` vs `int8_t`) +- Understand how floats and doubles are represented using IEEE 754 encoding +- Use inline assembly to control GPIO pins directly at the hardware level +- Debug numeric data types using GDB and the OpenOCD debugger +- Hack integer values by modifying registers at runtime +- Hack floating-point values by understanding and manipulating their binary representation +- Reconstruct 64-bit doubles from two 32-bit registers +--- + +## πŸ“š Part 1: Understanding Integer Data Types + +### What is an Integer? + +An **integer** is a whole number without any decimal point. Think of it like counting apples: you can have 0 apples, 1 apple, 42 apples, but you can't have 3.5 apples (that would be a fraction!). + +In C programming for embedded systems, we have special integer types that tell the compiler exactly how much memory to use: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Integer Types - Different Sizes for Different Needs β”‚ +β”‚ β”‚ +β”‚ uint8_t: 1 byte (0 to 255) - like a small box β”‚ +β”‚ int8_t: 1 byte (-128 to 127) - can hold negatives! β”‚ +β”‚ uint16_t: 2 bytes (0 to 65,535) - medium box β”‚ +β”‚ uint32_t: 4 bytes (0 to 4 billion) - big box β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Signed vs Unsigned Integers + +The difference between `uint8_t` and `int8_t` is whether the number can be **negative**: + +| Type | Prefix | Range | Use Case | +| --------- | ------ | ----------- | ----------------------------- | +| `uint8_t` | `u` | 0 to 255 | Ages, counts, always positive | +| `int8_t` | none | -128 to 127 | Temperature, can be negative | + +Let's review the code in `0x000b_integer-data-type`. + +```assembly +#include +#include "pico/stdlib.h" + +int main(void) { + uint8_t age = 43; + int8_t range = -42; + + stdio_init_all(); + + __asm volatile ( + "ldr r3, =0x40038000\n" // address of PADS_BANK0_BASE + "ldr r2, =0x40028004\n" // address of IO_BANK0 GPIO0.ctrl + "movs r0, #16\n" // GPIO16 (start pin) + + "init_loop:\n" // loop start + "lsls r1, r0, #2\n" // pin * 4 (pad offset) + "adds r4, r3, r1\n" // PADS base + offset + "ldr r5, [r4]\n" // load current config + "bic r5, r5, #0x180\n" // clear OD+ISO + "orr r5, r5, #0x40\n" // set IE + "str r5, [r4]\n" // store updated config + + "lsls r1, r0, #3\n" // pin * 8 (ctrl offset) + "adds r4, r2, r1\n" // IO_BANK0 base + offset + "ldr r5, [r4]\n" // load current config + "bic r5, r5, #0x1f\n" // clear FUNCSEL bits [4:0] + "orr r5, r5, #5\n" // set FUNCSEL = 5 (SIO) + "str r5, [r4]\n" // store updated config + + "mov r4, r0\n" // pin + "movs r5, #1\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c4\n" // gpioc_bit_oe_put(pin,1) + "adds r0, r0, #1\n" // increment pin + "cmp r0, #20\n" // stop after pin 18 + "blt init_loop\n" // loop until r0 == 20 + ); + + uint8_t pin = 16; + + while (1) { + __asm volatile ( + "mov r4, %0\n" // pin + "movs r5, #0x01\n" // bit 1; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(16, 1) + : + : "r"(pin) + : "r4","r5" + ); + sleep_ms(500); + + __asm volatile ( + "mov r4, %0\n" // pin + "movs r5, #0\n" // bit 0; used for OUT/OE writes + "mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(16, 0) + : + : "r"(pin) + : "r4","r5" + ); + sleep_ms(500); + + pin++; + if (pin > 18) pin = 16; + + printf("age: %d\r\n", age); + printf("range: %d\r\n", range); + } +} +``` + +### Breaking Down the Code + +#### The Integer Variables + +This program declares two integer variables that demonstrate the difference between **signed** and **unsigned** types: + +```c +uint8_t age = 43; +int8_t range = -42; +``` + +The variable `age` is a `uint8_t` β€” an **unsigned** 8-bit integer that can only hold values from `0` to `255`. Since age is always a positive number, unsigned is the right choice. The variable `range` is an `int8_t` β€” a **signed** 8-bit integer that can hold values from `-128` to `127`. The signed type allows it to represent negative numbers like `-42`. Under the hood, negative values are stored using **two's complement** encoding: the CPU flips all the bits of `42` (`0x2A`) and adds `1`, producing `0xD6`, which is how `-42` lives in a single byte of memory. + +#### GPIO Initialization with Inline Assembly + +Instead of using the Pico SDK's `gpio_init()`, `gpio_set_dir()`, and `gpio_set_function()` helpers, this program configures GPIO pins **directly at the register level** using inline assembly. This gives us a window into how the hardware actually works on the RP2350. + +The initialization loop configures GPIO pins 16 through 19 (our red, green, blue, and yellow LEDs) in three steps per pin: + +**Step 1 β€” Configure the pad.** Each GPIO pin has a pad control register in `PADS_BANK0` starting at base address `0x40038000`. The code calculates the offset as `pin * 4`, loads the current register value, clears the **OD** (output disable) and **ISO** (isolation) bits with `bic r5, r5, #0x180`, and sets the **IE** (input enable) bit with `orr r5, r5, #0x40`. This ensures the pad is electrically active and ready to drive output. + +**Step 2 β€” Set the pin function.** Each GPIO pin has a control register in `IO_BANK0` starting at `0x40028004`. The offset is `pin * 8` because each pin's control block is 8 bytes wide. The code clears the `FUNCSEL` field (bits `[4:0]`) and sets it to `5`, which selects the **SIO** (Single-cycle I/O) function. SIO is the mode that lets software directly control pin state through the GPIO coprocessor. + +**Step 3 β€” Enable the output driver.** The instruction `mcrr p0, #4, r4, r5, c4` writes to the RP2350's GPIO coprocessor. Coprocessor register `c4` controls the **output enable** β€” with `r4` holding the pin number and `r5` set to `1`, this tells the hardware "this pin is an output." The `mcrr` (Move to Coprocessor from two ARM Registers) instruction is how the Cortex-M33 on the RP2350 talks to its dedicated GPIO coprocessor, bypassing the normal memory-mapped I/O path for single-cycle pin control. + +> πŸ“– **Datasheet Reference:** PADS_BANK0 is at `0x40038000` (Section 9.8, p. 786) and IO_BANK0 is at `0x40028000` (Section 9.6, p. 605). FUNCSEL value 5 selects the SIO function (Section 9.6.1, p. 612). The GPIO coprocessor (GPIOC) is documented in Section 3.7.5 (p. 101–104) β€” it provides single-cycle Cortex-M33 coprocessor access to SIO GPIO registers via `mcrr`/`mrrc` instructions. + +#### The Blink Loop with Inline Assembly + +Inside the `while (1)` loop, the program uses two inline assembly blocks to toggle the current LED on and off: + +```c +"mcrr p0, #4, r4, r5, c0\n" // gpioc_bit_out_put(pin, 1) +``` + +This time the coprocessor register is `c0` instead of `c4`. Register `c0` controls the **output value** β€” setting `r5 = 1` drives the pin HIGH (LED on), and `r5 = 0` drives it LOW (LED off). Each toggle is followed by `sleep_ms(500)` for a half-second delay, creating a visible blink. + +The GCC extended assembly syntax `"r"(pin)` tells the compiler to load the C variable `pin` into a general-purpose register and make it available as `%0` inside the assembly block. The clobber list `"r4","r5"` warns the compiler that those registers are modified, so it won't store anything important there. + +#### The Pin Cycling and Print Statements + +After each on/off cycle, the program increments `pin` and wraps it back to `16` when it exceeds `18`: + +```c +pin++; +if (pin > 18) pin = 16; +``` + +This cycles through GPIO 16, 17, and 18 β€” red, green, and blue LEDs β€” creating a rotating blink pattern. Finally, `printf` prints both integer variables over UART so we can observe their values on the serial terminal: + +``` +age: 43 +range: -42 +``` + +> πŸ’‘ **Why use inline assembly instead of the SDK?** This program is designed to teach you what happens *beneath* the SDK. When you call `gpio_put(16, 1)` in normal Pico code, the SDK ultimately does the same coprocessor write β€” `mcrr p0, #4, r4, r5, c0`. By writing the assembly directly, you can see exactly how the RP2350 hardware is controlled, which is essential knowledge for reverse engineering and binary patching. + +--- + +## πŸ“š Part 2: Understanding Floating-Point Data Types + +### What is a Float? + +A **float** is a number that can have a decimal point. Unlike integers which can only hold whole numbers like `42`, a float can hold values like `42.5`, `3.14`, or `-0.001`. In C, the `float` type uses **32 bits (4 bytes)** to store a number using the **IEEE 754** standard. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ IEEE 754 Single-Precision (32-bit float) β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Sign β”‚ Exponent β”‚ Mantissa (Fraction) β”‚ β”‚ +β”‚ β”‚ 1bit β”‚ 8 bits β”‚ 23 bits β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Value = (-1)^sign Γ— 2^(exponent-127) Γ— 1.mantissa β”‚ +β”‚ β”‚ +β”‚ Example: 42.5 β”‚ +β”‚ Sign: 0 (positive) β”‚ +β”‚ Exponent: 10000100 (132 - 127 = 5) β”‚ +β”‚ Mantissa: 01010100000000000000000 β”‚ +β”‚ Full: 0 10000100 01010100000000000000000 β”‚ +β”‚ Hex: 0x422A0000 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Float vs Integer - Key Differences + +| Property | Integer (`uint8_t`) | Float (`float`) | +| -------------- | ---------------------- | --------------------------- | +| **Size** | 1 byte | 4 bytes | +| **Precision** | Exact | ~7 decimal digits | +| **Range** | 0 to 255 | Β±3.4 Γ— 10³⁸ | +| **Encoding** | Direct binary | IEEE 754 (sign/exp/mantissa)| +| **printf** | `%d` | `%f` | + +### Our Floating-Point Program + +Let's look at a simple program that uses a `float` variable: + +**File: `0x000e_floating-point-data-type.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + float fav_num = 42.5; + + stdio_init_all(); + + while (true) + printf("fav_num: %f\r\n", fav_num); +} +``` + +**What this code does:** +1. Declares a `float` variable `fav_num` and initializes it to `42.5` +2. Initializes the serial output +3. Prints `fav_num` forever in a loop using the `%f` format specifier + +> πŸ’‘ **Why `%f` instead of `%d`?** The `%d` format specifier tells `printf` to expect an integer. The `%f` specifier tells it to expect a floating-point number. Using the wrong one would print garbage! + +### Step 1: 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 `0x000e_floating-point-data-type.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 2: Verify It's Working + +Open your serial monitor (PuTTY) and you should see: + +**You should see:** + +``` +fav_num: 42.500000 +fav_num: 42.500000 +fav_num: 42.500000 +... +``` + +The program is printing `42.500000` because `printf` with `%f` defaults to 6 decimal places. + +--- + +## πŸ”¬ Part 2.5: Setting Up Ghidra for Float Analysis + +### Step 3: Start Ghidra + +**Open a terminal and type:** + +```powershell +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 4: Create a New Project + +1. Click **File** β†’ **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x000e_floating-point-data-type` +5. Click **Finish** + +### Step 5: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x000e_floating-point-data-type.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 6: 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 7: 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 2.6: Navigating and Resolving Functions + +### Step 8: 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 9: 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! | + +### Step 10: 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 2.7: Analyzing the Main Function + +### Step 11: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +int main(void) +{ + undefined4 uVar1; + undefined4 extraout_r1; + undefined4 uVar2; + undefined4 extraout_r1_00; + + FUN_10002f5c(); + uVar1 = DAT_1000024c; + uVar2 = extraout_r1; + do { + FUN_100030ec(DAT_10000250,uVar2,0,uVar1); + uVar2 = extraout_r1_00; + } while( true ); +} +``` + +### Step 12: Resolve stdio_init_all + +1. Click on `FUN_10002f5c` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 13: Resolve printf + +1. Click on `FUN_100030ec` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int __wrap_printf(char *format,...)` +4. Check the **Varargs** checkbox (printf takes variable arguments!) +5. Click **OK** + +### Step 14: Understand the Float Encoding + +Look at the decompiled code after resolving functions: + +```c +int main(void) +{ + undefined4 uVar1; + undefined4 extraout_r1; + undefined4 uVar2; + undefined4 extraout_r1_00; + + FUN_10002f5c(); + uVar1 = DAT_1000024c; + uVar2 = extraout_r1; + do { + FUN_100030ec(DAT_10000250,uVar2,0,uVar1); + uVar2 = extraout_r1_00; + } while( true ); +} +``` + +**Where's `float fav_num = 42.5`?** It's been optimized into an immediate value! + +The compiler replaced our float variable with constants passed directly to `printf`. But wait β€” we see **two** values: `0x0`, in `r2` and `DAT_1000024c` or `0x40454000`, in `r3`. That's because `printf` with `%f` always receives a **double** (64-bit), not a `float` (32-bit). The C standard requires that `float` arguments to variadic functions like `printf` are **promoted to `double`**. + +A 64-bit double is passed in two 32-bit registers: + +| Register | Value | Role | +| -------- | ------------ | ------------ | +| `r2` | `0x00000000` | Low 32 bits | +| `r3` | `0x40454000` | High 32 bits | + +Together they form `0x40454000_00000000` β€” the IEEE 754 **double-precision** encoding of `42.5`. + +### Step 15: Verify the Double Encoding + +We need to decode `0x4045400000000000` field by field. The two registers give us the full 64-bit value: + +``` +r3 (high 32 bits): 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 +r2 (low 32 bits): 0x00000000 = 0000 0000 0000 0000 0000 0000 0000 0000 +``` + +Laid out as a single 64-bit value with every bit numbered: + +``` +Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) + β”Œβ”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ 0 β”‚ 1 0 0 0 0 0 0 0 1 0 0 β”‚ 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 β”‚ 00000000000000000000000000000000 β”‚ + β””β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits + (from r3 bits 19–0) (from r2, all zero) +``` + +**Step-by-step field extraction:** + +**1. Sign bit** + +In IEEE 754, the **sign bit** is the very first (leftmost) bit of the 64-bit double. In the full 64-bit layout we call it **bit 63**: + +``` +64-bit double: [bit 63] [bit 62 ... bit 0] + ^ + sign bit +``` + +But we don't have a single 64-bit register β€” we have **two** 32-bit registers. The high register `r3` holds bits 63–32 of the double. So bit 63 of the double is the same physical bit as **bit 31 of r3** (the topmost bit of r3): + +``` +r3 holds bits 63–32 of the double +r2 holds bits 31–0 of the double +``` + +Now let's check it. IEEE 754 uses a simple rule for the sign bit: + +| Sign bit | Meaning | +|----------|----------| +| `0` | Positive | +| `1` | Negative | + +``` +r3 = 0x40454000 = 0100 0000 0100 0101 0100 0000 0000 0000 + ^ + r3 bit 31 = 0 β†’ sign = 0 β†’ Positive number +``` + +The topmost bit of r3 is `0`, so the number is **positive**. If that bit were `1` instead (e.g. `0xC0454000`), the number would be negative (`-42.5`). + +**2. Exponent β€” bits 62–52 of the 64-bit value = bits 30–20 of r3** + +Extract bits 30–20 from `0x40454000`: + +``` +0x40454000 in binary: 0 10000000100 01010100000000000000 + sign exponent mantissa (top 20 bits) +``` + +Exponent bits: `10000000100` + +Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ + +But `1028` is **not** the actual power of 2 yet. IEEE 754 stores exponents with a **bias** β€” a fixed number that gets added during encoding so that the stored value is always positive (no sign bit needed for the exponent). For doubles, the bias is **1023**. + +> πŸ’‘ **Why 1023?** The exponent field is 11 bits wide, giving $2^{11} = 2048$ total values. Half of that range should represent negative exponents and half positive. The midpoint is $(2^{11} / 2) - 1 = 1023$. So a stored exponent of `1023` means a real exponent of **0**, values below `1023` are negative exponents, and values above `1023` are positive exponents. + +To recover the real exponent, we subtract the bias: + +$$\text{real exponent} = \text{stored exponent} - \text{bias}$$ + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +This means the number is scaled by $2^5 = 32$. In other words, the mantissa gets shifted left by 5 binary places. + +**3. Mantissa β€” bits 51–0 of the 64-bit value** + +- **High 20 bits of mantissa** (bits 51–32) = bits 19–0 of r3: + +``` +r3 bits 19–0: 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +- **Low 32 bits of mantissa** (bits 31–0) = all of r2: + +``` +r2 = 0x00000000 β†’ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +``` + +Full 52-bit mantissa: + +``` +0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + ← top 20 bits from r3 β†’ ← bottom 32 bits from r2 (all zero) β†’ +``` + +IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: + +``` +1.010101 00000... (the 1. is implicit, not stored) +``` + +**4. Reconstruct the value** + +$$1.010101_2 \times 2^5$$ + +Shift the binary point 5 places right: + +$$101010.1_2$$ + +Now convert each bit position to decimal: + +| Bit position | Power of 2 | Value | +|---|---|---| +| `1` (bit 5) | $2^5$ | 32 | +| `0` (bit 4) | $2^4$ | 0 | +| `1` (bit 3) | $2^3$ | 8 | +| `0` (bit 2) | $2^2$ | 0 | +| `1` (bit 1) | $2^1$ | 2 | +| `0` (bit 0) | $2^0$ | 0 | +| `1` (bit βˆ’1) | $2^{-1}$ | 0.5 | + +$$32 + 8 + 2 + 0.5 = \mathbf{42.5} βœ“$$ + +### Step 16: Examine the Assembly + +Look at the **Listing** window (assembly view). Find the main function: + +``` + ************************************************************* + * FUNCTION + ************************************************************* + undefined FUN_10000234 () + undefined + FUN_10000234+1 XREF[1,1]: 1000018c (c) , 1000018a (*) + FUN_10000234 + 10000234 38 b5 push {r3,r4,r5,lr} + 10000236 02 f0 91 fe bl FUN_10002f5c undefined FUN_10002f5c() + 1000023a 00 24 movs r4,#0x0 + 1000023c 03 4d ldr r5,[DAT_1000024c ] = 40454000h + LAB_1000023e XREF[1]: 10000248 (j) + 1000023e 22 46 mov r2,r4 + 10000240 2b 46 mov r3,r5 + 10000242 03 48 ldr r0=>s_fav_num:_%f_100034a8 ,[DAT_10000250 ] = "fav_num: %f\r\n" + = 100034A8h + 10000244 02 f0 52 ff bl FUN_100030ec undefined FUN_100030ec() + 10000248 f9 e7 b LAB_1000023e + 1000024a 00 ?? 00h + 1000024b bf ?? BFh + DAT_1000024c XREF[1]: FUN_10000234:1000023c (R) + 1000024c 00 40 45 40 undefine 40454000h + DAT_10000250 XREF[1]: FUN_10000234:10000242 (R) + 10000250 a8 34 00 10 undefine 100034A8h ? -> 100034a8 + +``` + +> 🎯 **Key Insight:** The `mov.w r2, #0x0` loads the low 32 bits (all zeros) and `ldr r3, [DAT_...]` loads the high 32 bits (`0x40454000`) of the double. Together, `r2:r3` = `0x40454000_00000000` = `42.5` as a double. + +### Step 17: Find the Format String + +In the Listing view, click on the data reference to find the format string: + +``` + s_fav_num:_%f_100034a8 XREF[1]: FUN_10000234:10000242 (*) + 100034a8 66 61 76 ds "fav_num: %f\r\n" + 5f 6e 75 + 6d 3a 20 + +``` + +This confirms `printf` is called with the format string `"fav_num: %f\r\n"` and the double-precision value of `42.5`. + +--- + +## πŸ”¬ Part 2.8: Patching the Float - Changing 42.5 to 99.0 + +### Step 18: Calculate the New IEEE 754 Encoding + +We want to change `42.5` to `99.0`. First, we need to figure out the double-precision encoding of `99.0`: + +**Step A β€” Convert the integer part (99) to binary:** + +| Division | Quotient | Remainder | +|---------------|----------|-----------| +| 99 Γ· 2 | 49 | **1** | +| 49 Γ· 2 | 24 | **1** | +| 24 Γ· 2 | 12 | **0** | +| 12 Γ· 2 | 6 | **0** | +| 6 Γ· 2 | 3 | **0** | +| 3 Γ· 2 | 1 | **1** | +| 1 Γ· 2 | 0 | **1** | + +Read remainders bottom-to-top: $99_{10} = 1100011_2$ + +**Step B β€” Convert the fractional part (.0) to binary:** + +There is no fractional part β€” `.0` is exactly zero, so the fractional binary is just `0`. + +**Step C β€” Combine:** + +$$99.0_{10} = 1100011.0_2$$ + +**Step D β€” Normalize to IEEE 754 form** (move the binary point so there's exactly one `1` before it): + +$$1100011.0_2 = 1.100011_2 \times 2^6$$ + +We shifted the binary point 6 places left, so the exponent is **6**. + +**Step E β€” Extract the IEEE 754 fields:** + +1. **Sign:** `0` (positive) +2. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ +3. **Mantissa:** `1000110000000000...` (everything after the `1.`, padded with zeros to 52 bits) +4. **Full double:** `0x4058C00000000000` + +| Register | Old Value | New Value | +| -------- | ------------ | ------------ | +| `r2` | `0x00000000` | `0x00000000` | +| `r3` | `0x40454000` | `0x4058C000` | + +Since `r2` stays `0x00000000`, we only need to patch the high word loaded into `r3`. + +### Step 19: Find the Value to Patch + +Look in the Listing view for the data that loads the high word of the double: + +``` + 10000248 00 40 45 40 undefined4 40454000h +``` + +This is the 32-bit constant that gets loaded into `r3` β€” the high word of our double `42.5`. + +### Step 20: Patch the Constant + +1. Click on Window -> Bytes +2. Click on Pencil Icon in Bytes Editor +2. Right-click and select **Patch Data** +3. Change `00404540` to `00C05840` +4. Press Enter + +This changes the high word from `0x40454000` (42.5 as double) to `0x4058C000` (99.0 as double). + +--- + +## πŸ”¬ Part 2.9: Export and Test the Hacked Binary + +### Step 21: 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 `0x000e_floating-point-data-type-h.bin` +5. Click **OK** + +### Step 22: Convert to UF2 Format + +**Open a terminal and navigate to your project directory:** + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x000e_floating-point-data-type +``` + +**Run the conversion command:** + +```powershell +python ..\uf2conv.py build\0x000e_floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 23: 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:** + +``` +fav_num: 99.000000 +fav_num: 99.000000 +fav_num: 99.000000 +... +``` + +πŸŽ‰ **BOOM! We hacked the float!** The value changed from `42.5` to `99.0`! + +--- + +## πŸ“š Part 3: Understanding Double-Precision Floating-Point Data Types + +### What is a Double? + +A **double** (short for "double-precision floating-point") is like a `float` but with **twice the precision**. While a `float` uses 32 bits, a `double` uses **64 bits (8 bytes)**, giving it roughly **15–16 significant decimal digits** of precision compared to a float's ~7. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ IEEE 754 Double-Precision (64-bit double) β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Sign β”‚ Exponent β”‚ Mantissa (Fraction) β”‚ β”‚ +β”‚ β”‚ 1bit β”‚ 11 bits β”‚ 52 bits β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Value = (-1)^sign Γ— 2^(exponent-1023) Γ— 1.mantissa β”‚ +β”‚ β”‚ +β”‚ Example: 42.52525 β”‚ +β”‚ Sign: 0 (positive) β”‚ +β”‚ Exponent: 10000000100 (1028 - 1023 = 5) β”‚ +β”‚ Mantissa: 0101010000110011101101100100010110100001110010101100 β”‚ +β”‚ Hex: 0x4045433B645A1CAC β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Float vs Double - Key Differences + +| Property | Float (`float`) | Double (`double`) | +| --------------- | ---------------------- | --------------------------- | +| **Size** | 4 bytes (32 bits) | 8 bytes (64 bits) | +| **Precision** | ~7 decimal digits | ~15 decimal digits | +| **Exponent** | 8 bits (bias 127) | 11 bits (bias 1023) | +| **Mantissa** | 23 bits | 52 bits | +| **Range** | Β±3.4 Γ— 10³⁸ | Β±1.8 Γ— 10³⁰⁸ | +| **printf** | `%f` | `%lf` | +| **ARM passing** | Promoted to double | Native in `r2:r3` | + +> πŸ’‘ **Why does precision matter?** With a `float`, the value `42.52525` might be stored as `42.525249` due to rounding. A `double` can represent it as `42.525250` with much higher fidelity. For scientific or financial applications, that extra precision is critical! + +### Our Double-Precision Program + +Let's look at a program that uses a `double` variable: + +**File: `0x0011_double-floating-point-data-type.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + double fav_num = 42.52525; + + stdio_init_all(); + + while (true) + printf("fav_num: %lf\r\n", fav_num); +} +``` + +**What this code does:** +1. Declares a `double` variable `fav_num` and initializes it to `42.52525` +2. Initializes the serial output +3. Prints `fav_num` forever in a loop using the `%lf` format specifier + +> πŸ’‘ **`%lf` vs `%f`:** While `printf` actually treats `%f` and `%lf` identically (both expect a `double`), using `%lf` makes your intent clear β€” you're explicitly working with a `double`, not a `float`. It's good practice to match the format specifier to your variable type. + +### Step 1: 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 `0x000A_intro-to-doubles.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 2: Verify It's Working + +Open your serial monitor (PuTTY) and you should see: + +**You should see:** + +``` +fav_num: 42.525250 +fav_num: 42.525250 +fav_num: 42.525250 +... +``` + +The program is printing `42.525250` because `printf` with `%lf` defaults to 6 decimal places. + +--- + +## πŸ”¬ Part 3.5: Setting Up Ghidra for Double Analysis + +### Step 3: Start Ghidra + +**Open a terminal and type:** + +```powershell +ghidraRun +``` + +Ghidra will open. Now we need to create a new project. + +### Step 4: Create a New Project + +1. Click **File** β†’ **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x000A_intro-to-doubles` +5. Click **Finish** + +### Step 5: Import the Binary + +1. Open your file explorer +2. Navigate to the `Embedded-Hacking` folder +3. Find `0x0011_double-floating-point-data-type.bin` +4. Select Cortex M Little Endian 32 +5. Select Options and set up the .text and offset 10000000 +6. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 6: 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 7: 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 3.6: Navigating and Resolving Functions + +### Step 8: 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_10000238` + +These are auto-generated names because we imported a raw binary without symbols! + +### Step 9: 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_10000238` | `main` | This is where our code is! | + +### Step 10: 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 3.7: Analyzing the Main Function + +### Step 11: Examine Main in Ghidra + +Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: + +You'll see something like: + +```c +void FUN_10000238(void) +{ + undefined4 uVar1; + undefined4 uVar2; + undefined4 extraout_r1; + undefined4 uVar3; + undefined4 extraout_r1_00; + undefined4 in_r3; + + uVar2 = DAT_10000258; + uVar1 = DAT_10000254; + FUN_10002f64(); + uVar3 = extraout_r1; + do { + FUN_100030f4(DAT_10000250,uVar3,uVar1,uVar2,in_r3); + uVar3 = extraout_r1_00; + } while( true ); +} +``` + +### Step 12: Resolve stdio_init_all + +1. Click on `FUN_10002f64` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 13: Resolve printf + +1. Click on `FUN_100030f4` +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 14: Understand the Double Encoding + +Look at the decompiled code after resolving functions: + +```c +int main(void) +{ + undefined4 uVar1; + undefined4 uVar2; + undefined4 extraout_r1; + undefined4 uVar3; + undefined4 extraout_r1_00; + + uVar2 = DAT_10000258; + uVar1 = DAT_10000254; + FUN_10002f64(); + uVar3 = extraout_r1; + do { + FUN_100030f4(DAT_10000250,uVar3,uVar1,uVar2); + uVar3 = extraout_r1_00; + } while( true ); +} +``` + +**Where's `double fav_num = 42.52525`?** It's been optimized into immediate values! + +This time we see **two** non-zero values: `0x645a1cac` and `0x4045433b`. Unlike the float example where the low word was `0x0`, a double with a fractional part like `42.52525` needs **all 52 mantissa bits** β€” so both halves carry data. + +A 64-bit double is passed in two 32-bit registers: + +| Register | Value | Role | +| -------- | ------------ | ------------ | +| `r2` | `0x645A1CAC` | Low 32 bits | +| `r3` | `0x4045433B` | High 32 bits | + +Together they form `0x4045433B645A1CAC` β€” the IEEE 754 **double-precision** encoding of `42.52525`. + +> 🎯 **Key Difference from Float:** In the float example, `r2` was `0x00000000` because `42.5` has a clean fractional part. But `42.52525` has a repeating binary fraction, so the low 32 bits are non-zero (`0x645A1CAC`). This means **both** registers matter when patching doubles with complex fractional values! + +### Step 15: Verify the Double Encoding + +We need to decode `0x4045433B645A1CAC` field by field. The two registers give us the full 64-bit value: + +``` +r3 (high 32 bits): 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 +r2 (low 32 bits): 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100 +``` + +Laid out as a single 64-bit value with every bit numbered: + +``` +Bit: 63 62-52 (11 bits) 51-32 (20 bits) 31-0 (32 bits) + β”Œβ”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ 0 β”‚ 1 0 0 0 0 0 0 0 1 0 0 β”‚ 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 β”‚ 01100100010110100001110010101100 β”‚ + β””β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Sign Exponent (11) Mantissa high 20 bits Mantissa low 32 bits + (from r3 bits 19–0) (from r2) +``` + +> 🎯 **Key Difference from 42.5:** In the `42.5` example, r2 was `0x00000000` because `42.5` has a clean fractional part (`.5` = exactly one binary digit). But `42.52525` has a repeating binary fraction, so the low 32 bits are **non-zero** (`0x645A1CAC`). Every bit of both registers matters here! + +**Step-by-step field extraction:** + +**1. Sign bit** + +The sign bit is bit 63 of the 64-bit double, which is bit 31 of r3 (the high register holds bits 63–32): + +``` +r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011 + ^ + r3 bit 31 = 0 β†’ sign = 0 β†’ Positive number βœ“ +``` + +**2. Exponent β€” bits 62–52 = bits 30–20 of r3** + +Extract bits 30–20 from `0x4045433B`: + +``` +0x4045433B in binary: 0 10000000100 01010100001100111011 + sign exponent mantissa (top 20 bits) +``` + +Exponent bits: `10000000100` + +Convert to decimal: $2^{10} + 2^{2} = 1024 + 4 = 1028$ + +Subtract the bias (same formula as Part 2 β€” the bias is 1023 for all doubles): + +$$\text{real exponent} = 1028 - 1023 = \mathbf{5}$$ + +This means the mantissa gets shifted left by 5 binary places (i.e. multiplied by $2^5 = 32$). + +**3. Mantissa β€” bits 51–0** + +Unlike the `42.5` example where r2 was all zeros, **both registers contribute non-zero bits** here: + +- **High 20 bits of mantissa** (bits 51–32) = bits 19–0 of r3: + +``` +r3 bits 19–0: 0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 +``` + +- **Low 32 bits of mantissa** (bits 31–0) = all of r2: + +``` +r2 = 0x645A1CAC β†’ 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 +``` + +Full 52-bit mantissa: + +``` +0 1 0 1 0 1 0 0 0 0 1 1 0 0 1 1 1 0 1 1 | 0 1 1 0 0 1 0 0 0 1 0 1 1 0 1 0 0 0 0 1 1 1 0 0 1 0 1 0 1 1 0 0 + ← top 20 bits from r3 β†’ ← bottom 32 bits from r2 β†’ +``` + +IEEE 754 always prepends an **implied leading `1`**, so the actual value represented is: + +``` +1.0101010000110011101101100100010110100001110010101100 (the 1. is implicit, not stored) +``` + +**4. Reconstruct the value** + +$$1.0101010000110011101101100100..._2 \times 2^5$$ + +Shift the binary point 5 places right: + +$$101010.10000110011101101100100010110100001110010101100_2$$ + +**Integer part** (`101010`): + +| Bit position | Power of 2 | Value | +|---|---|---| +| `1` (bit 5) | $2^5$ | 32 | +| `0` (bit 4) | $2^4$ | 0 | +| `1` (bit 3) | $2^3$ | 8 | +| `0` (bit 2) | $2^2$ | 0 | +| `1` (bit 1) | $2^1$ | 2 | +| `0` (bit 0) | $2^0$ | 0 | + +$$32 + 8 + 2 = \mathbf{42}$$ + +**Fractional part** (`.10000110011101101...`): + +| Bit position | Power of 2 | Decimal value | +|---|---|---| +| `1` (bit βˆ’1) | $2^{-1}$ | 0.5 | +| `0` (bit βˆ’2) | $2^{-2}$ | 0 | +| `0` (bit βˆ’3) | $2^{-3}$ | 0 | +| `0` (bit βˆ’4) | $2^{-4}$ | 0 | +| `0` (bit βˆ’5) | $2^{-5}$ | 0 | +| `1` (bit βˆ’6) | $2^{-6}$ | 0.015625 | +| `1` (bit βˆ’7) | $2^{-7}$ | 0.0078125 | +| `0` (bit βˆ’8) | $2^{-8}$ | 0 | +| `0` (bit βˆ’9) | $2^{-9}$ | 0 | +| `1` (bit βˆ’10) | $2^{-10}$ | 0.0009765625 | +| `1` (bit βˆ’11) | $2^{-11}$ | 0.00048828125 | +| `1` (bit βˆ’12) | $2^{-12}$ | 0.000244140625 | +| ... | ... | *(remaining 35 bits add smaller and smaller fractions)* | + +First 12 fractional bits sum: $0.5 + 0.015625 + 0.0078125 + 0.0009765625 + 0.00048828125 + 0.000244140625 \approx 0.5251$ + +The remaining 35 fractional bits refine this to $\approx 0.52525$. This is because `0.52525` is a **repeating fraction** in binary β€” it can never be represented with a finite number of bits, so double precision stores the closest possible 52-bit approximation. + +$$42 + 0.52525 = \mathbf{42.52525} βœ“$$ + +### Step 16: Examine the Assembly + +Look at the **Listing** window (assembly view). Find the main function: + +``` + ************************************************************* + * FUNCTION + ************************************************************* + undefined FUN_10000238 () + undefined + FUN_10000238+1 XREF[1,1]: 1000018c (c) , 1000018a (*) + FUN_10000238 + 10000238 38 b5 push {r3,r4,r5,lr} + 1000023a 06 a5 adr r5,[0x10000254 ] + 1000023c d5 e9 00 45 ldrd r4,r5,[r5,#0x0 ]=>DAT_10000254 = 645A1CACh + = 4045433Bh + 10000240 02 f0 90 fe bl FUN_10002f64 undefined FUN_10002f64() + LAB_10000244 XREF[1]: 1000024e (j) + 10000244 22 46 mov r2,r4 + 10000246 2b 46 mov r3,r5 + 10000248 01 48 ldr r0=>s_fav_num:_%lf_100034b0 ,[DAT_10000250 ] = "fav_num: %lf\r\n" + = 100034B0h + 1000024a 02 f0 53 ff bl FUN_100030f4 undefined FUN_100030f4() + 1000024e f9 e7 b LAB_10000244 + DAT_10000250 XREF[1]: FUN_10000238:10000248 (R) + 10000250 b0 34 00 10 undefine 100034B0h ? -> 100034b0 + DAT_10000254 XREF[1]: FUN_10000238:1000023c (R) + 10000254 ac 1c 5a 64 undefine 645A1CACh + DAT_10000258 XREF[1]: FUN_10000238:1000023c (R) + 10000258 3b 43 45 40 undefine 4045433Bh +``` + +> 🎯 **Key Insight:** Notice that **both** `r2` and `r3` are loaded from data constants using `ldr`. Compare this to the float example where `r2` was loaded with `mov.w r2, #0x0`. Because `42.52525` requires all 52 mantissa bits, neither word can be zero β€” the compiler must store both halves as separate data constants. + +### Step 17: Find the Format String + +In the Listing view, click on the data reference to find the format string: + +``` + s_fav_num:_%lf_100034b0 XREF[1]: FUN_10000238:10000248 (*) + 100034b0 66 61 76 ds "fav_num: %lf\r\n" + 5f 6e 75 + 6d 3a 20 + +``` + +This confirms `printf` is called with the format string `"fav_num: %lf\r\n"` and the double-precision value of `42.52525`. + +--- + +## πŸ”¬ Part 3.8: Patching the Double - Changing 42.52525 to 99.99 + +### Step 18: Calculate the New IEEE 754 Encoding + +We want to change `42.52525` to `99.99`. First, we need to figure out the double-precision encoding of `99.99`: + +1. $99.99 = 1.5623... \times 2^6 = 1.100011111111..._2 \times 2^6$ +2. **Sign:** `0` (positive) +3. **Exponent:** $6 + 1023 = 1029 = 10000000101_2$ +4. **Mantissa:** `1000111111010111000010100011110101110000101000111..._2$ +5. **Full double:** `0x4058FF5C28F5C28F` + +| Register | Old Value | New Value | +| -------- | ------------ | ------------ | +| `r2` | `0x645A1CAC` | `0x28F5C28F` | +| `r3` | `0x4045433B` | `0x4058FF5C` | + +Unlike the float example, **both** registers change! The value `99.99` has a repeating binary fraction, so both the high and low words are different. + +### Step 19: Find the Values to Patch + +Look in the Listing view for the two data constants: + +**Low word (loaded into `r2`):** +``` + 10000254 ac 1c 5a 64 undefined4 645A1CACh +``` + +**High word (loaded into `r3`):** +``` + 10000258 3b 43 45 40 undefined4 4045433Bh +``` + +### Step 20: Patch Both Constants + +**Patch the low word:** +1. Click on the data at address `10000254` containing `645A1CAC` +2. Right-click and select **Patch Data** +3. Change `645A1CAC` to `28F5C28F` -> `8FC2F528` +4. Press Enter + +**Patch the high word:** +1. Click on the data at address `10000258` containing `4045433B` +2. Right-click and select **Patch Data** +3. Change `4045433B` to `4058FF5C` -> `5CFF5840` +4. Press Enter + +This changes the full 64-bit double from `0x4045433B645A1CAC` (42.52525) to `0x4058FF5C28F5C28F` (99.99). + +> 🎯 **Key Difference from Float Patching:** When we patched the float `42.5`, we only needed to change one word (the high word in `r3`) because the low word was all zeros. With `42.52525 β†’ 99.99`, **both** words change. Always check whether the low word is non-zero before patching! + +--- + +## πŸ”¬ Part 3.9: Export and Test the Hacked Binary + +### Step 21: 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 `0x0011_double-floating-point-data-type-h.bin` +5. Click **OK** + +### Step 22: Convert to UF2 Format + +**Open a terminal and navigate to your project directory:** + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0011_double-floating-point-data-type +``` + +**Run the conversion command:** + +```powershell +python ..\uf2conv.py build\0x0011_double-floating-point-data-type-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 23: 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:** + +``` +fav_num: 99.990000 +fav_num: 99.990000 +fav_num: 99.990000 +... +``` + +πŸŽ‰ **BOOM! We hacked the double!** The value changed from `42.52525` to `99.99`! + +--- + +## πŸ“Š Part 3.95: Summary - Float and Double Analysis + +### What We Accomplished + +1. **Learned about IEEE 754** - How floating-point numbers are encoded in 32-bit (float) and 64-bit (double) formats +2. **Discovered float-to-double promotion** - `printf` with `%f` always receives a `double`, even when you pass a `float` +3. **Decoded register pairs** - 64-bit doubles are split across `r2` (low) and `r3` (high) +4. **Patched a float value** - Changed `42.5` to `99.0` by modifying only the high word +5. **Patched a double value** - Changed `42.52525` to `99.99` by modifying **both** words +6. **Understood the key difference** - Clean fractions (like `42.5`) have a zero low word; complex fractions (like `42.52525`) require patching both words + +### IEEE 754 Quick Reference for Common Values + +| Value | Double Hex | High Word (r3) | Low Word (r2) | +| -------- | ------------------------ | --------------- | -------------- | +| 42.0 | `0x4045000000000000` | `0x40450000` | `0x00000000` | +| 42.5 | `0x4045400000000000` | `0x40454000` | `0x00000000` | +| 42.52525 | `0x4045433B645A1CAC` | `0x4045433B` | `0x645A1CAC` | +| 43.0 | `0x4045800000000000` | `0x40458000` | `0x00000000` | +| 99.0 | `0x4058C00000000000` | `0x4058C000` | `0x00000000` | +| 99.99 | `0x4058FF5C28F5C28F` | `0x4058FF5C` | `0x28F5C28F` | +| 100.0 | `0x4059000000000000` | `0x40590000` | `0x00000000` | +| 3.14 | `0x40091EB851EB851F` | `0x40091EB8` | `0x51EB851F` | + +### The Float/Double Patching Workflow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. Identify the float/double value in the decompiled view β”‚ +β”‚ - Look for hex constants like 0x40454000 or 0x4045433B β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 2. Determine if it's float (32-bit) or double (64-bit) β”‚ +β”‚ - printf promotes floats to doubles! β”‚ +β”‚ - Check if value spans r2:r3 (double) or just r0 (float) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 3. Check if the low word (r2) is zero or non-zero β”‚ +β”‚ - Zero low word = only patch the high word β”‚ +β”‚ - Non-zero low word = patch BOTH words β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 4. Calculate the new IEEE 754 encoding β”‚ +β”‚ - Convert your desired value to IEEE 754 β”‚ +β”‚ - Split into high/low words β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 5. Patch the constant(s) in Ghidra β”‚ +β”‚ - Right-click β†’ Patch Data β”‚ +β”‚ - Replace the old encoding with the new one β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 6. Export β†’ Convert to UF2 β†’ Flash β†’ Verify β”‚ +β”‚ - Same workflow as integer patching β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +> πŸ’‘ **Key takeaway:** Hacking doubles is the same process as hacking floats β€” find the IEEE 754 constant, calculate the new encoding, patch it. The only extra step is checking whether the **low word** (`r2`) is also non-zero. Clean values like `42.5` only need one patch; messy fractions like `42.52525` need two! + +--- + +## βœ… Practice Exercises + +### Exercise 1: Patch the Float to Pi +The float program stores `42.5`. Patch it in Ghidra so the serial output prints `3.14` instead. + +**Hint:** `3.14` as a double is `0x40091EB851EB851F` β€” the high word is `0x40091EB8` and the low word is `0x51EB851F`. You'll need to patch **both** words since the low word is non-zero! + +### Exercise 2: Patch the Double to a Whole Number +The double program stores `42.52525`. Instead of patching it to `99.99` (as we did in the chapter), patch it to `100.0`. + +**Hint:** `100.0` as a double is `0x4059000000000000` β€” high word `0x40590000`, low word `0x00000000`. Notice the low word is all zeros this time β€” but the original low word (`0x645A1CAC`) is non-zero, so you still need to patch it to `0x00000000`! + +### Exercise 3: Change the Blink Speed +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 before the delay call. + +### Exercise 4: Find the Format Strings +Both programs use format strings (`"fav_num: %f\r\n"` and `"fav_num: %lf\r\n"`). Can you find these strings in Ghidra and determine where they're stored? + +**Hint:** Look in the `.rodata` section. Try pressing `S` in Ghidra to search for strings, or navigate to addresses near `0x10003xxx`. + +--- + +## πŸŽ“ Key Takeaways + +1. **Integers have fixed sizes** - `uint8_t` is 1 byte (0–255), `int8_t` is 1 byte (-128 to 127). The `u` prefix means unsigned. + +2. **IEEE 754 encodes floats in binary** - Sign bit, exponent (with bias), and mantissa form the encoding for both 32-bit floats and 64-bit doubles. + +3. **printf promotes floats to doubles** - Even when you pass a `float`, `printf` receives a 64-bit `double` due to C's variadic function rules. + +4. **64-bit values span two registers** - On ARM Cortex-M33, doubles use `r2` (low 32 bits) and `r3` (high 32 bits). + +5. **Clean fractions have zero low words** - Values like `42.5` have `0x00000000` in the low word; complex fractions like `42.52525` have non-zero low words. + +6. **Inline assembly controls hardware directly** - The `mcrr` coprocessor instruction talks to the GPIO block without any SDK overhead. + +7. **Binary patching works on any data type** - Integers, floats, and doubles can all be patched in Ghidra using the same workflow. + +--- + +## πŸ“– Glossary + +| Term | Definition | +| ----------------------- | ------------------------------------------------------------------------------ | +| **Bias** | Constant added to the exponent in IEEE 754 (127 for float, 1023 for double) | +| **Double** | 64-bit floating-point type following IEEE 754 double-precision format | +| **Exponent** | Part of IEEE 754 encoding that determines the magnitude of the number | +| **Float** | 32-bit floating-point type following IEEE 754 single-precision format | +| **FUNCSEL** | Function Select - register field that assigns a GPIO pin's function (e.g., SIO)| +| **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | +| **IEEE 754** | International standard for floating-point arithmetic and binary encoding | +| **Inline Assembly** | Assembly code embedded directly within C source using `__asm volatile` | +| **int8_t** | Signed 8-bit integer type (-128 to 127) | +| **IO_BANK0** | Register block at `0x40028000` that controls GPIO pin function selection | +| **Mantissa** | Fractional part of IEEE 754 encoding (23 bits for float, 52 bits for double) | +| **mcrr** | ARM coprocessor register transfer instruction used for GPIO control | +| **PADS_BANK0** | Register block at `0x40038000` that controls GPIO pad electrical properties | +| **Promotion** | Automatic conversion of a smaller type to a larger type (float β†’ double) | +| **Register Pair** | Two 32-bit registers (r2:r3) used together to hold a 64-bit value | +| **UF2** | USB Flashing Format - file format for Pico 2 firmware | +| **uint8_t** | Unsigned 8-bit integer type (0 to 255) | + +--- + +## πŸ”— 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) | + +### IEEE 754 Encoding Formula + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Float (32-bit): [1 sign] [8 exponent] [23 mantissa] β”‚ +β”‚ Double (64-bit): [1 sign] [11 exponent] [52 mantissa] β”‚ +β”‚ β”‚ +β”‚ Value = (-1)^sign Γ— 2^(exponent - bias) Γ— (1 + mantissa) β”‚ +β”‚ β”‚ +β”‚ Float bias: 127 β”‚ +β”‚ Double bias: 1023 β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +**Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Whether it's an integer, a float, or a double β€” it's all just bits waiting to be decoded. Practice makes perfect! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG00.svg b/WEEKS/WEEK05/slides/WEEK05-IMG00.svg new file mode 100644 index 0000000..77db88a --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 05 + + +Integers and Floats in Embedded Systems: +Debugging and Hacking Integers and Floats +w/ Intermediate GPIO Output Analysis + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG01.svg b/WEEKS/WEEK05/slides/WEEK05-IMG01.svg new file mode 100644 index 0000000..1659bfa --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG01.svg @@ -0,0 +1,77 @@ + + + + + +Integer Data Types +Fixed-Size Types for Embedded Systems + + + +uint8_t +Unsigned 8-bit +1 byte +Range: +0 to 255 +Ages, counts, always positive + + + +int8_t +Signed 8-bit +1 byte +Range: +-128 to 127 +Temperature, can be negative + + + +uint16_t +Unsigned 16-bit +2 bytes +Range: +0 to 65,535 +Sensor readings, medium values + + + +uint32_t +Unsigned 32-bit +4 bytes +Range: +0 to ~4 billion +Addresses, timestamps + + + +Code Example + +uint8_t age = 43; +// unsigned, 0-255 +int8_t range = -42; +// signed, -128 to 127 + + + +Key Insight +The +u +prefix means +unsigned +(no negatives) +Without +u += signed (allows negatives) +Choose the smallest type that fits your data + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG02.svg b/WEEKS/WEEK05/slides/WEEK05-IMG02.svg new file mode 100644 index 0000000..229f5e9 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG02.svg @@ -0,0 +1,79 @@ + + + + + +Two's Complement +How Negative Numbers are Stored + + + +Encoding -42 as int8_t + + +Step 1: Start +42 = 0x2A + + +Step 2: Flip +~0x2A = 0xD5 + + +Step 3: Add 1 +0xD5+1 = 0xD6 + +Binary: +00101010 +-> +11010101 +-> +11010110 + +Result: -42 stored as 0xD6 in memory +Top bit = 1 means negative + + + +Signed vs Unsigned: Same Bits! + +Hex +Binary +uint8_t +int8_t + + + +0x2A +00101010 +42 +42 + +0xD6 +11010110 +214 +-42 + +Same byte 0xD6 = 214 unsigned, -42 signed + + + +GDB Verification + + +(gdb) +x/1xb &range +0x200003e7: +0xd6 +// -42 in memory + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG03.svg b/WEEKS/WEEK05/slides/WEEK05-IMG03.svg new file mode 100644 index 0000000..497a13d --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG03.svg @@ -0,0 +1,75 @@ + + + + + +Inline Assembly GPIO +Direct Hardware Control via ASM + + + +GPIO Init Loop (pins 16-19) + + +1. Config Pad +PADS_BANK0 +Clear OD+ISO, set IE +0x40038000 + + +2. Set Function +IO_BANK0 +FUNCSEL = 5 (SIO) +0x40028004 + + +3. Enable Out +GPIO Coprocessor +mcrr p0,#4,r4,r5,c4 +Output Enable + +Loop: r0 = 16 to 19 +Red, Green, Blue, Yellow LEDs +Each pin: pad config + function select + OE + + + +Blink Loop + + +mcrr p0,#4,r4,r5,c0 + +r4 = pin, r5 = value +c0 = output value register +r5=1 ON, r5=0 OFF + + + +Pin Cycling + + +pin++; +if (pin > 18) pin=16; + +Cycles: 16 -> 17 -> 18 -> 16 +Red -> Green -> Blue -> repeat + + + +Why Inline Assembly? +gpio_put(16,1) calls +mcrr +underneath +Inline ASM shows what the SDK does at hardware level + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG04.svg b/WEEKS/WEEK05/slides/WEEK05-IMG04.svg new file mode 100644 index 0000000..bc8fc23 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG04.svg @@ -0,0 +1,76 @@ + + + + + +IEEE 754 Float +32-bit Single Precision Encoding + + + +Float Bit Layout (32 bits) + + +S + + +Exponent (8 bits) + + +Mantissa (23 bits) + +1 bit +bias = 127 +implicit leading 1 + + + +Value = (-1)^sign x 2^(exp-127) x 1.mantissa + + + +Example: 42.5 + +Sign: 0 +positive + +Exponent: 10000100 += 132 +132-127 = 5 + +Mantissa: 01010100...0 += 1.010101 + +1.010101 x 2^5 += +101010.1 += +42.5 + +Hex: 0x422A0000 + + + +Float vs Integer + +Integer +exact, 1 byte +%d + +Float +~7 digits, 4B +%f + +Floats sacrifice exactness for huge range + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG05.svg b/WEEKS/WEEK05/slides/WEEK05-IMG05.svg new file mode 100644 index 0000000..6118c08 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG05.svg @@ -0,0 +1,76 @@ + + + + + +Float in Ghidra +Analyzing 42.5 in the Binary + + + +Decompiled main() + + +int main(void) { +stdio_init_all(); +uVar1 = DAT_1000024c; +do { +printf(fmt,0,uVar1); +} while(true); + + + +Key Discovery + +printf with %f always +receives a +double +(64-bit) + +C promotes float to double +for variadic functions! + + +42.5 sent as double + + + +Register Pair r2:r3 + + +r2 (low): +0x00000000 + + +r3 (high): +0x40454000 + +Combined: 0x4045400000000000 += 42.5 + + + +Assembly View + + +1000023a +00 24 +movs r4, #0x0 +// r2 = 0 + +1000023c +03 4d +ldr r5,[DAT...] +// r3 = 0x40454000 + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG06.svg b/WEEKS/WEEK05/slides/WEEK05-IMG06.svg new file mode 100644 index 0000000..3748cb5 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG06.svg @@ -0,0 +1,87 @@ + + + + + +Patching Float +Changing 42.5 to 99.0 in Ghidra + + + +Calculate 99.0 as Double + +99 decimal = +1100011 +binary + +Normalize: +1.100011 x 2^6 + +Sign: +0 +Exp: +6+1023 = 1029 + +Mantissa: +100011000...0 + +Full double: 0x4058C00000000000 + + + +Before (42.5) + + +r2: +0x00000000 + + +r3: +0x40454000 + +Output: fav_num: 42.500000 + + +After (99.0) + + +r2: +0x00000000 +same! + + +r3: +0x4058C000 +changed + +Output: fav_num: 99.000000 + + + +Patch in Ghidra + + +1. Window: Bytes +Open byte editor + + +2. Find 00404540 +High word of 42.5 + + +3. Patch 00C05840 +High word of 99.0 + +Only one word to patch (low word is 0) + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG07.svg b/WEEKS/WEEK05/slides/WEEK05-IMG07.svg new file mode 100644 index 0000000..d67d52e --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG07.svg @@ -0,0 +1,75 @@ + + + + + +Double Precision +IEEE 754 64-bit Floating Point + + + +64-Bit Layout + + +Sign +1 bit + + +Exponent +11 bits (bias 1023) + + +Mantissa (Fraction) +52 bits + +Formula: (-1)^S x 1.Mantissa x 2^(Exp-1023) + + + +Encoding 42.52525 + +Sign: +0 (positive) + +Exponent: +5 + 1023 = 1028 += 0x404 (hex) + +Mantissa: +0101010000110011... + +Full 64-bit hex: +0x4045433B645A1CAC + + + +Float (32-bit) + +Size: 4 bytes +Exp: 8 bits +Mantissa: 23 bits +Precision: ~7 digits +Bias: 127 +1 register (ARM) + + +Double (64-bit) + +Size: 8 bytes +Exp: 11 bits +Mantissa: 52 bits +Precision: ~15 digits +Bias: 1023 +2 registers (r2:r3) + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG08.svg b/WEEKS/WEEK05/slides/WEEK05-IMG08.svg new file mode 100644 index 0000000..8580606 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG08.svg @@ -0,0 +1,71 @@ + + + + + +Double in Ghidra +Analyzing 42.52525 in memory + + + +Decompiled main() + + +int main(void) { +double fav_num + = 42.52525; +stdio_init_all(); +} + + + +Key Difference + +Float (42.5): +r2 = 0x00000000 (zero!) +r3 = 0x40454000 + +Double (42.52525): +r2 = 0x645A1CAC (non-zero!) + + + +Register Pair r2:r3 + + +r3 (HIGH): +0x4045433B + + +r2 (LOW): +0x645A1CAC + +Combined: 0x4045433B645A1CAC = 42.52525 + + + +Assembly (ldrd) + + +ldrd r2,r3,[PC,#0x10] +; Loads BOTH words at once +; r2 gets low word +; r3 gets high word + + +ldrd = Load +Register +Doubleword +ARM Cortex-M33 + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG09.svg b/WEEKS/WEEK05/slides/WEEK05-IMG09.svg new file mode 100644 index 0000000..353d6b4 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG09.svg @@ -0,0 +1,88 @@ + + + + + +Patching Double +Changing 42.52525 to 99.99 + + + +99.99 as IEEE 754 Double + +Sign: +0 +Exp: +6 + 1023 = 1029 + +Result: +0x4058FF5C28F5C28F + +r3 (HIGH) = 0x4058FF5C +r2 (LOW) = 0x28F5C28F + + + +Before (42.52525) + + +r3: +0x4045433B + + +r2: +0x645A1CAC + +printf: 42.525250 +Both words non-zero + + +After (99.99) + + +r3: +0x4058FF5C +changed + + +r2: +0x28F5C28F +changed + +printf: 99.990000 +BOTH words changed! + + + +Float Patch + +Words changed: +1 +r2 (low): +stays 0x0 +r3 (high): +patched +Easier to patch + + +Double Patch + +Words changed: +2 +r2 (low): +patched +r3 (high): +patched +Both words must change + \ No newline at end of file diff --git a/WEEKS/WEEK05/slides/WEEK05-IMG10.svg b/WEEKS/WEEK05/slides/WEEK05-IMG10.svg new file mode 100644 index 0000000..ce71556 --- /dev/null +++ b/WEEKS/WEEK05/slides/WEEK05-IMG10.svg @@ -0,0 +1,101 @@ + + + + + +IEEE 754 & Data Types +Data Types and IEEE 754 Reference + + + +IEEE 754 Hex Values + + +Value +Float (32b) +Double (64b) + + + + +42.0 +0x42280000 +0x4045000000000000 + + +42.5 +0x422A0000 +0x4045400000000000 + + +99.0 +0x42C60000 +0x4058C00000000000 + + +99.99 +0x42C7F5C3 +0x4058FF5C28F5C28F + + +3.14 +0x4048F5C3 +0x40091EB851EB851F + + +100.0 +0x42C80000 +0x4059000000000000 + +Tip: float low word often 0x0; double low word usually non-zero + + + +Patching Workflow + + +1. Identify +float / double + + +2. Check r2 +zero = float + + +3. Calculate +new hex value + + +4. Patch +in byte editor + + +5. Export +UF2 + test + + + +Integer Types + +uint8_t: 0-255 +int8_t: -128 to 127 +Two's complement for signed + + +Key Insight + +Float: patch 1 word +Double: patch 2 words +Check r2 to detect type + \ No newline at end of file diff --git a/WEEKS/WEEK06/WEEK06-01-S.md b/WEEKS/WEEK06/WEEK06-01-S.md new file mode 100644 index 0000000..27c771b --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-01-S.md @@ -0,0 +1,53 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100 + +#### Answers + +##### Static Variable Location + +| Item | Value | Notes | +|---------------------------|-------------|--------------------------------| +| RAM address (runtime) | 0x200005a8 | Where variable lives at runtime | +| Flash address (init value)| Calculated | In .data section of flash | +| Original value (hex) | 0x2A | 42 decimal | +| Patched value (hex) | 0x64 | 100 decimal | +| File offset | flash_addr - 0x10000000 | Binary base subtraction | + +##### GDB Session + +```gdb +(gdb) x/1db 0x200005a8 +0x200005a8: 42 +(gdb) find /b 0x10000000, 0x10010000, 0x2a +``` + +##### Serial Output After Patch + +``` +regular_fav_num: 42 +static_fav_num: 100 +regular_fav_num: 42 +static_fav_num: 101 +regular_fav_num: 42 +static_fav_num: 102 +... +``` + +#### Reflection Answers + +1. **Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?** + Static variables need to be **modifiable** at runtimeβ€”the program increments `static_fav_num` each iteration. Flash memory is read-only during normal execution (it requires a special erase/program sequence to modify). So the initial value is stored in flash as a template, and the startup code (`crt0.S`) copies the entire `.data` section from flash to RAM before `main()` runs. This gives the variable its correct starting value (42) in writable RAM where subsequent `adds` and `strb` instructions can modify it freely. + +2. **The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0?** + A `uint8_t` overflows from 255 to 0. Starting at 100 and incrementing by 1: it takes `255 - 100 = 155` increments to reach 255, then one more to wrap to 0. So it overflows after **156 iterations**. Compare to the original: starting at 42, it takes `255 - 42 + 1 = 214` iterations. + +3. **If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?** + No. `regular_fav_num` is a **local variable** that the compiler optimized to an immediate constant (`movs r1, #0x2a`), just like Week 4's `age` variable. It's encoded directly in the instruction opcode in the `.text` section, not in the `.data` section. You would need to find the `movs r1, #0x2a` instruction in the code and patch the immediate byte from `0x2a` to your desired value. The `.data` section only contains initialized static/global variables. + +4. **What would happen if the `.data` section had TWO static variables β€” would their initial values be adjacent in flash?** + Yes. The linker places all initialized static variables contiguously in the `.data` section. Their initial values are stored in the same order in flash, packed adjacent to each other (possibly with alignment padding). The startup code performs a single `memcpy`-like loop that copies the entire `.data` block from flash to RAM. So if you had `static uint8_t a = 42;` and `static uint8_t b = 99;`, the bytes `0x2A` and `0x63` would be adjacent (or nearly so) in flash, and both would be copied to their respective RAM addresses during boot. diff --git a/WEEKS/WEEK06/WEEK06-01.md b/WEEKS/WEEK06/WEEK06-01.md new file mode 100644 index 0000000..5043b7b --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-01.md @@ -0,0 +1,157 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 1: Change the Static Variable Initial Value from 42 to 100 + +#### Objective +Use GDB to locate the static variable `static_fav_num` in the `.data` section of the `0x0014_static-variables` binary, calculate the corresponding file offset, patch the initial value from `42` (`0x2A`) to `100` (`0x64`) using a hex editor, convert the patched binary to UF2 format, and flash it to the Pico 2 to verify the change. + +#### Prerequisites +- Completed Week 6 tutorial (GDB section) +- `0x0014_static-variables.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 connected via USB +- Serial monitor software (PuTTY, minicom, or screen) + +#### Task Description +You will use GDB to examine the static variable at its known RAM address (`0x200005a8`), trace back to where the initial value is stored in the `.data` section of flash, calculate the file offset in the `.bin` file, patch the byte from `0x2A` to `0x64` with a hex editor, and verify on hardware that the serial output now starts counting from `100` instead of `42`. + +#### 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\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Examine the Static Variable in RAM + +We know from the tutorial that the static variable lives at `0x200005a8`. Examine its current value: + +```gdb +(gdb) x/1db 0x200005a8 +``` + +You should see the current value (probably `42` or a count if the program has been running). + +##### Step 3: Find Where the Initial Value Lives in Flash + +Static variables that are initialized get their starting values from the `.data` section in flash. The startup code copies these values from flash to RAM before `main()` runs. + +Examine the disassembly to find data copy references. The initial value `42` (`0x2A`) is stored somewhere in flash. Use GDB to search: + +```gdb +(gdb) find /b 0x10000000, 0x10010000, 0x2a +``` + +This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section). + +##### Step 4: Confirm the Address + +Once you identify a candidate address, verify it by examining the surrounding bytes: + +```gdb +(gdb) x/8xb +``` + +The `0x2A` byte at this address should be the initialization value for our static variable. + +##### Step 5: Calculate the File Offset + +The binary is loaded at base address `0x10000000`. To find the file offset: + +``` +file_offset = address - 0x10000000 +``` + +For example, if the initial value is at `0x10004xxx`: +- File offset = `0x10004xxx` - `0x10000000` = `0x4xxx` + +##### Step 6: Patch the Value with a Hex Editor + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +2. Press **Ctrl+G** (Go to offset) +3. Enter the calculated offset +4. You should see the byte `2A` at this position +5. Change `2A` to `64` (100 in decimal) +6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) + +##### Step 7: Convert to UF2 and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +python ..\uf2conv.py build\0x0014_static-variables-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 +3. Open your serial monitor + +##### Step 8: Verify the Hack + +**Expected serial output:** +``` +regular_fav_num: 42 +static_fav_num: 100 ? Starts at 100 now! +regular_fav_num: 42 +static_fav_num: 101 +regular_fav_num: 42 +static_fav_num: 102 +... +``` + +The static variable now starts counting from 100 instead of 42! + +#### Expected Output + +After completing this exercise, you should be able to: +- Use GDB `find` command to search for byte patterns in flash +- Distinguish between RAM addresses (where the variable lives at runtime) and flash addresses (where the initial value is stored) +- Calculate file offsets from memory addresses +- Patch initialization values with a hex editor +- Understand the `.data` section copy mechanism at startup + +#### Questions for Reflection + +###### Question 1: Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly? + +###### Question 2: The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0? + +###### Question 3: If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not? + +###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash? + +#### Tips and Hints +- The `find` command in GDB can search for bytes, halfwords, or words in any memory range +- Static variables with initial values are in `.data`; without initial values they're in `.bss` +- The startup code (`crt0`) copies the entire `.data` section from flash to RAM before calling `main()` +- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*` + +#### Next Steps +- Proceed to Exercise 2 to try a more complex hack (reversing GPIO logic) +- Try patching the static variable to `0xFF` (255) and observe immediate overflow behavior +- Look at the startup code in GDB to find the memcpy that copies `.data` from flash to RAM diff --git a/WEEKS/WEEK06/WEEK06-02-S.md b/WEEKS/WEEK06/WEEK06-02-S.md new file mode 100644 index 0000000..113ff3e --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-02-S.md @@ -0,0 +1,63 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB + +#### Answers + +##### Function Arguments + +```gdb +(gdb) b *0x100002d8 +(gdb) c +(gdb) info registers r0 r1 r2 +r0 = 15 ; GPIO pin +r1 = 1 ; pull-up enable (true) +r2 = 0 ; pull-down disable (false) +``` + +##### PADS_BANK0 Address Calculation + +``` +Base: 0x40038000 (PADS_BANK0) +GPIO 0 offset: 0x04 (first pad register) +GPIO N offset: 0x04 + (N Γ— 4) +GPIO 15: 0x40038000 + 0x04 + (15 Γ— 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040 +``` + +##### Function Behavior Summary + +| Step | Instruction(s) | Effect | +|------------------------|---------------------|-----------------------------------| +| Load PADS base address | `ldr rX, =0x40038000` | rX = PADS_BANK0 base | +| Calculate pad offset | `adds`, `lsls` | offset = 0x04 + pin Γ— 4 | +| Read current config | `ldr rX, [addr]` | Read existing pad register value | +| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 (PDE) and 3 (PUE) | +| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE = pull-up enable) | +| Write back | `str rX, [addr]` | Write updated config to hardware | + +##### Pad Register Bits + +| Bit | Name | Value | Meaning | +|-----|----------|-------|-------------------------| +| 3 | PUE | 1 | Pull-up enable | +| 2 | PDE | 0 | Pull-down disable | +| 1 | SCHMITT | 1 | Schmitt trigger enabled | +| 0 | SLEWFAST | 0 | Slow slew rate | + +#### Reflection Answers + +1. **Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?** + The pad register contains multiple configuration fields (drive strength, slew rate, Schmitt trigger, input enable, output disable, pull-up, pull-down). If the function wrote a new value without reading first, it would overwrite all other fields with zeros or arbitrary values, potentially disabling the Schmitt trigger, changing drive strength, or disabling input/output. The read-modify-write pattern uses `bic` to clear only the pull bits (2 and 3) and `orr` to set the desired pull configuration, leaving all other bits untouched. + +2. **The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?** + The formula is: offset = `0x04 + (GPIO_number Γ— 4)`. For GPIO 15: `0x04 + (15 Γ— 4) = 0x04 + 0x3C = 0x40`. The `0x04` initial offset exists because offset `0x00` is the VOLTAGE_SELECT register, not a pad register. For GPIO 0: offset = `0x04 + (0 Γ— 4) = 0x04`. So GPIO 0's pad register is at `0x40038004`. + +3. **What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?** + Enabling both creates a **resistive voltage divider** between VDD and GND through the internal pull resistors. On the RP2350, both pull resistors are typically ~50kΞ©. The pin voltage would settle at approximately VDD/2 (1.65V for 3.3V supply), which is in the undefined region between logic HIGH and LOW thresholds. This makes the digital input unreliableβ€”the Schmitt trigger may oscillate or read randomly. While not damaging to the hardware, it wastes power and produces unpredictable input reads. The SDK intentionally never sets both bits simultaneously. + +4. **The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?** + This indicates at least `-O1` or higher optimization (the Pico SDK defaults to `-O2`). The `gpio_pull_up` function is declared `static inline` in the SDK header, and the compiler eliminates the function call overhead by inserting `gpio_set_pulls(pin, true, false)` directly. For binary analysis, inlining means: (a) the original function name `gpio_pull_up` doesn't appear in the symbol table, (b) you see `gpio_set_pulls` called directly with hardcoded arguments, making it harder to identify the programmer's original intent, and (c) multiple calls to `gpio_pull_up` with different pins each become separate `gpio_set_pulls` calls rather than referencing a single function body. diff --git a/WEEKS/WEEK06/WEEK06-02.md b/WEEKS/WEEK06/WEEK06-02.md new file mode 100644 index 0000000..81f781c --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-02.md @@ -0,0 +1,187 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB + +#### Objective +Use GDB to disassemble the `gpio_set_pulls` function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level. + +#### Prerequisites +- Completed Week 6 tutorial (GDB section) +- `0x0014_static-variables.elf` binary available in your build directory +- GDB (`arm-none-eabi-gdb`) and OpenOCD installed +- Understanding of GPIO pull-up resistors from Week 6 Part 2 +- Understanding of function inlining from Week 6 Part 3 + +#### Task Description +You will use GDB to locate the `gpio_set_pulls` function (remember: `gpio_pull_up` is inlined and becomes `gpio_set_pulls`), disassemble it, step through it instruction by instruction, and identify the PADS_BANK0 register address it writes to. You will document the bit fields being set and explain how the hardware implements pull-up and pull-down resistors. + +#### 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\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find Where gpio_set_pulls is Called + +From the tutorial, we know `gpio_set_pulls` is called at `0x1000024e` with arguments for GPIO 15: + +```gdb +(gdb) x/5i 0x10000240 +``` + +You should see: +``` +0x10000240: movs r0, #15 ; GPIO pin 15 +0x10000242: mov.w r3, #0 ; GPIO_IN (direction) +0x10000246: mcrr 0, 4, r0, r3, cr4 ; gpio_set_dir via coprocessor +0x1000024a: movs r2, #0 ; down = false +0x1000024c: movs r1, #1 ; up = true +0x1000024e: bl 0x100002d8 ; gpio_set_pulls +``` + +##### Step 3: Disassemble gpio_set_pulls + +Now examine the function itself: + +```gdb +(gdb) x/30i 0x100002d8 +``` + +Study the disassembly carefully. Look for: +- Address calculations (base address of PADS_BANK0) +- Register loads and stores +- Bit manipulation instructions (AND, OR, BIC) +- The write to the hardware register + +##### Step 4: Set a Breakpoint at gpio_set_pulls + +```gdb +(gdb) b *0x100002d8 +(gdb) monitor reset halt +(gdb) c +``` + +When the breakpoint hits, examine the arguments: + +```gdb +(gdb) info registers r0 r1 r2 +``` + +You should see: +- `r0 = 15` (GPIO pin) +- `r1 = 1` (pull-up enable) +- `r2 = 0` (pull-down disable) + +##### Step 5: Step Through the Function + +Use `si` (step instruction) to execute one instruction at a time: + +```gdb +(gdb) si +(gdb) info registers +``` + +Repeat, watching the registers change. Pay attention to: + +1. **Address calculation**: The function computes the PADS_BANK0 register address for GPIO 15. The base address is `0x40038000`, and each GPIO pad has a 4-byte register. GPIO 0 starts at offset `0x04`, so GPIO 15 is at: + +$$0x40038000 + 0x04 + (15 \times 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040$$ + +2. **Read the current register value**: The function reads the existing pad configuration +3. **Modify the pull bits**: It sets or clears the pull-up (bit 3) and pull-down (bit 2) bits +4. **Write back**: It stores the modified value + +##### Step 6: Examine the PADS_BANK0 Register + +After the function completes, examine the result: + +```gdb +(gdb) x/1wx 0x40038040 +``` + +Document the value you see. The relevant bits are: + +| Bit | Name | Value | Meaning | +| --- | -------- | ----- | ----------------------- | +| 3 | PUE | 1 | Pull-up enable | +| 2 | PDE | 0 | Pull-down enable | +| 1 | SCHMITT | 1 | Schmitt trigger enabled | +| 0 | SLEWFAST | 0 | Slow slew rate | + +##### Step 7: Compare with gpio_set_pulls for the LED Pin + +Continue execution until gpio_set_pulls is called again (if it is). Or, examine the pad register for GPIO 16 (the LED pin): + +```gdb +(gdb) x/1wx 0x40038044 +``` + +Compare the values. The LED pin (output) should NOT have pull-up enabled. + +##### Step 8: Document Your Findings + +Create a table documenting the function's behavior: + +| Step | Instruction(s) | Register Changes | +| ------------------------ | ------------------ | --------------------------- | +| Load PADS base address | `ldr rX, =...` | rX = `0x40038000` | +| Calculate pad offset | `adds`, `lsls` | offset = `0x04 + pin * 4` | +| Read current pad config | `ldr rX, [addr]` | rX = current register value | +| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 and 3 | +| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE) | +| Write back | `str rX, [addr]` | Updated pad register | + +#### Expected Output + +After completing this exercise, you should be able to: +- Disassemble and trace through a hardware configuration function +- Identify PADS_BANK0 register addresses for any GPIO pin +- Understand how pull-up and pull-down resistors are controlled at the register level +- Explain why `gpio_pull_up(pin)` becomes `gpio_set_pulls(pin, true, false)` in the binary + +#### Questions for Reflection + +###### Question 1: Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first? + +###### Question 2: The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0? + +###### Question 3: What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)? + +###### Question 4: The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis? + +#### Tips and Hints +- PADS_BANK0 base address on RP2350 is `0x40038000` +- Each GPIO has a 4-byte pad control register starting at offset `0x04` +- The `bic` instruction clears bits (Bit Clear); `orr` sets bits (OR) +- Use `x/1wx` to examine a 32-bit word; use `x/1tb` to see individual bits +- The RP2350 datasheet section on PADS_BANK0 has the full register bit map + +#### Next Steps +- Proceed to Exercise 3 to reverse engineer the `eor` (XOR) instruction in the GPIO logic +- Try enabling pull-down instead of pull-up by modifying the `gpio_set_pulls` arguments in GDB: `set $r1 = 0` and `set $r2 = 1` before the function call +- Examine the PADS registers for all GPIOs to see which pins have pull-ups enabled after boot diff --git a/WEEKS/WEEK06/WEEK06-03-S.md b/WEEKS/WEEK06/WEEK06-03-S.md new file mode 100644 index 0000000..ed4a6d2 --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-03-S.md @@ -0,0 +1,57 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster + +#### Answers + +##### Patch Details + +| Item | Original | Patched | +|--------------------|-----------------|-----------------| +| Instruction | adds r3, #0x1 | adds r3, #0xa | +| Address | 0x1000027c | 0x1000027c | +| Hex bytes | 01 33 | 0A 33 | +| Increment value | 1 | 10 | +| File offset | 0x27c | 0x27c | + +##### Instruction Encoding + +``` +Thumb adds rD, #imm8: + Byte 0: immediate value (0x01 β†’ 0x0A) + Byte 1: opcode + register (0x33 = adds r3) +``` + +##### Serial Output After Patch + +``` +regular_fav_num: 42 +static_fav_num: 42 +regular_fav_num: 42 +static_fav_num: 52 +regular_fav_num: 42 +static_fav_num: 62 +... +regular_fav_num: 42 +static_fav_num: 252 +regular_fav_num: 42 +static_fav_num: 6 ← Overflow! 252 + 10 = 262 mod 256 = 6 +``` + +#### Reflection Answers + +1. **The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).** + A `uint8_t` stores values modulo 256. Starting at 42 and incrementing by 10: the sequence passes through 42, 52, 62, ..., 242, 252. The next value is 252 + 10 = 262. Since `uint8_t` can only hold 0–255: $262 \bmod 256 = 6$. The wrap value is non-zero because the increment (10) does not evenly divide into 256. With increment 1, the value hits exactly 255, and $255 + 1 = 256 \bmod 256 = 0$. With increment 10, it skips from 252 directly to 262, bypassing 0 and landing on 6. + +2. **What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255?** + The maximum is **255** (`0xFF`). The `adds rD, #imm8` Thumb encoding has an 8-bit immediate field, so valid values are 0–255. For an increment larger than 255, you would need to: (a) use a 32-bit Thumb-2 `adds.w` instruction which supports a wider range of modified immediates, (b) use a `movs` + `adds` two-instruction sequence to load a larger value, or (c) load the value from a literal pool with `ldr` then use a register-register `add`. Each approach requires different instruction sizes, so patching in a hex editor becomes more complexβ€”you may need to shift code or use NOP padding. + +3. **If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?** + Starting at 42, incrementing by 128: 42 β†’ 170 β†’ 42 β†’ 170 β†’ ... Wait, let's compute: $42 + 128 = 170$ (first iteration), $170 + 128 = 298 \bmod 256 = 42$ (second iteration). It wraps after **2 iterations** back to 42. The variable alternates between 42 and 170 forever because $2 \times 128 = 256$, and $42 + 256 = 42 \bmod 256$. The value never reaches 0β€”it cycles between exactly two values. + +4. **Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?** + No. Changing `strb` to `strh` would store 16 bits instead of 8, which means the variable would be treated as a `uint16_t` (range 0–65535). This would actually **slow down** overflowβ€”it would take 65,535 βˆ’ 42 = 65,493 iterations to wrap instead of 213. Additionally, `strh` writes 2 bytes to RAM, potentially corrupting the adjacent byte at `0x200005a9`. The proper way to speed up overflow is to increase the increment value, not change the storage width. The `strb` truncation to 8 bits is what enforces the `uint8_t` modular arithmetic. diff --git a/WEEKS/WEEK06/WEEK06-03.md b/WEEKS/WEEK06/WEEK06-03.md new file mode 100644 index 0000000..d770f2d --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-03.md @@ -0,0 +1,183 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 3: Make the Overflow Happen Faster + +#### Objective +Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner. + +#### Prerequisites +- Completed Week 6 tutorial (GDB and hex editor sections) +- `0x0014_static-variables.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 connected via USB +- Serial monitor software (PuTTY, minicom, or screen) + +#### Task Description +The static variable `static_fav_num` is a `uint8_t` that counts from 42 to 255 before wrapping to 0. Currently it increments by 1 each iteration, so it takes 214 steps to overflow. You will change the increment value from 1 to 10 so that it overflows after only ~22 steps. This exercise teaches you how Thumb immediate encoding works for small arithmetic instructions and demonstrates the real-world impact of patching arithmetic operations. + +#### 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\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Locate the Increment Instruction + +From the tutorial, we know the static variable operations are in the loop body starting at `0x10000274`. Disassemble the loop body: + +```gdb +(gdb) x/20i 0x10000274 +``` + +Look for this sequence: + +``` +0x10000278: ldrb r3, [r4, #0] ; Load static_fav_num from RAM +0x1000027a: movs r2, #16 ; LED GPIO pin number +0x1000027c: adds r3, #1 ; Increment by 1 ? THIS IS OUR TARGET +0x1000027e: strb r3, [r4, #0] ; Store back to RAM +``` + +The `adds r3, #1` instruction is at address `0x1000027c`. + +##### Step 3: Examine the Instruction Encoding + +Look at the raw bytes of the instruction: + +```gdb +(gdb) x/2bx 0x1000027c +``` + +You should see: + +``` +01 33 +``` + +**Thumb encoding breakdown:** +- `01` = the immediate value `0x01` (decimal 1) +- `33` = the opcode for `adds r3, #imm8` + +##### Step 4: Test the Change in GDB First + +Before making a permanent patch, test the change in RAM: + +```gdb +(gdb) b *0x1000027c +(gdb) c +``` + +When the breakpoint hits: + +```gdb +(gdb) x/1db 0x200005a8 +``` + +Note the current value of `static_fav_num`. Now continue a few iterations and check how it increments. + +##### Step 5: Calculate the File Offset + +``` +file_offset = address - 0x10000000 +``` + +For the `adds r3, #0x1` instruction at its address, calculate the offset. + +##### Step 6: Patch with the Hex Editor + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +2. Press **Ctrl+G** (Go to offset) and enter the calculated offset +3. You should see the byte `01` followed by `33` +4. Change `01` to `0A` (10 in decimal) +5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa` +6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) + +> ?? **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe. + +##### Step 7: Convert to UF2 and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +python ..\uf2conv.py build\0x0014_static-variables-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 +3. Open your serial monitor + +##### Step 8: Verify the Hack + +**Expected serial output:** +``` +regular_fav_num: 42 +static_fav_num: 42 +regular_fav_num: 42 +static_fav_num: 52 ? Jumped by 10! +regular_fav_num: 42 +static_fav_num: 62 +... +regular_fav_num: 42 +static_fav_num: 242 +regular_fav_num: 42 +static_fav_num: 252 +regular_fav_num: 42 +static_fav_num: 6 ? Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6 +``` + +Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`. + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate arithmetic instructions in disassembled code +- Understand Thumb `adds rD, #imm8` encoding +- Patch an immediate operand in a hex editor +- Predict the effects of changing an increment value on overflow behavior + +#### Questions for Reflection + +###### Question 1: The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255). + +###### Question 2: What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255? + +###### Question 3: If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to? + +###### Question 4: Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not? + +#### Tips and Hints +- In Thumb encoding, `adds rD, #imm8` has the immediate in the first byte and opcode in the second +- The `imm8` field is 8 bits, so valid ranges are `0x00` to `0xFF` (0-255) +- Overflow for `uint8_t` is $(value) \bmod 256$ +- Use GDB to verify: `set *(unsigned char*)0x200005a8 = 250` then continue to watch the wrap quickly + +#### Next Steps +- Proceed to Exercise 4 to learn about inverting button logic with XOR +- Try changing the increment to other values (2, 5, 50, 128) and predict the wrap behavior before flashing +- Consider: what would happen if you changed `adds` to `subs` (subtract)? diff --git a/WEEKS/WEEK06/WEEK06-04-S.md b/WEEKS/WEEK06/WEEK06-04-S.md new file mode 100644 index 0000000..9464c45 --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-04-S.md @@ -0,0 +1,60 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR + +#### Answers + +##### Patch Details + +| Item | Original | Patched | +|--------------------|-----------------------|-----------------------| +| Instruction | eor.w r3, r3, #1 | eor.w r3, r3, #0 | +| Address | 0x10000286 | 0x10000286 | +| Hex bytes | 83 F0 01 03 | 83 F0 00 03 | +| Patched byte offset| 0x288 (3rd byte) | 01 β†’ 00 | + +##### Logic Table Comparison + +**Original (EOR #1):** + +| Button State | GPIO 15 | After UBFX | After EOR #1 | LED (GPIO 16) | +|-------------|---------|------------|-------------|---------------| +| Released | 1 (HIGH)| 1 | 0 | OFF | +| Pressed | 0 (LOW) | 0 | 1 | ON | + +**Patched (EOR #0):** + +| Button State | GPIO 15 | After UBFX | After EOR #0 | LED (GPIO 16) | +|-------------|---------|------------|-------------|---------------| +| Released | 1 (HIGH)| 1 | 1 | **ON** | +| Pressed | 0 (LOW) | 0 | 0 | **OFF** | + +##### Hardware Result + +- Button NOT pressed: LED **ON** (was OFF) +- Button PRESSED: LED **OFF** (was ON) +- Behavior completely reversed by changing a single byte (01 β†’ 00) + +#### Reflection Answers + +1. **Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1.** + + | x | x XOR 1 | x XOR 0 | + |---|---------|---------| + | 0 | 1 | 0 | + | 1 | 0 | 1 | + + `x XOR 1` always flips the bit (acts as NOT): 0β†’1, 1β†’0. `x XOR 0` always preserves the bit (acts as identity): 0β†’0, 1β†’1. This works because XOR returns 1 when inputs differ and 0 when they match. XOR with 1 forces a difference; XOR with 0 forces a match. This property only applies to the single affected bitβ€”for multi-bit values, each bit is XORed independently. + +2. **Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?** + Yes. Removing the EOR instruction entirely would have the same effect as EOR #0β€”the value passes through unchanged. A Thumb NOP is encoded as `00 BF` (2 bytes). Since `eor.w` is a 32-bit Thumb-2 instruction (4 bytes), you would need **two** NOPs to replace it: `00 BF 00 BF`. In the hex editor, replace bytes at offset 0x286–0x289 from `83 F0 01 03` to `00 BF 00 BF`. Both approaches yield identical behavior, but the EOR #0 patch is "cleaner" because it preserves the instruction structureβ€”making the modification more obvious during reverse engineering. + +3. **The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not?** + It would be unreliable. Without a pull-up or pull-down resistor, the GPIO input is **floating** when the button is not pressedβ€”there's no defined voltage on the pin. The input would pick up electrical noise, stray capacitance, and electromagnetic interference, causing random readings (0 or 1 unpredictably). When the button IS pressed, it connects to ground (LOW), which works. But when released, the pin has no path to any voltage, so `gpio_get(15)` returns garbage. The pull-up provides a defined HIGH state when the button circuit is open. + +4. **The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin?** + You would be reading **GPIO 16**, which is the LED output pin. Since GPIO 16 is configured as an output (not input), reading its input register returns the current output stateβ€”either 0 or 1 depending on whether the LED is currently on or off. This would create a **feedback loop**: the LED's current state determines its next state (after the XOR), causing unpredictable oscillation or a stuck state. If GPIO 16 had nothing connected and was unconfigured, the floating input would return random values, similar to Q3's scenario. diff --git a/WEEKS/WEEK06/WEEK06-04.md b/WEEKS/WEEK06/WEEK06-04.md new file mode 100644 index 0000000..211d41d --- /dev/null +++ b/WEEKS/WEEK06/WEEK06-04.md @@ -0,0 +1,193 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 6 +Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR + +#### Objective +Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior. + +#### Prerequisites +- Completed Week 6 tutorial (all GDB and hex editor sections) +- `0x0014_static-variables.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 button on GP15 and LED on GP16 + +#### Task Description +The original program uses `gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO))` which the compiler implements as an XOR (`eor.w r3, r3, #1`) to invert the button state. With the pull-up resistor, button released = HIGH, so `HIGH XOR 1 = 0` (LED off). You will patch the XOR operand from `#1` to `#0`, which effectively removes the inversion: `HIGH XOR 0 = 1` (LED on when released). This exercise demonstrates how a single-byte binary patch can completely reverse hardware behavior. + +#### 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\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Locate the GPIO Logic + +From the tutorial, the GPIO input/output logic is near address `0x10000274`. Disassemble: + +```gdb +(gdb) x/10i 0x10000274 +``` + +Look for this sequence: + +``` +0x10000274: mov.w r1, #0xd0000000 ; SIO base address +0x10000280: ldr r3, [r1, #4] ; Read GPIO input register +0x10000282: ubfx r3, r3, #15, #1 ; Extract bit 15 (button state) +0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT ? OUR TARGET +0x1000028a: mcrr 0, 4, r2, r3, cr0 ; Write to GPIO output +``` + +The `eor.w r3, r3, #1` instruction is at address `0x10000286`. + +##### Step 3: Understand the Current Logic + +Trace the logic with the pull-up resistor: + +| Button State | GPIO 15 Input | After UBFX | After EOR #1 | LED (GPIO 16) | +| ------------ | ------------- | ---------- | ------------ | -------------- | +| Released | 1 (HIGH) | 1 | 0 | OFF | +| Pressed | 0 (LOW) | 0 | 1 | ON | + +The `eor.w #1` flips the bit, implementing the `!` (NOT) from the C code. + +##### Step 4: Verify with GDB + +Set a breakpoint at the `eor.w` instruction: + +```gdb +(gdb) b *0x10000286 +(gdb) c +``` + +When it hits, check what value is about to be XORed: + +```gdb +(gdb) info registers r3 +``` + +- If button is **released**: `r3 = 1` ? after EOR: `r3 = 0` +- If button is **pressed**: `r3 = 0` ? after EOR: `r3 = 1` + +##### Step 5: Test the Patch in GDB + +Modify the EOR operand in RAM to see the effect live: + +```gdb +(gdb) set $r3 = 0 +(gdb) si +(gdb) info registers r3 +``` + +Or skip the EOR entirely by advancing the PC past it, then observe the LED behavior. + +##### Step 6: Examine the Instruction Encoding + +Look at the raw bytes: + +```gdb +(gdb) x/4bx 0x10000286 +0x10000286 : 0x83 0xf0 0x01 0x03 +``` + +The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as: +- `0x83 0xF0` — opcode + source register (r3) +- `0x01` — **the immediate value (`#1`)** ? this is what we change +- `0x03` — destination register (r3) + +##### Step 7: Patch with the Hex Editor + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +2. The instruction starts at file offset: `0x10000286 - 0x10000000 = 0x286` +3. The immediate byte is the 3rd byte: offset `0x286 + 2 = 0x288` +4. Press **Ctrl+G** and enter offset: `288` +5. You should see `01` — change it to `00` +6. Verify the surrounding bytes (`83 F0` before and `03` after) are unchanged +7. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory) + +> ?? **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`. + +##### Step 8: Predict the New Behavior + +After patching, the logic changes: + +| Button State | GPIO 15 Input | After UBFX | After EOR #0 | LED (GPIO 16) | +| ------------ | ------------- | ---------- | ------------ | -------------- | +| Released | 1 (HIGH) | 1 | 1 | **ON** | +| Pressed | 0 (LOW) | 0 | 0 | **OFF** | + +The LED behavior is now **inverted** from the original! + +##### Step 9: Convert to UF2 and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +python ..\uf2conv.py build\0x0014_static-variables-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 10: Verify the Hack + +Test the button: +- **Button NOT pressed**: LED should now be **ON** (was OFF before patching) +- **Button PRESSED**: LED should now be **OFF** (was ON before patching) + +The LED behavior is completely reversed by changing a single byte! + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate XOR / EOR instructions in disassembled GPIO logic +- Understand how XOR implements logical NOT for single-bit values +- Patch a Thumb-2 encoded immediate operand +- Predict hardware behavior changes from binary patches + +#### Questions for Reflection + +###### Question 1: Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1. + +###### Question 2: Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb? + +###### Question 3: The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not? + +###### Question 4: The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin? + +#### Tips and Hints +- `eor.w r3, r3, #1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction +- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction +- Use GDB `x/1tw` to view a word in binary format, making bit manipulation easier to see +- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000` + +#### Next Steps +- Review all four exercises and verify you can patch any part of the binary: data values, arithmetic operations, and logic operations +- Try combining multiple hacks in a single binary: change the initial value, speed up the overflow, AND invert the button logic +- Compare your patched binary with the original using `fc /b original.bin patched.bin` in the command prompt to see all changed bytes diff --git a/WEEKS/WEEK06/WEEK06-SLIDES.pdf b/WEEKS/WEEK06/WEEK06-SLIDES.pdf new file mode 100644 index 0000000..54ad3ac Binary files /dev/null and b/WEEKS/WEEK06/WEEK06-SLIDES.pdf differ diff --git a/WEEKS/WEEK06/WEEK06.md b/WEEKS/WEEK06/WEEK06.md new file mode 100644 index 0000000..fd20e89 --- /dev/null +++ b/WEEKS/WEEK06/WEEK06.md @@ -0,0 +1,1068 @@ +ο»Ώ# Week 6: Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand the difference between regular (automatic) variables and static variables +- Know where different types of variables are stored (stack vs static storage) +- Configure GPIO pins as inputs and use internal pull-up resistors +- Read button states using `gpio_get()` and control LEDs based on input +- Use GDB to examine how the compiler handles static vs automatic variables +- Identify compiler optimizations by stepping through assembly +- Hack variable values and invert GPIO input/output logic using a hex editor +- Convert patched binaries to UF2 format for flashing + +--- + +## πŸ“š Part 1: Understanding Static Variables + +### What is a Static Variable? + +A **static variable** is a special kind of variable that "remembers" its value between function calls or loop iterations. Unlike regular variables that get created and destroyed each time, static variables **persist** for the entire lifetime of your program. + +Think of it like this: +- **Regular variable:** Like writing on a whiteboard that gets erased after each class +- **Static variable:** Like writing in a notebook that you keep forever + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Regular vs Static Variables β”‚ +β”‚ β”‚ +β”‚ REGULAR (automatic): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Loop 1: Create β†’ Set to 42 β†’ Increment to 43 β†’ Destroy β”‚ β”‚ +β”‚ β”‚ Loop 2: Create β†’ Set to 42 β†’ Increment to 43 β†’ Destroy β”‚ β”‚ +β”‚ β”‚ Loop 3: Create β†’ Set to 42 β†’ Increment to 43 β†’ Destroy β”‚ β”‚ +β”‚ β”‚ Result: Always appears as 42! β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ STATIC: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Loop 1: Already exists β†’ Read 42 β†’ Increment β†’ Store 43 β”‚ β”‚ +β”‚ β”‚ Loop 2: Already exists β†’ Read 43 β†’ Increment β†’ Store 44 β”‚ β”‚ +β”‚ β”‚ Loop 3: Already exists β†’ Read 44 β†’ Increment β†’ Store 45 β”‚ β”‚ +β”‚ β”‚ Result: Keeps incrementing! β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The `static` Keyword + +In C, you declare a static variable by adding the `static` keyword: + +```c +uint8_t regular_fav_num = 42; // Regular - recreated each time +static uint8_t static_fav_num = 42; // Static - persists forever +``` + +### Where Do Variables Live in Memory? + +Different types of variables are stored in different memory locations: + +| Variable Type | Storage Location | Lifetime | Example | +| ----------------- | ---------------- | ------------------------- | ------------------------------------ | +| Automatic (local) | Stack | Until function/block ends | `uint8_t x = 5;` | +| Static | Static Storage | Entire program lifetime | `static uint8_t x = 5;` | +| Global | Static Storage | Entire program lifetime | `uint8_t x = 5;` (outside functions) | +| Dynamic (heap) | Heap | Until `free()` is called | `malloc(sizeof(int))` | + +### Stack vs Static Storage vs Heap + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Memory Layout β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” High Address (0x20082000) β”‚ +β”‚ β”‚ STACK β”‚ ← Automatic/local variables β”‚ +β”‚ β”‚ (grows down) β”‚ Created/destroyed per function β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ (free space) β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ HEAP β”‚ ← Dynamic allocation (malloc/free) β”‚ +β”‚ β”‚ (grows up) β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ .bss section β”‚ ← Uninitialized static/global vars β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ .data section β”‚ ← Initialized static/global vars β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Low Address (0x20000000) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Key Point:** Static variables are NOT on the heap! They live in a fixed location in the `.data` section (if initialized) or `.bss` section (if uninitialized). This is different from heap memory which is dynamically allocated at runtime. + +### What Happens with Overflow? + +Since `static_fav_num` is a `uint8_t` (unsigned 8-bit), it can only hold values 0-255. What happens when it reaches 255 and we add 1? + +``` +255 + 1 = 256... but that doesn't fit in 8 bits! +Binary: 11111111 + 1 = 100000000 (9 bits) +The 9th bit is lost, so we get: 00000000 = 0 +``` + +This is called **overflow** or **wrap-around**. The value "wraps" back to 0 and starts counting again! + +--- + +## πŸ“š Part 2: Understanding GPIO Inputs + +### Input vs Output + +So far, we've used GPIO pins as **outputs** to control LEDs. Now we'll learn to use them as **inputs** to read button states! + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ GPIO Direction β”‚ +β”‚ β”‚ +β”‚ OUTPUT (what we've done before): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Pico β”‚ ───────► LED β”‚ +β”‚ β”‚ GPIO 16 β”‚ (We control the LED) β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ INPUT (new this week): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Pico β”‚ ◄─────── Button β”‚ +β”‚ β”‚ GPIO 15 β”‚ (We read the button state) β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Floating Input Problem + +When a GPIO pin is set as an input but nothing is connected, it's called a **floating input**. The voltage on the pin is undefined and can randomly read as HIGH (1) or LOW (0) due to electrical noise. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Floating Input = Random Values! β”‚ +β”‚ β”‚ +β”‚ GPIO Pin (no connection): β”‚ +β”‚ Reading 1: HIGH β”‚ +β”‚ Reading 2: LOW β”‚ +β”‚ Reading 3: HIGH β”‚ +β”‚ Reading 4: HIGH β”‚ +β”‚ Reading 5: LOW β”‚ +β”‚ (Completely unpredictable!) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Pull-Up and Pull-Down Resistors + +To solve the floating input problem, we use **pull resistors**: + +| Resistor Type | Default State | When Button Pressed | +| ------------- | ------------- | ------------------- | +| **Pull-Up** | HIGH (1) | LOW (0) | +| **Pull-Down** | LOW (0) | HIGH (1) | + +The Pico 2 has **internal** pull resistors that you can enable with software - no external components needed! + +> πŸ“– **Datasheet Reference:** Each GPIO pad has configurable pull-up and pull-down resistors in the PADS_BANK0 registers at `0x40038000` (Section 9.8, p. 786). The PUE (pull-up enable) and PDE (pull-down enable) bits are in each GPIO's pad control register. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Pull-Up Resistor (what we're using) β”‚ +β”‚ β”‚ +β”‚ 3.3V β”‚ +β”‚ β”‚ β”‚ +β”‚ β”΄ (internal pull-up resistor) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β–Ί GPIO 15 (reads HIGH normally) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”΄β”€β” β”‚ +β”‚ β”‚BTNβ”‚ ← Button connects GPIO to GND when pressed β”‚ +β”‚ β””β”€β”¬β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ GND β”‚ +β”‚ β”‚ +β”‚ Button NOT pressed: GPIO reads 1 (HIGH) β”‚ +β”‚ Button PRESSED: GPIO reads 0 (LOW) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### GPIO Input Functions + +| Function | Purpose | +| ---------------------------- | --------------------------------------- | +| `gpio_init(pin)` | Initialize a GPIO pin for use | +| `gpio_set_dir(pin, GPIO_IN)` | Set pin as INPUT | +| `gpio_pull_up(pin)` | Enable internal pull-up resistor | +| `gpio_pull_down(pin)` | Enable internal pull-down resistor | +| `gpio_get(pin)` | Read the current state (returns 0 or 1) | + +### The Ternary Operator + +The code uses a **ternary operator** to control the LED based on button state: + +```c +gpio_put(LED_GPIO, pressed ? 0 : 1); +``` + +This is a compact if-else statement: +- If `pressed` is **true (1)**: output `0` (LED OFF... wait, that seems backwards!) +- If `pressed` is **false (0)**: output `1` (LED ON) + +**Why is it inverted?** Because of the pull-up resistor! +- Button **released** β†’ GPIO reads `1` β†’ `pressed = 1` β†’ output `0` β†’ LED OFF +- Button **pressed** β†’ GPIO reads `0` β†’ `pressed = 0` β†’ output `1` β†’ LED ON + +A clearer way to write this: +```c +gpio_put(LED_GPIO, !gpio_get(BUTTON_GPIO)); +``` + +--- + +## πŸ“š Part 3: Understanding Compiler Optimizations + +### Why Does Code Disappear? + +When you compile code, the compiler tries to make it faster and smaller. This is called **optimization**. Sometimes the compiler removes code that it thinks has no effect! + +**Example from our code:** +```c +while (true) { + uint8_t regular_fav_num = 42; // Created + regular_fav_num++; // Incremented to 43 + // But then it's destroyed and recreated as 42 next loop! +} +``` + +The compiler sees that incrementing `regular_fav_num` has no lasting effect (because it's recreated as 42 each loop), so it may **optimize away** the increment operation entirely! + +### Function Inlining + +Sometimes the compiler **inlines** functions, meaning it replaces a function call with the function's code directly. + +**Original code:** +```c +gpio_pull_up(BUTTON_GPIO); +``` + +**What the compiler might do:** +```c +// Instead of calling gpio_pull_up, it calls the underlying function: +gpio_set_pulls(BUTTON_GPIO, true, false); +``` + +This is why when you look for `gpio_pull_up` in the binary, you might find `gpio_set_pulls` instead! + +--- + +## πŸ“š Part 4: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. OpenOCD installed and configured +4. GDB (`arm-none-eabi-gdb`) installed +5. Python installed (for UF2 conversion) +6. A serial monitor (PuTTY, minicom, or screen) +7. A push button connected to GPIO 15 +8. An LED connected to GPIO 16 (or use the breadboard LED) +9. A hex editor (HxD, ImHex, or similar) +10. The sample project: `0x0014_static-variables` + +### Hardware Setup + +Connect your button like this: +- One side of button β†’ GPIO 15 +- Other side of button β†’ GND + +The internal pull-up resistor provides the 3.3V connection, so you only need to connect to GND! + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Breadboard Wiring β”‚ +β”‚ β”‚ +β”‚ Pico 2 β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 15 │────────┐ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 16 │────────┼───► LED (with resistor to GND) β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GND │────────┼───┐ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”΄β”€β” β”‚ β”‚ +β”‚ β”‚BTNβ”‚β”€β”˜ β”‚ +β”‚ β””β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Project Structure + +``` +Embedded-Hacking/ +β”œβ”€β”€ 0x0014_static-variables/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x0014_static-variables.uf2 +β”‚ β”‚ └── 0x0014_static-variables.elf +β”‚ └── 0x0014_static-variables.c +└── uf2conv.py +``` + +--- + +## πŸ”¬ Part 5: Hands-On Tutorial - Static Variables and GPIO Input + +### Step 1: Review the Source Code + +Let's examine the static variables code: + +**File: `0x0014_static-variables.c`** + +```c +#include +#include "pico/stdlib.h" + +int main(void) { + stdio_init_all(); + + const uint BUTTON_GPIO = 15; + const uint LED_GPIO = 16; + bool pressed = 0; + + gpio_init(BUTTON_GPIO); + gpio_set_dir(BUTTON_GPIO, GPIO_IN); + gpio_pull_up(BUTTON_GPIO); + + gpio_init(LED_GPIO); + gpio_set_dir(LED_GPIO, GPIO_OUT); + + while (true) { + uint8_t regular_fav_num = 42; + static uint8_t static_fav_num = 42; + + printf("regular_fav_num: %d\r\n", regular_fav_num); + printf("static_fav_num: %d\r\n", static_fav_num); + + regular_fav_num++; + static_fav_num++; + + pressed = gpio_get(BUTTON_GPIO); + gpio_put(LED_GPIO, pressed ? 0 : 1); + } +} +``` + +**What this code does:** + +1. **Line 6-8:** Defines constants for button (GPIO 15) and LED (GPIO 16) pins +2. **Line 10-12:** Sets up GPIO 15 as input with internal pull-up resistor +3. **Line 14-15:** Sets up GPIO 16 as output for the LED +4. **Line 18-19:** Creates two variables: + - `regular_fav_num` - a normal local variable (recreated each loop) + - `static_fav_num` - a static variable (persists across loops) +5. **Line 21-22:** Prints both values to the serial terminal +6. **Line 24-25:** Increments both values +7. **Line 27-28:** Reads button and controls LED accordingly + +### 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 `0x0014_static-variables.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Open Your Serial Monitor + +Open PuTTY, minicom, or screen and connect to your Pico's serial port. + +**You should see output like this:** + +``` +... +regular_fav_num: 42 +static_fav_num: 42 +regular_fav_num: 42 +static_fav_num: 43 +regular_fav_num: 42 +static_fav_num: 44 +regular_fav_num: 42 +static_fav_num: 45 +... +``` + +**Notice the difference:** +- `regular_fav_num` stays at 42 every time (it's recreated each loop) +- `static_fav_num` increases each time (it persists and remembers its value) + +### Step 4: Test the Button + +Now test the button behavior: +- **Button NOT pressed:** LED should be ON (because of the inverted logic) +- **Button PRESSED:** LED should turn OFF + +Wait... that seems backwards from what you'd expect! That's because of the pull-up resistor and the ternary operator. We'll hack this later to make it more intuitive! + +### Step 5: Watch for Overflow + +Keep the program running and watch `static_fav_num`. After 255, you'll see: + +``` +static_fav_num: 254 +static_fav_num: 255 +static_fav_num: 0 ← Wrapped around! +static_fav_num: 1 +static_fav_num: 2 +... +``` + +This demonstrates unsigned integer overflow! + +--- + +## πŸ”¬ Part 6: 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. + +### Starting 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\0x0014_static-variables.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +### Step 6: Examine Main Function + +Let's examine the main function at its entry point. First, disassemble from the start: + +``` +x/38i 0x10000234 +``` + +You should see output like: + +``` + 0x10000234 <+0>: push {r4, lr} + 0x10000236 <+2>: bl 0x10003014 + 0x1000023a <+6>: movs r0, #15 + 0x1000023c <+8>: bl 0x10000300 + 0x10000240 <+12>: movs r0, #15 + 0x10000242 <+14>: mov.w r3, #0 + 0x10000246 <+18>: mcrr 0, 4, r0, r3, cr4 + 0x1000024a <+22>: movs r2, #0 + 0x1000024c <+24>: movs r1, #1 + 0x1000024e <+26>: bl 0x100002d8 + 0x10000252 <+30>: movs r0, #16 + 0x10000254 <+32>: bl 0x10000300 + 0x10000258 <+36>: movs r3, #16 + 0x1000025a <+38>: mov.w r2, #1 + 0x1000025e <+42>: mcrr 0, 4, r3, r2, cr4 + 0x10000262 <+46>: ldr r4, [pc, #44] @ (0x10000290 ) + 0x10000264 <+48>: movs r1, #42 @ 0x2a + 0x10000266 <+50>: ldr r0, [pc, #44] @ (0x10000294 ) + 0x10000268 <+52>: bl 0x100031a4 <__wrap_printf> + 0x1000026c <+56>: ldrb r1, [r4, #0] + 0x1000026e <+58>: ldr r0, [pc, #40] @ (0x10000298 ) + 0x10000270 <+60>: bl 0x100031a4 <__wrap_printf> + 0x10000274 <+64>: mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 <+68>: ldrb r3, [r4, #0] + 0x1000027a <+70>: movs r2, #16 + 0x1000027c <+72>: adds r3, #1 + 0x1000027e <+74>: strb r3, [r4, #0] + 0x10000280 <+76>: ldr r3, [r1, #4] + 0x10000282 <+78>: ubfx r3, r3, #15, #1 + 0x10000286 <+82>: eor.w r3, r3, #1 + 0x1000028a <+86>: mcrr 0, 4, r2, r3, cr0 + 0x1000028e <+90>: b.n 0x10000264 + 0x10000290 <+92>: lsls r0, r5, #22 + 0x10000292 <+94>: movs r0, #0 + 0x10000294 <+96>: adds r5, #96 @ 0x60 + 0x10000296 <+98>: asrs r0, r0, #32 + 0x10000298 <+100>: adds r5, #120 @ 0x78 + 0x1000029a <+102>: asrs r0, r0, #32 +``` + +### Step 7: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +GDB responds: +``` +Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/0x0014_static-variables/0x0014_static-variables.c, line 5. +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/0x0014_static-variables/0x0014_static-variables.c:5 +5 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. + +### Step 8: Examine the Static Variable Location + +Static variables live at fixed RAM addresses. But how do we find that address? Look at the first instruction in the disassembly from Step 6: + +``` +0x10000262: ldr r4, [pc, #44] @ (0x10000290 ) +``` + +This loads `r4` from the **literal pool** at address `0x10000290`. The literal pool stores constants that are too large for immediate encoding β€” in this case, a 32-bit RAM address. Let's examine what's stored there: + +```gdb +(gdb) x/1wx 0x10000290 +0x10000290 : 0x200005a8 +``` + +That's `0x200005a8` β€” the RAM address of `static_fav_num`! The compiler placed this address in the literal pool because it can't encode a full 32-bit address in a single Thumb instruction. + +> πŸ’‘ **Why did the disassembly at `0x10000290` show `lsls r0, r5, #22` instead?** Because `x/i` (disassemble) interprets raw data as instructions. The bytes `A8 05 00 20` at that address are the little-endian encoding of `0x200005A8`, but GDB's disassembler doesn't know it's data β€” it tries to decode it as a Thumb instruction. Using `x/wx` (examine as word) shows the actual value. + +### Step 9: Step Through the Loop + +Set a breakpoint at the start of the loop and step through: + +```gdb +(gdb) b *0x10000264 +(gdb) c +``` + +Now use `si` (step instruction) to execute one instruction at a time: + +```gdb +(gdb) si +``` + +Watch how the static variable gets loaded (`ldrb`), incremented (`adds`), and stored back (`strb`). + +### Step 10: Examine Register Values + +After stepping to `0x10000262` or later, check the registers: + +```gdb +(gdb) i r +``` + +Pay attention to: +- `r4` β€” Should hold `0x200005a8` (static variable's RAM address, loaded from literal pool) +- `r1` β€” Used for `printf` arguments (holds `42` or the static variable value) +- `r3` β€” Used for load/increment/store of the static variable +- `pc` β€” Program counter (current instruction address) + +### Step 11: Watch the Static Variable Change + +Now that we know the static variable lives at `0x200005a8`, examine it directly: + +```gdb +(gdb) x/1db 0x200005a8 +0x200005a8: 42 +``` + +Step through a full loop iteration (back to `0x10000264`) and re-examine: + +```gdb +(gdb) c +(gdb) x/1db 0x200005a8 +0x200005a8: 43 +``` + +The value incremented from 42 to 43! Each loop iteration, the `adds r3, #1` at `0x1000027c` bumps it by 1, and `strb r3, [r4, #0]` at `0x1000027e` writes it back to RAM. + +### Step 12: Examine GPIO State + +Read the GPIO input register to see the button state: + +```gdb +(gdb) x/1wx 0xd0000004 +``` + +The SIO GPIO input register at `0xd0000004` shows the current state of all GPIO pins. Bit 15 corresponds to our button on GPIO 15. To extract just bit 15: + +```gdb +(gdb) p/x (*(unsigned int *)0xd0000004 >> 15) & 1 +``` + +- Returns `1` when button is **not pressed** (pull-up holds it HIGH) +- Returns `0` when button is **pressed** (connected to GND) + +TRY IT! + +--- + +## πŸ”¬ Part 7: Understanding the Assembly + +Now that we've explored the binary in GDB, let's make sense of the key patterns. + +### Step 13: Analyze the Regular Variable + +In GDB, examine the code at the start of the loop: + +```gdb +(gdb) x/5i 0x10000262 +``` + +Look for this instruction: + +``` +0x10000264: movs r1, #42 @ 0x2a +``` + +This loads the value `0x2a` (42 in decimal) directly into register `r1` for the first `printf` call. + +**Key insight:** The compiler **optimized away** the `regular_fav_num` variable entirely! Since it's always 42 when printed, the compiler just uses the constant `42` directly. The `regular_fav_num++` after the print is also removed because it has no observable effect. + +### Step 14: Analyze the Static Variable + +Examine the static variable operations in the second half of the loop body: + +```gdb +(gdb) x/10i 0x10000274 +``` + +Look for the load-increment-store pattern using `r4` (which holds the static variable's RAM address): + +``` + 0x10000274 : mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + 0x10000280 : ldr r3, [r1, #4] + 0x10000282 : ubfx r3, r3, #15, #1 + 0x10000286 : eor.w r3, r3, #1 + 0x1000028a : mcrr 0, 4, r2, r3, cr0 + 0x1000028e : b.n 0x10000264 +``` + +Note that `r4` was loaded earlier at `0x10000262` via `ldr r4, [pc, #44]` β€” this pulled the static variable's RAM address (`0x200005a8`) from the literal pool at `0x10000290`. + +**Key insight:** The static variable lives at a **fixed RAM address** (`0x200005a8`). It's loaded, incremented, and stored back β€” unlike the regular variable which was optimized away! + +Verify the static variable value which should be `43`: + +```gdb +(gdb) x/1db 0x200005a8 +``` + +### Step 15: Analyze the GPIO Logic + +Examine the GPIO input/output code: + +```gdb +(gdb) x/10i 0x10000274 +``` + +Look for this sequence: + +``` + 0x10000274 : mov.w r1, #3489660928 @ 0xd0000000 + 0x10000278 : ldrb r3, [r4, #0] + 0x1000027a : movs r2, #16 + 0x1000027c : adds r3, #1 + 0x1000027e : strb r3, [r4, #0] + 0x10000280 : ldr r3, [r1, #4] + 0x10000282 : ubfx r3, r3, #15, #1 + 0x10000286 : eor.w r3, r3, #1 + 0x1000028a : mcrr 0, 4, r2, r3, cr0 + 0x1000028e : b.n 0x10000264 +``` + +**Breaking this down:** + +| Address | Instruction | Purpose | +| -------------- | -------------------------- | ---------------------------------------------------- | +| `0x10000274` | `mov.w r1, #0xd0000000` | Load SIO (Single-cycle I/O) base address into `r1` | +| `0x10000278` | `ldrb r3, [r4, #0]` | Load `static_fav_num` from RAM into `r3` | +| `0x1000027a` | `movs r2, #16` | Load LED pin number (16) into `r2` for later | +| `0x1000027c` | `adds r3, #1` | Increment `static_fav_num` by 1 | +| `0x1000027e` | `strb r3, [r4, #0]` | Store incremented value back to RAM | +| `0x10000280` | `ldr r3, [r1, #4]` | Read GPIO input state (SIO_GPIO_IN at offset `0x04`) | +| `0x10000282` | `ubfx r3, r3, #15, #1` | Extract bit 15 (GPIO 15 = button) | +| `0x10000286` | `eor.w r3, r3, #1` | XOR with 1 to invert (implements `? 0 : 1`) | +| `0x1000028a` | `mcrr 0, 4, r2, r3, cr0` | Write `r3` (button) and `r2` (pin 16) to GPIO output | +| `0x1000028e` | `b.n 0x10000264` | Loop back to start (`while (true)`) | + +> πŸ“– **Datasheet Reference:** The SIO block is at `0xd0000000` (Section 2.2, p. 31). GPIO_IN is at SIO offset `0x004` (Section 2.3.1.7, p. 55), returning the state of all GPIOs in a single read. The GPIO coprocessor (`mcrr p0`) provides single-cycle access to SIO GPIO registers (Section 3.7.5, p. 101–104). + +> πŸ’‘ **Notice how the compiler interleaves the static variable increment with the GPIO logic.** It loads the SIO base address (`r1`) *before* doing the increment, and sets up `r2 = 16` (LED pin) in between. This is called **instruction scheduling** β€” the compiler reorders instructions to avoid pipeline stalls while waiting for memory reads. + +### Step 16: Find the Infinite Loop + +The last instruction at `0x1000028e` is already covered in the table above: + +``` +0x1000028e: b.n 0x10000264 +``` + +This is an **unconditional branch** back to `0x10000264` (the `movs r1, #42` at the top of the loop) β€” this is the `while (true)` in our code! There is no `pop` or `bx lr` to return from `main` because the loop never exits. + +--- + +## πŸ”¬ Part 8: Hacking the Binary with 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 live. Trying `set *(char *)0x10000264 = 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 17: 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\0x0014_static-variables\build\` +4. Open `0x0014_static-variables.bin` + +### Step 18: Calculate the File Offset + +The binary is loaded at base address `0x10000000`. To find the file offset of any address: + +``` +file_offset = address - 0x10000000 +``` + +For example: +- Address `0x10000264` β†’ file offset `0x264` (612 in decimal) +- Address `0x10000286` β†’ file offset `0x286` (646 in decimal) + +### Step 19: Hack #1 β€” Change regular_fav_num from 42 to 43 + +From our GDB analysis, we know the instruction at `0x10000264` is: + +``` +movs r1, #0x2a β†’ bytes: 2a 21 +``` + +To change the value from 42 (`0x2a`) to 43 (`0x2b`): + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables\build\0x0014_static-variables.bin` +2. Press **Ctrl+G** (Go to offset) +3. Enter offset: `264` +4. You should see the byte `2A` at this position +5. Change `2A` to `2B` +6. The instruction is now `movs r1, #0x2b` (43 in decimal) + +> πŸ” **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`. + +### Step 20: Hack #2 β€” Invert the Button Logic + +#### Understand the Encoding + +From GDB, we found the `eor.w r3, r3, #1` instruction at `0x10000286` that inverts the button value. Examine the exact bytes: + +```gdb +(gdb) x/4bx 0x10000286 +0x10000286 : 0x83 0xf0 0x01 0x03 +``` + +This is the 32-bit Thumb-2 encoding of `eor.w r3, r3, #1`. The bytes break down as: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ eor.w r3, r3, #1 β†’ bytes: 83 F0 01 03 β”‚ +β”‚ β”‚ +β”‚ Byte 0: 0x83 ─┐ β”‚ +β”‚ Byte 1: 0xF0 β”€β”˜ First halfword (opcode + source register) β”‚ +β”‚ Byte 2: 0x01 ──── Immediate value (#1) ← CHANGE THIS β”‚ +β”‚ Byte 3: 0x03 ──── Destination register (r3) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0` (making XOR do nothing): + +The file offset is `0x10000286 - 0x10000000 = 0x286`. The immediate byte is the 3rd byte of the instruction, so: `0x286 + 2 = 0x288`. + +To change `eor.w r3, r3, #1` to `eor.w r3, r3, #0`: + +1. In HxD, press **Ctrl+G** (Go to offset) +2. Enter offset: `288` (the third byte of the 4-byte instruction) +3. You should see the byte `01` at this position +4. Change `01` to `00` + +> πŸ” **Why offset `0x288` and not `0x286`?** The immediate value `#1` is in the **third byte** of the 4-byte instruction. The instruction starts at file offset `0x286`, so the immediate byte is at `0x286 + 2 = 0x288`. + +Now the logic is permanently changed: +- Button released (input = 1): `1 XOR 0 = 1` β†’ LED **ON** +- Button pressed (input = 0): `0 XOR 0 = 0` β†’ LED **OFF** + +This is the **opposite** of the original behavior! + +### Step 21: Save the Patched Binary + +1. Click **File** β†’ **Save As** +2. Save as `0x0014_static-variables-h.bin` in the build directory +3. Close the hex editor + +--- + +## πŸ”¬ Part 9: Converting and Flashing the Hacked Binary + +### Step 22: Convert to UF2 Format + +Open a terminal and navigate to your project directory: + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0014_static-variables +``` + +Run the conversion command: + +```powershell +python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +**What this command means:** +- `uf2conv.py` = the conversion script (in the parent `Embedded-Hacking-main` directory) +- `--base 0x10000000` = the XIP base address where code runs from +- `--family 0xe48bff59` = the RP2350 family ID +- `--output build\hacked.uf2` = the output filename + +### Step 23: 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 + +### Step 24: Verify the Hacks + +**Check the serial output:** +``` +regular_fav_num: 43 ← Changed from 42! +static_fav_num: 42 +regular_fav_num: 43 +static_fav_num: 43 +... +``` + +**Check the LED behavior:** +- LED should now be **ON by default** (when button is NOT pressed) +- LED should turn **OFF** when you press the button + +πŸŽ‰ **BOOM! We successfully:** +1. Changed the printed value from 42 to 43 +2. Inverted the LED/button logic + +--- + +## πŸ“Š Part 10: Summary and Review + +### What We Accomplished + +1. **Learned about static variables** - How they persist across function calls and loop iterations +2. **Understood memory layout** - Stack vs static storage vs heap +3. **Configured GPIO inputs** - Using pull-up resistors and reading button states +4. **Analyzed compiled code in GDB** - Saw how the compiler optimizes code +5. **Discovered function inlining** - `gpio_pull_up` became `gpio_set_pulls` +6. **Hacked variable values** - Changed 42 to 43 using a hex editor +7. **Inverted GPIO logic** - Made LED behavior opposite + +### Static vs Automatic Variables + +| Aspect | Automatic (Regular) | Static | +| ------------------ | ------------------------ | --------------------------- | +| **Storage** | Stack | Static storage (.data/.bss) | +| **Lifetime** | Block/function scope | Entire program | +| **Initialization** | Every time block entered | Once at program start | +| **Persistence** | Lost when scope exits | Retained between calls | +| **Compiler view** | May be optimized away | Always has memory location | + +### GPIO Input Configuration + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ GPIO Input Setup Steps β”‚ +β”‚ β”‚ +β”‚ 1. gpio_init(pin) - Initialize the pin β”‚ +β”‚ 2. gpio_set_dir(pin, GPIO_IN) - Set as input β”‚ +β”‚ 3. gpio_pull_up(pin) - Enable pull-up β”‚ +β”‚ OR gpio_pull_down(pin) - OR enable pull-down β”‚ +β”‚ 4. gpio_get(pin) - Read the state β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Binary Hacking Workflow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 1. Analyze the binary with GDB β”‚ +β”‚ - Disassemble functions with x/Ni β”‚ +β”‚ - Identify key instructions and addresses β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 2. Understand compiler optimizations β”‚ +β”‚ - Some functions get inlined (gpio_pull_up β†’ gpio_set_pulls)β”‚ +β”‚ - Some variables are optimized away β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 3. Calculate file offsets β”‚ +β”‚ - file_offset = address - 0x10000000 β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 4. Patch the .bin file with a hex editor β”‚ +β”‚ - Open the .bin file in HxD / ImHex β”‚ +β”‚ - Go to the calculated offset β”‚ +β”‚ - Change the target byte(s) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 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 button/LED behavior β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Memory Addresses + +| Address | Description | +| ------------ | ----------------------------------- | +| `0x10000234` | Typical main() entry point | +| `0x10003014` | stdio_init_all() function | +| `0x200005a8` | Static variable storage (example) | +| `0xd0000000` | SIO (Single-cycle I/O) base address | + +--- + +## βœ… Practice Exercises + +### Exercise 1: Change Static Variable Initial Value +The static variable starts at 42. Hack the binary to make it start at 100 instead. + +**Hint:** Find where `DAT_200005a8` is initialized in the .data section. + +### Exercise 2: Make the LED Blink +Instead of responding to button presses, hack the binary to make the LED blink continuously. + +**Hint:** You'll need to change the GPIO output logic to toggle instead of following button state. + +### Exercise 3: Reverse Engineer gpio_set_pulls +Using GDB, disassemble the `gpio_set_pulls` function and figure out what registers it writes to. + +**Hint:** Look for writes to addresses around `0x40038000` (PADS_BANK0). + +### Exercise 4: Add a Second Static Variable +If you had two static variables, where would they be stored in memory? Would they be next to each other? + +**Hint:** Static variables in the same compilation unit are typically placed consecutively in the .data section. + +### Exercise 5: Overflow Faster +The static variable overflows after 255 iterations. Can you hack it to overflow sooner? + +**Hint:** Change the increment from `+1` to `+10` by modifying the `adds r3,#0x1` instruction. + +--- + +## πŸŽ“ Key Takeaways + +1. **Static variables persist** - They keep their value between function calls and loop iterations. + +2. **Static storage β‰  heap** - Static variables are in a fixed location, not dynamically allocated. + +3. **Compilers optimize aggressively** - Regular variables may be optimized away if the compiler sees no effect. + +4. **Function inlining is common** - `gpio_pull_up` becomes `gpio_set_pulls` in the binary. + +5. **Pull-up resistors invert logic** - Button pressed = LOW, button released = HIGH. + +6. **XOR is useful for inverting** - `eor r3,r3,#0x1` flips a bit between 0 and 1. + +7. **Static variables have fixed addresses** - You can find them in the .data section at known RAM addresses. + +8. **Overflow wraps around** - A `uint8_t` at 255 becomes 0 when incremented. + +9. **UBFX extracts bits** - Used to read a single GPIO pin from a register. + +10. **Binary patching is powerful** - Change values and logic without source code! + +--- + +## πŸ“– Glossary + +| Term | Definition | +| --------------------- | ---------------------------------------------------------------- | +| **Automatic** | Variable that's created and destroyed automatically (local vars) | +| **eor/XOR** | Exclusive OR - flips bits where operands differ | +| **Floating Input** | GPIO input with undefined voltage (reads random values) | +| **Function Inlining** | Compiler replaces function call with the function's code | +| **gpio_get** | Function to read the current state of a GPIO pin | +| **Heap** | Memory area for dynamic allocation (malloc/free) | +| **Overflow** | When a value exceeds its type's maximum and wraps around | +| **Pull-Down** | Resistor that holds a pin LOW when nothing drives it | +| **Pull-Up** | Resistor that holds a pin HIGH when nothing drives it | +| **SIO** | Single-cycle I/O - fast GPIO access on RP2350 | +| **Stack** | Memory area for local variables and function call frames | +| **Static Storage** | Fixed memory area for static and global variables | +| **Static Variable** | Variable declared with `static` that persists across calls | +| **Ternary Operator** | `condition ? value_if_true : value_if_false` | +| **UBFX** | Unsigned Bit Field Extract - extracts bits from a register | +| **Varargs** | Variable arguments - functions that take unlimited parameters | + +--- + +## πŸ”— Additional Resources + +### GPIO Input Reference + +| Function | Purpose | +| ----------------------------- | -------------------------- | +| `gpio_init(pin)` | Initialize GPIO pin | +| `gpio_set_dir(pin, GPIO_IN)` | Set pin as input | +| `gpio_set_dir(pin, GPIO_OUT)` | Set pin as output | +| `gpio_pull_up(pin)` | Enable internal pull-up | +| `gpio_pull_down(pin)` | Enable internal pull-down | +| `gpio_disable_pulls(pin)` | Disable all pull resistors | +| `gpio_get(pin)` | Read pin state (0 or 1) | +| `gpio_put(pin, value)` | Set pin output (0 or 1) | + +### Key Assembly Instructions + +| Instruction | Description | +| ----------------------- | -------------------------------------------- | +| `movs rN, #imm` | Move immediate value to register | +| `ldrb rN, [rM, #off]` | Load byte from memory | +| `strb rN, [rM, #off]` | Store byte to memory | +| `adds rN, #imm` | Add immediate value to register | +| `eor rN, rM, #imm` | Exclusive OR (XOR) with immediate | +| `ubfx rN, rM, #lsb, #w` | Extract unsigned bit field | +| `mcrr p0, ...` | Move to coprocessor (GPIO control on RP2350) | +| `b LABEL` | Unconditional branch (jump) | + +### Memory Map Quick Reference + +| Address Range | Description | +| --------------------- | ------------------------------ | +| `0x10000000` | XIP Flash (code execution) | +| `0x20000000-200005xx` | SRAM (.data section) | +| `0x20082000` | Stack top (initial SP) | +| `0x40038000` | PADS_BANK0 (pad configuration) | +| `0xd0000000` | SIO (single-cycle I/O) | + +--- + +**Remember:** Static variables are your friends when you need to remember values across function calls. But they also make your program's behavior more complex to analyze - which is exactly why we practice reverse engineering! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG00.svg b/WEEKS/WEEK06/slides/WEEK06-IMG00.svg new file mode 100644 index 0000000..0feedec --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 06 + + +Static Variables in Embedded Systems: +Debugging and Hacking Static Variables +w/ GPIO Input Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG01.svg b/WEEKS/WEEK06/slides/WEEK06-IMG01.svg new file mode 100644 index 0000000..45e7fee --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG01.svg @@ -0,0 +1,74 @@ + + + + + +Static vs Regular Vars +Persistence across loop iterations + + + +Regular (auto) + + +Loop 1: 42 -> 43 -> destroy + + +Loop 2: 42 -> 43 -> destroy + + +Loop 3: 42 -> 43 -> destroy + +Always prints: +42 + + + +Static + + +Loop 1: 42 -> 43 (kept!) + + +Loop 2: 43 -> 44 (kept!) + + +Loop 3: 44 -> 45 (kept!) + +Keeps incrementing: +42,43,44... + + + +Declaration Syntax + + +uint8_t reg = 42; +Recreated each iteration + + +static uint8_t s = 42; +Persists for program life + + + +uint8_t Overflow + +255 + 1 = +0 +(wraps around!) + +Binary: 11111111 + 1 = 100000000 (9 bits) +Only 8 bits kept: 00000000 = 0 + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG02.svg b/WEEKS/WEEK06/slides/WEEK06-IMG02.svg new file mode 100644 index 0000000..dca4829 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG02.svg @@ -0,0 +1,93 @@ + + + + + +Memory Layout +Where variables live in RAM + + + +RP2350 SRAM Map + + + +STACK (grows down) +Local/auto variables + +0x20082000 + + + +(free space) + + + +HEAP (grows up) +malloc / free + + + +.bss section +Uninit static/global + + + +.data section +Initialized static/global + +0x20000000 + +Static vars: .data (init) +Static vars: .bss (uninit) + + + +Variable Storage + +Type +Location + + + +Automatic +Stack + +Static +.data / .bss + +Global +.data / .bss + +Dynamic +Heap + +Static vars are NOT on heap! +Fixed location, set at compile +time. Lives entire program. +Example: +static_fav_num @ 0x200005A8 + + + +Key Insight + +Stack vars: +Created + destroyed +each function call + +Static vars: +Fixed RAM address +persist entire runtime + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG03.svg b/WEEKS/WEEK06/slides/WEEK06-IMG03.svg new file mode 100644 index 0000000..279854a --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG03.svg @@ -0,0 +1,93 @@ + + + + + +GPIO Input Basics +Reading buttons with the RP2350 + + + +OUTPUT (before) + + +Pico GPIO 16 +--> +LED + +We CONTROL the LED +gpio_put(pin, value) + + +INPUT (new!) + + +Pico GPIO 15 +<-- +BTN + +We READ button state +gpio_get(pin) + + + +Floating Input + +No connection = +RANDOM values! + +Read 1: HIGH +Read 2: LOW +Read 3: HIGH +Read 4: HIGH +??? + + + +Pull Resistors + +Type +Default +Pressed + + + +Pull-Up +HIGH(1) +LOW(0) + +Pull-Down +LOW(0) +HIGH(1) + +Pico 2 has internal pull resistors! + + + +GPIO Input Functions + +gpio_init(pin) +Initialize pin + +gpio_set_dir(pin, GPIO_IN) +Set as input + +gpio_pull_up(pin) +Enable pull-up + +gpio_get(pin) +Read state (0 or 1) + +No external resistor needed -- internal pull-up! + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG04.svg b/WEEKS/WEEK06/slides/WEEK06-IMG04.svg new file mode 100644 index 0000000..4ab0ae6 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG04.svg @@ -0,0 +1,99 @@ + + + + + +Pull-Up Resistor +Internal pull-up on GPIO 15 + + + +Circuit + +3.3V + + + +pull-up R + + + +GPIO 15 + + + + +BTN + + +GND + + + +Button Logic + +State +GPIO +LED + + + +Released +HIGH (1) +OFF + +Pressed +LOW (0) +ON + +Inverted! Pull-up = backwards + + + +Ternary Operator + + +gpio_put(LED, pressed?0:1); + +pressed=1 -> LED OFF (inverted!) + + + +Hardware Wiring + + +Pico 2 +GPIO 15 +GPIO 16 +GND +BOOTSEL + +--> +--> +--> + + +Components +Button (one leg) +LED + resistor +Button (other leg) +Hold to flash UF2 + + +Key Point +No external +resistor needed! +Internal pull-up +handles it all + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG05.svg b/WEEKS/WEEK06/slides/WEEK06-IMG05.svg new file mode 100644 index 0000000..b612ee1 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG05.svg @@ -0,0 +1,83 @@ + + + + + +Source Code +0x0014_static-variables.c + + + +main() + + +int main(void) { +stdio_init_all(); +// GPIO setup +gpio_init(15); +gpio_set_dir(15, GPIO_IN); +gpio_pull_up(15); +gpio_init(16); +gpio_set_dir(16, GPIO_OUT); +while (true) { +uint8_t reg = 42; +static uint8_t s = 42; +printf(... reg, s); +reg++; s++; +} +} + + + +GPIO Setup + +Pin 15: +Input +Pull-up enabled + +Pin 16: +Output +LED control + + + +Serial Output + + +reg: 42 +s: 42 +reg: 42 +s: 43 + +reg always 42, s grows + + + +Button Logic + +pressed = gpio_get(15); + + + +Key Behaviors + + +reg: always 42 + + +s: 42,43,44... + + +wraps at 255->0 + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG06.svg b/WEEKS/WEEK06/slides/WEEK06-IMG06.svg new file mode 100644 index 0000000..a0919c3 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG06.svg @@ -0,0 +1,65 @@ + + + + + +Compiler Optimizations +What the compiler does to your code + + + +Optimization 1: Dead Code Removal + + +Your code: +uint8_t reg = 42; +reg++; +// No lasting effect! + + +Compiler output: +movs r1, #42 +// reg++ is GONE +// Uses constant 42 directly + + + +Optimization 2: Function Inlining + + +Your code: +gpio_pull_up(15); +// Simple wrapper func + + +Compiler output: +gpio_set_pulls(15,1,0); +// Inlined to real func + + + +Optimization 3: Scheduling + +Compiler reorders instructions +to avoid pipeline stalls: + + +Load SIO base + + +Increment s + + +Read GPIO + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG07.svg b/WEEKS/WEEK06/slides/WEEK06-IMG07.svg new file mode 100644 index 0000000..c359080 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG07.svg @@ -0,0 +1,90 @@ + + + + + +Assembly Analysis +Key instructions in main() loop + + + +Loop Body (0x10000264) + + +movs r1, #0x2a +; reg=42 +bl __wrap_printf +; print reg +ldrb r1, [r4] +; load static +bl __wrap_printf +; print s +mov.w r1, #0xd0000000 +; SIO base +ldrb r3, [r4] +; reload s +adds r3, #1 +; s++ +strb r3, [r4] +; store s +ldr r3, [r1, #4] +; read GPIO +ubfx r3, r3, #15, #1 +; bit 15 +eor.w r3, r3, #1 +; invert +mcrr 0, 4, r2, r3, cr0 +; GPIO out +b.n 0x10000264 +; loop + + + +Key Registers + +r1: +printf arg +r3: +temp / static +r4: +0x200005a8 +(static var addr) +r2: +LED pin (16) + + + +Key Patterns + +ldrb/adds/strb +Load-increment-store +(static var update) + +ubfx #15, #1 +Extract GPIO bit 15 + +eor.w #1 +Inverts (? 0 : 1) + + + +Infinite Loop + + +b.n 0x10000264 +; while(true) + +No pop/bx lr +main() never returns + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG08.svg b/WEEKS/WEEK06/slides/WEEK06-IMG08.svg new file mode 100644 index 0000000..702d322 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG08.svg @@ -0,0 +1,75 @@ + + + + + +GDB: Static Variable +Finding static_fav_num in RAM + + + +Literal Pool Lookup + + +ldr r4, [pc, #44] +@ 0x10000290 + +Examine literal pool: + +x/1wx 0x10000290 = 0x200005A8 + +r4 now holds the +RAM address of +static_fav_num! + + + +Read Value + + +x/1db 0x200005a8 = 42 + +After one loop iteration: + +x/1db 0x200005a8 = 43 + +It incremented! Persists in RAM. + + + +Disasm Gotcha + +x/i 0x10000290 shows: + + +lsls r0, r5, #22 + +GDB decodes DATA as code! +Bytes A8 05 00 20 = 0x200005A8 +Use x/wx not x/i for data + + + +GPIO Input Register + +Read button state in GDB: + + +p/x (*(uint*)0xd0000004 >> 15) & 1 + +Returns 1: +not pressed (pull-up) +Returns 0: +button pressed + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG09.svg b/WEEKS/WEEK06/slides/WEEK06-IMG09.svg new file mode 100644 index 0000000..c603290 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG09.svg @@ -0,0 +1,63 @@ + + + + + +Hacking the Binary +Two patches with a hex editor + + + +File Offset Formula +offset = address - 0x10000000 +Example: 0x10000264 -> 0x264 + + + +Hack 1: Change 42 to 43 + +Target: movs r1, #0x2a +at address 0x10000264 (offset 0x264) + + +Before: 2A 21 +movs r1, #42 + + +After: 2B 21 +movs r1, #43 + +Thumb encoding: imm8 byte first, opcode 0x21 second + + + +Hack 2: Invert Button Logic + +Target: eor.w r3, r3, #1 +at address 0x10000286 (offset 0x286) + + +Before: 83 F0 01 03 + + +After: 83 F0 00 03 + +Byte at offset 0x288: +01 +-> +00 + +XOR with 0 = no invert +LED now ON by default, OFF when pressed + \ No newline at end of file diff --git a/WEEKS/WEEK06/slides/WEEK06-IMG10.svg b/WEEKS/WEEK06/slides/WEEK06-IMG10.svg new file mode 100644 index 0000000..f88fa45 --- /dev/null +++ b/WEEKS/WEEK06/slides/WEEK06-IMG10.svg @@ -0,0 +1,112 @@ + + + + + +Static Vars & GPIO Input +Static variables, GPIO input, hacking + + + +Static vs Auto + +Aspect +Auto +Static + + + +Where +Stack +.data + +Life +Scope +Forever + +Init +Every +Once + +Keeps? +No +Yes + +Optimized? +Often +In RAM + +Compiler may remove auto vars + + + +GPIO Input Setup + +1. +gpio_init(pin) + +2. +gpio_set_dir(pin, GPIO_IN) + +3. +gpio_pull_up(pin) + +4. +gpio_get(pin) + +Pull-up: released=HIGH +pressed=LOW (inverted!) +Internal R, no hardware + + + +Key Instructions + +ubfx r3,r3,#15,#1 +Extract single GPIO bit + +eor.w r3,r3,#1 +XOR to invert logic + +b.n 0x10000264 + + + +Hacking Workflow + +1. +Analyze in GDB + +2. +Calculate offset + +3. +Patch .bin in HxD + +4. +uf2conv.py + flash + + + +Takeaways + + +42 -> 43 (1 byte) + + +XOR 1->0 (invert) + + +offset = addr - base + \ No newline at end of file diff --git a/WEEKS/WEEK07/WEEK07-01-S.md b/WEEKS/WEEK07/WEEK07-01-S.md new file mode 100644 index 0000000..6c8de05 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-01-S.md @@ -0,0 +1,91 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 7 +Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +### Non-Credit Practice Exercise 1 Solution: Change Both LCD Lines + +#### Answers + +##### String Locations in Flash + +| String | Address | File Offset | Length (bytes) | Hex Encoding | +|---------------|--------------|-------------|----------------|---------------------------------------------------| +| "Reverse" | 0x10003ee8 | 0x3EE8 | 8 (7 + null) | 52 65 76 65 72 73 65 00 | +| "Engineering" | 0x10003ef0 | 0x3EF0 | 12 (11 + null) | 45 6E 67 69 6E 65 65 72 69 6E 67 00 | + +##### Line 1 Patch: "Reverse" β†’ "Exploit" + +| Character | Hex | +|-----------|--------| +| E | 0x45 | +| x | 0x78 | +| p | 0x70 | +| l | 0x6c | +| o | 0x6f | +| i | 0x69 | +| t | 0x74 | +| \0 | 0x00 | + +``` +Offset 0x3EE8: +Before: 52 65 76 65 72 73 65 00 ("Reverse") +After: 45 78 70 6C 6F 69 74 00 ("Exploit") +``` + +##### Line 2 Patch: "Engineering" β†’ "Hacking!!!!" + +| Character | Hex | +|-----------|--------| +| H | 0x48 | +| a | 0x61 | +| c | 0x63 | +| k | 0x6b | +| i | 0x69 | +| n | 0x6e | +| g | 0x67 | +| ! | 0x21 | +| ! | 0x21 | +| ! | 0x21 | +| ! | 0x21 | +| \0 | 0x00 | + +``` +Offset 0x3EF0: +Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering") +After: 48 61 63 6B 69 6E 67 21 21 21 21 00 ("Hacking!!!!") +``` + +##### Conversion and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants +python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### LCD Verification + +``` +Line 1: Exploit +Line 2: Hacking!!!! +``` + +#### Reflection Answers + +1. **Why must the replacement string be the same length (or shorter) as the original? What specific data would you corrupt if you used a longer string?** + Strings are stored consecutively in the `.rodata` section. "Reverse" occupies 8 bytes starting at `0x10003ee8` and "Engineering" starts immediately at `0x10003ef0`. If the replacement string is longer than 8 bytes, the extra bytes would overwrite the beginning of "Engineering" (or whatever data follows). The `.rodata` section has no gapsβ€”it's a packed sequence of constants, format strings, and other read-only data. Corrupting adjacent data could break LCD line 2, crash `printf` format strings, or cause undefined behavior. + +2. **The two strings are stored only 8 bytes apart (0x3EE8 to 0x3EF0). "Reverse" is 7 characters + null = 8 bytes. What would happen if you patched "Reverse" with "Reversal" (8 characters + null = 9 bytes)?** + "Reversal" needs 9 bytes (8 chars + null terminator). The 9th byte (the `0x00` null terminator) would be written to address `0x10003ef0`, which is the first byte of "Engineering" β€” the letter 'E' (`0x45`). This would overwrite 'E' with `0x00`, turning "Engineering" into an empty string. The LCD would display "Reversal" on line 1 and nothing on line 2, because `lcd_puts` would see a null terminator immediately at the start of the second string. + +3. **If you wanted the LCD to display "Hello" on line 1 (5 characters instead of 7), what would you put in the remaining 2 bytes plus null? Write out the full 8-byte hex sequence.** + "Hello" = 5 characters, followed by the null terminator and 2 padding null bytes: + ``` + 48 65 6C 6C 6F 00 00 00 + H e l l o \0 \0 \0 + ``` + The first `0x00` at position 5 terminates the string. The remaining two `0x00` bytes are padding that fills the original 8-byte allocation. These padding bytes are never read by `lcd_puts` because it stops at the first null terminator. + +4. **Could you change the LCD to display nothing on line 1 by patching just one byte? Which byte and what value?** + Yes. Change the first byte at offset `0x3EE8` from `0x52` ('R') to `0x00` (null). This makes the string start with a null terminator, so `lcd_puts` sees an empty string and displays nothing. Only one byte needs to change: the byte at file offset `0x3EE8`, from `0x52` to `0x00`. diff --git a/WEEKS/WEEK07/WEEK07-01.md b/WEEKS/WEEK07/WEEK07-01.md new file mode 100644 index 0000000..ade4e69 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-01.md @@ -0,0 +1,205 @@ +# 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 diff --git a/WEEKS/WEEK07/WEEK07-02-S.md b/WEEKS/WEEK07/WEEK07-02-S.md new file mode 100644 index 0000000..f288ca8 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-02-S.md @@ -0,0 +1,68 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 7 +Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +### Non-Credit Practice Exercise 2 Solution: Find All String Literals in the Binary + +#### Answers + +##### String Catalog + +| Address | File Offset | String Content | Length | Purpose | +|---------------|-------------|--------------------------|--------|-----------------------------| +| `0x10003ee8` | `0x3EE8` | "Reverse" | 8 | LCD line 1 text | +| `0x10003ef0` | `0x3EF0` | "Engineering" | 12 | LCD line 2 text | +| `0x10003efc` | `0x3EFC` | "FAV_NUM: %d\r\n" | 16 | printf format string | +| `0x10003f0c` | `0x3F0C` | "OTHER_FAV_NUM: %d\r\n" | 22 | printf format string | +| Various | Various | SDK panic/assert strings | Varies | Pico SDK internal messages | +| Various | Various | Source file paths | Varies | SDK debug/assert references | + +##### GDB String Search Commands + +```gdb +(gdb) x/s 0x10003ee8 +0x10003ee8: "Reverse" + +(gdb) x/s 0x10003ef0 +0x10003ef0: "Engineering" + +(gdb) x/s 0x10003efc +0x10003efc: "FAV_NUM: %d\r\n" + +(gdb) x/s 0x10003f0c +0x10003f0c: "OTHER_FAV_NUM: %d\r\n" +``` + +##### Scanning for Strings + +```gdb +(gdb) x/20s 0x10003e00 +(gdb) x/50s 0x10003d00 +``` + +##### Literal Pool Reference + +From the literal pool at `0x100002a4`: + +| Pool Address | Value | String It Points To | +|----------------|---------------|---------------------------| +| `0x100002ac` | `0x10003EE8` | "Reverse" | +| `0x100002b0` | `0x10003EF0` | "Engineering" | +| `0x100002b4` | `0x10003EFC` | "FAV_NUM: %d\r\n" | +| `0x100002b8` | `0x10003F0C` | "OTHER_FAV_NUM: %d\r\n" | + +#### Reflection Answers + +1. **How many distinct strings did you find? Were any of them surprising or unexpected?** + At minimum 4 application-level strings: "Reverse", "Engineering", "FAV_NUM: %d\r\n", and "OTHER_FAV_NUM: %d\r\n". Beyond these, the Pico SDK embeds additional strings β€” panic handler messages, assert failure messages, and source file path strings used for debug output. The SDK strings are surprising because they reveal internal implementation details: file paths expose the build environment directory structure, and error messages reveal which SDK functions have built-in error checking. A reverse engineer can learn the SDK version and build configuration just from these strings. + +2. **Why are strings so valuable to a reverse engineer? What can an attacker learn about a program just from its strings?** + Strings are high-entropy human-readable data that reveals program behavior without reading assembly. An attacker can learn: what the program displays or communicates (LCD messages, serial output), what libraries it uses (SDK error messages), how it handles errors (panic/assert strings), what data formats it processes (`printf` format strings with `%d`, `%s`, `%f`), network endpoints or credentials (URLs, passwords, API keys), the build environment (file paths), and the overall purpose of the firmware. Strings are often the first thing a reverse engineer examines in an unknown binary. + +3. **What technique could a developer use to make strings harder to find in a binary? (Think about what the strings look like in memory.)** + String encryption/obfuscation: encrypt all string literals at compile time using XOR, AES, or a custom cipher, and decrypt them into a RAM buffer only when needed at runtime. This way, scanning the binary with `strings` or a hex editor reveals only ciphertext β€” random-looking bytes instead of readable text. Other techniques include: splitting strings across multiple locations and assembling them at runtime, using character arrays initialized by code rather than string literals, replacing strings with numeric lookup indices into an encrypted table, or using compile-time obfuscation tools that automatically transform string constants. + +4. **The printf format strings contain \r\n. In the binary, these appear as two bytes: 0x0D 0x0A. Why two bytes instead of the four characters \, r, \, n?** + The C compiler processes escape sequences during compilation. In source code, `\r` is written as two characters (backslash + r), but the compiler converts it to a single byte: `0x0D` (carriage return, ASCII 13). Similarly, `\n` becomes `0x0A` (line feed, ASCII 10). These are **control characters** β€” non-printable ASCII codes that control terminal behavior. The backslash notation is just a human-readable way to represent these bytes in source code. By the time the string reaches the binary, all escape sequences have been resolved to their single-byte equivalents. diff --git a/WEEKS/WEEK07/WEEK07-02.md b/WEEKS/WEEK07/WEEK07-02.md new file mode 100644 index 0000000..9a3224c --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-02.md @@ -0,0 +1,157 @@ +# 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? diff --git a/WEEKS/WEEK07/WEEK07-03-S.md b/WEEKS/WEEK07/WEEK07-03-S.md new file mode 100644 index 0000000..b3af2d1 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-03-S.md @@ -0,0 +1,91 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 7 +Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +### Non-Credit Practice Exercise 3 Solution: Trace the IΒ²C Struct Pointer Chain + +#### Answers + +##### Complete Pointer Chain + +``` +I2C_PORT (source macro: #define I2C_PORT i2c1) + ↓ +i2c1 (SDK macro: #define i2c1 (&i2c1_inst)) + ↓ +&i2c1_inst = 0x2000062c (SRAM address of i2c_inst_t struct) + ↓ +i2c1_inst.hw = 0x40098000 (pointer to IΒ²C1 hardware register base) + ↓ +IΒ²C1 Hardware Registers (memory-mapped I/O silicon) + +-- IC_CON at 0x40098000 + +-- IC_TAR at 0x40098004 + +-- IC_SAR at 0x40098008 + +-- IC_DATA_CMD at 0x40098010 +``` + +##### Literal Pool Load + +```gdb +(gdb) x/6wx 0x100002a4 +0x100002a4: 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0 +0x100002b4: 0x10003efc 0x10003f0c +``` + +The value `0x2000062c` at pool address `0x100002a8` is loaded into `r0` by a `ldr r0, [pc, #offset]` instruction before the `bl i2c_init` call. + +##### i2c1_inst Struct in SRAM + +```gdb +(gdb) x/2wx 0x2000062c +0x2000062c : 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. diff --git a/WEEKS/WEEK07/WEEK07-03.md b/WEEKS/WEEK07/WEEK07-03.md new file mode 100644 index 0000000..6f0cc58 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-03.md @@ -0,0 +1,206 @@ +# 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 * +(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? diff --git a/WEEKS/WEEK07/WEEK07-04-S.md b/WEEKS/WEEK07/WEEK07-04-S.md new file mode 100644 index 0000000..fb7e7ad --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-04-S.md @@ -0,0 +1,96 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 7 +Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +### Non-Credit Practice Exercise 4 Solution: Display Your Own Custom Message on the LCD + +#### Answers + +##### String Constraints + +| Line | Original | Address | File Offset | Max Chars | Allocated Bytes | +|------|--------------|-------------|-------------|-----------|-----------------| +| 1 | "Reverse" | 0x10003ee8 | 0x3EE8 | 7 | 8 | +| 2 | "Engineering" | 0x10003ef0 | 0x3EF0 | 11 | 12 | + +##### Example Patch: "Hello!!" and "World!!" + +**Line 1: "Hello!!" (7 characters β€” exact fit)** + +| Character | Hex | +|-----------|--------| +| H | 0x48 | +| e | 0x65 | +| l | 0x6C | +| l | 0x6C | +| o | 0x6F | +| ! | 0x21 | +| ! | 0x21 | +| \0 | 0x00 | + +``` +Offset 0x3EE8: +Before: 52 65 76 65 72 73 65 00 ("Reverse") +After: 48 65 6C 6C 6F 21 21 00 ("Hello!!") +``` + +**Line 2: "World!!" (7 characters β€” needs 5 bytes of null padding)** + +| Character | Hex | +|-----------|--------| +| W | 0x57 | +| o | 0x6F | +| r | 0x72 | +| l | 0x6C | +| d | 0x64 | +| ! | 0x21 | +| ! | 0x21 | +| \0 | 0x00 | +| \0 (pad) | 0x00 | +| \0 (pad) | 0x00 | +| \0 (pad) | 0x00 | +| \0 (pad) | 0x00 | + +``` +Offset 0x3EF0: +Before: 45 6E 67 69 6E 65 65 72 69 6E 67 00 ("Engineering") +After: 57 6F 72 6C 64 21 21 00 00 00 00 00 ("World!!") +``` + +##### Example Patch: Short String "Hi" + +**Line 1: "Hi" (2 characters β€” needs 5 bytes of null padding)** + +``` +Offset 0x3EE8: +Before: 52 65 76 65 72 73 65 00 ("Reverse") +After: 48 69 00 00 00 00 00 00 ("Hi") +``` + +##### Conversion and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0017_constants +python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +#### Reflection Answers + +1. **You padded short strings with 0x00 null bytes. Would it also work to pad with 0x20 (space characters)? What would be the difference on the LCD display?** + Both approaches produce valid strings, but the display differs. With `0x00` padding, the string terminates at the first null byte β€” `lcd_puts` stops reading there, and the remaining bytes are ignored. The LCD shows only your text. With `0x20` (space) padding, the spaces become part of the string β€” `lcd_puts` sends them to the LCD as visible blank characters. The LCD would show your text followed by trailing spaces. Functionally both work, but `0x00` padding is cleaner because the string length matches your intended text, and the LCD positions after your text remain in whatever state the LCD controller initialized them to (typically blank anyway). + +2. **The LCD is a 1602 (16 columns Γ— 2 rows). What would happen if you could somehow put a 20-character string in memory? Would the LCD display all 20, or only the first 16?** + The LCD would display only the first 16 characters in the visible area. The HD44780 controller (used in 1602 LCD modules) has 40 bytes of DDRAM per line, so characters 17-20 would be written to DDRAM but are beyond the visible 16-column window. They would only become visible if you issued a display shift command to scroll the view. The `lcd_puts` function writes all characters to the controller regardless of line length β€” it has no built-in truncation. The 16-character limit is a physical display constraint, not a software one. + +3. **If you wanted to combine the string hacks from Exercise 1 (changing both LCD lines) AND a hypothetical numeric hack (e.g., changing the movs r1, #42 encoding at offset 0x28E), could you do all patches in a single .bin file? What offsets would you need to modify?** + Yes, all patches can be applied to the same `.bin` file since they are at non-overlapping offsets. The three patch locations are: + - **Offset 0x28E**: FAV_NUM β€” change `movs r1, #42` immediate byte from `0x2A` to desired value (1 byte) + - **Offset 0x3EE8**: LCD line 1 β€” replace the 8-byte "Reverse" string + - **Offset 0x3EF0**: LCD line 2 β€” replace the 12-byte "Engineering" string + + Each patch modifies a different region of the binary, so they are completely independent. You could also patch the `movw r1, #1337` instruction at offset `0x298` (the imm8 byte of the OTHER_FAV_NUM encoding) for a fourth independent patch. + +4. **Besides LCD text, what other strings could you patch in a real-world embedded device to change its behavior? Think about Wi-Fi SSIDs, Bluetooth device names, HTTP headers, etc.** + Real-world embedded devices contain many patchable strings: **Wi-Fi SSIDs** and **passwords** (change what network the device connects to), **Bluetooth device names** (change how it appears during pairing), **HTTP/HTTPS URLs** (redirect API calls to a different server), **MQTT broker addresses** (redirect IoT telemetry), **DNS hostnames**, **firmware version strings** (spoof version for update bypass), **serial number formats**, **command-line interface prompts**, **error and debug messages** (hide forensic evidence), **TLS/SSL certificate fields**, **NTP server addresses** (manipulate time synchronization), and **authentication tokens or API keys**. String patching is one of the most practical firmware modification techniques because it's simple to execute and can dramatically change device behavior. diff --git a/WEEKS/WEEK07/WEEK07-04.md b/WEEKS/WEEK07/WEEK07-04.md new file mode 100644 index 0000000..5339d50 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07-04.md @@ -0,0 +1,148 @@ +# 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)? diff --git a/WEEKS/WEEK07/WEEK07-SLIDES.pdf b/WEEKS/WEEK07/WEEK07-SLIDES.pdf new file mode 100644 index 0000000..3f9666f Binary files /dev/null and b/WEEKS/WEEK07/WEEK07-SLIDES.pdf differ diff --git a/WEEKS/WEEK07/WEEK07.md b/WEEKS/WEEK07/WEEK07.md new file mode 100644 index 0000000..4e91bd6 --- /dev/null +++ b/WEEKS/WEEK07/WEEK07.md @@ -0,0 +1,1107 @@ +ο»Ώ# Week 7: Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics + +## 🎯 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 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 +- Patch LCD display text without access to source code + +--- + +## πŸ“š Part 1: Understanding Constants in C + +### Two Types of Constants + +In C, there are two ways to create values that shouldn't change: + +| Type | Syntax | Where It Lives | When Resolved | +| ----------- | ----------------------- | ------------------ | ------------- | +| **#define** | `#define FAV_NUM 42` | Nowhere (replaced) | Compile time | +| **const** | `const int NUM = 1337;` | Flash (.rodata) | Runtime | + +### Preprocessor Macros (#define) + +A **preprocessor macro** is a text replacement that happens BEFORE your code is compiled: + +```c +#define FAV_NUM 42 + +printf("Value: %d", FAV_NUM); +// Becomes: printf("Value: %d", 42); +``` + +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. β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Const Variables + +A **const variable** is an actual variable stored in memory, but marked as read-only: + +```c +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. β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Comparison: #define vs const + +| Feature | #define | const | +| -------------------- | ------------------------ | ---------------------------- | +| **Type checking** | None (just text) | Yes (compiler enforced) | +| **Memory usage** | None (inlined) | Uses flash space | +| **Debugger visible** | No | Yes (with symbols) | +| **Can take address** | No (`&FAV_NUM` fails) | Yes (`&OTHER_FAV_NUM` works) | +| **Scope** | Global (from definition) | Normal C scoping rules | + +--- + +## πŸ“š Part 2: Understanding IΒ²C Communication + +### What is IΒ²C? + +**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! + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ 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...) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Two IΒ²C Wires + +| Wire | Name | Purpose | +| ------- | ------------ | ------------------------------------ | +| **SDA** | Serial Data | Carries the actual data bits | +| **SCL** | Serial Clock | Timing signal that synchronizes data | + +### 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. + +The Pico 2 has internal pull-ups that we can enable with `gpio_pull_up()`. + +### IΒ²C Addresses + +Every IΒ²C device has a unique **7-bit address**. Common addresses: + +| Device Type | Typical Address | +| --------------------- | ---------------- | +| 1602 LCD with PCF8574 | `0x27` or `0x3F` | +| Temperature sensor | `0x48` | +| EEPROM | `0x50` | +| Real-time clock | `0x68` | + +### IΒ²C 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 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 3: Understanding C Structs + +### What is a Struct? + +A **struct** (short for "structure") is a way to group related variables together under one name. Think of it like a form with multiple fields: + +```c +// A struct definition - like a template +struct student { + char name[50]; + int age; + float gpa; +}; + +// Creating a variable of this struct type +struct student alice = {"Alice", 16, 3.8}; +``` + +### Why Use Structs? + +Instead of passing many separate variables: +```c +void print_student(char *name, int age, float gpa); // Messy! +``` + +You pass one struct: +```c +void print_student(struct student s); // Clean! +``` + +### The typedef Keyword + +Writing `struct student` everywhere is tedious. The `typedef` keyword creates an alias: + +```c +typedef struct student student_t; + +// Now you can write: +student_t alice; // Instead of: struct student alice; +``` + +### Forward Declaration + +Sometimes you need to tell the compiler "this struct exists" before defining it: + +```c +typedef struct i2c_inst i2c_inst_t; // Forward declaration + alias + +// Later, the full definition: +struct i2c_inst { + i2c_hw_t *hw; + bool restart_on_next; +}; +``` + +--- + +## πŸ“š Part 4: Understanding the Pico SDK's IΒ²C Structs + +### The i2c_inst_t Struct + +The Pico SDK uses a struct to represent each IΒ²C controller: + +```c +struct i2c_inst { + i2c_hw_t *hw; // Pointer to hardware registers + bool restart_on_next; // SDK internal flag +}; +``` + +**What each member means:** + +| Member | Type | Purpose | +| ----------------- | ------------ | ---------------------------------------- | +| `hw` | `i2c_hw_t *` | Pointer to the actual hardware registers | +| `restart_on_next` | `bool` | Tracks if next transfer needs a restart | + +### The Macro Chain + +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 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +So `I2C_PORT` eventually becomes a pointer to a struct that contains a pointer to hardware registers at address `0x40098000`! + +> πŸ“– **Datasheet Reference:** I2C0 base address is `0x40090000` and I2C1 base address is `0x40098000` (Section 2.2, p. 33; Section 12.1, p. 1009). The `i2c_hw_t` struct in the Pico SDK maps directly to these memory-mapped I/O registers. + +### The Hardware Register Pointer + +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! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 5: The ARM Calling Convention (AAPCS) + +### How Arguments Are Passed + +On ARM Cortex-M, the **ARM Architecture Procedure Call Standard (AAPCS)** defines how functions receive arguments: + +| Register | Purpose | +| -------- | ---------------- | +| `r0` | First argument | +| `r1` | Second argument | +| `r2` | Third argument | +| `r3` | Fourth argument | +| Stack | Fifth+ arguments | +| `r0` | Return value | + +### Example: i2c_init(i2c1, 100000) + +```c +i2c_init(I2C_PORT, 100000); +``` + +In assembly: +```assembly +ldr r0, [address of i2c1_inst] ; r0 = pointer to struct (first arg) +ldr r1, =0x186A0 ; r1 = 100000 (second arg) +bl i2c_init ; Call the function +``` + +--- + +## πŸ“š Part 6: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. OpenOCD installed and configured +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) +8. A hex editor (HxD, ImHex, or similar) +9. The sample project: `0x0017_constants` + +### Hardware Setup + +Connect your LCD like this: + +| LCD Pin | Pico 2 Pin | +| ------- | ---------- | +| VCC | 3.3V or 5V | +| GND | GND | +| SDA | GPIO 2 | +| 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 β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Project Structure + +``` +Embedded-Hacking/ +β”œβ”€β”€ 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 + +### Step 1: Review the Source Code + +Let's examine the constants code: + +**File: `0x0017_constants.c`** + +```c +#include +#include +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "lcd_1602.h" + +#define FAV_NUM 42 +#define I2C_PORT i2c1 +#define I2C_SDA_PIN 2 +#define I2C_SCL_PIN 3 + +const int OTHER_FAV_NUM = 1337; + +int main(void) { + stdio_init_all(); + + i2c_init(I2C_PORT, 100000); + gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA_PIN); + gpio_pull_up(I2C_SCL_PIN); + + lcd_i2c_init(I2C_PORT, 0x27, 4, 0x08); + lcd_set_cursor(0, 0); + lcd_puts("Reverse"); + lcd_set_cursor(1, 0); + lcd_puts("Engineering"); + + while (true) { + printf("FAV_NUM: %d\r\n", FAV_NUM); + printf("OTHER_FAV_NUM: %d\r\n", OTHER_FAV_NUM); + } +} +``` + +**What this code does:** + +1. **Lines 7-10:** Define preprocessor macros for constants and IΒ²C 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 +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 + +### 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 `0x0017_constants.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 3: Verify It's Working + +**Check the LCD:** +- Line 1 should show: `Reverse` +- Line 2 should show: `Engineering` + +**Check the serial monitor (PuTTY/screen):** +``` +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +... +``` + +--- + +## πŸ”¬ 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. + +### Starting 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 4: Examine Main Function + +Let's examine the main function. Disassemble from the entry point: + +``` +x/54i 0x10000234 +``` + +You should see output like: + +``` + 0x10000234
: push {r3, lr} + 0x10000236 : bl 0x100037fc + 0x1000023a : ldr r1, [pc, #104] @ (0x100002a4 ) + 0x1000023c : ldr r0, [pc, #104] @ (0x100002a8 ) + 0x1000023e : bl 0x10003cdc + 0x10000242 : movs r1, #3 + 0x10000244 : movs r0, #2 + 0x10000246 : bl 0x100008f0 + 0x1000024a : movs r1, #3 + 0x1000024c : mov r0, r1 + 0x1000024e : bl 0x100008f0 + 0x10000252 : movs r2, #0 + 0x10000254 : movs r1, #1 + 0x10000256 : movs r0, #2 + 0x10000258 : bl 0x1000092c + 0x1000025c : movs r2, #0 + 0x1000025e : movs r1, #1 + 0x10000260 : movs r0, #3 + 0x10000262 : bl 0x1000092c + 0x10000266 : movs r3, #8 + 0x10000268 : movs r2, #4 + 0x1000026a : movs r1, #39 @ 0x27 + 0x1000026c : + ldr r0, [pc, #56] @ (0x100002a8 ) + 0x1000026e : bl 0x100002bc + 0x10000272 : movs r1, #0 + 0x10000274 : mov r0, r1 + 0x10000276 : bl 0x100006f4 + 0x1000027a : + ldr r0, [pc, #48] @ (0x100002ac ) + 0x1000027c : bl 0x100007f0 + 0x10000280 : movs r0, #1 + 0x10000282 : movs r1, #0 + 0x10000284 : bl 0x100006f4 + 0x10000288 : + ldr r0, [pc, #36] @ (0x100002b0 ) + 0x1000028a : bl 0x100007f0 + 0x1000028e : movs r1, #42 @ 0x2a + 0x10000290 : + ldr r0, [pc, #32] @ (0x100002b4 ) + 0x10000292 : bl 0x1000398c <__wrap_printf> + 0x10000296 : movw r1, #1337 @ 0x539 + 0x1000029a : + ldr r0, [pc, #28] @ (0x100002b8 ) + 0x1000029c : bl 0x1000398c <__wrap_printf> + 0x100002a0 : b.n 0x1000028e + 0x100002a2 : nop + 0x100002a4 : strh r0, [r4, #52] @ 0x34 + 0x100002a6 : movs r1, r0 + 0x100002a8 : lsls r4, r5, #24 + 0x100002aa : movs r0, #0 + 0x100002ac : subs r6, #232 @ 0xe8 + 0x100002ae : asrs r0, r0, #32 + 0x100002b0 : subs r6, #240 @ 0xf0 + 0x100002b2 : asrs r0, r0, #32 + 0x100002b4 : subs r6, #252 @ 0xfc + 0x100002b6 : asrs r0, r0, #32 + 0x100002b8 : subs r7, #12 + 0x100002ba : asrs r0, r0, #32 +``` + +### Step 5: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +GDB responds: +``` +Breakpoint 1 at 0x10000234: file C:/Users/flare-vm/Desktop/Embedded-Hacking-main/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 +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. + +### Step 6: Find the #define Constant (FAV_NUM) + +Step through to the printf call and examine the registers: + +``` +x/20i 0x1000028e +``` + +Look for: +``` +... +0x1000028e : movs r1, #42 @ 0x2a +... +``` + +The `#define` constant is embedded directly as an immediate value in the instruction! + +### Step 7: Find the const Variable (OTHER_FAV_NUM) + +Continue examining the loop body: + +```gdb +(gdb) x/5i 0x10000296 +``` + +Look for this instruction: + +``` +... +0x10000296 : movw r1, #1337 @ 0x539 +... +``` + +**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) + +> πŸ’‘ **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 + +The literal pool after the loop contains addresses and constants that are too large for regular instruction immediates. Let's examine it: + +```gdb +(gdb) x/6wx 0x100002a4 +0x100002a4 : 0x000186a0 0x2000062c 0x10003ee8 0x10003ef0 +0x100002b4 : 0x10003efc 0x10003f0c +``` + +These are the values that `ldr rN, [pc, #offset]` instructions load: + +| Literal Pool Addr | Value | Used By | +| ----------------- | -------------- | ------------------------------ | +| `0x100002a4` | `0x000186A0` | I2C baudrate (100000) | +| `0x100002a8` | `0x2000062C` | &i2c1_inst (I2C struct in RAM) | +| `0x100002ac` | `0x10003EE8` | "Reverse" string address | +| `0x100002b0` | `0x10003EF0` | "Engineering" string address | +| `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. + +### Step 9: Examine the IΒ²C Struct + +Find the i2c1_inst struct address loaded into r0 before i2c_init: + +``` +x/2wx 0x2000062c +``` + +You should see: +``` +0x2000062c : 0x40098000 0x00000000 +``` + +### Step 10: Examine the LCD String Literals + +Find the strings passed to lcd_puts: + +``` +x/s 0x10003ee8 +``` + +Output: +``` +0x10003ee8: "Reverse" +``` + +``` +x/s 0x10003ef0 +``` + +Output: +``` +0x10003ef0: "Engineering" +``` + +### Step 11: Step Through IΒ²C Initialization + +Use `si` to step through instructions and watch the IΒ²C setup: + +``` +si +i r r0 r1 +``` + +--- + +## πŸ”¬ 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!** + +**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`. + +**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. + +**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. + +### Step 13: Analyze the IΒ²C Struct Layout + +In GDB, we examined the `i2c1_inst` struct at `0x2000062c`: + +```gdb +(gdb) x/2wx 0x2000062c +0x2000062c : 0x40098000 0x00000000 +``` + +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) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +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`. + +### Step 14: Locate the String Literals + +We found the LCD strings in flash memory: + +```gdb +(gdb) x/s 0x10003ee8 +0x10003ee8: "Reverse" + +(gdb) x/s 0x10003ef0 +0x10003ef0: "Engineering" +``` + +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 + +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. + +### 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\` +4. Open `0x0017_constants.bin` + +### Step 16: Calculate the File Offset + +The binary is loaded at base address `0x10000000`. To find the file offset of any address: + +``` +file_offset = address - 0x10000000 +``` + +For example: +- 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) + +From our GDB analysis, we know the instruction at `0x1000028e` is: + +``` +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`. + +### 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: + +```gdb +(gdb) x/4bx 0x10000296 +0x10000296 : 0x40 0xf2 0x39 0x51 +``` + +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) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +The file offset is `0x10000296 - 0x10000000 = 0x296`. The imm8 byte is the 3rd byte of the instruction: `0x296 + 2 = 0x298`. + +To change `movw r1, #1337` to `movw r1, #1344`: + +1. In HxD, press **Ctrl+G** (Go to offset) +2. Enter offset: `298` (the third byte of the 4-byte instruction) +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). + +### 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! + +From our GDB analysis in Step 10, we found the string at `0x10003ee8`. File offset = `0x10003ee8 - 0x10000000 = 0x3EE8`. + +1. In HxD, press **Ctrl+G** and enter offset: `3EE8` +2. You should see the bytes for "Reverse": `52 65 76 65 72 73 65 00` +3. Change the bytes to spell "Exploit": `45 78 70 6c 6f 69 74 00` + +**ASCII Reference:** + +| Character | Hex | +| --------- | ------ | +| E | `0x45` | +| x | `0x78` | +| p | `0x70` | +| l | `0x6c` | +| o | `0x6f` | +| i | `0x69` | +| t | `0x74` | + +### Step 20: Save the Patched Binary + +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 + +### 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 +``` + +Run the conversion command: + +```powershell +python ..\uf2conv.py build\0x0017_constants-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 22: 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. Check your LCD and serial monitor + +### Step 23: Verify the Hack + +**Check the LCD:** +- Line 1 should now show: `Exploit` (instead of "Reverse") +- Line 2 should still show: `Engineering` + +**Check the serial monitor:** +``` +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 +... +``` + +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!** + +--- + +## πŸ“Š 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 +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` +6. **Analyzed instruction encodings** - Both `movs` (8-bit) and `movw` (16-bit) immediates in the hex editor +7. **Patched a string literal** - Changed LCD display text from "Reverse" to "Exploit" + +### #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) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### IΒ²C 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 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Struct Chain + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ I2C_PORT β†’ i2c1 β†’ &i2c1_inst β†’ i2c_inst_t β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€ hw β†’ i2c_hw_t * β”‚ +β”‚ β”‚ └── 0x40098000 β”‚ +β”‚ β”‚ β”‚ +β”‚ └── restart_on_next (bool) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Memory Addresses + +| Address | Description | +| ------------ | ---------------------------------- | +| `0x10000234` | main() entry point | +| `0x1000028e` | FAV_NUM value in instruction | +| `0x10000296` | OTHER_FAV_NUM value in instruction | +| `0x10003ee8` | "Reverse" string literal (example) | +| `0x40098000` | IΒ²C1 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 + +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. + +4. **Structs group related data** - The SDK uses them to abstract hardware. + +5. **Macros can chain** - `I2C_PORT` β†’ `i2c1` β†’ `&i2c1_inst` β†’ hardware pointer. + +6. **ARM passes args in registers** - r0-r3 for first four arguments. + +7. **GDB reveals struct layouts** - Examine memory to understand data organization. + +8. **String hacking requires same length** - Or you'll corrupt adjacent data! + +9. **Constants aren't constant** - With binary patching, everything can change! + +10. **Compiler optimization changes code** - `gpio_pull_up` becomes `gpio_set_pulls`. + +--- + +## πŸ“– Glossary + +| Term | Definition | +| ----------------------- | --------------------------------------------------- | +| **#define** | Preprocessor directive for text replacement | +| **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 | +| **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 | +| **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 | +| **Struct** | User-defined type grouping related variables | +| **typedef** | Creates an alias for a type | + +--- + +## πŸ”— Additional Resources + +### IΒ²C Timing Reference + +| Speed Mode | Maximum Frequency | +| ---------- | ----------------- | +| Standard | 100 kHz | +| Fast | 400 kHz | +| Fast Plus | 1 MHz | + +### Common IΒ²C Addresses + +| Device | Address | +| --------------------- | ------------- | +| PCF8574 LCD (default) | `0x27` | +| PCF8574A LCD | `0x3F` | +| DS3231 RTC | `0x68` | +| BMP280 Sensor | `0x76`/`0x77` | +| SSD1306 OLED | `0x3C`/`0x3D` | + +### Key ARM Instructions for Constants + +| Instruction | Description | +| -------------------- | ------------------------------------------- | +| `movs rN, #imm` | Load small immediate (0-255) directly | +| `ldr rN, [pc, #off]` | Load larger value from literal pool | +| `ldr rN, =value` | Pseudo-instruction for loading any constant | + +### RP2350 IΒ²C Memory Map + +| Address | Description | +| ------------ | ----------------------- | +| `0x40090000` | IΒ²C0 hardware registers | +| `0x40098000` | IΒ²C1 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! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG00.svg b/WEEKS/WEEK07/slides/WEEK07-IMG00.svg new file mode 100644 index 0000000..427c6f7 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 07 + + +Constants in Embedded Systems: +Debugging and Hacking Constants +w/ 1602 LCD I2C Basics + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG01.svg b/WEEKS/WEEK07/slides/WEEK07-IMG01.svg new file mode 100644 index 0000000..bbe54c4 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG01.svg @@ -0,0 +1,63 @@ + + + + +#define vs const +Preprocessor Macros vs Constant Variables + + + +#define FAV_NUM 42 + +Preprocessor text replacement +Happens BEFORE compilation +No memory allocated +Cannot take address (&) + +In Binary: + +movs r1, #42 @ 0x2a + +16-bit Thumb instruction +Value embedded as immediate +Compiler sees only "42" + + + +const int OTHER_FAV_NUM=1337 + +Creates real variable +Theoretically in .rodata +Has an address (if needed) +Type-checked by compiler + +In Binary: + +movw r1, #1337 @ 0x539 + +32-bit Thumb-2 instruction +Also embedded as immediate! +Compiler optimized it away + + + +KEY INSIGHT: +Both ended up as instruction immediates! +The compiler saw &OTHER_FAV_NUM is never used, so it +optimized const the same way as #define -- no memory load needed. +Lesson: const is a source-level concept -- not guaranteed in binary + + + + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG02.svg b/WEEKS/WEEK07/slides/WEEK07-IMG02.svg new file mode 100644 index 0000000..d798cd5 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG02.svg @@ -0,0 +1,85 @@ + + + + +I2C Protocol +Two-Wire Serial Communication + + + +What is I2C? +Two-wire serial protocol +SDA += Serial Data +SCL += Serial Clock +Open-drain with pull-up resistors + + + +I2C Bus + +Pico + + +SDA +SCL + +LCD +GPIO 2 = SDA, GPIO 3 = SCL +Pull-ups hold lines HIGH + + + +Common I2C Addresses (7-bit) + +0x27 +LCD + +0x3F +LCD Alt + +0x48 +Sensor + +0x50 +EEPROM + + + +I2C Transaction Flow + +START +--> + +Address +--> + +ACK +--> + +Data +--> + +ACK +--> + +STOP + +Master sends START, then 7-bit address + R/W bit +Slave responds with ACK, then data bytes follow + + + + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG03.svg b/WEEKS/WEEK07/slides/WEEK07-IMG03.svg new file mode 100644 index 0000000..d69b142 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG03.svg @@ -0,0 +1,66 @@ + + + + +C Structs & typedef +Grouping Related Data in C + + + +Struct Definition + +typedef struct { +i2c_hw_t *hw; +bool restart_on_next; +} i2c_inst_t; +typedef creates an alias +so we can write: i2c_inst_t var; +instead of: struct { ... } var; + + + +Memory Layout +i2c_inst_t at 0x2000062C + + +Offset 0x00 +hw += 0x40098000 +i2c_hw_t* (4 bytes) + + +Offset 0x04 +restart_on_next += 0x00 (false) +bool (1 byte) + +Total struct size: 8 bytes +hw points to I2C1 registers + + + +Forward Declaration + +struct i2c_inst; +// tells compiler: this type exists, define later + + + +Why Structs Matter in RE +GDB shows raw memory -- you must recognize struct layouts +x/2wx 0x2000062c shows: 0x40098000 0x00000000 + + + + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG04.svg b/WEEKS/WEEK07/slides/WEEK07-IMG04.svg new file mode 100644 index 0000000..927fd8c --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG04.svg @@ -0,0 +1,80 @@ + + + + +Pico SDK Macro Chain +From I2C_PORT to Hardware Registers + + + +Macro Expansion Chain + + + +I2C_PORT +#define I2C_PORT i2c1 + + +--> + + + +i2c1 +#define i2c1 (&i2c1_inst) + + +--> + + + +&i2c1_inst +Address of global struct + + +Struct Contents at 0x2000062C + +i2c_inst_t i2c1_inst = { +.hw = (i2c_hw_t *)0x40098000, +.restart_on_next = false +}; + + +Hardware Register Access + + +i2c1_inst.hw +--> +i2c1_hw +--> +(i2c_hw_t*)0x40098000 + +I2C1_BASE = 0x40098000 +I2C0_BASE = 0x40090000 +Direct memory-mapped I/O to RP2350 peripheral + + + +FULL CHAIN: +I2C_PORT +--> +i2c1 +--> +&i2c1_inst +--> +0x40098000 +Macro --> Macro --> Struct pointer --> HW register base + + + + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG05.svg b/WEEKS/WEEK07/slides/WEEK07-IMG05.svg new file mode 100644 index 0000000..de2e153 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG05.svg @@ -0,0 +1,53 @@ + + + + +Source Code +0x0017_constants.c + + + + + +//--- Defines and Constants --- +#define FAV_NUM 42 +#define I2C_PORT i2c1 +#define I2C_SDA_PIN 2 +#define I2C_SCL_PIN 3 +const int OTHER_FAV_NUM = 1337; + +//--- Main Loop --- +lcd_set_cursor(0, 0); +lcd_puts("Reverse"); +lcd_set_cursor(1, 0); +lcd_puts("Engineering"); + +//--- Serial Output Loop --- +printf("FAV_NUM: %d\r\n", FAV_NUM); +printf("OTHER_FAV_NUM: %d\r\n", OTHER_FAV_NUM); + + + +LCD Output +Line 0: "Reverse" +Line 1: "Engineering" + + +Serial Output +FAV_NUM: 42 +OTHER_FAV_NUM: 1337 + + + + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG06.svg b/WEEKS/WEEK07/slides/WEEK07-IMG06.svg new file mode 100644 index 0000000..738b366 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG06.svg @@ -0,0 +1,65 @@ + + + + +GDB Analysis +Disassembly of main() at 0x10000234 + + + +Key Instructions from x/54i 0x10000234 + + +push {r3, lr} +// save return addr +bl stdio_init_all +// init serial +ldr r1, [pc, #104] +// r1 = 100000 (baud) +ldr r0, [pc, #104] +// r0 = &i2c1_inst +bl i2c_init +// init I2C at 100kHz +movs r0, #2 +// GPIO 2 (SDA) +bl gpio_set_function +// set pin to I2C +movs r1, #39 +// 0x27 = LCD addr +bl lcd_i2c_init +// init LCD device + +b.n 0x1000028e +// infinite loop start +... +AAPCS: r0-r3 = first 4 args, r0 = return value + + + +Literal Pool at 0x100002A4 + + +0x000186A0 +I2C baudrate (100000) +0x2000062C +&i2c1_inst struct in RAM +0x10003EE8 +"Reverse" string in flash +0x10003EF0 +"Engineering" string in flash +0x10003EFC +"FAV_NUM: %d\r\n" +0x10003F0C +"OTHER_FAV_NUM: %d\r\n" + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG07.svg b/WEEKS/WEEK07/slides/WEEK07-IMG07.svg new file mode 100644 index 0000000..7862b21 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG07.svg @@ -0,0 +1,71 @@ + + + + +Instruction Encoding +movs (16-bit Thumb) vs movw (32-bit Thumb-2) + + + +movs r1, #42 (FAV_NUM) +At address 0x1000028E + + +Bytes: 2A 21 + +2A = immediate value (42) +21 = opcode (movs r1) +16-bit Thumb instruction +Fits values 0-255 in 8 bits +File offset: 0x28E + + + +movw r1, #1337 (OTHER_FAV) +At address 0x10000296 + + +Bytes: 40 F2 39 51 + +40 F2 = opcode (first halfword) +39 = imm8 (lower 8 bits) +51 = dest reg + upper imm +32-bit Thumb-2 instruction +File offset: 0x296 + + + +movw Byte Layout (40 F2 39 51) + + +40 F2 +Opcode + upper imm + + +39 +imm8 (lower 8 bits) + + +51 +Dest reg (r1) + bits + +imm16 = 0x539 += 1337 decimal + + + +Why movw instead of movs? +1337 > 255 -- does not fit in 8-bit movs immediate +movw encodes 0-65535 in 32-bit instruction + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG08.svg b/WEEKS/WEEK07/slides/WEEK07-IMG08.svg new file mode 100644 index 0000000..9f8ea92 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG08.svg @@ -0,0 +1,68 @@ + + + + +I2C Struct in Memory +Examining i2c1_inst at 0x2000062C + + + +GDB Memory Dump + +x/2wx 0x2000062c: +0x40098000 +0x00000000 + + + +i2c_inst_t Struct Layout + + + +Offset 0x00 | 4 bytes +i2c_hw_t *hw += 0x40098000 + + +--> + + + +I2C1 HW Registers +Base: 0x40098000 (MMIO) + + + +Offset 0x04 | 1 byte +bool restart_on_next += false + +I2C0 base = 0x40090000 +I2C1 base = 0x40098000 + + + +String Literals in Flash (.rodata) + + +x/s 0x10003ee8: +"Reverse" + + +x/s 0x10003ef0: +"Engineering" + +Stored consecutively in .rodata (flash) +These addresses are targets for patching + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG09.svg b/WEEKS/WEEK07/slides/WEEK07-IMG09.svg new file mode 100644 index 0000000..5934306 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG09.svg @@ -0,0 +1,63 @@ + + + + +Hacking the Binary +Patching LCD Text: "Reverse" --> "Exploit" + + + +File Offset Formula +file_offset = address - 0x10000000 +Binary loaded at 0x10000000 + + + +Hack: Change LCD String +Address 0x10003EE8 --> File offset 0x3EE8 + +Original: + +52 65 76 65 72 73 65 00 +"Reverse" + +Patched: + +45 78 70 6C 6F 69 74 00 +"Exploit" + +Same length (7 chars) -- null terminator stays + + + +Flash the Hacked Binary + + +python uf2conv.py build\patched.bin + +1. +Save patched .bin file +2. +Convert to .uf2 format +3. +Hold BOOTSEL, plug in Pico +4. +Drag hacked.uf2 to drive + + + +LCD now shows: "Exploit" +instead of "Reverse" +No source code needed! + \ No newline at end of file diff --git a/WEEKS/WEEK07/slides/WEEK07-IMG10.svg b/WEEKS/WEEK07/slides/WEEK07-IMG10.svg new file mode 100644 index 0000000..e6df942 --- /dev/null +++ b/WEEKS/WEEK07/slides/WEEK07-IMG10.svg @@ -0,0 +1,88 @@ + + + + +I2C & Macro Exploitation +Constants, I2C, Structs, and Hacking + + + +Key Concepts + +#define +Text replacement, no memory +const +Variable in .rodata (maybe) +I2C +Two-wire: SDA + SCL +struct +Groups related data fields +typedef +Creates type alias +AAPCS +r0-r3 args, r0 return +movs +16-bit, imm 0-255 +movw +32-bit, imm 0-65535 +Literal Pool +Large consts after code + + + +Key Addresses + +0x10000234 +main() entry +0x1000028E +FAV_NUM (movs) +0x10000296 +OTHER_FAV_NUM (movw) +0x10003EE8 +"Reverse" string +0x10003EF0 +"Engineering" string +0x40098000 +I2C1 HW base +0x2000062C +i2c1_inst struct + +file_offset = addr - 0x10000000 +String patches must be same length + + + +Macro Chain +I2C_PORT +--> +i2c1 +--> +&i2c1_inst +--> +0x40098000 + + + +Binary Hack Result +LCD: "Reverse" --> +"Exploit" +Patched at 0x3EE8 +Compiler may optimize const same as #define + + + +TAKEAWAY: +const is a source-level concept. +In binary, everything can change! + \ No newline at end of file diff --git a/WEEKS/WEEK09/WEEK09-01-S.md b/WEEKS/WEEK09/WEEK09-01-S.md new file mode 100644 index 0000000..a10bc14 --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-01-S.md @@ -0,0 +1,66 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics + +### Non-Credit Practice Exercise 1 Solution: Change the Sleep Duration + +#### Answers + +##### Sleep Duration Values + +| Parameter | Original | Patched | +|---------------|-------------|-------------| +| Duration (ms) | 2000 | 5000 | +| Hex | 0x000007D0 | 0x00001388 | +| Little-endian | D0 07 00 00 | 88 13 00 00 | + +##### Why a Literal Pool Is Needed + +The value 2000 exceeds the 8-bit immediate range of `movs` (0–255) and the 16-bit range is also impractical for a single-instruction load. The compiler stores `0x000007D0` in a **literal pool** near the function code and loads it with a `ldr r0, [pc, #offset]` instruction that reads the 32-bit word from the pool into `r0` before the `bl sleep_ms` call. + +##### Patch Procedure + +1. Find the literal pool entry containing `D0 07 00 00` in HxD +2. Replace with `88 13 00 00` + +``` +Before: D0 07 00 00 (2000) +After: 88 13 00 00 (5000) +``` + +##### Conversion and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Serial Output After Patch + +``` +arithmetic_operator: 50 +increment_operator: 5 +relational_operator: 0 +logical_operator: 0 +bitwise_operator: 12 +assignment_operator: 11 +Humidity: 51.0%, Temperature: 24.0Β°C +``` + +Output repeats every **5 seconds** instead of 2 seconds. + +#### Reflection Answers + +1. **Why can't 2000 be encoded as a movs immediate? What is the maximum value movs can hold?** + The `movs Rd, #imm8` instruction is a 16-bit Thumb encoding that has only 8 bits for the immediate value, giving a range of 0–255. The value 2000 (`0x7D0`) is far beyond this range. Even the 32-bit Thumb-2 `movw` instruction can only encode 0–65535, which could handle 2000, but the compiler chose a literal pool approach. The literal pool is a general-purpose solution that works for any 32-bit value, including addresses and large constants. + +2. **If you wanted to change the sleep to exactly 1 second (1000ms), what 4 bytes would you write in little-endian? Show your work.** + 1000 decimal = `0x000003E8` hex. In little-endian byte order (LSB first): `E8 03 00 00`. Breakdown: byte 0 = `0xE8` (LSB), byte 1 = `0x03`, byte 2 = `0x00`, byte 3 = `0x00` (MSB). + +3. **Could other code in the program reference the same literal pool entry containing 0x7D0? What would happen if it did?** + Yes, the compiler may share literal pool entries to save space. If another instruction also loads `0x7D0` from the same pool address (using its own `ldr rN, [pc, #offset]`), then patching that pool entry would change the value for ALL instructions that reference it. This is a risk with literal pool patching β€” you might unintentionally modify other parts of the program. To check, search for all `ldr` instructions whose PC-relative offset resolves to the same pool address. + +4. **What would happen if you set sleep_ms to 0? Would the program crash or just run extremely fast?** + The program would not crash β€” `sleep_ms(0)` is a valid call that returns immediately. The loop would run as fast as the processor can execute it, printing operator values and reading the DHT11 sensor with no delay between iterations. The serial output would flood extremely quickly. However, the DHT11 sensor has a minimum sampling interval of about 1 second; reading it more frequently may return stale data or read errors ("DHT11 read failed"), but the program itself would continue running. diff --git a/WEEKS/WEEK09/WEEK09-01.md b/WEEKS/WEEK09/WEEK09-01.md new file mode 100644 index 0000000..bb40eec --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-01.md @@ -0,0 +1,141 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics + +### Non-Credit Practice Exercise 1: Change the Sleep Duration + +#### Objective +Find the `sleep_ms(2000)` call in the `0x001a_operators` binary using GDB, identify the immediate value `0x7d0` (2000) being loaded into `r0`, calculate the file offset, patch it to `0x1388` (5000) using a hex editor, and verify on hardware that the serial output now prints every 5 seconds instead of every 2 seconds. + +#### Prerequisites +- Completed Week 9 tutorial (GDB and hex editor sections) +- `0x001a_operators.elf` and `0x001a_operators.bin` 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 DHT11 sensor connected + +#### Task Description +The program calls `sleep_ms(2000)` at the end of its main loop, causing a 2-second delay between each set of serial output. The value `2000` (`0x7D0`) is loaded into register `r0` before the `bl sleep_ms` call. You will locate this value in the disassembly, find the corresponding bytes in the `.bin` file, and patch them to `5000` (`0x1388`) so the loop runs every 5 seconds instead. + +#### 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\0x001a_operators.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the sleep_ms Call + +Disassemble main and look for the `sleep_ms` call: + +```gdb +(gdb) x/60i 0x10000234 +``` + +Look for an instruction pattern like: + +``` +ldr r0, =0x7d0 ; 2000 milliseconds +bl sleep_ms +``` + +The value `0x7d0` will be loaded from the literal pool. + +##### Step 3: Examine the Literal Pool Value + +Once you find the `ldr r0, [pc, #offset]` instruction, examine the literal pool entry it references: + +```gdb +(gdb) x/wx +``` + +You should see `0x000007d0` (2000 in hex). + +##### Step 4: Calculate the File Offset + +``` +file_offset = literal_pool_address - 0x10000000 +``` + +Note the file offset of the 4-byte word containing `0x7d0`. + +##### Step 5: Encode the New Value + +The new value `5000` in hex is `0x1388`. In little-endian byte order: + +| Value | Hex | Little-Endian Bytes | +| ----- | ---------- | ------------------- | +| 2000 | `0x000007D0` | `D0 07 00 00` | +| 5000 | `0x00001388` | `88 13 00 00` | + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` +2. Press **Ctrl+G** and enter the file offset you calculated +3. You should see: `D0 07 00 00` +4. Replace with: `88 13 00 00` + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x001a_operators-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 serial monitor:** +- Output should now appear every **5 seconds** instead of every 2 seconds +- All operator values should remain unchanged (50, 5, 0, 0, 12, 11) + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate literal pool values referenced by `ldr` instructions +- Understand little-endian byte ordering for 32-bit values +- Patch timing constants in embedded firmware +- Calculate file offsets from memory addresses + +#### Questions for Reflection + +###### Question 1: The value `2000` is stored in the literal pool as a 32-bit word, not as an immediate in the instruction. Why can't `2000` be encoded as an immediate in a single `movs` instruction? + +###### Question 2: If you wanted to change the delay to exactly 1 second (1000ms = `0x3E8`), what bytes would you write in little-endian order? + +###### Question 3: The literal pool value is shared β€” could other code in the binary also reference this same `0x7D0` value? How would you check? + +###### Question 4: What would happen if you changed the sleep value to `0` (`00 00 00 00`)? Would the program crash or just run extremely fast? + +#### Tips and Hints +- `movs` can only encode immediates 0-255; values larger than 255 must be loaded from the literal pool via `ldr` +- Always verify the bytes BEFORE patching β€” make sure you see `D0 07 00 00` at your calculated offset +- The literal pool is typically right after the function's `b.n` (loop branch) instruction +- Use a stopwatch or count "one-one-thousand" to verify the timing change diff --git a/WEEKS/WEEK09/WEEK09-02-S.md b/WEEKS/WEEK09/WEEK09-02-S.md new file mode 100644 index 0000000..8c06b2c --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-02-S.md @@ -0,0 +1,66 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics + +### Non-Credit Practice Exercise 2 Solution: Invert the Temperature Reading + +#### Answers + +##### IEEE-754 Sign Bit Flip + +| Value | IEEE-754 Hex | Little-Endian Bytes | Sign Bit | +|---------|--------------|--------------------|---------| +| +0.1f | 0x3DCCCCCD | CD CC CC 3D | 0 | +| -0.1f | 0xBDCCCCCD | CD CC CC BD | 1 | + +Only the **last byte** changes in little-endian: `3D` β†’ `BD`. This flips bit 31 (the IEEE-754 sign bit) from 0 to 1, negating the value. + +##### Patch at File Offset 0x42C + +``` +Offset 0x42C: +Before: CD CC CC 3D (+0.1f) +After: CD CC CC BD (-0.1f) +``` + +Only 1 byte changes: offset `0x42F` from `0x3D` to `0xBD`. + +##### How the Temperature Changes + +The DHT11 returns raw integer and decimal parts (e.g., integer=24, decimal=0). The firmware computes: + +``` +temperature = integer_part + (decimal_part Γ— 0.1f) +``` + +With `vfma.f32 s15, s13, s11`: result = s15 + (s13 Γ— s11) = integer + (decimal Γ— 0.1f) + +After patching to -0.1f: result = integer + (decimal Γ— -0.1f) + +For a reading of 24.5Β°C: original = 24 + (5 Γ— 0.1) = 24.5Β°C, patched = 24 + (5 Γ— -0.1) = 23.5Β°C + +For a reading of 24.0Β°C: integer=24, decimal=0, so 24 + (0 Γ— -0.1) = 24.0Β°C (unchanged when decimal is 0). + +##### Serial Output After Patch + +``` +Humidity: 51.0%, Temperature: 23.5Β°C +``` + +(Temperature decimal contribution is inverted; effect depends on the decimal component.) + +#### Reflection Answers + +1. **Why does changing the byte from 0x3D to 0xBD negate the float? What specific bit is being flipped?** + In IEEE-754 single-precision format, bit 31 is the **sign bit**: 0 = positive, 1 = negative. The byte `0x3D` in binary is `0011 1101` and `0xBD` is `1011 1101` β€” only bit 7 of that byte differs, which corresponds to bit 31 of the 32-bit float (since it's the MSB of the last byte in little-endian storage). The exponent and mantissa bits remain identical, so the magnitude stays exactly `0.1` β€” only the sign changes. + +2. **Why does patching one constant affect both humidity AND temperature? They use different vfma instructions (at 0x410 and 0x414) β€” so why are both affected?** + Both `vfma` instructions at `0x410` (humidity) and `0x414` (temperature) load the scaling constant from the **same literal pool entry** at offset `0x42C`. The compiler recognized that both computations use the same `0.1f` value and stored it only once to save space. Both `vldr s11, [pc, #offset]` instructions resolve to address `0x1000042C`. So patching that single 4-byte value changes the scaling factor for both humidity and temperature simultaneously. + +3. **What is the IEEE-754 encoding of 0.5f? If the raw sensor decimal reading was 8, what would the computed value be with 0.5f instead of 0.1f?** + 0.5f in IEEE-754: sign=0, exponent=126 (`0x7E`), mantissa=0. Hex = `0x3F000000`. Little-endian bytes: `00 00 00 3F`. With a raw decimal reading of 8: `8 Γ— 0.5 = 4.0`. So if the integer part was 24, the result would be `24 + 4.0 = 28.0Β°C` instead of `24 + 0.8 = 24.8Β°C`. + +4. **Could you achieve the same inversion by patching the vfma instruction instead of the constant? What instruction change would work?** + Yes. You could change `vfma.f32` (fused multiply-add: d = d + aΓ—b) to `vfms.f32` (fused multiply-subtract: d = d - aΓ—b). This would compute `temperature = integer - (decimal Γ— 0.1f)` instead of `integer + (decimal Γ— 0.1f)`, achieving the same sign inversion on the decimal contribution. The instruction encoding difference between `vfma` and `vfms` is typically a single bit in the opcode. However, this approach is more complex than simply flipping the sign bit of the constant. diff --git a/WEEKS/WEEK09/WEEK09-02.md b/WEEKS/WEEK09/WEEK09-02.md new file mode 100644 index 0000000..ac0e5cf --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-02.md @@ -0,0 +1,136 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics + +### Non-Credit Practice Exercise 2: Invert the Temperature Reading + +#### Objective +Using GDB to locate the IEEE-754 scaling constant `0.1f` at file offset `0x42C`, patch it to `-0.1f` using a hex editor, and verify on hardware that the serial output now displays negative temperature values. + +#### Prerequisites +- Completed Week 9 tutorial (GDB, Ghidra, and hex editor sections) +- `0x001a_operators.elf` and `0x001a_operators.bin` 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 DHT11 sensor connected + +#### Task Description +The DHT11 driver uses a scaling constant of `0.1f` stored at address `0x1000042C` (file offset `0x42C`) to convert raw sensor data into human-readable values. By changing this constant to `-0.1f`, you will invert the decimal component of the temperature calculation, causing the reported temperature to drop. This exercise teaches IEEE-754 float encoding and how a single 4-byte patch can dramatically change sensor behavior. + +#### 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\0x001a_operators.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Verify the Current Scaling Constant + +Examine the float constant at the known address: + +```gdb +(gdb) x/wx 0x1000042c +``` + +Output: +``` +0x1000042c: 0x3dcccccd +``` + +This is `0.1f` in IEEE-754 encoding (approximately β€” the repeating binary fraction makes it `0x3dcccccd`). + +##### Step 3: Understand the IEEE-754 Encoding + +**Current value (0.1f):** + +| Field | Bits | Value | +| -------- | ---------- | ----- | +| Sign | `0` | Positive | +| Exponent | `01111011` | 123 (biased) | +| Mantissa | `10011001100110011001101` | ~1.6 | + +**New value (-0.1f):** +- Flip only the sign bit (bit 31) from `0` to `1` +- `0x3dcccccd` β†’ `0xbdcccccd` + +| Value | Hex | Little-Endian Bytes | +| ------ | ------------ | ------------------- | +| 0.1f | `0x3dcccccd` | `cd cc cc 3d` | +| -0.1f | `0xbdcccccd` | `cd cc cc bd` | + +> πŸ’‘ **Key insight:** To negate an IEEE-754 float, you only need to flip the most significant bit. In little-endian, this is the **last** byte β€” change `3d` to `bd`. + +##### Step 4: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` +2. Press **Ctrl+G** and enter offset: `42C` +3. You should see: `cd cc cc 3d` (or `cc cc cc 3d`) +4. Replace with: `cd cc cc bd` (or `cc cc cc bd`) + +> ⚠️ **Note:** The exact bytes may be `cc cc cc 3d` or `cd cc cc 3d` depending on compiler rounding. Just change the last byte from `3d` to `bd`. + +##### Step 5: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x001a_operators-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 6: 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 serial monitor:** +- All operator values remain unchanged (50, 5, 0, 0, 12, 11) +- Temperature should now display with an inverted decimal component +- Humidity will also be affected (same constant is shared) + +#### Expected Output + +After completing this exercise, you should be able to: +- Decode and encode IEEE-754 floating-point values +- Understand that flipping one bit (sign bit) negates a float +- Patch floating-point constants in compiled binaries +- Predict how a constant change propagates through calculations + +#### Questions for Reflection + +###### Question 1: Why does changing one byte (`3d` β†’ `bd`) negate the entire float value? What does the sign bit (bit 31) control in IEEE-754? + +###### Question 2: The scaling constant `0.1f` is used by BOTH the humidity and temperature `vfma.f32` instructions. Why does patching this single constant affect both readings? + +###### Question 3: If you wanted to change the constant to `0.5f` (`0x3f000000`, little-endian `00 00 00 3f`) instead of `-0.1f`, how would the temperature reading change? If the raw decimal part is 8, what would the new output be? + +###### Question 4: Could you achieve negative temperature by patching the `vfma.f32` instruction itself instead of the constant? What instruction might you replace it with? + +#### Tips and Hints +- IEEE-754 sign bit is the MSB (bit 31) β€” `0` = positive, `1` = negative +- In little-endian, the sign bit is in the **last** (highest address) byte +- Use an online IEEE-754 converter to verify your understanding +- If the output looks completely wrong (NaN, inf), you may have changed the wrong byte β€” start over with a fresh copy of the `.bin` file diff --git a/WEEKS/WEEK09/WEEK09-03-S.md b/WEEKS/WEEK09/WEEK09-03-S.md new file mode 100644 index 0000000..f0fa0d1 --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-03-S.md @@ -0,0 +1,74 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics + +### Non-Credit Practice Exercise 3 Solution: Add a Fixed Temperature Offset + +#### Answers + +##### Two Patches Required + +This exercise requires **two** simultaneous patches to add a fixed +10Β°C offset to every temperature reading. + +##### Patch 1: Instruction at Offset 0x414 + +Change `vfma.f32` to `vadd.f32`: + +| Item | Original | Patched | +|------------|------------------------------|------------------------------| +| Instruction | vfma.f32 s15, s13, s11 | vadd.f32 s15, s15, s11 | +| Encoding | e6 ee a5 7a | b4 ee a5 7a | +| Operation | s15 = s15 + (s13 Γ— s11) | s15 = s15 + s11 | + +``` +Offset 0x414: +Before: E6 EE A5 7A (vfma.f32) +After: B4 EE A5 7A (vadd.f32) +``` + +Only the first two bytes change: `E6 EE` β†’ `B4 EE`. + +##### Patch 2: Constant at Offset 0x42C + +Change the constant from 0.1f to 10.0f: + +| Value | IEEE-754 Hex | Little-Endian Bytes | +|--------|--------------|--------------------| +| 0.1f | 0x3DCCCCCD | CD CC CC 3D | +| 10.0f | 0x41200000 | 00 00 20 41 | + +``` +Offset 0x42C: +Before: CD CC CC 3D (0.1f) +After: 00 00 20 41 (10.0f) +``` + +##### Why Both Patches Are Needed + +- **Original:** `vfma.f32` computes `temp = integer + (decimal Γ— 0.1f)` β€” fractional scaling +- **After both patches:** `vadd.f32` computes `temp = integer + 10.0f` β€” fixed offset addition +- **If only constant changed:** `vfma.f32` would compute `temp = integer + (decimal Γ— 10.0f)` β€” amplified decimal, not a fixed offset + +##### Serial Output After Patch + +``` +Humidity: 51.0%, Temperature: 34.0Β°C +``` + +(Original 24.0Β°C + 10.0Β°C offset = 34.0Β°C) + +#### Reflection Answers + +1. **Why are both patches needed? What would happen if you only changed the constant to 10.0f but left vfma unchanged?** + If you only changed `0.1f` to `10.0f` but left `vfma.f32`, the computation would be `temp = integer + (decimal Γ— 10.0f)`. For a reading of 24.5Β°C (integer=24, decimal=5): result = 24 + (5 Γ— 10.0) = 74.0Β°C β€” wildly incorrect. The `vfma` instruction multiplies two operands and adds, so the constant serves as a multiplier for the decimal part. By changing to `vadd.f32`, we eliminate the multiplication entirely and just add the constant directly to the integer, giving `24 + 10.0 = 34.0Β°C`. + +2. **The humidity vfma instruction at 0x410 was NOT changed. Both vfma instructions (0x410 and 0x414) load the same 0.1f constant from 0x42C. With the constant now 10.0f, what happens to the humidity computation?** + The humidity `vfma` at `0x410` now computes `hum = integer + (decimal Γ— 10.0f)`. If the humidity decimal part is 0 (e.g., raw humidity = 51.0%), then `51 + (0 Γ— 10.0) = 51.0%` β€” unchanged. But if the decimal part is non-zero (e.g., raw = 51.3%, decimal=3), the result would be `51 + (3 Γ— 10.0) = 81.0%` β€” grossly incorrect. The DHT11 sensor's humidity decimal is often 0, so you might not notice the bug immediately, but it's a latent defect. + +3. **If you wanted to add a 10Β°C offset to temperature WITHOUT affecting humidity, what additional patch(es) would you need?** + You would need to ensure humidity still uses the original `0.1f` scaling. Options: (1) Also change the humidity `vfma` at `0x410` to `vadd.f32` and create a separate literal pool entry with `0.1f` for it β€” but this requires finding free space. (2) More practically, place a second copy of `0.1f` (`CD CC CC 3D`) in unused space in the binary, and redirect the humidity `vldr` instruction's PC-relative offset to point to that new location instead of `0x42C`. (3) Alternatively, NOP out the humidity `vfma` entirely if the decimal contribution is negligible. + +4. **Why do only the first 2 bytes differ between vfma and vadd? What do the last 2 bytes encode?** + In the ARM VFPv4 encoding, the first two bytes (`E6 EE` vs `B4 EE`) contain the **opcode** that distinguishes the operation type (fused multiply-add vs addition). The last two bytes (`A5 7A`) encode the **operand registers**: the source and destination VFP registers (s15, s13, s11). Since both instructions operate on the same registers, the operand encoding is identical. Only the operation code changes β€” this is a characteristic of the ARM instruction set where opcode and operand fields are cleanly separated. diff --git a/WEEKS/WEEK09/WEEK09-03.md b/WEEKS/WEEK09/WEEK09-03.md new file mode 100644 index 0000000..7060ce6 --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-03.md @@ -0,0 +1,152 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics + +### Non-Credit Practice Exercise 3: Add a Fixed Temperature Offset + +#### Objective +Patch both the `vfma.f32` instruction at file offset `0x414` and the scaling constant at `0x42C` to replace the multiply-add with a simple add of `10.0f`, causing every temperature reading to be increased by exactly 10Β°C, and verify on hardware. + +#### Prerequisites +- Completed Week 9 tutorial and Exercise 2 +- `0x001a_operators.elf` and `0x001a_operators.bin` 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 DHT11 sensor connected + +#### Task Description +The temperature calculation uses `vfma.f32 s15, s13, s11` which computes `s15 = s15 + (s13 Γ— s11)` where `s11 = 0.1f`. You will make TWO patches: (1) change the `vfma.f32` instruction to `vadd.f32` so it performs addition instead of multiply-add, and (2) change the constant from `0.1f` to `10.0f`. The result: every temperature reading will have 10Β°C added to it. This exercise teaches ARM floating-point instruction encoding and multi-site patching. + +#### 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\0x001a_operators.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Verify the Current Instructions + +Examine the floating-point instructions near the temperature calculation: + +```gdb +(gdb) x/4bx 0x10000414 +``` + +Output: +``` +0x10000414: 0xe6 0xee 0xa5 0x7a +``` + +This is `vfma.f32 s15, s13, s11` β€” the temperature fused multiply-add. + +##### Step 3: Verify the Current Constant + +```gdb +(gdb) x/wx 0x1000042c +``` + +Output: +``` +0x1000042c: 0x3dcccccd +``` + +This is `0.1f`. + +##### Step 4: Understand the Two Patches + +**Patch 1 β€” Instruction at offset `0x414`:** + +| Original | Bytes (LE) | New | Bytes (LE) | +| --------------------------- | --------------- | --------------------------- | --------------- | +| `vfma.f32 s15, s13, s11` | `e6 ee a5 7a` | `vadd.f32 s15, s15, s11` | `b4 ee a5 7a` | + +> πŸ” Only the first two bytes change: `e6 ee` β†’ `b4 ee`. The operand bytes `a5 7a` stay the same. + +**Patch 2 β€” Constant at offset `0x42C`:** + +| Original | Hex | LE Bytes | New | Hex | LE Bytes | +| -------- | ------------ | --------------- | ----- | ------------ | --------------- | +| 0.1f | `0x3dcccccd` | `cd cc cc 3d` | 10.0f | `0x41200000` | `00 00 20 41` | + +##### Step 5: Patch the Instruction with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` +2. Press **Ctrl+G** and enter offset: `414` +3. You should see: `e6 ee a5 7a` +4. Change the first two bytes: `e6 ee` β†’ `b4 ee` +5. Result should be: `b4 ee a5 7a` + +##### Step 6: Patch the Constant with HxD + +1. Press **Ctrl+G** and enter offset: `42C` +2. You should see: `cd cc cc 3d` (or `cc cc cc 3d`) +3. Replace all 4 bytes with: `00 00 20 41` + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x001a_operators-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators +python ..\uf2conv.py build\0x001a_operators-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 serial monitor:** +- All operator values remain unchanged (50, 5, 0, 0, 12, 11) +- Temperature should now read approximately **10Β°C higher** than the real temperature +- Humidity will also be affected (the constant is shared) + +> ⚠️ **Note:** Since the constant at `0x42C` is shared between the humidity `vfma.f32` at `0x410` and the temperature one at `0x414`, changing it to `10.0f` will also affect humidity. The humidity instruction still uses `vfma` (multiply-add), so it will compute `humidity_int + (decimal Γ— 10.0)` β€” producing very large humidity values. + +#### Expected Output + +After completing this exercise, you should be able to: +- Distinguish between `vfma.f32` (multiply-add) and `vadd.f32` (add) encodings +- Perform multi-site patches in a single binary +- Understand how shared constants affect multiple code paths +- Predict the side effects of patching shared data + +#### Questions for Reflection + +###### Question 1: Why did we need to change BOTH the instruction AND the constant? What would happen if you only changed the constant to `10.0f` but left the `vfma.f32` instruction unchanged? + +###### Question 2: The humidity `vfma.f32` at offset `0x410` was NOT changed to `vadd`. With the constant now set to `10.0f`, what formula does the humidity instruction compute? If the raw humidity integer is 51 and decimal is 0, what value would it display? + +###### Question 3: If you wanted to add 10Β°C to temperature WITHOUT affecting humidity, you would need to change both instructions. What bytes would you write at offset `0x410` to change the humidity `vfma` to `vadd` as well, and what constant would preserve the original humidity calculation? + +###### Question 4: The `vadd.f32 s15, s15, s11` encoding is `b4 ee a5 7a`. In the original `vfma.f32 s15, s13, s11` (`e6 ee a5 7a`), why do only the first two bytes differ? What do those bytes encode? + +#### Tips and Hints +- Make BOTH patches before saving β€” if you only patch one, the results will be wrong +- The `vfma` β†’ `vadd` change only affects the first two bytes of the 4-byte instruction +- `10.0f` in IEEE-754 is `0x41200000` β€” memorize this common value +- Always start with a fresh copy of the `.bin` file if you need to redo the exercise +- Compare the original and hacked serial output side by side to verify only temperature changed as expected diff --git a/WEEKS/WEEK09/WEEK09-04-S.md b/WEEKS/WEEK09/WEEK09-04-S.md new file mode 100644 index 0000000..b58ca2e --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-04-S.md @@ -0,0 +1,72 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Basics + +### Non-Credit Practice Exercise 4 Solution: Find All printf Format Strings + +#### Answers + +##### Complete String Catalog + +| # | String | Type | Used By | +|---|-----------------------------------------------------|---------------|-------------| +| 1 | `"arithmetic_operator: %d\r\n"` | Format string | printf #1 | +| 2 | `"increment_operator: %d\r\n"` | Format string | printf #2 | +| 3 | `"relational_operator: %d\r\n"` | Format string | printf #3 | +| 4 | `"logical_operator: %d\r\n"` | Format string | printf #4 | +| 5 | `"bitwise_operator: %d\r\n"` | Format string | printf #5 | +| 6 | `"assignment_operator: %d\r\n"` | Format string | printf #6 | +| 7 | `"Humidity: %.1f%%, Temperature: %.1fΒ°C\r\n"` | Format string | printf #7 | +| 8 | `"DHT11 read failed\r\n"` | Plain string | puts | + +##### Format Specifier Analysis + +| Specifier | Meaning | Used In | +|-----------|------------------------------------------|---------------| +| `%d` | Signed decimal integer | Strings 1–6 | +| `%.1f` | Float with 1 decimal place | String 7 | +| `%%` | Literal percent sign (escaped) | String 7 | +| `\r\n` | Carriage return + line feed (0x0D 0x0A) | All strings | +| `Β°C` | UTF-8 degree symbol + C (0xC2 0xB0 0x43)| String 7 | + +##### Expected Operator Output Values + +| Operator | Expression | Value | Explanation | +|-----------------------|------------------------|-------|------------------------------------| +| arithmetic_operator | 5 Γ— 10 | 50 | Multiplication | +| increment_operator | x++ (x=5) | 5 | Post-increment returns old value | +| relational_operator | 6 > 10 | 0 | False | +| logical_operator | (6>10) && (10>6) | 0 | Short-circuit: first operand false | +| bitwise_operator | 6 << 1 | 12 | Left shift = multiply by 2 | +| assignment_operator | 6 + 5 | 11 | Addition assignment | + +##### GDB Search Commands + +```gdb +(gdb) x/20s 0x10003e00 +(gdb) x/50s 0x10003d00 +``` + +##### Special Byte Sequences in Strings + +| Sequence | Bytes | Meaning | +|----------|------------|---------------------| +| `\r\n` | 0x0D 0x0A | CRLF line ending | +| `%%` | 0x25 0x25 | Literal % character | +| `Β°` | 0xC2 0xB0 | UTF-8 degree symbol | + +#### Reflection Answers + +1. **The humidity/temperature format string contains %%. What would happen if you patched one of the % characters to a different character (e.g., changed %% to %,)?** + The `%%` escape produces a literal `%` in the output. If you change it to `%,` (bytes `25 2C`), `printf` would interpret `%,` as the start of a format specifier where `,` is the conversion character. Since `,` is not a valid `printf` conversion specifier, the behavior is **undefined** β€” most implementations would either print garbage, skip it, or consume the next argument from the stack incorrectly. This could corrupt the remaining output or even crash if `printf` tries to read a non-existent argument. + +2. **If you changed "arithmetic_operator" to "hacked_operator__" (same length) in the binary, what would the serial output look like? Would the computed value change?** + The serial output would show `hacked_operator__: 50` instead of `arithmetic_operator: 50`. The **computed value (50) would not change** β€” it's determined by the actual multiplication instruction in the code, not by the format string. The format string is just a label for display purposes. The `%d` specifier still reads the same `r1` register value (50) passed as the second argument to `printf`. + +3. **What happens if you make a format string 1 byte longer (e.g., add a character)? Where would the extra byte be stored?** + The extra byte would overwrite the **null terminator** (`0x00`) of the current string, and the byte after that is the first byte of the next consecutive string in `.rodata`. This effectively merges the two strings: `printf` would continue reading past the intended end into the next string's data until it finds another `0x00`. The output would include garbage characters from the adjacent string. If the adjacent data happens to contain `%` followed by a valid specifier, `printf` might try to read additional arguments from the stack, potentially causing a crash or information leak. + +4. **The "DHT11 read failed" message uses puts instead of printf. Why would the compiler choose puts over printf for this particular string?** + The compiler (with optimizations enabled) recognizes that `printf("DHT11 read failed\r\n")` has **no format specifiers** β€” it's a plain string with no `%d`, `%s`, `%f`, etc. Since no formatting is needed, the compiler optimizes it to `puts("DHT11 read failed")` (which automatically appends a newline). This is a common GCC optimization (`-fprintf-return-value`) because `puts` is simpler and faster than `printf` β€” it doesn't need to parse the format string looking for specifiers. The `\r\n` may be handled slightly differently depending on the implementation, but the key insight is that the compiler automatically selects the more efficient function. diff --git a/WEEKS/WEEK09/WEEK09-04.md b/WEEKS/WEEK09/WEEK09-04.md new file mode 100644 index 0000000..661833d --- /dev/null +++ b/WEEKS/WEEK09/WEEK09-04.md @@ -0,0 +1,140 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 9 +Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics + +### Non-Credit Practice Exercise 4: Find All printf Format Strings + +#### Objective +Systematically search through the `0x001a_operators` binary using GDB and a hex editor to locate every `printf` format string, catalog their addresses, file offsets, contents, and purposes, and gain experience identifying data structures in compiled binaries. + +#### Prerequisites +- Completed Week 9 tutorial (GDB section) +- `0x001a_operators.elf` and `0x001a_operators.bin` available in your build directory +- GDB (`arm-none-eabi-gdb`) and OpenOCD installed +- A hex editor (HxD, ImHex, or similar) + +#### Task Description +The `0x001a_operators` program contains multiple `printf` calls, each with a format string like `"arithmetic_operator: %d\r\n"`. These strings are stored in the `.rodata` section of flash memory. You will use GDB to trace each `printf` call to its format string argument, then verify the strings in HxD. You will also search for strings that are NOT directly referenced by `printf` (like the `"DHT11 read failed"` error message). This exercise builds your ability to map out all human-readable data in a binary. + +#### 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\0x001a_operators.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Disassemble Main to Find printf Calls + +```gdb +(gdb) x/60i 0x10000234 +``` + +Look for repeated patterns of: +``` +ldr r0, [pc, #offset] ; Load format string address +movs r1, #value ; Load the value to print +bl printf ; Call printf +``` + +Each `ldr r0` before a `bl printf` loads the format string address from the literal pool. + +##### Step 3: Examine the Literal Pool + +Find the literal pool after the loop branch (typically after the `b.n` instruction). Examine the word values: + +```gdb +(gdb) x/10wx +``` + +Each word that falls in the `0x10003xxx` range is likely a pointer to a string in `.rodata`. + +##### Step 4: Read Each Format String + +For each address found in the literal pool, use `x/s` to read the string: + +```gdb +(gdb) x/s +``` + +Document each one. You should find at least these format strings: +- `"arithmetic_operator: %d\r\n"` +- `"increment_operator: %d\r\n"` +- `"relational_operator: %d\r\n"` +- `"logical_operator: %d\r\n"` +- `"bitwise_operator: %d\r\n"` +- `"assignment_operator: %d\r\n"` +- `"Humidity: %.1f%%, Temperature: %.1fΒ°C\r\n"` +- `"DHT11 read failed"` + +##### Step 5: Build a String Catalog + +Fill in a table like this (addresses will vary β€” use the ones you find): + +| Address | File Offset | String Content | Used By | +| -------------- | ----------- | --------------------------------------- | ----------- | +| `0x10003xxx` | `0x3xxx` | `"arithmetic_operator: %d\r\n"` | printf #1 | +| `0x10003xxx` | `0x3xxx` | `"increment_operator: %d\r\n"` | printf #2 | +| `0x10003xxx` | `0x3xxx` | `"relational_operator: %d\r\n"` | printf #3 | +| `0x10003xxx` | `0x3xxx` | `"logical_operator: %d\r\n"` | printf #4 | +| `0x10003xxx` | `0x3xxx` | `"bitwise_operator: %d\r\n"` | printf #5 | +| `0x10003xxx` | `0x3xxx` | `"assignment_operator: %d\r\n"` | printf #6 | +| `0x10003xxx` | `0x3xxx` | `"Humidity: %.1f%%, Temperature: %.1fΒ°C\r\n"` | printf #7 | +| `0x10003xxx` | `0x3xxx` | `"DHT11 read failed"` | puts | + +##### Step 6: Verify in HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001a_operators\build\0x001a_operators.bin` +2. For each string, press **Ctrl+G** and enter the file offset +3. Verify you can read the ASCII text in the right panel +4. Note how the strings are stored consecutively in memory, each terminated by `0x00` + +##### Step 7: Search for Hidden Strings + +Scroll through the `.rodata` region in HxD (typically starting around offset `0x3000`+) and look for any ASCII text in the right panel that you didn't find via `printf` calls. Library functions may have their own strings. + +#### Expected Output + +After completing this exercise, you should be able to: +- Trace `printf` calls to their format string arguments using GDB +- Read string addresses from literal pools +- Build a complete catalog of strings in a binary +- Navigate to specific offsets in a hex editor to verify string data + +#### Questions for Reflection + +###### Question 1: The format string `"Humidity: %.1f%%, Temperature: %.1fΒ°C\r\n"` uses `%%` to print a literal percent sign. What would happen if you patched one of the `%` characters to a space (`0x20`)? + +###### Question 2: If you patched the string `"arithmetic_operator: %d\r\n"` to `"HACKED_OPERATOR!: %d\r\n"` (same length, 27 characters + null), what would the serial output show? Would the actual computed value change? + +###### Question 3: The strings are stored consecutively in `.rodata`. If you made `"arithmetic_operator: %d\r\n"` one byte longer, which string would be corrupted and how? + +###### Question 4: Why does the compiler use `puts` instead of `printf` for `"DHT11 read failed"`? What's the difference between the two functions for strings with no format specifiers? + +#### Tips and Hints +- Format strings always start with a printable ASCII character β€” look for bytes in the `0x20`-`0x7E` range +- The `\r\n` at the end of format strings shows up as bytes `0x0D 0x0A` +- Use HxD's **Search** β†’ **Find** (Ctrl+F) with "Text string" mode to search for known text like "arithmetic" +- Strings in `.rodata` are typically 4-byte aligned, so there may be padding bytes (`0x00`) between them +- The `Β°` character in "Β°C" is a multi-byte UTF-8 sequence (`0xC2 0xB0`), not a single byte diff --git a/WEEKS/WEEK09/WEEK09-SLIDES.pdf b/WEEKS/WEEK09/WEEK09-SLIDES.pdf new file mode 100644 index 0000000..130da37 Binary files /dev/null and b/WEEKS/WEEK09/WEEK09-SLIDES.pdf differ diff --git a/WEEKS/WEEK09/WEEK09.md b/WEEKS/WEEK09/WEEK09.md new file mode 100644 index 0000000..8f5a9f2 --- /dev/null +++ b/WEEKS/WEEK09/WEEK09.md @@ -0,0 +1,1215 @@ +# Week 9: Operators in Embedded Systems: Debugging and Hacking Operators w/ DHT11 Temperature & Humidity Sensor Single-Wire Protocol Basics. + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand all six types of C operators (arithmetic, increment, relational, logical, bitwise, assignment) +- Know how the DHT11 temperature and humidity sensor communicates with the Pico 2 +- Understand how post-increment operators affect variable values +- Navigate to the Reset_Handler and main function in stripped binaries +- Identify function arguments by analyzing register values in Ghidra +- Understand IEEE-754 floating-point representation +- Hack floating-point constants to manipulate sensor readings + +--- + +## πŸ“š Part 1: Understanding C Operators + +### What Are Operators? + +**Operators** are symbols that tell the compiler to perform specific mathematical, logical, or data manipulation operations. Think of them as the "verbs" of programming - they describe actions to perform on data. + +### The Six Types of Operators + +| Type | Example | What It Does | +| -------------- | -------------------- | ----------------------------------- | +| **Arithmetic** | `x * y` | Math operations (+, -, *, /, %) | +| **Increment** | `x++` or `++x` | Increase/decrease by 1 | +| **Relational** | `x > y` | Compare values (returns true/false) | +| **Logical** | `(x > y) && (y > x)` | Combine conditions (AND, OR, NOT) | +| **Bitwise** | `x << 1` | Manipulate individual bits | +| **Assignment** | `x += 5` | Assign and modify values | + +--- + +## πŸ“š Part 2: Arithmetic Operators + +### Basic Math in C + +Arithmetic operators perform mathematical calculations: + +```c +int x = 5; +int y = 10; +int result = x * y; // result = 50 +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Arithmetic Operators β”‚ +β”‚ β”‚ +β”‚ Operator Example Result Description β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ + 5 + 10 15 Addition β”‚ +β”‚ - 10 - 5 5 Subtraction β”‚ +β”‚ * 5 * 10 50 Multiplication β”‚ +β”‚ / 10 / 5 2 Division β”‚ +β”‚ % 10 % 3 1 Modulus (remainder) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 3: Increment and Decrement Operators + +### Pre-Increment vs Post-Increment + +This is where many beginners get confused! There are TWO ways to increment: + +```c +int x = 5; +int a = x++; // Post-increment: a = 5, then x becomes 6 +int b = ++x; // Pre-increment: x becomes 7, then b = 7 +``` + +**The Key Difference:** + +| Type | Syntax | When Value Changes | Example Result | +| ------------------ | ------ | --------------------- | -------------------- | +| **Post-increment** | `x++` | AFTER the expression | `a = x++` β†’ a=5, x=6 | +| **Pre-increment** | `++x` | BEFORE the expression | `b = ++x` β†’ x=7, b=7 | + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Post-Increment (x++) β”‚ +β”‚ β”‚ +β”‚ int x = 5; β”‚ +β”‚ int result = x++; β”‚ +β”‚ β”‚ +β”‚ Step 1: result = x (result gets 5) β”‚ +β”‚ Step 2: x = x + 1 (x becomes 6) β”‚ +β”‚ β”‚ +β”‚ Final: result = 5, x = 6 β”‚ +β”‚ β”‚ +β”‚ Think of it as: "Use first, THEN increment" β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 4: Relational Operators + +### Comparing Values + +Relational operators compare two values and return `true` (1) or `false` (0): + +```c +int x = 6; +int y = 10; +bool result = (x > y); // false, because 6 is NOT greater than 10 +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Relational Operators β”‚ +β”‚ β”‚ +β”‚ Operator Example Result Description β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ > 6 > 10 false Greater than β”‚ +β”‚ < 6 < 10 true Less than β”‚ +β”‚ >= 6 >= 6 true Greater than or equal β”‚ +β”‚ <= 6 <= 10 true Less than or equal β”‚ +β”‚ == 6 == 10 false Equal to β”‚ +β”‚ != 6 != 10 true Not equal to β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 5: Logical Operators + +### Combining Conditions + +Logical operators combine multiple conditions: + +```c +int x = 6; +int y = 10; +bool result = (x > y) && (y > x); // false AND true = false +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Logical Operators β”‚ +β”‚ β”‚ +β”‚ Operator Name Example Result β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ && AND true && true true β”‚ +β”‚ && AND true && false false β”‚ +β”‚ || OR true || false true β”‚ +β”‚ || OR false || false false β”‚ +β”‚ ! NOT !true false β”‚ +β”‚ β”‚ +β”‚ Truth Table for AND (&&): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ A β”‚ B β”‚ A && B β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ false β”‚ false β”‚ false β”‚ β”‚ +β”‚ β”‚ false β”‚ true β”‚ false β”‚ β”‚ +β”‚ β”‚ true β”‚ false β”‚ false β”‚ β”‚ +β”‚ β”‚ true β”‚ true β”‚ true β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 6: Bitwise Operators + +### Manipulating Individual Bits + +Bitwise operators work on the binary representation of numbers: + +```c +int x = 6; // Binary: 0b00000110 +int result = x << 1; // Shift left by 1: 0b00001100 = 12 +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Bitwise Left Shift (<<) β”‚ +β”‚ β”‚ +β”‚ x = 6 = 0b00000110 β”‚ +β”‚ β”‚ +β”‚ x << 1 means "shift all bits LEFT by 1 position" β”‚ +β”‚ β”‚ +β”‚ Before: 0 0 0 0 0 1 1 0 (6) β”‚ +β”‚ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ β”‚ +β”‚ After: 0 0 0 0 1 1 0 0 (12) β”‚ +β”‚ β”‚ +β”‚ Each left shift DOUBLES the value! β”‚ +β”‚ 6 << 1 = 12 (same as 6 * 2) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +**Common Bitwise Operators:** + +| Operator | Name | Example | Result | +| -------- | ----------- | -------- | ------------------ | +| `<<` | Left shift | `6 << 1` | 12 (multiply by 2) | +| `>>` | Right shift | `6 >> 1` | 3 (divide by 2) | +| `&` | AND | `6 & 3` | 2 (bits in common) | +| `\|` | OR | `6 \| 3` | 7 (all set bits) | +| `^` | XOR | `6 ^ 3` | 5 (different bits) | +| `~` | NOT | `~6` | Inverts all bits | + +--- + +## πŸ“š Part 7: Assignment Operators + +### Shorthand for Math + Assign + +Assignment operators combine math with assignment: + +```c +int x = 6; +x += 5; // Same as: x = x + 5; Result: x = 11 +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Compound Assignment Operators β”‚ +β”‚ β”‚ +β”‚ Operator Example Equivalent To Result (if x=6) β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ += x += 5 x = x + 5 x = 11 β”‚ +β”‚ -= x -= 2 x = x - 2 x = 4 β”‚ +β”‚ *= x *= 3 x = x * 3 x = 18 β”‚ +β”‚ /= x /= 2 x = x / 2 x = 3 β”‚ +β”‚ %= x %= 4 x = x % 4 x = 2 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 8: Understanding the DHT11 Sensor + +### What is the DHT11? + +The **DHT11** is a low-cost digital temperature and humidity sensor. It uses a single wire for communication (plus power and ground). + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DHT11 Pinout β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ DHT11 β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ 1 2 3 4 β”‚ β”‚ +β”‚ β””β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”€β”¬β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ VCC DATA NC GND β”‚ +β”‚ β”‚ +β”‚ Pin 1: VCC (3.3V or 5V) β”‚ +β”‚ Pin 2: DATA (connect to GPIO) β”‚ +β”‚ Pin 3: Not Connected β”‚ +β”‚ Pin 4: GND (Ground) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### DHT11 Specifications + +| Parameter | Range | Accuracy | +| --------------- | ------------ | -------- | +| **Humidity** | 20% - 90% RH | Β±5% RH | +| **Temperature** | 0Β°C - 50Β°C | Β±2Β°C | + +### How DHT11 Communication Works + +The DHT11 uses a custom one-wire protocol: + +1. **Host sends start signal** - Pull data line LOW for 18ms +2. **DHT11 responds** - Pulls line LOW for 80Β΅s, then HIGH for 80Β΅s +3. **Data transmission** - 40 bits sent (8 humidity int, 8 humidity decimal, 8 temp int, 8 temp decimal, 8 checksum) + +--- + +## πŸ“š Part 9: Understanding Pointers (Quick Review) + +### The & Operator (Address-Of) + +When you see `&variable`, it means "the memory address of variable": + +```c +float hum, temp; + +// Pass ADDRESSES to the function so it can modify our variables +if (dht11_read(&hum, &temp)) { + printf("Humidity: %.1f%%, Temperature: %.1fΒ°C\n", hum, temp); +} +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Passing by Reference β”‚ +β”‚ β”‚ +β”‚ Stack Memory β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Address 0x20000008: hum │◄─── &hum (passed to function) β”‚ +β”‚ β”‚ Value: 51.0 β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ Address 0x2000000C: temp │◄─── &temp (passed to function) β”‚ +β”‚ β”‚ Value: 23.8 β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ dht11_read() receives the ADDRESSES, so it can write β”‚ +β”‚ new values directly into hum and temp! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 10: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. A DHT11 temperature and humidity sensor +7. The sample project: `0x001a_operators` + +### Hardware Setup + +Connect your DHT11 like this: + +| DHT11 Pin | Pico 2 Pin | +| --------- | ---------- | +| VCC | 3.3V | +| DATA | GPIO 4 | +| GND | GND | + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DHT11 Wiring β”‚ +β”‚ β”‚ +β”‚ Pico 2 DHT11 Sensor β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 4 │────────── DATA ──────►│ DATA β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 3.3V │────────── VCC ───────►│ VCC β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GND │────────── GND ───────►│ GND β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Note: Some DHT11 modules have a built-in pull-up resistor. β”‚ +β”‚ If yours doesn't, add a 10K resistor between DATA and VCC. β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Project Structure + +``` +Embedded-Hacking/ +β”œβ”€β”€ 0x001a_operators/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x001a_operators.uf2 +β”‚ β”‚ └── 0x001a_operators.bin +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ └── 0x001a_operators.c +β”‚ └── dht11.h +└── uf2conv.py +``` + +--- + +## πŸ”¬ Part 11: Hands-On Tutorial - The Operators Code + +### Step 1: Review the Source Code + +Let's examine the operators code: + +**File: `0x001a_operators.c`** + +```c +#include +#include "pico/stdlib.h" +#include "dht11.h" + +int main(void) { + stdio_init_all(); + + dht11_init(4); + + int x = 5; + int y = 10; + int arithmetic_operator = (x * y); + int increment_operator = x++; + bool relational_operator = (x > y); + bool logical_operator = (x > y) && (y > x); + int bitwise_operator = (x<<1); // x is now 6 because of x++ or 0b00000110 and (x<<1) is 0b00001100 or 12 + int assignment_operator = (x += 5); + + while (true) { + printf("arithmetic_operator: %d\r\n", arithmetic_operator); + printf("increment_operator: %d\r\n", increment_operator); + printf("relational_operator: %d\r\n", relational_operator); + printf("logical_operator: %d\r\n", logical_operator); + printf("bitwise_operator: %d\r\n", bitwise_operator); + printf("assignment_operator: %d\r\n", assignment_operator); + + float hum, temp; + if (dht11_read(&hum, &temp)) { + printf("Humidity: %.1f%%, Temperature: %.1fΒ°C\r\n", hum, temp); + } else { + printf("DHT11 read failed\r\n"); + } + + sleep_ms(2000); + } +} +``` + +### Step 2: Understand the Variable Flow + +Let's trace through what happens to `x`: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Variable x Through the Program β”‚ +β”‚ β”‚ +β”‚ Line β”‚ x value β”‚ Result β”‚ +β”‚ ──────────────────┼─────────┼───────────────────────────────── β”‚ +β”‚ int x = 5; β”‚ 5 β”‚ x initialized to 5 β”‚ +β”‚ x * y β”‚ 5 β”‚ arithmetic = 5 * 10 = 50 β”‚ +β”‚ x++ β”‚ 5β†’6 β”‚ increment = 5 (then x becomes 6) β”‚ +β”‚ x > y β”‚ 6 β”‚ relational = (6 > 10) = false β”‚ +β”‚ (x>y) && (y>x) β”‚ 6 β”‚ logical = false && true = false β”‚ +β”‚ x << 1 β”‚ 6 β”‚ bitwise = 6 << 1 = 12 β”‚ +β”‚ x += 5 β”‚ 6β†’11 β”‚ assignment = 6 + 5 = 11 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 3: 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 `0x001a_operators.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +Open your serial monitor (PuTTY at 115200 baud) and you should see: + +``` +arithmetic_operator: 50 +increment_operator: 5 +relational_operator: 0 +logical_operator: 0 +bitwise_operator: 12 +assignment_operator: 11 +Humidity: 51.0%, Temperature: 23.8Β°C +``` + +**Understanding the Output:** + +| Variable | Value | Explanation | +| ------------------- | ------ | --------------------------------------------- | +| arithmetic_operator | 50 | 5 Γ— 10 = 50 | +| increment_operator | 5 | Post-increment returns value BEFORE increment | +| relational_operator | 0 | 6 > 10 is false (0) | +| logical_operator | 0 | false AND true = false (0) | +| bitwise_operator | 12 | 6 (0b0110) << 1 = 12 (0b1100) | +| assignment_operator | 11 | 6 + 5 = 11 | +| Humidity | 51.0% | Real reading from DHT11 | +| Temperature | 23.8Β°C | Real reading from DHT11 | + +--- + +## πŸ”¬ Part 12: Debugging with GDB + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and 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" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```powershell +arm-none-eabi-gdb build\0x001a_operators.elf +``` + +### Step 7: Connect to the Remote Target + +Inside GDB, type: + +``` +target remote :3333 +``` + +This connects GDB to OpenOCD. + +### Step 8: Halt the Running Binary + +``` +monitor reset halt +``` + +This stops the Pico 2 so we can examine its state. + +### Step 9: Examine Main Function + +Let's examine the main function. Disassemble from the entry point: + +``` +x/60i 0x10000234 +``` + +You should see the operator calculations and function calls: + +``` +0x10000234: push {r4, r5, r6, r7, lr} +0x10000236: sub sp, #20 +0x10000238: bl 0x10003014 ; stdio_init_all +0x1000023c: movs r0, #4 ; GPIO 4 for DHT11 +0x1000023e: bl 0x100003b4 ; dht11_init +... +``` + +### Step 10: Set a Breakpoint at Main + +``` +b *0x10000234 +c +``` + +GDB responds: +``` +Breakpoint 1 at 0x10000234 +Continuing. + +Breakpoint 1, 0x10000234 in ?? () +``` + +### Step 11: Find the Operator Calculations + +The compiler likely optimized many of these calculations at compile time. Look for immediate values: + +``` +x/30i 0x10000240 +``` + +You may see values like: +- `#0x32` (50) for arithmetic_operator +- `#0x5` (5) for increment_operator +- `#0x0` (0) for relational and logical operators +- `#0xc` (12) for bitwise_operator +- `#0xb` (11) for assignment_operator + +### Step 12: Examine Printf Arguments + +Set a breakpoint before the first printf and examine registers: + +``` +b *0x10000262 +c +i r r0 r1 +``` + +You should see: +- `r0` = address of format string +- `r1` = value to print (50 for arithmetic_operator) + +### Step 13: Examine the Format Strings + +``` +x/s 0x10003xxx +``` + +Find the format strings like: +``` +"arithmetic_operator: %d\r\n" +"increment_operator: %d\r\n" +... +``` + +### Step 14: Examine DHT11 Function Call + +Find where dht11_read is called: + +``` +x/10i 0x100002a0 +``` + +You'll see stack addresses being passed as arguments: +``` +add r0, sp, #0x8 ; Address of hum variable +add r1, sp, #0xc ; Address of temp variable +bl dht11_read +``` + +### Step 15: Watch the Float Values + +After dht11_read returns, examine the float values on the stack: + +``` +x/2fw $sp+8 +``` + +This shows the humidity and temperature as floats. + +### Step 16: Step Through the Loop + +Continue execution and watch the values: + +``` +c +``` + +The program will loop, printing values to serial. + +--- + +## πŸ”¬ Part 13: Setting Up Ghidra for Analysis + +### Step 17: Start Ghidra + +Open a terminal and type: + +```powershell +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** β†’ **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x001a_operators` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Open your file explorer +2. Navigate to the `0x001a_operators/build/` folder +3. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**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` +3. Click **OK** + +### Step 21: Analyze the Binary + +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. + +--- + +## πŸ”¬ Part 14: Finding the Reset_Handler + +### Step 22: Understand the Vector Table + +In ARM Cortex-M, the **vector table** is at the base of flash (0x10000000). The second entry (offset 4) contains the Reset_Handler address. + +> πŸ“– **Datasheet Reference:** On the RP2350, the vector table location is configured via the VTOR (Vector Table Offset Register) item in the IMAGE_DEF block (Section 5.9.3.3, p. 425). The XIP flash base address is `0x10000000` (Section 2.2, p. 31). The Cortex-M33 vector table format follows the standard ARMv8-M layout with Initial SP at offset 0x00 and Reset at offset 0x04. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ ARM Vector Table at 0x10000000 β”‚ +β”‚ β”‚ +β”‚ Offset Contents Description β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ 0x00 Initial SP value Stack pointer at reset β”‚ +β”‚ 0x04 Reset_Handler addr First code to execute β”‚ +β”‚ 0x08 NMI_Handler addr Non-maskable interrupt β”‚ +β”‚ 0x0C HardFault_Handler Hard fault handler β”‚ +β”‚ ... β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 23: Read the Reset_Handler Address + +1. Press `G` (Go to address) and type `10000004` +2. You'll see bytes like `5d 01 00 10` (your exact bytes may vary) + +**Important:** This is **little-endian**, so we need to reverse the byte order! + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Little-Endian Byte Order β”‚ +β”‚ β”‚ +β”‚ In memory: 5d 01 00 10 β”‚ +β”‚ Reversed: 10 00 01 5d β”‚ +β”‚ As hex: 0x1000015d β”‚ +β”‚ β”‚ +β”‚ But wait! ARM uses the THUMB bit! β”‚ +β”‚ The lowest bit indicates Thumb mode (always set for Cortex-M) β”‚ +β”‚ Real address: 0x1000015d - 1 = 0x1000015c β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 24: Navigate to Reset_Handler + +1. Press `G` and type `1000015c` (or your calculated address) +2. You might see undefined data - that's OK! + +### Step 25: Create the Reset_Handler Function + +If Ghidra didn't automatically recognize this as a function: + +1. Click on the address `0x1000015c` +2. Right-click and press `F` to create a function +3. Right-click β†’ **Edit Function Signature** +4. Change the name to `Reset_Handler` +5. Click **OK** + +### Step 26: Find Main from Reset_Handler + +The Reset_Handler typically calls three functions: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Reset_Handler Flow (crt0.S) β”‚ +β”‚ β”‚ +β”‚ Reset_Handler: β”‚ +β”‚ 1. Call some_init() ◄── Initialize hardware β”‚ +β”‚ 2. Call main() ◄── THIS IS WHAT WE WANT! β”‚ +β”‚ 3. Call exit() ◄── Never returns β”‚ +β”‚ β”‚ +β”‚ The MIDDLE function is main! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +Look at the end of Reset_Handler for three function calls. The middle one is `main`! + +### Step 27: Navigate to Main + +1. Double-click on the middle function call (should be around `0x10000234`) +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +--- + +## πŸ”¬ Part 15: Resolving Functions in Ghidra + +### Step 28: Resolve stdio_init_all + +The first function call in main is `stdio_init_all`: + +1. Find the call at approximately `0x10000238` +2. Double-click to navigate to the function +3. Right-click β†’ **Edit Function Signature** +4. Change to: `bool stdio_init_all(void)` +5. Click **OK** + +### Step 29: Resolve dht11_init + +Look for a function call where `r0` is loaded with `0x4`: + +```assembly +movs r0, #0x4 ; GPIO pin 4 +bl FUN_xxxxx ; dht11_init +``` + +**How do we know it's dht11_init?** +- The argument `4` is the GPIO pin number +- We physically connected the DHT11 to GPIO 4! + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void dht11_init(uint pin)` +3. Click **OK** + +### Step 30: Resolve printf + +Look for repeated function calls with string addresses: + +1. Find a call like the one at `0x10000262` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int printf(char *format, ...)` +4. Check the **Varargs** checkbox +5. Click **OK** + +### Step 31: Resolve sleep_ms + +Look for a function call where `r0` is loaded with `0x7d0` (2000 in decimal): + +```assembly +ldr r0, =0x7d0 ; 2000 milliseconds +bl FUN_xxxxx ; sleep_ms +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +### Step 32: Resolve dht11_read + +This is trickier! Look for a function call with TWO address arguments: + +```assembly +add r0, sp, #0x8 ; Address of hum on stack +add r1, sp, #0xc ; Address of temp on stack +bl FUN_xxxxx ; dht11_read +``` + +**Understanding the stack offsets:** +- `sp + 0x8` = address of `hum` variable +- `sp + 0xc` = address of `temp` variable +- These are `float` pointers passed to the function + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `bool dht11_read(float *humidity, float *temperature)` +3. Click **OK** + +### Step 33: Resolve puts + +Look for a function call after the `if` statement that takes a single string argument: + +```assembly +ldr r0, ="DHT11 read failed" +bl FUN_xxxxx ; puts +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `int puts(char *s)` +3. Click **OK** + +--- + +## πŸ”¬ Part 16: Understanding IEEE-754 Floating-Point + +### What is IEEE-754? + +IEEE-754 is the standard for representing decimal numbers in binary. A 32-bit float is divided into three parts: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ IEEE-754 Single Precision (32-bit) Float β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ S β”‚ Exponent β”‚ Mantissa (Fraction) β”‚ β”‚ +β”‚ β”‚ 1 β”‚ 8 bits β”‚ 23 bits β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ bit bits bits β”‚ +β”‚ 31 30-23 22-0 β”‚ +β”‚ β”‚ +β”‚ Value = (-1)^S Γ— (1 + Mantissa) Γ— 2^(Exponent - 127) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Example: Decoding 0x3dcccccc (0.1f) + +Let's decode the bytes `cc cc cc 3d`: + +1. **Reverse for little-endian:** `0x3dcccccc` +2. **Convert to binary:** `00111101 11001100 11001100 11001100` +3. **Extract fields:** + - Sign (bit 31): `0` (positive) + - Exponent (bits 30-23): `01111011` = 123 + - Mantissa (bits 22-0): `10011001100110011001100` + +4. **Calculate value:** + - Actual exponent: 123 - 127 = -4 + - Mantissa value: 1.6 (approximately) + - Final value: 1.6 Γ— 2^(-4) β‰ˆ 0.1 + +### Example: Encoding -1.0f as 0xbf800000 + +For the number -1.0: + +1. **Sign:** 1 (negative) +2. **Exponent:** 127 (for 2^0 = 1) +3. **Mantissa:** 0 (because value is exactly 1.0) + +Binary: `1 01111111 00000000000000000000000` +Hex: `0xbf800000` +Little-endian: `00 00 80 bf` + +### Python for Float Conversion + +```python +import struct + +# Decode bytes to float +bytes_data = bytes.fromhex('cdcccc3d') +value = struct.unpack('>, &, \|, ^) | +| **DHT11** | Digital humidity and temperature sensor | +| **Exponent** | Power of 2 in IEEE-754 float representation | +| **IEEE-754** | Standard for floating-point number representation | +| **Increment Op** | Operators that add/subtract 1 (++, --) | +| **Little-Endian** | Byte order where least significant byte comes first | +| **Logical Op** | Operators combining conditions (&&, \|\|, !) | +| **Mantissa** | Fractional part of IEEE-754 float | +| **Post-Increment** | `x++` - returns value, then increments | +| **Pre-Increment** | `++x` - increments, then returns value | +| **Relational Op** | Operators comparing values (<, >, ==, !=) | +| **Reset_Handler** | First function executed after CPU reset | +| **Thumb Bit** | Lowest bit of ARM address indicating Thumb mode | +| **Vector Table** | Table of exception/interrupt handler addresses | +| **vfma.f32** | ARM floating-point fused multiply-add instruction | +| **vadd.f32** | ARM floating-point add instruction | + +--- + +## πŸ”— Additional Resources + +### IEEE-754 Float Quick Reference + +| Value | Hex Encoding | Bytes (LE) | +| ----- | ------------ | ----------- | +| 0.1 | 0x3dcccccd | cd cc cc 3d | +| 1.0 | 0x3f800000 | 00 00 80 3f | +| -1.0 | 0xbf800000 | 00 00 80 bf | +| 2.0 | 0x40000000 | 00 00 00 40 | +| -2.0 | 0xc0000000 | 00 00 00 c0 | +| 5.0 | 0x40a00000 | 00 00 a0 40 | +| 10.0 | 0x41200000 | 00 00 20 41 | + +### ARM Floating-Point Instructions + +| Instruction | Description | +| --------------------- | ---------------------------------------- | +| `vfma.f32 Sd, Sn, Sm` | Sd = Sd + (Sn Γ— Sm) (fused multiply-add) | +| `vadd.f32 Sd, Sn, Sm` | Sd = Sn + Sm | +| `vsub.f32 Sd, Sn, Sm` | Sd = Sn - Sm | +| `vmul.f32 Sd, Sn, Sm` | Sd = Sn Γ— Sm | +| `vldr.f32 Sd, [addr]` | Load float from memory | +| `vstr.f32 Sd, [addr]` | Store float to memory | + +--- + +## 🚨 Real-World Implications + +### Why This Matters + +Imagine a scenario where temperature sensors control critical systems: + +- **Industrial processes** - Chemical reactions that must stay within temperature ranges +- **Medical equipment** - Refrigerators storing vaccines or organs +- **Nuclear facilities** - Cooling systems for reactors +- **HVAC systems** - Climate control in sensitive environments + +By manipulating sensor readings, an attacker could: +- Cause equipment to overheat while displaying normal temperatures +- Trigger false alarms +- Bypass safety interlocks +- Cause physical damage or safety hazards + +### Defensive Measures + +1. **Redundant sensors** - Multiple sensors with consistency checks +2. **Physical security** - Prevent access to programming interfaces +3. **Anomaly detection** - Alert on sudden reading changes + +--- + +**Remember:** The techniques you learned today can be used for good (security research, debugging) or bad (sabotage, fraud). Always use your skills ethically and legally. Understanding how attacks work helps us build more secure systems! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG00.svg b/WEEKS/WEEK09/slides/WEEK09-IMG00.svg new file mode 100644 index 0000000..3aadd8e --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 09 + + +Operators in Embedded Systems: +Debugging and Hacking Operators +w/ DHT11 Sensor Single-Wire Protocol + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG01.svg b/WEEKS/WEEK09/slides/WEEK09-IMG01.svg new file mode 100644 index 0000000..b431706 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG01.svg @@ -0,0 +1,74 @@ + + + + +C Operators Overview +Six Types of Operators in C + + + +Arithmetic + ++ - * / % +Math operations +5 * 10 = 50 + + +Increment + +x++ ++x x-- +Add/subtract by 1 +x++ returns old val + + +Relational + +> < >= <= == != +Compare values +(6 > 10) = false + + + +Logical + +&& || ! +Combine conditions +AND, OR, NOT + + +Bitwise + +<< >> & | ^ ~ +Manipulate bits +6 << 1 = 12 + + +Assignment + ++= -= *= /= +Assign and modify +x += 5 (x=x+5) + + + +This Week's Program +0x001a_operators.c demonstrates all 6 types +DHT11 temperature/humidity sensor + operator calculations + + + +KEY: +Compiler pre-computes constant expressions +In the binary, most operators become immediate values + \ No newline at end of file diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG02.svg b/WEEKS/WEEK09/slides/WEEK09-IMG02.svg new file mode 100644 index 0000000..3ba9f91 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG02.svg @@ -0,0 +1,75 @@ + + + + +Arithmetic & Increment +Math Operations and Post/Pre Increment + + + +Arithmetic Operators + ++ +5 + 10 = 15 +Addition +- +10 - 5 = 5 +Subtraction +* +5 * 10 = 50 +Multiplication +/ +10 / 5 = 2 +Division +% +10 % 3 = 1 +Modulus + + + +Post vs Pre Increment + + +Post: x++ +Use value THEN increment +a = x++ --> a=5, x=6 + + +Pre: ++x +Increment THEN use value +b = ++x --> x=7, b=7 + + + +Post-Increment Step by Step + + +int x = 5; +int result = x++; + +Step 1: result = x +result gets 5 +Step 2: x = x + 1 +x becomes 6 + +Final: result = 5 +x = 6 +"Use first, THEN increment" + + + +In our code: +int increment_operator = x++; +x was 5, so increment_operator = 5, then x becomes 6 + \ No newline at end of file diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG03.svg b/WEEKS/WEEK09/slides/WEEK09-IMG03.svg new file mode 100644 index 0000000..1244936 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG03.svg @@ -0,0 +1,95 @@ + + + + +Relational & Logical +Comparing Values and Combining Conditions + + + +Relational Operators +Compare two values --> true (1) or false (0) + +> +6 > 10 +false +Greater than +< +6 < 10 +true +Less than +>= +6 >= 6 +true +Greater/equal +<= +6 <= 10 +true +Less or equal +== +6 == 10 +false +Equal to +!= +6 != 10 +true +Not equal + + + +Logical Operators +Combine conditions into one result + +&& +AND -- both must be true +|| +OR -- at least one true +! +NOT -- inverts result + + +AND Truth Table + +A +B +A && B +false +false +false +false +true +false +true +false +false +true +true +true + + + +In Our Code (x=6, y=10) + +bool relational = (x > y); +(6 > 10) = false = 0 +bool logical = (x>y) && (y>x); +false && true = false = 0 + + + +In the binary: +Both compile to immediate #0 +Compiler pre-computes: constants are known at compile time +Result 0 = false, Result 1 = true + \ No newline at end of file diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG04.svg b/WEEKS/WEEK09/slides/WEEK09-IMG04.svg new file mode 100644 index 0000000..02cb827 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG04.svg @@ -0,0 +1,89 @@ + + + + +Bitwise & Assignment +Bit Manipulation and Compound Assignment + + + +Bitwise Operators + +<< +6 << 1 = 12 +Left shift +>> +6 >> 1 = 3 +Right shift +& +6 & 3 = 2 +AND +| +6 | 3 = 7 +OR +^ +6 ^ 3 = 5 +XOR +~ +~6 +NOT (invert) + + +Left shift = multiply by 2 + +0 0 0 0 0 1 1 0 += 6 +0 0 0 0 1 1 0 0 += 12 + + + +Assignment Operators +Shorthand for math + assign + ++= +x += 5 +x = x + 5 +-= +x -= 2 +x = x - 2 +*= +x *= 3 +x = x * 3 +/= +x /= 2 +x = x / 2 +%= +x %= 4 +x = x % 4 + + +In our code (x=6 after x++): + +x += 5 --> 6 + 5 = 11 + + + +In Our Code (x=6, y=10) + +int bitwise = (x<<1); +6 << 1 = 12 (0b0110 --> 0b1100) + + + +Expected Output +bitwise_operator: 12 +assignment_operator: 11 +Both pre-computed by compiler as immediates + \ No newline at end of file diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG05.svg b/WEEKS/WEEK09/slides/WEEK09-IMG05.svg new file mode 100644 index 0000000..4975302 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG05.svg @@ -0,0 +1,72 @@ + + + + +DHT11 Sensor +Single-Wire Temperature and Humidity + + + +DHT11 Pinout + + +DHT11 +1:VCC 2:DATA 3:NC 4:GND + +Humidity: 20-90% RH (+/-5%) +Temp: 0-50C (+/-2C) +Protocol: custom one-wire + + + +Wiring to Pico 2 + + +Pico + + +DHT11 + + + + +GPIO 4 = DATA +3.3V = VCC +GND = GND + + + +1. Host pulls LOW 18ms +2. DHT11 responds, sends 40 bits + + + +Source Code: 0x001a_operators.c + +int x = 5, y = 10; +int arithmetic = (x * y); +// 50 +int increment = x++; +// 5 (post) +bool relational = (x > y); +// false +bool logical = (x>y)&&(y>x); +// false +int bitwise = (x<<1); +// 12 +int assignment = (x += 5); +// 11 +float hum, temp; +dht11_read(&hum, &temp); + \ No newline at end of file diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG06.svg b/WEEKS/WEEK09/slides/WEEK09-IMG06.svg new file mode 100644 index 0000000..ef11927 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG06.svg @@ -0,0 +1,75 @@ + + + + +Variable Flow +Tracing x Through Every Operator + + + +Tracing x Step-by-Step + + +Line +x +Result + + + +int x = 5, y = 10; +5 +x initialized to 5 + + +int arithmetic = (x * y); +5 +arithmetic = 50 + + +int increment = x++; +5-->6 +increment = 5 +use THEN increment + + +bool relational = (x > y); +6 +relational = false +6 > 10 is false + + +bool logical = (x>y)&&(y>x); +6 +logical = false +false AND true = false + + +int bitwise = (x<<1); +6 +bitwise = 12 +0b0110 << 1 = 0b1100 + + +int assignment = (x += 5); +6-->11 +assignment = 11 +6 + 5 = 11 + + + +DHT11 Output +Humidity: 51.0% +Temperature: 23.8C +dht11_read(&hum, &temp) -- passes addresses so function can write values + diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG07.svg b/WEEKS/WEEK09/slides/WEEK09-IMG07.svg new file mode 100644 index 0000000..1e5d297 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG07.svg @@ -0,0 +1,77 @@ + + + + +Vector Table +Finding Reset_Handler and main() + + + +ARM Vector Table +Base address: 0x10000000 + +Offset +Contents +Purpose + + +0x00 +Initial SP +Stack ptr + +0x04 +Reset_Handler +Entry point + +0x08 +NMI_Handler +NMI + +0x0C +HardFault +Fault + + + +Decoding the Address + +At 0x10000004: +Bytes: 5d 01 00 10 + +Step 1: Reverse (little-endian) +10 00 01 5d = 0x1000015d + +Step 2: Remove Thumb bit +0x1000015d - 1 = 0x1000015c + + + +Reset_Handler --> main() + +Reset_Handler at 0x1000015c calls 3 functions: + + +Call 1: some_init() +Hardware initialization + +Call 2: main() +THIS IS WHAT WE WANT +Address: 0x10000234 + +Call 3: exit() +Never returns + +The MIDDLE function call is always main() +Navigate to 0x10000234 in Ghidra to find it + diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG08.svg b/WEEKS/WEEK09/slides/WEEK09-IMG08.svg new file mode 100644 index 0000000..f520db0 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG08.svg @@ -0,0 +1,81 @@ + + + + +IEEE-754 Floats +How Computers Store Decimal Numbers + + + +32-bit Float Structure + + + +S +1 bit + + +Exponent +8 bits + + +Mantissa (Fraction) +23 bits + +Value = (-1)^S x (1 + Mantissa) x 2^(Exponent - 127) + + + +Example: Decoding 0.1f + +Little-endian bytes: +cd cc cc 3d + +Reversed (big-endian): +0x3dcccccd + +Sign: 0 +Exp: 01111011 = 123 +Mantissa: 1001100... + +Exp - 127 = -4, so value = 1.6 x 2^(-4) += 0.1 + + + +IEEE-754 Quick Reference + +Value +Hex +Bytes (LE) + + +0.1 +0x3dcccccd +cd cc cc 3d +1.0 +0x3f800000 +00 00 80 3f + +5.0 +0x40a00000 +00 00 a0 40 +10.0 +0x41200000 +00 00 20 41 + +-1.0 +0xbf800000 +00 00 80 bf + diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG09.svg b/WEEKS/WEEK09/slides/WEEK09-IMG09.svg new file mode 100644 index 0000000..5c40649 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG09.svg @@ -0,0 +1,64 @@ + + + + +Hacking the Float +Changing the DHT11 Scaling Constant + + + +DHT11 Scaling Calculation +result = integer + (decimal x 0.1) +Example: temp = 23 + (8 x 0.1) = 23.8C +0.1f is our target! + + + +Key Offsets in Binary + +Offset +Bytes +Meaning + + +0x410 +a6 ee 25 7a +vfma.f32 s14,s12,s11 (humidity) + +0x414 +e6 ee a5 7a +vfma.f32 s15,s13,s11 (temp) + +0x42C +cd cc cc 3d +0.1f -- the scaling constant + + + +The Hack: 0.1f --> 5.0f + +At offset 0x42C, change: + + +Original: cd cc cc 3d +(0.1f) + + +Patched: 00 00 a0 40 +(5.0f) + +New result: 23 + (8 x 5.0) = 63.0C +Decimal part is now multiplied by 5.0 instead of 0.1 +Export .bin from Ghidra, convert to UF2, flash to Pico + diff --git a/WEEKS/WEEK09/slides/WEEK09-IMG10.svg b/WEEKS/WEEK09/slides/WEEK09-IMG10.svg new file mode 100644 index 0000000..9655865 --- /dev/null +++ b/WEEKS/WEEK09/slides/WEEK09-IMG10.svg @@ -0,0 +1,97 @@ + + + + +Operators & DHT11 Hacking +Operators, DHT11, IEEE-754, and Hacking + + + +6 Operator Types + +Arithmetic +x * y = 50 + +Increment +x++ returns 5, x becomes 6 + +Relational +(6 > 10) = false + +Logical +false && true = false + +Bitwise +6 << 1 = 12 + +Assignment +x += 5 = 11 + +Post-increment: use THEN increment + + + +Key Addresses + +0x10000000 +Vector table + +0x10000004 +Reset_Handler addr + +0x10000234 +main() + +0x10000410 +Humidity vfma + +0x10000414 +Temp vfma + +0x1000042C +0.1f constant (hack) + + + +IEEE-754 Format +S(1) + Exp(8) + Mantissa(23) +(-1)^S x (1+M) x 2^(E-127) +0.1f = 0x3dcccccd = cd cc cc 3d + + + +Hack Workflow +1. Analyze in Ghidra +2. Find float at 0x42C +3. Patch cd cc cc 3d + + + +Binary Hacking Steps + +Analyze +--> +Identify +--> +Offset +--> +Patch +--> +Export +--> +Test + +Project: 0x001a_operators +Source: 0x001a_operators.c with DHT11 sensor on GPIO 4 + diff --git a/WEEKS/WEEK10/WEEK10-01-S.md b/WEEKS/WEEK10/WEEK10-01-S.md new file mode 100644 index 0000000..25d7eba --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-01-S.md @@ -0,0 +1,57 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics + +### Non-Credit Practice Exercise 1 Solution: Change Servo Angle Range + +#### Answers + +##### IEEE-754 Angle Encodings + +| Angle | IEEE-754 Hex | Little-Endian Bytes | Sign | Exponent | Mantissa | +|--------|--------------|--------------------|----- |----------|-----------| +| 0.0f | 0x00000000 | 00 00 00 00 | 0 | 0 | 0 | +| 45.0f | 0x42340000 | 00 00 34 42 | 0 | 132 | 0x340000 | +| 135.0f | 0x43070000 | 00 00 07 43 | 0 | 134 | 0x070000 | +| 180.0f | 0x43340000 | 00 00 34 43 | 0 | 134 | 0x340000 | + +##### Patch 1: Maximum Angle 180.0f β†’ 135.0f + +``` +Before: 00 00 34 43 (180.0f) +After: 00 00 07 43 (135.0f) +``` + +##### Patch 2: Minimum Angle 0.0f β†’ 45.0f + +``` +Before: 00 00 00 00 (0.0f) +After: 00 00 34 42 (45.0f) +``` + +##### Result + +Servo sweeps from **45Β° to 135Β°** instead of 0Β° to 180Β°, a 90Β° range centered at the midpoint. + +##### Conversion and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +#### Reflection Answers + +1. **Break down the IEEE-754 encoding of 180.0f (0x43340000). What are the sign bit, exponent, and mantissa fields?** + `0x43340000` in binary: `0 10000110 01101000000000000000000`. **Sign** = 0 (positive). **Exponent** = `10000110` = 134, biased exponent = 134 - 127 = 7. **Mantissa** = `0x340000` = `01101000...0`, representing 1.01101β‚‚ = 1 + 0.25 + 0.125 + 0.03125 = 1.40625. Value = 1.40625 Γ— 2⁷ = 1.40625 Γ— 128 = **180.0**. + +2. **Why is 0.0f represented as 0x00000000 (all zeros) in IEEE-754? Most floats have a non-zero exponent β€” what makes zero special?** + Zero is a **special case** in IEEE-754. When both the exponent and mantissa are all zeros, the value is defined as Β±0.0 (the sign bit distinguishes +0.0 from -0.0). This is by design β€” the IEEE-754 standard reserves the all-zeros exponent for zero and denormalized numbers. Unlike normal floats that have an implicit leading 1 in the mantissa (1.xxx), zero has no such implicit bit. This special encoding means you can check for zero by testing if all 32 bits are 0, which is efficient for hardware. + +3. **What is the IEEE-754 encoding of 90.0f? Show the sign, exponent, and mantissa calculation.** + 90.0 = 1.40625 Γ— 2⁢. **Sign** = 0. **Exponent** = 6 + 127 = 133 = `0x85` = `10000101`. **Mantissa**: 90.0 / 64 = 1.40625, fractional part = 0.40625 = 0.25 + 0.125 + 0.03125 = `0110100...0` = `0x340000`. Result: `0 10000101 01101000000000000000000` = `0x42B40000`. Little-endian: `00 00 B4 42`. + +4. **The compiler might use movs r0, #0 instead of loading 0.0f from a literal pool. Why would it choose one approach over the other?** + For integer zero, the compiler prefers `movs r0, #0` (a 2-byte Thumb instruction) because it's smaller and faster than a literal pool load. However, for **floating-point** zero used with VFP instructions, the compiler must load it into an FPU register (e.g., `s0`). If the FPU has a `vmov.f32 s0, #0.0` immediate form available, it can encode zero directly. Otherwise, it loads from a literal pool or uses `movs` to set an integer register to 0 and transfers it with `vmov s0, r0`. The choice depends on instruction context β€” integer vs. FPU register β€” and optimization level. diff --git a/WEEKS/WEEK10/WEEK10-01.md b/WEEKS/WEEK10/WEEK10-01.md new file mode 100644 index 0000000..9ce41ca --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-01.md @@ -0,0 +1,155 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +### Non-Credit Practice Exercise 1: Change Servo Angle Range + +#### Objective +Find the IEEE-754 floating-point value `0x43340000` (180.0f) in the `0x0020_dynamic-conditionals` binary using GDB, calculate the file offset, patch it to `0x43070000` (135.0f) using a hex editor, then find and patch the `0x00000000` (0.0f) value to `0x42340000` (45.0f), and verify on hardware that the servo now sweeps from 45Β° to 135Β° instead of 0Β° to 180Β°. + +#### Prerequisites +- Completed Week 10 tutorial (GDB and hex editor sections) +- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` 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 SG90 servo motor connected on GPIO 6 + +#### Task Description +The program uses `servo_set_angle(0.0f)` and `servo_set_angle(180.0f)` to sweep the servo across its full range. The float values `0.0` (`0x00000000`) and `180.0` (`0x43340000`) are loaded from the literal pool into registers before the `bl servo_set_angle` calls. You will locate these values in the disassembly, find the corresponding bytes in the `.bin` file, and patch them to `45.0` (`0x42340000`) and `135.0` (`0x43070000`) so the servo sweeps a narrower 90Β° range centered at 90Β°. + +#### 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\0x0020_dynamic-conditionals.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the servo_set_angle Calls + +Disassemble main and look for the `servo_set_angle` calls: + +```gdb +(gdb) disassemble 0x10000234,+250 +``` + +Look for instruction patterns like: + +``` +ldr r0, [pc, #offset] ; load float from literal pool +bl servo_set_angle +``` + +There will be multiple pairs β€” one loading `0x00000000` (0.0f) and another loading `0x43340000` (180.0f). + +##### Step 3: Examine the Literal Pool Values + +Once you find the `ldr r0, [pc, #offset]` instructions, examine the literal pool entries they reference: + +```gdb +(gdb) x/wx +``` + +You should see `0x43340000` (180.0f in IEEE-754). + +```gdb +(gdb) x/wx +``` + +You should see `0x00000000` (0.0f in IEEE-754). + +##### Step 4: Calculate the File Offsets + +``` +file_offset = literal_pool_address - 0x10000000 +``` + +Note the file offsets for both 4-byte words. + +##### Step 5: Encode the New Values + +**IEEE-754 Encoding:** + +| Angle | IEEE-754 Hex | Little-Endian Bytes | +| ----- | -------------- | ------------------- | +| 0.0f | `0x00000000` | `00 00 00 00` | +| 45.0f | `0x42340000` | `00 00 34 42` | +| 135.0f| `0x43070000` | `00 00 07 43` | +| 180.0f| `0x43340000` | `00 00 34 43` | + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` +2. Press **Ctrl+G** and enter the file offset for the 180.0f value +3. You should see: `00 00 34 43` +4. Replace with: `00 00 07 43` (135.0f) +5. Press **Ctrl+G** and enter the file offset for the 0.0f value +6. You should see: `00 00 00 00` +7. Replace with: `00 00 34 42` (45.0f) + +###### Question 1: How many literal pool entries reference `0x43340000`? Do you need to patch all of them? + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0020_dynamic-conditionals-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 servo behavior:** +- Press '1' β†’ servo sweeps from **45Β°** to **135Β°** (was 0Β° to 180Β°) +- Press '2' β†’ servo sweeps from **135Β°** to **45Β°** (was 180Β° to 0Β°) +- The total sweep range should be noticeably smaller + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate IEEE-754 floating-point values in the literal pool +- Understand the IEEE-754 single-precision encoding format +- Patch floating-point angle constants in embedded firmware +- Calculate file offsets from memory addresses + +#### Questions for Reflection + +###### Question 1: The value `180.0f` is encoded as `0x43340000` in IEEE-754. Break this down: what are the sign bit, exponent, and mantissa fields? + +###### Question 2: Why is `0.0f` stored as `0x00000000` and not some other pattern? What makes zero special in IEEE-754? + +###### Question 3: If you wanted to set the servo to exactly 90Β° (center), what IEEE-754 hex value would you use? Show the calculation. + +###### Question 4: Could the compiler optimize `0.0f` differently β€” for example, using `movs r0, #0` instead of a literal pool load? How would that affect your patching strategy? + +#### Tips and Hints +- IEEE-754 for 90.0f: sign=0, exponent=127+6=133=0x85, mantissa=0x340000 β†’ `0x42b40000` +- There may be multiple references to `0x43340000` in the literal pool β€” the case '1' and case '2' branches each load both angles +- Be careful with `0x00000000` β€” make sure you are patching a float literal pool entry and not zeroed data +- Use `x/4wx
` in GDB to view several literal pool words at once diff --git a/WEEKS/WEEK10/WEEK10-02-S.md b/WEEKS/WEEK10/WEEK10-02-S.md new file mode 100644 index 0000000..8158c68 --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-02-S.md @@ -0,0 +1,64 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics + +### Non-Credit Practice Exercise 2 Solution: Add a Third Command + +#### Answers + +##### Patch 1: Compare Byte '2' β†’ '3' + +Change `cmp r4, #0x32` to `cmp r4, #0x33`: + +``` +Before: 32 2C (cmp r4, #0x32 = '2') +After: 33 2C (cmp r4, #0x33 = '3') +``` + +Only the immediate byte changes: `0x32` β†’ `0x33`. + +##### Patch 2: Case Angle 1 β€” 180.0f β†’ 90.0f + +``` +Before: 00 00 34 43 (180.0f) +After: 00 00 B4 42 (90.0f) +``` + +##### Patch 3: Case Angle 2 β€” 0.0f β†’ 90.0f + +``` +Before: 00 00 00 00 (0.0f) +After: 00 00 B4 42 (90.0f) +``` + +##### IEEE-754 Reference + +| Angle | Hex | Little-Endian | +|--------|-------------|----------------| +| 0.0f | 0x00000000 | 00 00 00 00 | +| 90.0f | 0x42B40000 | 00 00 B4 42 | +| 180.0f | 0x43340000 | 00 00 34 43 | + +##### Behavior After Patch + +| Key | Action | +|-----|--------------------------------------| +| '1' | Sweep 0Β° β†’ 180Β° (unchanged) | +| '3' | Move to 90Β° center (new command) | +| '2' | Falls to default β€” prints "??" | + +#### Reflection Answers + +1. **Why does this exercise repurpose the existing case '2' path instead of adding a completely new branch? What would adding a new branch require?** + Adding a new branch would require inserting new instructions into the binary β€” additional `cmp`, `beq`, angle-loading code, and a `servo_set_angle` call. This would shift all subsequent code addresses, breaking every PC-relative branch, literal pool reference, and function call in the program. In a compiled binary without relocation information, inserting bytes is extremely difficult. Repurposing the existing case '2' path reuses the existing branch structure, angle-loading instructions, and function calls β€” only the data values change, not the code layout. + +2. **The cmp instruction uses an 8-bit immediate field. What is the range of characters you could compare against? Could you use a non-ASCII value?** + The `cmp Rn, #imm8` Thumb instruction has an 8-bit unsigned immediate, giving a range of 0–255 (`0x00`–`0xFF`). This covers all ASCII characters (0–127) plus extended values (128–255). You could compare against any byte value, including non-printable characters (`0x01`–`0x1F`), DEL (`0x7F`), or extended characters (`0x80`–`0xFF`). However, the user needs to be able to type the character via `getchar()` β€” non-printable characters would require special terminal input (e.g., Ctrl combinations). + +3. **How would you keep BOTH the original '2' command AND add '3' as a new command, using only data patches (no instruction insertion)?** + You could repurpose the **default/else** branch path. After the `cmp r4, #0x32` (case '2'), there's typically a branch to a default handler that prints "??". If you change the compare in the default path (or an unused branch) to `cmp r4, #0x33`, and redirect its logic to reuse one of the existing `servo_set_angle` code paths, you could handle both. Alternatively, if the binary has any unreachable code or NOP sleds, you could repurpose that space. The constraint is that you cannot increase the binary size β€” only modify existing bytes. + +4. **What would happen if you changed the compare value to 0x00 (null)? Could a user ever trigger this case?** + A compare against `0x00` would trigger on a null byte. In terminal input via `getchar()`, a null character is not easily typed β€” most terminals don't send `0x00` on any key press. On some systems, Ctrl+@ or Ctrl+Shift+2 generates a null byte, but this is platform-dependent. In practice, comparing against `0x00` would create an unreachable case β€” the command would exist in the binary but could never be triggered via normal serial terminal input, effectively making it a dead code path. diff --git a/WEEKS/WEEK10/WEEK10-02.md b/WEEKS/WEEK10/WEEK10-02.md new file mode 100644 index 0000000..ac088ea --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-02.md @@ -0,0 +1,159 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +### Non-Credit Practice Exercise 2: Add a Third Command + +#### Objective +Find the comparison instruction `cmp r4, #0x32` ('2') in the `0x0020_dynamic-conditionals` binary using GDB, locate the branch target for case '2', and modify existing code so that pressing '3' (0x33) moves the servo to the center position (90Β°) by patching one of the existing comparisons and its corresponding angle value to `0x42b40000` (90.0f). + +#### Prerequisites +- Completed Week 10 tutorial (GDB and hex editor sections) +- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` 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 SG90 servo motor connected on GPIO 6 + +#### Task Description +The program has two active commands: '1' (0x31) moves the servo 0Β°β†’180Β° and '2' (0x32) moves it 180Β°β†’0Β°. Adding a completely new code path would require rewriting branch offsets throughout the binary. Instead, you will repurpose the '2' command by changing its comparison value from `0x32` ('2') to `0x33` ('3') and patching both of its angle values to `0x42b40000` (90.0f), so pressing '3' moves the servo to center and holds it there. + +#### 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\0x0020_dynamic-conditionals.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the Comparison Instructions + +Disassemble main and locate both `cmp` instructions: + +```gdb +(gdb) disassemble 0x10000234,+250 +``` + +Look for: + +``` +cmp r4, #0x31 ; compare with '1' +beq +cmp r4, #0x32 ; compare with '2' +beq +``` + +Note the address of the `cmp r4, #0x32` instruction. + +##### Step 3: Examine the Comparison Byte + +The `cmp r4, #0x32` instruction encodes `0x32` as an immediate. Examine the instruction bytes: + +```gdb +(gdb) x/2bx +``` + +You should see a byte containing `0x32`. + +##### Step 4: Find the Angle Values for Case '2' + +Follow the `beq` target for case '2' and look for the literal pool loads: + +```gdb +(gdb) x/wx +(gdb) x/wx +``` + +Case '2' loads `0x43340000` (180.0f) first, then `0x00000000` (0.0f). + +##### Step 5: Calculate the File Offsets + +``` +file_offset = address - 0x10000000 +``` + +Note offsets for: +1. The `0x32` byte in the `cmp` instruction +2. Both angle literal pool entries for case '2' + +##### Step 6: Encode the New Values + +| Patch Target | Original | New | Purpose | +| ---------------- | ---------------- | ---------------- | ------------------------- | +| Compare byte | `32` | `33` | Match '3' instead of '2' | +| Angle 1 (180.0f) | `00 00 34 43` | `00 00 b4 42` | 90.0f center position | +| Angle 2 (0.0f) | `00 00 00 00` | `00 00 b4 42` | 90.0f center position | + +##### Step 7: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` +2. Press **Ctrl+G** and go to the compare byte offset +3. Change `32` to `33` +4. Go to the first angle offset for case '2' and replace `00 00 34 43` with `00 00 b4 42` +5. Go to the second angle offset for case '2' and replace `00 00 00 00` with `00 00 b4 42` + +###### Question 1: Why do we set both angle values to 90.0f instead of just one? + +##### Step 8: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0020_dynamic-conditionals-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-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 behavior:** +- Press '1' β†’ servo sweeps 0Β° to 180Β° (unchanged) +- Press '3' β†’ servo moves to **90Β° center** and stays +- Press '2' β†’ now falls through to default "??" (no longer mapped) + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate and patch comparison immediate values in ARM Thumb instructions +- Repurpose existing code paths instead of adding new ones +- Understand how `cmp` immediates are encoded in the instruction stream +- Combine multiple patches (comparison + data) for a single behavior change + +#### Questions for Reflection + +###### Question 1: Why is it easier to repurpose an existing command than to add a truly new third code path in the binary? + +###### Question 2: The `cmp` instruction uses an 8-bit immediate field. What range of ASCII characters could you use as command keys? + +###### Question 3: After your patch, pressing '2' now triggers the default "??" branch. Could you keep both '2' AND '3' working? What would that require? + +###### Question 4: What would happen if you changed the comparison to `0x00`? Could a user ever trigger that command via `getchar()`? + +#### Tips and Hints +- The `cmp rN, #imm8` instruction in Thumb has the immediate in the lower byte of the instruction word +- IEEE-754 for 90.0f: `0x42b40000` β†’ little-endian `00 00 b4 42` +- Be careful not to confuse literal pool entries between case '1' and case '2' β€” trace the branch targets carefully +- The `getchar()` function reads one byte from UART, so any byte value 0x00-0xFF is theoretically possible diff --git a/WEEKS/WEEK10/WEEK10-03-S.md b/WEEKS/WEEK10/WEEK10-03-S.md new file mode 100644 index 0000000..45d9b41 --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-03-S.md @@ -0,0 +1,49 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics + +### Non-Credit Practice Exercise 3 Solution: Reverse the Servo Direction + +#### Answers + +##### Four Literal Pool Swaps + +| Patch | Location | Original | Patched | +|-------|---------------|-------------------|-------------------| +| Case 1 Angle 1 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) | +| Case 1 Angle 2 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) | +| Case 2 Angle 1 | Literal pool | 00 00 34 43 (180.0f) | 00 00 00 00 (0.0f) | +| Case 2 Angle 2 | Literal pool | 00 00 00 00 (0.0f) | 00 00 34 43 (180.0f) | + +##### Behavior After Patch + +| Key | Original | Patched | +|-----|---------------------|---------------------| +| '1' | 0Β° β†’ 180Β° (sweep up) | 180Β° β†’ 0Β° (sweep down) | +| '2' | 180Β° β†’ 0Β° (sweep down) | 0Β° β†’ 180Β° (sweep up) | + +The terminal output text ("Moving to 180..." / "Moving to 0...") remains unchanged β€” it still says the original directions. Only the physical servo behavior is reversed. + +##### GDB Verification + +```gdb +(gdb) x/4wx +``` + +Examine all angle entries in the literal pool to identify which 4-byte words to swap. + +#### Reflection Answers + +1. **After this patch, the serial output still says "Moving to 180" when the servo actually moves to 0. Why is this a security concern? What real-world attack does this mimic?** + This is a classic **display spoofing** attack. The user interface (serial output) shows one thing while the hardware does another. In real-world systems, this mimics attacks on SCADA/ICS systems where operator displays show "normal" readings while the physical process is manipulated (similar to Stuxnet, which showed normal centrifuge speeds while actually damaging them). In medical devices, this could display a safe dosage while delivering a different amount. The lesson is that **you cannot trust the display if the firmware has been tampered with** β€” the display text and the actual behavior are patched independently. + +2. **Instead of swapping the data values, could you achieve the same result by swapping the branch targets (making case '1' jump to case '2' code and vice versa)? What are the trade-offs?** + Yes, you could swap the `beq` target addresses so that when the user presses '1', execution jumps to the case '2' code path and vice versa. **Trade-offs:** Swapping branch targets changes the instructions (modifying the offset bytes in `beq`), which is more complex β€” you need to correctly calculate the new PC-relative offsets. Swapping data values is simpler (just exchange 4-byte float values) and less error-prone. However, swapping branches would also swap the printf messages, so "Moving to 180" would display for the path that actually moves to 180 β€” keeping the display consistent. The data-swap approach intentionally creates a mismatch between display and behavior. + +3. **If the compiler shares a single literal pool entry for 0x43340000 (180.0f) across both cases, how does swapping that one entry affect the behavior?** + If the compiler optimized by sharing a single `0x43340000` literal pool entry for all references to 180.0f, then both case '1' and case '2' load from the same address. Changing that one entry to `0x00000000` (0.0f) would affect **both** cases simultaneously β€” they would both use 0.0f where they originally used 180.0f. Similarly, if there's only one `0x00000000` entry shared, changing it affects both cases. You would need to verify whether each case uses its own pool entry or shares entries by examining the `ldr` offsets. If shared, you may need to find unused space to create a second copy of the value. + +4. **How would you verify the patch is correct without physical hardware? What GDB commands would you use?** + Set breakpoints before each `bl servo_set_angle` call, then examine `r0` (or `s0`) which holds the angle argument. Run through both cases and verify: (1) `b *` β†’ `c` β†’ press '1' β†’ `info float` or `p $s0` β€” should show 180.0f (was 0.0f). (2) Continue to second call β€” should show 0.0f (was 180.0f). Repeat for case '2'. You can also examine the literal pool directly: `x/wx ` to verify the bytes were swapped. Additionally, `x/f ` displays the value as a float for quick verification. diff --git a/WEEKS/WEEK10/WEEK10-03.md b/WEEKS/WEEK10/WEEK10-03.md new file mode 100644 index 0000000..a6479a0 --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-03.md @@ -0,0 +1,141 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +### Non-Credit Practice Exercise 3: Reverse the Servo Direction + +#### Objective +Find the branch targets for case '1' and case '2' in the `0x0020_dynamic-conditionals` binary using GDB, identify where each case loads its angle values from the literal pool, and swap the angle pairs so that pressing '1' now does what '2' originally did (180Β°β†’0Β°) and pressing '2' does what '1' originally did (0Β°β†’180Β°). + +#### Prerequisites +- Completed Week 10 tutorial (GDB and hex editor sections) +- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` 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 SG90 servo motor connected on GPIO 6 + +#### Task Description +Currently, case '1' calls `servo_set_angle(0.0f)` then `servo_set_angle(180.0f)`, while case '2' calls `servo_set_angle(180.0f)` then `servo_set_angle(0.0f)`. To reverse the servo direction, you will swap the literal pool angle values between the two cases. This means patching case '1' to load 180.0f first and 0.0f second, and case '2' to load 0.0f first and 180.0f second. + +#### 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\0x0020_dynamic-conditionals.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Map Both Case Branch Targets + +Disassemble main and trace both paths: + +```gdb +(gdb) disassemble 0x10000234,+250 +``` + +Identify: +- Case '1' branch target β†’ first `ldr r0` (loads 0.0f), `bl servo_set_angle`, second `ldr r0` (loads 180.0f), `bl servo_set_angle` +- Case '2' branch target β†’ first `ldr r0` (loads 180.0f), `bl servo_set_angle`, second `ldr r0` (loads 0.0f), `bl servo_set_angle` + +##### Step 3: Find All Four Literal Pool Entries + +Examine the literal pool entries for each angle load: + +```gdb +(gdb) x/wx +(gdb) x/wx +(gdb) x/wx +(gdb) x/wx +``` + +Record which addresses contain `0x00000000` (0.0f) and which contain `0x43340000` (180.0f). + +##### Step 4: Calculate All File Offsets + +``` +file_offset = literal_pool_address - 0x10000000 +``` + +You need four offsets β€” two for case '1' angles and two for case '2' angles. + +##### Step 5: Plan the Swap + +| Location | Original | New | +| -------------- | ---------------- | ---------------- | +| Case 1 Angle 1 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) | +| Case 1 Angle 2 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) | +| Case 2 Angle 1 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) | +| Case 2 Angle 2 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) | + +###### Question 1: Could the compiler share a single literal pool entry for all references to `0x43340000`? How would that affect your patching plan? + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` +2. For each of the four literal pool entries, navigate to its file offset and swap the values as planned +3. Be methodical β€” patch one at a time and verify each before moving to the next + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0020_dynamic-conditionals-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 behavior:** +- Press '1' β†’ servo sweeps from **180Β° to 0Β°** (was 0Β° to 180Β°) +- Press '2' β†’ servo sweeps from **0Β° to 180Β°** (was 180Β° to 0Β°) +- The text output still says "one" and "two" β€” only the physical behavior changed + +#### Expected Output + +After completing this exercise, you should be able to: +- Trace multiple branch paths to their literal pool references +- Understand how data (angles) and code (branches) are separate concerns +- Swap data values to reverse physical behavior while keeping code structure intact +- Recognize shared vs. separate literal pool entries + +#### Questions for Reflection + +###### Question 1: The terminal still prints "one" and "two" with the original meanings, but the servo does the opposite. Why is this a security concern in real embedded systems? + +###### Question 2: Instead of swapping literal pool values, could you swap the branch targets themselves? What are the pros and cons of each approach? + +###### Question 3: If the literal pool entries are shared between cases (one `0x43340000` word referenced by both), how would your patch strategy change? + +###### Question 4: What tool could you use to confirm the swapped behavior without physical hardware β€” just by reading the patched disassembly? + +#### Tips and Hints +- Use `x/4wx ` to dump the entire literal pool at once and see all angle values together +- If the compiler shares a single literal pool entry for 180.0f across both cases, swapping it would affect both β€” you may need to create a duplicate entry +- The simplest approach: if each case has its own literal pool entries, just swap the 4-byte values at each offset +- Verify by disassembling the patched binary in GDB to confirm the `ldr` instructions now reference the swapped values diff --git a/WEEKS/WEEK10/WEEK10-04-S.md b/WEEKS/WEEK10/WEEK10-04-S.md new file mode 100644 index 0000000..fac3964 --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-04-S.md @@ -0,0 +1,56 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Dynamic Conditionals in Embedded Systems: Debugging and Hacking Dynamic Conditionals w/ SG90 Servo Basics + +### Non-Credit Practice Exercise 4 Solution: Speed Profile + +#### Answers + +##### Sleep Duration Values + +| Parameter | Original | Case 1 (Fast Snap) | Case 2 (Slow Sweep) | +|-------------|---------|--------------------|--------------------| +| Duration | 500 ms | 100 ms | 1000 ms | +| Hex | 0x1F4 | 0x64 | 0x3E8 | +| LE Bytes | F4 01 00 00 | 64 00 00 00 | E8 03 00 00 | + +##### Patch Case 1: 500ms β†’ 100ms (Fast Snap) + +``` +Before: F4 01 00 00 (500ms) +After: 64 00 00 00 (100ms) +``` + +##### Patch Case 2: 500ms β†’ 1000ms (Slow Sweep) + +``` +Before: F4 01 00 00 (500ms) +After: E8 03 00 00 (1000ms) +``` + +##### Literal Pool Considerations + +If the compiler shares a single literal pool entry for `0x000001F4` across both cases, you **cannot** patch them independently without additional work. Verify by checking whether case 1 and case 2 `ldr` instructions reference the same pool address. If shared, you need to find unused space in the binary for a second value or repurpose another unused literal pool entry. + +##### Behavior After Patch + +| Key | Original | Patched | +|-----|-------------------|----------------------------------| +| '1' | 500ms between moves | 100ms β€” near-instantaneous snap | +| '2' | 500ms between moves | 1000ms β€” slow, deliberate sweep | + +#### Reflection Answers + +1. **Why does 100ms feel like an instant "snap" while 1000ms feels like a smooth sweep? The servo moves the same distance either way.** + Human perception of motion depends on the **pause between position updates**, not the motor speed. At 100ms delay, the servo reaches each angle before the next one is set β€” the positions update so quickly that the motion appears continuous and instant. At 1000ms delay, there's a full second between movements, so you can see the servo pause at each intermediate angle. The SG90 servo physically takes about 200–300ms to traverse its full range at no load, so 100ms is faster than the travel time (the servo is still moving when the next command arrives), creating a snappy feel. At 1000ms, the servo has already completed its move and waits idle before the next command. + +2. **If both cases share the same literal pool entry for 500ms, what strategy would you use to give them different sleep values?** + Several approaches: (1) **Find unused literal pool space** β€” look for entries that are no longer referenced and overwrite one with `0x64` (100ms) while keeping the other for `0x3E8` (1000ms). (2) **Repurpose an existing value** β€” if another constant in the pool happens to equal your desired value, redirect the `ldr` offset to point there. (3) **Change the `ldr` to a `movs`** β€” for values ≀ 255 (like 100), replace the 4-byte `ldr r0, [pc, #offset]` with `movs r0, #0x64` (2 bytes) + `nop` (2 bytes) for padding. This works for case 1 (100 fits in 8 bits) but not case 2 (1000 exceeds 255). + +3. **What is the minimum sleep_ms value where the SG90 servo can actually complete a full 0°–180Β° sweep before the next command?** + The SG90 servo has a rated speed of approximately 0.12 seconds per 60Β° at 4.8V. For a full 180Β° sweep: 0.12 Γ— (180/60) = 0.12 Γ— 3 = **0.36 seconds (360ms)**. In practice, with load and signal processing overhead, **400–500ms** is a safe minimum for reliable full-range travel. Below this, the servo may not reach the target angle before the next position command arrives, resulting in incomplete movements or jittery behavior. The original 500ms value was chosen to reliably allow full travel. + +4. **What would happen if you set sleep_ms to 0 for both cases? How would the servo physically behave?** + With `sleep_ms(0)`, the loop runs at full CPU speed, sending angle commands as fast as the processor can execute. The servo would receive thousands of position updates per second, alternating between two angles. Physically, the servo would **vibrate or oscillate** β€” it never has time to reach either target angle before being told to go to the other one. The PWM signal would switch so rapidly that the servo's control circuit would see constantly changing targets, producing a buzzing sound and erratic oscillation near the midpoint. This could also overheat the servo motor due to constant direction changes. diff --git a/WEEKS/WEEK10/WEEK10-04.md b/WEEKS/WEEK10/WEEK10-04.md new file mode 100644 index 0000000..1642eae --- /dev/null +++ b/WEEKS/WEEK10/WEEK10-04.md @@ -0,0 +1,144 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 10 +Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +### Non-Credit Practice Exercise 4: Speed Profile + +#### Objective +Find both `sleep_ms(500)` calls in the `0x0020_dynamic-conditionals` binary using GDB, identify the literal pool values `0x1f4` (500) loaded into `r0` before each `bl sleep_ms`, calculate the file offsets, and patch case '1' to use `0x64` (100ms) for fast movement and case '2' to use `0x3e8` (1000ms) for slow movement, then verify on hardware that the two keys produce visibly different servo speeds. + +#### Prerequisites +- Completed Week 10 tutorial (GDB and hex editor sections) +- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` 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 SG90 servo motor connected on GPIO 6 + +#### Task Description +Both case '1' and case '2' call `sleep_ms(500)` between their two `servo_set_angle` calls. The value `500` (`0x1F4`) is loaded from the literal pool into `r0` before each `bl sleep_ms`. You will find both `sleep_ms` literal pool entries β€” one in case '1' and one in case '2' β€” and patch them to different values: `100` (`0x64`) for fast snapping and `1000` (`0x3E8`) for slow sweeping, creating distinct speed profiles per key. + +#### 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\0x0020_dynamic-conditionals.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find Both sleep_ms Calls + +Disassemble main and locate the `sleep_ms` calls: + +```gdb +(gdb) disassemble 0x10000234,+250 +``` + +Look for the pattern repeated in both cases: + +``` +ldr r0, [pc, #offset] ; load delay value +bl sleep_ms +``` + +Each case has at least one `sleep_ms` call. Identify which belongs to case '1' and which to case '2' by tracing the branch targets. + +##### Step 3: Examine the Literal Pool Entries + +For each `sleep_ms` call, examine the referenced literal pool entry: + +```gdb +(gdb) x/wx +(gdb) x/wx +``` + +Both should show `0x000001f4` (500). + +##### Step 4: Calculate the File Offsets + +``` +file_offset = literal_pool_address - 0x10000000 +``` + +Note the file offsets for both 4-byte sleep values. + +##### Step 5: Encode the New Values + +| Case | Original | New | Speed | +| ------ | ----------------- | ----------------- | -------- | +| Case 1 | `F4 01 00 00` (500ms) | `64 00 00 00` (100ms) | Fast snap | +| Case 2 | `F4 01 00 00` (500ms) | `E8 03 00 00` (1000ms) | Slow sweep | + +###### Question 1: Each case calls `sleep_ms` twice (once between the first and second `servo_set_angle`). Do both share the same literal pool entry, or does each have its own? + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin` +2. Press **Ctrl+G** and go to the case '1' sleep value offset +3. Replace `F4 01 00 00` with `64 00 00 00` (100ms) +4. Press **Ctrl+G** and go to the case '2' sleep value offset +5. Replace `F4 01 00 00` with `E8 03 00 00` (1000ms) + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0020_dynamic-conditionals-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 behavior:** +- Press '1' β†’ servo snaps **fast** (100ms between angles) β€” almost instant jump +- Press '2' β†’ servo moves **slow** (1000ms between angles) β€” takes a full second at each position +- The speed difference should be very obvious visually and audibly + +#### Expected Output + +After completing this exercise, you should be able to: +- Distinguish between multiple literal pool entries for the same value +- Trace which code path references which literal pool entry +- Patch timing constants independently per branch +- Understand how sleep duration affects perceived motor behavior + +#### Questions for Reflection + +###### Question 1: Why does 100ms make the servo appear to "snap" while 1000ms makes it appear to "sweep"? Is the servo actually moving faster, or is it about the pause between commands? + +###### Question 2: If the compiler uses a single shared literal pool entry for all `sleep_ms(500)` calls, what alternative patching strategy would you need to create different speeds per case? + +###### Question 3: What is the minimum `sleep_ms` value that would still allow the servo to physically reach its target angle before the next command? How would you determine this experimentally? + +###### Question 4: Could you set the sleep to `0` (`00 00 00 00`)? What would happen to the servo behavior? + +#### Tips and Hints +- `100` decimal = `0x64`, fits in one byte: `64 00 00 00` in little-endian +- `1000` decimal = `0x3E8`: `E8 03 00 00` in little-endian +- If both `sleep_ms` calls share one literal pool word, you cannot give them different values by patching data alone β€” you would need to patch one `ldr` instruction to point to a different pool entry or use a `movs` immediate +- The SG90 servo takes about 200-300ms to traverse its full range, so 100ms will cause it to not quite reach the endpoint before the next command fires diff --git a/WEEKS/WEEK10/WEEK10-SLIDES.pdf b/WEEKS/WEEK10/WEEK10-SLIDES.pdf new file mode 100644 index 0000000..3d6454f Binary files /dev/null and b/WEEKS/WEEK10/WEEK10-SLIDES.pdf differ diff --git a/WEEKS/WEEK10/WEEK10.md b/WEEKS/WEEK10/WEEK10.md new file mode 100644 index 0000000..4a80bc3 --- /dev/null +++ b/WEEKS/WEEK10/WEEK10.md @@ -0,0 +1,1533 @@ +# Week 10: Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand the difference between static and dynamic conditionals in C +- Know how if/else statements and switch/case blocks work at the assembly level +- Understand Pulse Width Modulation (PWM) and how it controls servo motors +- Calculate PWM timing from system clock to servo pulse width +- Identify conditional branches in Ghidra (beq, bne instructions) +- Hack string literals and timing delays in binary files +- Modify branch targets to change program flow +- Create "stealth" functionality by NOP-ing out print statements +- Understand IEEE-754 floating-point for angle calculations + +--- + +## πŸ“š Part 1: Understanding Conditionals in C + +### What Are Conditionals? + +**Conditionals** are programming structures that make decisions. They let your program choose different paths based on whether a condition is true or false. Think of them like a fork in the road - the program checks a condition and decides which way to go. + +### Two Types of Conditionals + +| Type | Description | Example | +| ----------- | ---------------------------------------------- | ------------------------------------------------- | +| **Static** | Condition value is known/fixed at compile time | `if (choice == 1)` where choice never changes | +| **Dynamic** | Condition value changes based on runtime input | `if (choice == getchar())` where user types input | + +--- + +## πŸ“š Part 2: Static Conditionals + +### What Makes a Conditional "Static"? + +A **static conditional** is one where the outcome is predetermined because the condition variable never changes during program execution: + +```c +int choice = 1; // This NEVER changes! + +while (true) { + if (choice == 1) { + printf("1\r\n"); // This ALWAYS runs + } else if (choice == 2) { + printf("2\r\n"); // This NEVER runs + } else { + printf("?\r\n"); // This NEVER runs + } +} +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Static Conditional Flow β”‚ +β”‚ β”‚ +β”‚ choice = 1 (set once, never changes) β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ choice == 1 │────YES────► printf("1") β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚NO (never taken) β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ choice == 2 │────YES────► printf("2") (never reached) β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚NO β”‚ +β”‚ β–Ό β”‚ +β”‚ printf("?") (never reached) β”‚ +β”‚ β”‚ +β”‚ The branching logic EXISTS but only ONE path ever executes! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The if/else Statement + +The `if/else` structure checks conditions in order: + +```c +if (choice == 1) { + // Do something if choice is 1 +} else if (choice == 2) { + // Do something if choice is 2 +} else { + // Do something for all other values +} +``` + +### The switch Statement + +The `switch` statement is another way to handle multiple conditions: + +```c +switch (choice) { + case 1: + printf("one\r\n"); + break; + case 2: + printf("two\r\n"); + break; + default: + printf("??\r\n"); +} +``` + +**Key Differences:** + +| Feature | if/else | switch | +| ---------------- | ----------------------- | -------------------------- | +| **Condition** | Any boolean expression | Single variable comparison | +| **Values** | Ranges, complex logic | Discrete values only | +| **Fall-through** | No | Yes (without `break`) | +| **Readability** | Good for 2-3 conditions | Better for many conditions | + +--- + +## πŸ“š Part 3: Dynamic Conditionals + +### What Makes a Conditional "Dynamic"? + +A **dynamic conditional** is one where the condition variable changes based on runtime input: + +```c +uint8_t choice = 0; + +while (true) { + choice = getchar(); // User types a key - VALUE CHANGES! + + if (choice == '1') { + printf("1\r\n"); + } else if (choice == '2') { + printf("2\r\n"); + } else { + printf("??\r\n"); + } +} +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Dynamic Conditional Flow β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ choice=getchar │◄──── User types 'x' on keyboard β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ choice=='1' │────YES────► printf("1"), move servo β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚NO β”‚ +β”‚ β–Ό β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ choice=='2' │────YES────► printf("2"), move servo β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚NO β”‚ +β”‚ β–Ό β”‚ +β”‚ printf("??") β”‚ +β”‚ β”‚ β”‚ +β”‚ └──────────────────► Loop back to getchar() β”‚ +β”‚ β”‚ +β”‚ EACH iteration can take a DIFFERENT path! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The getchar() Function + +`getchar()` reads a single character from the serial terminal: + +```c +uint8_t choice = getchar(); // Waits for user to type something +``` + +- Returns the ASCII value of the key pressed +- `'1'` = 0x31, `'2'` = 0x32, `'x'` = 0x78, `'y'` = 0x79 +- Blocks (waits) until a key is pressed + +--- + +## πŸ“š Part 4: Understanding PWM (Pulse Width Modulation) + +### What is PWM? + +**PWM** (Pulse Width Modulation) is a technique for controlling power by rapidly switching a signal on and off. The ratio of "on time" to "off time" determines the average power delivered. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ PWM Signal - 50% Duty Cycle β”‚ +β”‚ β”‚ +β”‚ HIGH ─┐ β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ LOW β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ └───── β”‚ +β”‚ ◄──T──► β”‚ +β”‚ ON OFF β”‚ +β”‚ β”‚ +β”‚ Duty Cycle = ON time / Total period = 50% β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### PWM for Servo Control + +Servo motors use PWM differently - they care about the **pulse width**, not the duty cycle percentage: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Servo PWM Signal (50 Hz = 20ms period) β”‚ +β”‚ β”‚ +β”‚ 0Β° Position (1ms pulse): β”‚ +β”‚ HIGH ─┐ β”‚ +β”‚ β”‚ 1ms β”‚ +β”‚ LOW └────────────────────────────── (19ms) ────────── β”‚ +β”‚ ◄────────────── 20ms ─────────────────────────► β”‚ +β”‚ β”‚ +β”‚ 90Β° Position (1.5ms pulse): β”‚ +β”‚ HIGH ─────┐ β”‚ +β”‚ β”‚ 1.5ms β”‚ +β”‚ LOW └─────────────────────────── (18.5ms) ─────── β”‚ +β”‚ β”‚ +β”‚ 180Β° Position (2ms pulse): β”‚ +β”‚ HIGH ─────────┐ β”‚ +β”‚ β”‚ 2ms β”‚ +β”‚ LOW └───────────────────────── (18ms) ─────── β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Magic Numbers + +| Angle | Pulse Width | PWM Ticks (at 1MHz) | +| ----- | ----------- | ------------------- | +| 0Β° | 1000 Β΅s | 1000 | +| 90Β° | 1500 Β΅s | 1500 | +| 180Β° | 2000 Β΅s | 2000 | + +--- + +## πŸ“š Part 5: PWM Timing Calculations + +### From 150 MHz to 50 Hz + +The RP2350's system clock runs at **150 MHz** (150 million cycles per second). A servo needs a **50 Hz** signal (one pulse every 20 ms). How do we bridge this gap? + +> πŸ“– **Datasheet Reference:** The RP2350 supports a system clock up to 150 MHz via two on-chip PLLs (Section 8.1, p. 456). The PWM peripheral is documented in Section 12.6 (p. 1078), with 12 identical PWM slices, each featuring an 8.4 fractional clock divider (Section 12.6.3, p. 1079) and a 16-bit wrap register (TOP) that sets the period. The PWM base address is `0x40050000` (Section 2.2, p. 33). + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Clock Division Chain β”‚ +β”‚ β”‚ +β”‚ System Clock: 150 MHz β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ Γ· 150 (clock divider) β”‚ +β”‚ β–Ό β”‚ +β”‚ PWM Tick Rate: 1 MHz (1 tick = 1 microsecond) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ Count to 20,000 (wrap value = 19,999) β”‚ +β”‚ β–Ό β”‚ +β”‚ PWM Frequency: 50 Hz (20 ms period) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The Math + +**Step 1: Clock Division** +``` +PWM Tick Rate = System Clock Γ· Divider +1,000,000 Hz = 150,000,000 Hz Γ· 150 +``` + +**Step 2: Frame Period** +``` +Period = (Wrap Value + 1) Γ— Tick Duration +20 ms = 20,000 ticks Γ— 1 Β΅s/tick +``` + +**Step 3: Pulse Width to Ticks** +``` +Ticks = Pulse Width (Β΅s) Γ— 1 tick/Β΅s +1500 ticks = 1500 Β΅s Γ— 1 +``` + +### Worked Example: 90Β° Angle + +Let's calculate what happens when we command 90Β°: + +1. **Angle to Pulse Width:** + ``` + Pulse = MIN + (angle/180) Γ— (MAX - MIN) + Pulse = 1000 + (90/180) Γ— (2000 - 1000) + Pulse = 1000 + 0.5 Γ— 1000 + Pulse = 1500 Β΅s + ``` + +2. **Pulse to PWM Ticks:** + ``` + Level = 1500 Β΅s Γ— 1 tick/Β΅s = 1500 ticks + ``` + +3. **Hardware Timing:** + - Signal HIGH for 1500 ticks (1.5 ms) + - Signal LOW for 18,500 ticks (18.5 ms) + - Total period: 20,000 ticks (20 ms) + +--- + +## πŸ“š Part 6: Understanding the SG90 Servo Motor + +### What is the SG90? + +The **SG90** is a small, inexpensive hobby servo motor commonly used in robotics projects: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SG90 Servo Motor β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ ARM β”‚ β”‚ ◄── Rotates 0Β° to 180Β° β”‚ +β”‚ β”‚ β””β”€β”€β”¬β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ MOTOR β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ GEAR β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ BOX β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ ORANGE RED BROWN β”‚ +β”‚ Signal VCC GND β”‚ +β”‚ (PWM) (5V) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### SG90 Specifications + +| Parameter | Value | +| ----------------- | ------------------------- | +| **Voltage** | 4.8V - 6V (typically 5V) | +| **Rotation** | 0Β° to 180Β° | +| **Pulse Width** | 1000Β΅s - 2000Β΅s | +| **Frequency** | 50 Hz (20ms period) | +| **Stall Current** | ~650mA (can spike to 1A+) | + +### Wire Colors + +| Wire Color | Function | Connect To | +| ---------- | -------- | --------------- | +| **Brown** | GND | Ground | +| **Red** | VCC | 5V Power (VBUS) | +| **Orange** | Signal | GPIO Pin (PWM) | + +--- + +## πŸ“š Part 7: Power Supply Safety + +### ⚠️ CRITICAL WARNING ⚠️ + +**NEVER power the servo directly from the Pico's 3.3V pin!** + +Servos can draw over 1000mA during movement spikes. The Pico's 3.3V regulator cannot handle this and you will: +- Cause brownouts (Pico resets) +- Damage the Pico's voltage regulator +- Potentially damage your USB port + +### Correct Power Setup + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CORRECT Power Wiring β”‚ +β”‚ β”‚ +β”‚ USB ────► VBUS (5V) ───┬──► Servo VCC (Red) β”‚ +β”‚ β”‚ β”‚ +β”‚ └──► Capacitor (+) β”‚ +β”‚ β”‚ β”‚ +β”‚ Pico GND ──────────────┬────┴──► Capacitor (-) β”‚ +β”‚ β”‚ β”‚ +β”‚ └──────► Servo GND (Brown) β”‚ +β”‚ β”‚ +β”‚ Pico GPIO 6 ──────────────────► Servo Signal (Orange) β”‚ +β”‚ β”‚ +β”‚ IMPORTANT: Use a 1000Β΅F 25V capacitor across the servo β”‚ +β”‚ power to absorb current spikes! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Why the Capacitor? + +The **1000Β΅F capacitor** acts as a tiny battery: +- Absorbs sudden current demands when servo moves +- Prevents voltage drops that could reset the Pico +- Smooths out electrical noise + +--- + +## πŸ“š Part 8: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. An SG90 servo motor +7. A 1000Β΅F 25V capacitor +8. The sample projects: `0x001d_static-conditionals` and `0x0020_dynamic-conditionals` + +### Hardware Setup + +Connect your servo like this: + +| Servo Wire | Pico 2 Pin | +| --------------- | ---------- | +| Brown (GND) | GND | +| Red (VCC) | VBUS (5V) | +| Orange (Signal) | GPIO 6 | + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Servo Wiring with Capacitor β”‚ +β”‚ β”‚ +β”‚ Pico 2 SG90 Servo β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 6 │─────── Orange ───────►│ Signal β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ VBUS(5V) │───┬─── Red ──────────►│ VCC β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GND │───┼─── Brown ────────►│ GND β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ └───── + CAP - β”œβ”€β”€β”€β”€ GND β”‚ +β”‚ β”‚ 1000Β΅F β”‚ β”‚ +β”‚ β”‚ 25V β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Project Structure + +``` +Embedded-Hacking/ +β”œβ”€β”€ 0x001d_static-conditionals/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x001d_static-conditionals.uf2 +β”‚ β”‚ └── 0x001d_static-conditionals.bin +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ └── 0x001d_static-conditionals.c +β”‚ └── servo.h +β”œβ”€β”€ 0x0020_dynamic-conditionals/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x0020_dynamic-conditionals.uf2 +β”‚ β”‚ └── 0x0020_dynamic-conditionals.bin +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ └── 0x0020_dynamic-conditionals.c +β”‚ └── servo.h +└── uf2conv.py +``` + +--- + +## πŸ”¬ Part 9: Hands-On Tutorial - Static Conditionals Code + +### Step 1: Review the Source Code + +Let's examine the static conditionals code: + +**File: `0x001d_static-conditionals.c`** + +```c +#include +#include "pico/stdlib.h" +#include "servo.h" + +#define SERVO_GPIO 6 + +int main(void) { + stdio_init_all(); + + int choice = 1; // STATIC - never changes! + + servo_init(SERVO_GPIO); + + while (true) { + // if/else conditional + if (choice == 1) { + printf("1\r\n"); + } else if (choice == 2) { + printf("2\r\n"); + } else { + printf("?\r\n"); + } + + // switch/case conditional + switch (choice) { + case 1: + printf("one\r\n"); + break; + case 2: + printf("two\r\n"); + break; + default: + printf("??\r\n"); + } + + // Servo movement + servo_set_angle(0.0f); + sleep_ms(500); + servo_set_angle(180.0f); + sleep_ms(500); + } +} +``` + +### Step 2: Understand the Program Flow + +Since `choice = 1` and NEVER changes: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Static Conditional Execution β”‚ +β”‚ β”‚ +β”‚ Every loop iteration: β”‚ +β”‚ β”‚ +β”‚ 1. Check if (choice == 1) β†’ TRUE β†’ print "1" β”‚ +β”‚ 2. Check switch case 1 β†’ MATCH β†’ print "one" β”‚ +β”‚ 3. Move servo to 0Β° β”‚ +β”‚ 4. Wait 500ms β”‚ +β”‚ 5. Move servo to 180Β° β”‚ +β”‚ 6. Wait 500ms β”‚ +β”‚ 7. Repeat forever... β”‚ +β”‚ β”‚ +β”‚ Output always: "1" then "one" (forever) β”‚ +β”‚ Servo: sweeps 0Β° β†’ 180Β° β†’ 0Β° β†’ 180Β° (forever) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 3: 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 `0x001d_static-conditionals.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +**Check the serial monitor (PuTTY at 115200 baud):** +``` +1 +one +1 +one +1 +one +... +``` + +**Watch the servo:** +- It should sweep from 0Β° to 180Β° every second +- The movement is continuous and repetitive + +--- + +## πŸ”¬ Part 10: Debugging with GDB (Static Conditionals) + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and 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" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```powershell +arm-none-eabi-gdb build\0x001d_static-conditionals.elf +``` + +### Step 7: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target remote :3333 +``` + +### Step 8: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 9: Examine Main Function + +Disassemble around main to see the conditionals: + +```gdb +disassemble 0x10000234,+200 +``` + +Look for comparison and branch instructions that implement the if/else and switch logic. + +### Step 10: Set a Breakpoint at Main + +```gdb +break *0x10000234 +``` + +Reset and run to hit the breakpoint: + +```gdb +monitor reset halt +continue +``` + +### Step 11: Find the Comparison Instructions + +Step through and examine the comparison: + +```gdb +stepi 20 +info registers +``` + +Look for `cmp` instructions that compare the `choice` variable (value 1). + +### Step 12: Examine the Printf Arguments + +Before printf calls, check r0 for the format string address: + +```gdb +x/s $r0 +``` + +You should see strings like `"1\r\n"` or `"one\r\n"`. + +### Step 13: Watch the Servo Commands + +Set a breakpoint on servo_set_angle: + +```gdb +break *0x10000280 +continue +``` + +Check the floating-point register for the angle: + +```gdb +info registers s0 +``` + +### Step 14: Examine the Timing Delay + +Set a breakpoint on sleep_ms and check the delay value: + +```gdb +break *0x10000290 +continue +info registers r0 +``` + +You should see `0x1f4` (500 decimal) for the 500ms delay. + +### Step 15: Step Through the Loop + +Watch one complete iteration: + +```gdb +stepi 100 +info registers +``` + +Notice how the static conditional always takes the same branch path. + +### Step 16: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## πŸ”¬ Part 11: Setting Up Ghidra for Static Conditionals + +### Step 17: Start Ghidra + +Open a terminal and type: + +```powershell +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** β†’ **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x001d_static-conditionals` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Open your file explorer +2. Navigate to the `0x001d_static-conditionals/build/` folder +3. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**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` +3. Click **OK** + +### Step 21: Analyze the Binary + +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. + +--- + +## πŸ”¬ Part 12: Resolving Functions in Ghidra (Static) + +### Step 22: Navigate to Main + +1. Press `G` (Go to address) and type `10000234` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +### Step 23: Resolve stdio_init_all + +At address `0x10000236`: + +1. Double-click on the called function +2. Right-click β†’ **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 24: Resolve servo_init + +Look for a function call where `r0` is loaded with `0x6` (GPIO pin 6): + +```assembly +movs r0, #0x6 ; GPIO pin 6 +bl FUN_xxxxx ; servo_init +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void servo_init(uint pin)` +3. Click **OK** + +### Step 25: Resolve puts + +Look for function calls that load string addresses into `r0`: + +```assembly +ldr r0, =0x10001c54 ; Address of "1" string +bl FUN_xxxxx ; puts +``` + +**How do we know it's puts?** +- It takes a single string argument +- The hex `0x31` is ASCII "1" +- The hex `0x0d` is carriage return "\r" +- We see "1" echoed in PuTTY + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `int puts(char *s)` +3. Click **OK** + +### Step 26: Resolve servo_set_angle + +Look for a function that loads float constants. Inside the function, you'll find: +- `0x7D0` (2000 decimal) - maximum pulse width +- `0x3E8` (1000 decimal) - minimum pulse width + +These are the servo pulse limits! + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void servo_set_angle(float degrees)` +3. Click **OK** + +### Step 27: Resolve sleep_ms + +Look for a function where `r0` is loaded with `0x1f4` (500 decimal): + +```assembly +ldr r0, =0x1f4 ; 500 milliseconds +bl FUN_xxxxx ; sleep_ms +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +--- + +## πŸ”¬ Part 13: Hacking Static Conditionals + +### Step 28: Open the Bytes Editor + +1. Click **Window** β†’ **Bytes** +2. A new panel appears showing raw hex bytes +3. Click the pencil icon to enable editing + +### Step 29: Hack #1 - Change "1" to "2" + +Find the string "1" in memory: + +1. Double-click on the address reference for "1" (around `0x10001c54`) +2. You'll see `31` (ASCII for "1") +3. Change `31` to `32` (ASCII for "2") + +### Step 30: Hack #2 - Change "one" to "fun" + +Find the string "one": + +1. Navigate to the "one" string address +2. Find bytes `6f 6e 65` ("one" in ASCII) +3. Change to `66 75 6e` ("fun" in ASCII) + +**ASCII Reference:** +| Character | Hex | +| --------- | ---- | +| o | 0x6f | +| n | 0x6e | +| e | 0x65 | +| f | 0x66 | +| u | 0x75 | +| n | 0x6e | + +### Step 31: Hack #3 - Speed Up the Servo + +Find the sleep_ms delay value: + +1. Look for `0x1f4` (500) in the code +2. This appears TWICE (once for each sleep_ms call) +3. Change both from `f4 01` to `64 00` (100 in little-endian) + +**Before:** 500ms delay (servo moves slowly) +**After:** 100ms delay (servo moves FAST!) + +### Step 32: Export and Flash + +1. Click **File** β†’ **Export Program** +2. Set **Format** to **Binary** +3. Name: `0x001d_static-conditionals-h.bin` +4. Click **OK** + +Convert and flash: + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x001d_static-conditionals +python ..\uf2conv.py build\0x001d_static-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 33: Verify the Hacks + +**Serial output now shows:** +``` +2 +fun +2 +fun +... +``` + +**The servo now moves 5x faster!** It's spinning like crazy! + +--- + +## πŸ”¬ Part 14: Dynamic Conditionals - The Source Code + +### Step 34: Review the Dynamic Code + +**File: `0x0020_dynamic-conditionals.c`** + +```c +#include +#include "pico/stdlib.h" +#include "servo.h" + +#define SERVO_GPIO 6 + +int main(void) { + stdio_init_all(); + + uint8_t choice = 0; // DYNAMIC - changes with user input! + + servo_init(SERVO_GPIO); + + while (true) { + choice = getchar(); // Wait for keyboard input + + if (choice == 0x31) { // '1' + printf("1\r\n"); + } else if (choice == 0x32) { // '2' + printf("2\r\n"); + } else { + printf("??\r\n"); + } + + switch (choice) { + case '1': + printf("one\r\n"); + servo_set_angle(0.0f); + sleep_ms(500); + servo_set_angle(180.0f); + sleep_ms(500); + break; + case '2': + printf("two\r\n"); + servo_set_angle(180.0f); + sleep_ms(500); + servo_set_angle(0.0f); + sleep_ms(500); + break; + default: + printf("??\r\n"); + } + } +} +``` + +### Step 35: Understand the Dynamic Behavior + +| User Types | Output | Servo Action | +| ------------- | ----------- | ------------ | +| '1' (0x31) | "1" + "one" | 0Β° β†’ 180Β° | +| '2' (0x32) | "2" + "two" | 180Β° β†’ 0Β° | +| Anything else | "??" + "??" | No movement | + +### Step 36: Flash and Test + +1. Flash `0x0020_dynamic-conditionals.uf2` +2. Open PuTTY +3. Press '1' - servo sweeps one direction +4. Press '2' - servo sweeps the other direction +5. Press 'x' - prints "??" and no movement + +--- + +## πŸ”¬ Part 15: Debugging with GDB (Dynamic Conditionals) + +### Step 37: Start OpenOCD (Terminal 1) + +Open a terminal and 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" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 38: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```powershell +arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf +``` + +### Step 39: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target remote :3333 +``` + +### Step 40: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 41: Examine Main Function + +Disassemble around main to see the dynamic conditionals: + +```gdb +disassemble 0x10000234,+250 +``` + +Look for the `getchar` call followed by comparison and branch instructions. + +### Step 42: Set a Breakpoint After getchar + +Find where `choice` gets its value: + +```gdb +break *0x10000250 +``` + +Reset and continue: + +```gdb +monitor reset halt +continue +``` + +### Step 43: Watch the Input Value + +When you press a key in PuTTY, the breakpoint hits. Check the return value: + +```gdb +info registers r0 +``` + +If you pressed '1', you should see `0x31`. If you pressed '2', you should see `0x32`. + +### Step 44: Trace the Comparison Logic + +Step through the comparison instructions: + +```gdb +stepi 10 +info registers +``` + +Watch for `cmp r0, #0x31` and `cmp r0, #0x32` instructions. + +### Step 45: Examine the Branch Decisions + +Look at the condition flags after comparison: + +```gdb +info registers cpsr +``` + +The zero flag (Z) determines if the branch is taken. + +### Step 46: Watch Different Input Paths + +Continue and press different keys to see how the program takes different branches: + +```gdb +continue +``` + +Press '2' in PuTTY, then examine registers again. + +### Step 47: Examine Servo Control + +Set a breakpoint on servo_set_angle: + +```gdb +break *0x10000280 +continue +``` + +Check the angle value: + +```gdb +info registers s0 +``` + +### Step 48: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## πŸ”¬ Part 16: Setting Up Ghidra for Dynamic Conditionals + +### Step 49: Create New Project + +1. Create project: `0x0020_dynamic-conditionals` +2. Import the `.bin` file +3. Configure as ARM Cortex, base address `10000000` +4. Analyze + +### Step 50: Navigate to Main + +Press `G` and go to `10000234`. + +### Step 51: Resolve Functions + +Follow the same process: + +1. **main** at `0x10000234` β†’ `int main(void)` +2. **stdio_init_all** β†’ `bool stdio_init_all(void)` +3. **servo_init** β†’ `void servo_init(uint pin)` +4. **puts** β†’ `int puts(char *s)` +5. **servo_set_angle** β†’ `void servo_set_angle(float degrees)` +6. **sleep_ms** β†’ `void sleep_ms(uint ms)` + +### Step 52: Identify getchar + +Look for a function that: +- Returns a value in `r0` +- That value is then compared against `0x31` ("1") + +```assembly +bl FUN_xxxxx ; This is getchar! +mov r4, r0 ; Save return value +cmp r4, #0x31 ; Compare to '1' +beq LAB_xxxxx ; Branch if equal +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `int getchar(void)` +3. Click **OK** + +### Step 53: Identify Hardware Addresses + +Double-click into `stdio_init_all` and look for hardware addresses: + +```assembly +ldr r0, =0x40070000 ; UART0 base address +``` + +Check the RP2350 datasheet Section 2.2 (Address Map): +- `0x40070000` = UART0 + +This confirms it's a UART initialization function! + +--- + +## πŸ”¬ Part 17: Understanding Branch Instructions + +### ARM Branch Instructions + +| Instruction | Meaning | Condition | +| ----------- | ---------------------- | ------------------ | +| `b` | Branch (always) | Unconditional jump | +| `beq` | Branch if Equal | Zero flag set | +| `bne` | Branch if Not Equal | Zero flag clear | +| `bgt` | Branch if Greater Than | Signed greater | +| `blt` | Branch if Less Than | Signed less | + +### How Conditionals Become Branches + +```c +if (choice == 0x31) { + printf("1"); +} +``` + +Becomes: + +```assembly +cmp r4, #0x31 ; Compare choice to '1' +bne skip_printf ; If NOT equal, skip the printf +; ... printf code here ... +skip_printf: +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Conditional Branch Flow β”‚ +β”‚ β”‚ +β”‚ cmp r4, #0x31 β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ (Sets flags based on r4 - 0x31) β”‚ +β”‚ β–Ό β”‚ +β”‚ beq target_address β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€ If r4 == 0x31: Jump to target_address β”‚ +β”‚ β”‚ β”‚ +β”‚ └── If r4 != 0x31: Continue to next instruction β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ”¬ Part 18: Advanced Hacking - Creating Stealth Commands + +### The Goal + +We want to create **secret commands** that: +1. Respond to 'x' and 'y' instead of '1' and '2' +2. Move the servo WITHOUT printing anything +3. Leave NO trace in the terminal + +### Step 54: Plan the Patches + +**Original behavior:** +- '1' (0x31) β†’ prints "1" and "one", moves servo +- '2' (0x32) β†’ prints "2" and "two", moves servo + +**Hacked behavior:** +- 'x' (0x78) β†’ moves servo SILENTLY +- 'y' (0x79) β†’ moves servo SILENTLY +- '1' (0x31) β†’ prints "1" and "one" (normal) +- '2' (0x32) β†’ prints "2" and "two" (normal) + +### Step 55: Change Comparison Values + +Find the compare instructions: + +```assembly +cmp r4, #0x31 ; Change to #0x78 ('x') +... +cmp r4, #0x32 ; Change to #0x79 ('y') +``` + +### Step 56: Redirect Branches to Skip Prints + +For the stealth keys, we need to jump PAST the printf calls directly to the servo code. + +**Original flow:** +``` +compare β†’ branch β†’ printf("1") β†’ printf("one") β†’ servo code +``` + +**Hacked flow:** +``` +compare 'x' β†’ branch β†’ [skip prints] β†’ servo code +``` + +Change the `beq` target addresses: +- Original: `beq 0x10000270` (goes to printf) +- Hacked: `beq 0x1000027c` (skips to servo) + +### Step 57: NOP Out Print Calls + +**NOP** (No Operation) is an instruction that does nothing. We use it to "erase" code without changing the size of the binary. + +ARM Thumb NOP encoding: `00 bf` (2 bytes) + +To NOP out a `bl puts` instruction (4 bytes), use `00 bf 00 bf`. + +### Step 58: Apply All Patches + +Here's the complete patch list: + +| Offset | Original | Patched | Purpose | +| -------- | ------------ | ------------ | ---------------------------- | +| Compare1 | `31` | `78` | Check for 'x' instead of '1' | +| Compare2 | `32` | `79` | Check for 'y' instead of '2' | +| Branch1 | target=0x270 | target=0x27c | Skip printf for 'x' | +| Branch2 | target=0x29a | target=0x2a6 | Skip printf for 'y' | +| puts1 | `bl puts` | `nop nop` | Remove print | +| puts2 | `bl puts` | `nop nop` | Remove print | + +### Step 59: Hack the Angle Value + +Let's also change 180Β° to 30Β° for fun! + +**Original:** `0x43340000` (180.0f in IEEE-754) +**New:** `0x41f00000` (30.0f in IEEE-754) + +**Calculation for 30.0f:** +``` +30.0 = 1.875 Γ— 2^4 +Sign = 0 +Exponent = 127 + 4 = 131 = 0x83 +Mantissa = 0.875 = 0x700000 + +Binary: 0 10000011 11100000000000000000000 +Hex: 0x41f00000 +Little-endian: 00 00 f0 41 +``` + +### Step 60: Export and Test + +1. Export as `0x0020_dynamic-conditionals-h.bin` +2. Convert to UF2: + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals +python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +3. Flash and test: + - Press '1' β†’ prints "1" and "one", servo moves + - Press '2' β†’ prints "2" and "two", servo moves + - Press 'x' β†’ NO OUTPUT, but servo moves silently! + - Press 'y' β†’ NO OUTPUT, but servo moves silently! + +--- + +## πŸ“Š Part 19: Summary and Review + +### What We Accomplished + +1. **Learned static vs dynamic conditionals** - Fixed vs runtime-determined values +2. **Understood if/else and switch/case** - Two ways to branch in C +3. **Mastered PWM calculations** - 150MHz to 50Hz servo signal +4. **Identified conditional branches in assembly** - beq, bne, cmp instructions +5. **Hacked string literals** - Changed "one" to "fun" +6. **Modified timing values** - Sped up servo from 500ms to 100ms +7. **Created stealth commands** - Hidden 'x' and 'y' keys +8. **NOPed out print statements** - Removed logging for stealth +9. **Redirected branch targets** - Changed program flow + +### Static vs Dynamic Summary + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Static Conditionals β”‚ +β”‚ ─────────────────── β”‚ +β”‚ β€’ Variable set once, never changes β”‚ +β”‚ β€’ Same path taken every iteration β”‚ +β”‚ β€’ Compiler may optimize out dead branches β”‚ +β”‚ β€’ Example: int choice = 1; if (choice == 1) β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ Dynamic Conditionals β”‚ +β”‚ ──────────────────── β”‚ +β”‚ β€’ Variable changes based on input/sensors β”‚ +β”‚ β€’ Different paths taken based on runtime state β”‚ +β”‚ β€’ All branches must remain in binary β”‚ +β”‚ β€’ Example: choice = getchar(); if (choice == '1') β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### PWM Calculation Summary + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Servo PWM Calculation Chain β”‚ +β”‚ β”‚ +β”‚ Angle (degrees) β†’ Pulse Width (Β΅s) β†’ PWM Ticks β†’ Servo Motion β”‚ +β”‚ β”‚ +β”‚ 0Β° β†’ 1000 Β΅s β†’ 1000 ticks β†’ Fully counter-clockwise β”‚ +β”‚ 90Β° β†’ 1500 Β΅s β†’ 1500 ticks β†’ Center position β”‚ +β”‚ 180Β° β†’ 2000 Β΅s β†’ 2000 ticks β†’ Fully clockwise β”‚ +β”‚ β”‚ +β”‚ Formula: pulse = 1000 + (angle/180) Γ— 1000 β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Memory Addresses + +| Memory Address | Description | +| -------------- | ------------------------ | +| `0x10000234` | main() function | +| `0x40070000` | UART0 hardware registers | +| `0x1f4` | 500 (sleep_ms delay) | +| `0x7D0` | 2000 (max pulse width) | +| `0x3E8` | 1000 (min pulse width) | +| `0x43340000` | 180.0f (max angle) | + +> πŸ“– **Datasheet Reference:** UART0 base address is `0x40070000` and UART1 is `0x40078000` (Section 2.2, p. 33; Section 12.4, p. 1029). The flash XIP base at `0x10000000` is where the vector table and application code reside (Section 2.2, p. 31). + +--- + +## βœ… Practice Exercises + +### Exercise 1: Change Servo Angle Range +Modify the servo to sweep from 45Β° to 135Β° instead of 0Β° to 180Β°. + +**Hint:** Calculate IEEE-754 values for 45.0f and 135.0f. + +### Exercise 2: Add a Third Command +Add support for key '3' that moves the servo to 90Β° (center position). + +**Hint:** You'll need to find space in the binary or modify existing code. + +### Exercise 3: Reverse the Servo Direction +Make '1' do what '2' does and vice versa. + +**Hint:** Swap the branch targets. + +### Exercise 4: Speed Profile +Create fast movement for '1' (100ms) and slow movement for '2' (1000ms). + +**Hint:** Find both sleep_ms calls and patch them differently. + +### Exercise 5: Complete Stealth Mode +Make ALL servo movements silent - remove ALL printf and puts calls. + +**Hint:** NOP out every output function call. + +--- + +## πŸŽ“ Key Takeaways + +1. **Static conditionals have fixed outcomes** - The same path always executes + +2. **Dynamic conditionals respond to input** - Different paths based on runtime state + +3. **PWM frequency = 50Hz for servos** - One pulse every 20ms + +4. **Pulse width encodes position** - 1ms=0Β°, 1.5ms=90Β°, 2ms=180Β° + +5. **beq = branch if equal** - Jumps when comparison matches + +6. **bne = branch if not equal** - Jumps when comparison doesn't match + +7. **NOP erases code without changing size** - `00 bf` in ARM Thumb + +8. **Branch targets can be redirected** - Change where code jumps to + +9. **IEEE-754 is needed for angles** - Floats have specific bit patterns + +10. **Stealth requires removing ALL output** - NOP out printf AND puts + +--- + +## πŸ“– Glossary + +| Term | Definition | +| ----------------------- | --------------------------------------------------- | +| **beq** | Branch if Equal - ARM conditional jump | +| **bne** | Branch if Not Equal - ARM conditional jump | +| **Dynamic Conditional** | Condition that changes based on runtime input | +| **Duty Cycle** | Percentage of time signal is HIGH | +| **getchar()** | C function that reads one character from input | +| **NOP** | No Operation - instruction that does nothing | +| **PWM** | Pulse Width Modulation - variable duty cycle signal | +| **SG90** | Common hobby servo motor model | +| **Static Conditional** | Condition with fixed/predetermined outcome | +| **switch/case** | C structure for multiple discrete value comparisons | +| **Wrap Value** | PWM counter maximum before reset | + +--- + +## πŸ”— Additional Resources + +### ASCII Reference Table + +| Character | Hex | Decimal | +| --------- | ---- | ------- | +| '0' | 0x30 | 48 | +| '1' | 0x31 | 49 | +| '2' | 0x32 | 50 | +| 'x' | 0x78 | 120 | +| 'y' | 0x79 | 121 | +| '\r' | 0x0d | 13 | +| '\n' | 0x0a | 10 | + +### IEEE-754 Common Angles + +| Angle | IEEE-754 Hex | Little-Endian Bytes | +| ----- | ------------ | ------------------- | +| 0.0 | 0x00000000 | 00 00 00 00 | +| 30.0 | 0x41f00000 | 00 00 f0 41 | +| 45.0 | 0x42340000 | 00 00 34 42 | +| 90.0 | 0x42b40000 | 00 00 b4 42 | +| 135.0 | 0x43070000 | 00 00 07 43 | +| 180.0 | 0x43340000 | 00 00 34 43 | + +### ARM Thumb NOP Encodings + +| Instruction | Encoding | Size | +| ----------- | ------------- | ------- | +| `nop` | `00 bf` | 2 bytes | +| `nop.w` | `00 f0 00 80` | 4 bytes | + +### RP2350 Key Addresses + +| Address | Peripheral | +| ------------ | ---------- | +| `0x40070000` | UART0 | +| `0x40078000` | UART1 | +| `0x40050000` | PWM | + +--- + +## 🚨 Real-World Implications + +### Why Stealth Commands Matter + +The ability to create hidden commands has serious implications: + +**Legitimate Uses:** +- Factory test modes +- Debugging interfaces +- Emergency recovery features + +**Malicious Uses:** +- Backdoors in firmware +- Hidden surveillance features +- Unauthorized control of systems + +### Real-World Example + +Imagine a drone with hacked firmware: +- Normal keys ('1', '2') control it visibly with logging +- Hidden keys ('x', 'y') control it with NO log entries +- An attacker could operate the drone while security monitors show nothing + +### The Nuclear Fuel Rod Analogy + +A fast-moving servo is like a nuclear fuel rod: +- Both are small components with immense power +- Both require precise control to prevent damage +- Both can "go critical" if pushed beyond limits +- Both teach the importance of safety margins + +--- + +**Remember:** The techniques you learned today demonstrate how conditional logic can be manipulated at the binary level. Understanding these attacks helps us build more secure embedded systems. Always use your skills ethically and responsibly! + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG00.svg b/WEEKS/WEEK10/slides/WEEK10-IMG00.svg new file mode 100644 index 0000000..84c608d --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 10 + + +Conditionals in Embedded Systems: +Debugging and Hacking Static & Dynamic +Conditionals w/ SG90 Servo Motor PWM + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG01.svg b/WEEKS/WEEK10/slides/WEEK10-IMG01.svg new file mode 100644 index 0000000..38d8eb0 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG01.svg @@ -0,0 +1,82 @@ + + + + +Conditionals Overview +Static vs Dynamic Decision Making + + + +What Are Conditionals? +Structures that let programs choose +different paths based on conditions + + + +Static Conditional +Value fixed at compile time + + +int choice = 1; +// never changes +if (choice == 1) +printf("1"); +// always runs +else printf("2"); +// never runs + + + +Dynamic Conditional +Value changes at runtime + + +choice = getchar(); +// user types a key +if (choice == '1') +printf("1"); +// maybe runs + + + +if/else + +Feature +Description + +Condition +Any boolean expr +Values +Ranges, complex logic +Fall-through +No +Best for +2-3 conditions + + + +switch/case + +Feature +Description + +Condition +Single variable +Values +Discrete only +Fall-through +Yes (no break) +Best for +Many conditions + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG02.svg b/WEEKS/WEEK10/slides/WEEK10-IMG02.svg new file mode 100644 index 0000000..cbd97b4 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG02.svg @@ -0,0 +1,89 @@ + + + + +Static Conditionals +Fixed Outcome -- Same Path Every Time + + + +Static Code Pattern + +int choice = 1; +// NEVER changes +while (true) { +if (choice == 1) +printf("1"); +else if (choice == 2) +printf("2"); +// dead code +else + + + +Execution Flow + + +choice == 1? + + +YES + + +print "1" + +NO (never taken) + + + +choice == 2? + +NO (never reached) + + +print "?" + +Only ONE path ever executes! + + + +Every Loop Iteration (Always the Same) +1. if(1==1) --> TRUE +2. print "1" +3. switch(1) case 1 +4. print "one" +5. servo 0deg +6. sleep 500ms +7. servo 180deg + + + +Serial Output (Forever) + +1 +one +1 +// repeats forever + + + +Servo Motion (Forever) +0deg +--> +180deg +--> +0deg +Sweeps back and forth, 500ms each +Continuous, predictable motion + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG03.svg b/WEEKS/WEEK10/slides/WEEK10-IMG03.svg new file mode 100644 index 0000000..1e69e2e --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG03.svg @@ -0,0 +1,94 @@ + + + + +Dynamic Conditionals +Runtime Input Changes the Path + + + +Dynamic Code Pattern + +uint8_t choice = 0; +while (true) { +choice = getchar(); +// waits for keyboard input +if (choice == 0x31) +printf("1"); +else if (choice == 0x32) +printf("2"); + + + +Execution Flow + + +choice = getchar() + + + + +choice=='1'? + +YES + +servo 0-180 + + +NO + + +choice=='2'? + +YES + +servo 180-0 + + + +print "??" +Each iteration can take a DIFFERENT path + + + +getchar() Returns ASCII +'1' = 0x31 +'2' = 0x32 +'x' = 0x78 +'y' = 0x79 +Blocks until keypress + + + +Input --> Behavior + +Key +Output +Servo + +'1' +"1" + "one" +0deg --> 180deg +'2' +"2" + "two" +180deg --> 0deg + + + +Two Projects +0x001d_static-conditionals +choice = 1 (fixed) +0x0020_dynamic-conditionals +choice = getchar() (user input) + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG04.svg b/WEEKS/WEEK10/slides/WEEK10-IMG04.svg new file mode 100644 index 0000000..a405f7e --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG04.svg @@ -0,0 +1,96 @@ + + + + +PWM Basics +Pulse Width Modulation for Servo Control + + + +What is PWM? +Rapidly switching a signal ON and OFF +Ratio of on-time to off-time controls power + + +HIGH + + + + + + + + +ON +OFF +ON +OFF + + + +Servo PWM (50Hz = 20ms period) + + +0deg (1ms pulse): + + + +1ms HIGH +19ms LOW + + +90deg (1.5ms pulse): + + + +1.5ms HIGH +18.5ms LOW + + +180deg (2ms pulse): + + + +2ms HIGH +18ms LOW + +Pulse WIDTH determines angle, not duty cycle +Total period always 20ms (50Hz) + + + +Angle to Pulse Width + +Angle +Pulse +Ticks (1MHz) + +0deg +1000us +1000 +90deg +1500us +1500 +180deg +2000us +2000 + + + +Formula +pulse = 1000 + (angle/180) x 1000 +Example for 90deg: +1000 + (90/180) x 1000 += 1500us = 1500 ticks + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG05.svg b/WEEKS/WEEK10/slides/WEEK10-IMG05.svg new file mode 100644 index 0000000..9499ded --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG05.svg @@ -0,0 +1,83 @@ + + + + +PWM Timing Chain +150MHz System Clock to 50Hz Servo Signal + + + +Clock Division + + +150 MHz Clock + +/ 150 + + + + +1 MHz PWM + +1 tick = 1us + + + +Step 1: 150,000,000 / 150 = 1,000,000 Hz +Each PWM tick = exactly 1 microsecond + +Step 2: Wrap at 20,000 ticks = 20ms = 50Hz +Wrap value = 19,999 + + + +SG90 Servo Motor + +Parameter +Value + +Voltage +4.8V - 6V (use 5V) +Rotation +0deg to 180deg +Pulse Width +1000us - 2000us +Frequency +50Hz (20ms period) + + + +Wiring to Pico 2 + + +Pico + + +SG90 + + + + +GPIO 6 = Signal (Orange) +VBUS 5V = VCC (Red) +GND = GND (Brown) +Add 1000uF capacitor on power! + + + +Power Safety +NEVER use 3.3V pin for servo! +Servos draw 650mA+ (spikes to 1A) +Use VBUS (5V from USB) with 1000uF 25V capacitor + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG06.svg b/WEEKS/WEEK10/slides/WEEK10-IMG06.svg new file mode 100644 index 0000000..bb7867d --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG06.svg @@ -0,0 +1,55 @@ + + + + +Static Source Code +0x001d_static-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +int choice = 1; +// STATIC! +servo_init(SERVO_GPIO); + +while (true) { +if (choice == 1) +printf("1\r\n"); +else if (choice == 2) +printf("2\r\n"); +// dead code + + + +switch Block + +switch(choice) { +case 1: puts("one"); break; + + +Servo Loop + +servo_set_angle(0.0f); +sleep_ms(500); +// then 180 + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG07.svg b/WEEKS/WEEK10/slides/WEEK10-IMG07.svg new file mode 100644 index 0000000..edb9558 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG07.svg @@ -0,0 +1,50 @@ + + + + +Dynamic Source Code +0x0020_dynamic-conditionals.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "servo.h" +#define SERVO_GPIO 6 + +int main(void) { +stdio_init_all(); +uint8_t choice = 0; +// DYNAMIC! +servo_init(SERVO_GPIO); + +while (true) { +choice = getchar(); +// wait for input +if (choice == 0x31) +// '1' +printf("1\r\n"); +else if (choice == 0x32) +// '2' +printf("2\r\n"); + + + +switch Block (with servo control) +case '1': print "one", servo 0-->180, sleep 500ms +case '2': print "two", servo 180-->0, sleep 500ms + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG08.svg b/WEEKS/WEEK10/slides/WEEK10-IMG08.svg new file mode 100644 index 0000000..295c649 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG08.svg @@ -0,0 +1,101 @@ + + + + +Branch Instructions +How Conditionals Become Assembly + + + +ARM Branch Instructions + +Instr +Meaning +Condition + + +b +Branch always +Always + +beq +Branch if Equal +Z flag set + +bne +Branch if != +Z flag clear + +bgt +Branch if > +Signed > + +blt +Branch if < +Signed < + + + +C --> Assembly + +C code: + +if (choice == 0x31) +printf("1"); + +Assembly: + +cmp r4, #0x31 +// compare +bne skip_printf +// skip if != + + + +Conditional Branch Flow + + +cmp r4, #0x31 + + + + +beq target_addr + + +r4==0x31: JUMP + + +r4!=0x31: continue next + +cmp sets CPU flags, branch reads them + + + +NOP (No Operation) +ARM Thumb NOP: +00 bf +2 bytes +Wide NOP: +00 f0 00 80 +Replaces 4-byte bl instruction + + + +Hacking Branches +Change branch target addr +Redirect program flow +NOP out instructions +Erase code silently + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG09.svg b/WEEKS/WEEK10/slides/WEEK10-IMG09.svg new file mode 100644 index 0000000..68d47e9 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG09.svg @@ -0,0 +1,83 @@ + + + + +Hacking Conditionals +Strings, Timing, Stealth Commands + + + +Hack 1: Change Strings +Change "1" to "2": +0x31 +--> +0x32 +"one" to "fun": +6f 6e 65 +--> +66 75 6e + + + +Hack 2: Speed Up Servo +Change sleep_ms delay: +0x1F4 (500ms) +--> +0x064 (100ms) + + + +Hack 3: Stealth Commands +Hidden keys move servo with NO output + +Patch +Original +Hacked +Purpose + + +Compare 1 +#0x31 ('1') +#0x78 ('x') +New trigger key + +Compare 2 +#0x32 ('2') +#0x79 ('y') +New trigger key + +puts calls +bl puts +00 bf 00 bf +NOP out prints + + + +Hack 4: Change Angle +180.0f --> 30.0f: +00 00 34 43 +--> +00 00 f0 41 + + + +Stealth Result +'1','2': normal output + servo +'x','y': NO output, servo moves + + + +Workflow +Patch bytes in Ghidra --> export .bin --> convert to UF2 --> flash to Pico + diff --git a/WEEKS/WEEK10/slides/WEEK10-IMG10.svg b/WEEKS/WEEK10/slides/WEEK10-IMG10.svg new file mode 100644 index 0000000..eb92463 --- /dev/null +++ b/WEEKS/WEEK10/slides/WEEK10-IMG10.svg @@ -0,0 +1,85 @@ + + + + +PWM & Servo Hacking +Conditionals, PWM, Servo, and Hacking + + + +Static vs Dynamic + +Static +choice = 1 (fixed) +Same path every iteration +Compiler may optimize + +Dynamic +choice = getchar() +Different paths at runtime + + + +PWM for Servos +150MHz / 150 = 1MHz tick +Wrap 20000 = 50Hz (20ms) +0deg=1000us 90deg=1500us 180deg=2000us +pulse = 1000 + (angle/180) x 1000 + + + +Branch Instructions +cmp r4, #0x31 +Compare +beq target +Jump if equal +bne target +Jump if not equal +NOP = 00 bf (erase code) + + + +Key Values +0x10000234 +main() +0x40070000 +UART0 +0x1F4 +500 (sleep_ms) +0x43340000 +180.0f IEEE-754 + + + +4 Hack Types Applied +String +"one"-->"fun" +Timing +500ms-->100ms +Stealth +NOP out prints +Angle +180.0f-->30.0f + + + +Projects +0x001d_static-conditionals +0x0020_dynamic-conditionals + + +IEEE-754 Angles +0.0f=00000000 90.0f=42b40000 +180.0f=43340000 30.0f=41f00000 + diff --git a/WEEKS/WEEK11/WEEK11-01-S.md b/WEEKS/WEEK11/WEEK11-01-S.md new file mode 100644 index 0000000..a4e934b --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-01-S.md @@ -0,0 +1,66 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control + +### Non-Credit Practice Exercise 1 Solution: Add a Fourth LED + +#### Answers + +##### Struct Layout (simple_led_ctrl_t) + +| Offset | Field | Size | Original Value | Hex | +|--------|------------|--------|----------------|------| +| 0 | led1_pin | 1 byte | GPIO 16 | 0x10 | +| 1 | led2_pin | 1 byte | GPIO 17 | 0x11 | +| 2 | led3_pin | 1 byte | GPIO 18 | 0x12 | +| 3 | led1_state | 1 byte | false (0) | 0x00 | +| 4 | led2_state | 1 byte | false (0) | 0x00 | +| 5 | led3_state | 1 byte | false (0) | 0x00 | + +Total struct size: **6 bytes** (3 pin bytes + 3 state bytes). + +##### Assembly Initialization Pattern + +```asm +movs r0, #0x10 ; led1_pin = 16 +strb r0, [r4, #0] ; struct offset 0 +movs r0, #0x11 ; led2_pin = 17 +strb r0, [r4, #1] ; struct offset 1 +movs r0, #0x12 ; led3_pin = 18 +strb r0, [r4, #2] ; struct offset 2 +``` + +##### Patch: Add GPIO 19 at Struct Offset 3 + +Writing `0x13` to offset 3 **overwrites led1_state**: + +| Offset | Before | After | Impact | +|--------|-------------|-------------|----------------------| +| 3 | 0x00 (led1_state = false) | 0x13 (led4_pin = GPIO 19) | led1_state corrupted | + +##### GDB Verification + +```gdb +(gdb) b *0x10000280 +(gdb) c +(gdb) x/8bx +``` + +Before patch: `10 11 12 00 00 00` +After patch: `10 11 12 13 00 00` + +#### Reflection Answers + +1. **The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite led1_state. What is the practical impact on LED 1 behavior?** + The byte `0x13` (decimal 19) is written to offset 3, which the program reads as `led1_state`. Since `bool` in C treats any non-zero value as `true`, `led1_state` would be interpreted as `true` (on) immediately after the struct is initialized. LED 1 would appear to be in the "on" state from the start, regardless of whether the user pressed button 1. The `leds_all_off` function may reset it to 0, but every time the struct is re-initialized on the stack (each loop iteration), the corrupted state returns. The fourth LED at GPIO 19 would need additional `gpio_init` and `gpio_set_dir` calls to actually function β€” just writing the pin number into the struct doesn't configure the GPIO hardware. + +2. **How would you verify the exact struct layout and offsets using GDB's memory examination commands?** + Set a breakpoint after struct initialization (`b *0x10000280`), then `x/6bx ` to see all 6 bytes. Verify: offsets 0–2 should show `10 11 12` (pin values), offsets 3–5 should show `00 00 00` (state values). Use `x/1bx ` for individual fields. To find the struct base, examine `r4` at the breakpoint since the `strb r0, [r4, #N]` instructions use r4 as the base. You can also use `p/x $r4` to get the base address, then `x/6bx $r4` for the complete layout. + +3. **If the get_led_pin function uses a bounds check (e.g., if led_num > 3 return 0), what additional patch would you need?** + You would need to find the comparison instruction in `get_led_pin` (at approximately `0x100002a0`) β€” likely a `cmp rN, #3` followed by a conditional branch. Patch the immediate from `#3` to `#4` so the bounds check allows led_num = 4. For example, if the check is `cmp r1, #3; bhi default`, change `03` to `04` in the `cmp` instruction's immediate byte. Without this patch, passing led_num=4 would fail the bounds check and return 0 (no pin), so the fourth LED would never be addressed. + +4. **Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce?** + You could find unused space (padding, NOP sleds, or unused data) and place the extended struct there. However, this introduces major challenges: (1) Every instruction that references the original struct address via `r4` would need to be redirected to the new location. (2) All `strb`/`ldrb` offsets would need updating. (3) Stack-allocated structs are recreated each loop iteration β€” you'd need to change the stack frame size (`sub sp, sp, #N`). (4) Functions that receive the struct pointer as an argument would need their call sites updated. In practice, relocating a struct in a compiled binary is extremely complex and error-prone β€” overwriting adjacent fields is the pragmatic (if destructive) approach. diff --git a/WEEKS/WEEK11/WEEK11-01.md b/WEEKS/WEEK11/WEEK11-01.md new file mode 100644 index 0000000..6dcdb83 --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-01.md @@ -0,0 +1,156 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +### Non-Credit Practice Exercise 1: Add a Fourth LED + +#### Objective +Find the struct initialization pattern in the `0x0026_functions` binary using GDB where `led1_pin` (0x10), `led2_pin` (0x11), and `led3_pin` (0x12) are stored, locate an unused byte in the struct memory region, and patch it to include a fourth LED on GPIO 19 (0x13) by extending the struct data and modifying the `ir_to_led_number` function to handle a fourth button mapping. + +#### Prerequisites +- Completed Week 11 tutorial (GDB and hex editor sections) +- `0x0026_functions.elf` and `0x0026_functions.bin` 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 IR remote and LEDs on GPIO 16, 17, 18 (and GPIO 19 wired for the new LED) + +#### Task Description +The `simple_led_ctrl_t` struct stores three LED pin numbers: `led1_pin` (16/0x10), `led2_pin` (17/0x11), `led3_pin` (18/0x12). These are stored as consecutive bytes in the struct initialization. You will find where the struct is initialized in the binary, locate the `movs` instructions that set the pin values, and add `led4_pin` = 19 (0x13) by patching a nearby unused or default byte. You will also need to find where `ir_to_led_number` returns values 1, 2, or 3 and adjust the NEC command comparison to map a fourth button to LED 4. + +#### 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\0x0026_functions.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the Struct Initialization + +Disassemble main and look for the struct pin assignments: + +```gdb +(gdb) disassemble 0x10000234,+300 +``` + +Look for consecutive `movs` instructions: + +``` +movs r0, #0x10 ; led1_pin = 16 +strb r0, [r4, #0] ; store to struct offset 0 +movs r0, #0x11 ; led2_pin = 17 +strb r0, [r4, #1] ; store to struct offset 1 +movs r0, #0x12 ; led3_pin = 18 +strb r0, [r4, #2] ; store to struct offset 2 +``` + +##### Step 3: Examine the Struct in Memory + +Set a breakpoint after initialization and examine the struct: + +```gdb +(gdb) break *0x10000280 +(gdb) monitor reset halt +(gdb) continue +(gdb) x/8bx +``` + +You should see: `10 11 12 00 00 00` β€” the three pin values followed by the state booleans (all false/0x00). + +##### Step 4: Find the `get_led_pin` Function + +Look for the function that reads from the struct based on LED number: + +```gdb +(gdb) disassemble 0x100002a0,+50 +``` + +This function takes a struct pointer and LED number and returns the GPIO pin by reading from a struct offset. + +##### Step 5: Calculate File Offsets + +``` +file_offset = address - 0x10000000 +``` + +Note offsets for: +1. The `movs r0, #0x12` instruction (last pin assignment) +2. The byte after `led3_pin` in the struct (where `led4_pin` would go) + +##### Step 6: Plan the Patches + +| Patch Target | Original | New | Purpose | +| --------------------- | -------- | ------ | ------------------------- | +| Struct byte after 0x12 | `00` | `13` | Add led4_pin = GPIO 19 | + +###### Question 1: The struct layout has `led3_pin` at offset 2 and `led1_state` at offset 3. If you write `0x13` to offset 3, what happens to `led1_state`? + +##### Step 7: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` +2. Navigate to the struct initialization area +3. Apply the patches identified in Step 6 + +##### Step 8: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0026_functions-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-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 behavior:** +- Existing buttons 1, 2, 3 should still control their LEDs +- Verify with GDB that the struct now contains `10 11 12 13` at the pin offsets + +#### Expected Output + +After completing this exercise, you should be able to: +- Understand struct memory layout and member offsets +- Identify struct initialization patterns in ARM assembly +- Patch struct data members in binary firmware +- Reason about the consequences of overwriting adjacent struct fields + +#### Questions for Reflection + +###### Question 1: The original struct has 6 members (3 pins + 3 states) in 6 bytes. If you add a fourth pin at offset 3, you overwrite `led1_state`. What is the practical impact on LED 1 behavior? + +###### Question 2: How would you verify the exact struct layout and offsets using GDB's memory examination commands? + +###### Question 3: If the `get_led_pin` function uses a bounds check (e.g., `if led_num > 3 return 0`), what additional patch would you need? + +###### Question 4: Could you extend the struct without overwriting existing fields by finding free space elsewhere in the binary? What challenges would that introduce? + +#### Tips and Hints +- GPIO 19 = `0x13` in hex +- The struct is likely stack-allocated, so the initialization `movs`/`strb` sequence happens every loop iteration +- Overwriting `led1_state` (offset 3) with `0x13` means LED 1 will appear as "on" (non-zero boolean) β€” this may cause LED 1 to be on at startup +- The `get_led_pin` function likely uses the LED number as an index into the struct β€” trace how it calculates the offset diff --git a/WEEKS/WEEK11/WEEK11-02-S.md b/WEEKS/WEEK11/WEEK11-02-S.md new file mode 100644 index 0000000..7b81a3b --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-02-S.md @@ -0,0 +1,62 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control + +### Non-Credit Practice Exercise 2 Solution: Change Blink Count + +#### Answers + +##### Blink Count Parameter + +| Parameter | Original | Patched | +|-------------|---------|---------| +| Blink count | 3 | 5 | +| Hex | 0x03 | 0x05 | +| Register | r1 | r1 | +| Instruction | movs r1, #3 | movs r1, #5 | + +##### Assembly Context (blink_led Call) + +```asm +movs r0, ; r0 = GPIO pin number +movs r1, #3 ; r1 = blink count ← PATCH THIS +movs r2, #0x32 ; r2 = delay (50ms) +bl blink_led ; blink_led(pin, 3, 50) +``` + +##### Patch + +The immediate byte in `movs r1, #3` is the first byte of the 2-byte Thumb instruction: + +``` +Before: 03 21 (movs r1, #3) +After: 05 21 (movs r1, #5) +``` + +File offset = instruction address - 0x10000000. + +##### Behavior After Patch + +| Button | LED | Original | Patched | +|--------|--------|-------------------|---------------------| +| 1 | Red | Blinks 3Γ—, stays on | Blinks 5Γ—, stays on | +| 2 | Green | Blinks 3Γ—, stays on | Blinks 5Γ—, stays on | +| 3 | Yellow | Blinks 3Γ—, stays on | Blinks 5Γ—, stays on | + +Total blink time at 50ms delay: 5 Γ— (50 + 50) = **500ms** (was 300ms). + +#### Reflection Answers + +1. **movs rN, #imm8 can encode values 0–255. What is the maximum blink count with a single byte patch?** + The maximum is **255** (`0xFF`). The `movs Rd, #imm8` Thumb instruction uses a full 8-bit immediate field, giving an unsigned range of 0–255. Setting the blink count to 255 would make each LED blink 255 times per button press β€” at 50ms on + 50ms off per blink, that's 255 Γ— 100ms = **25.5 seconds** of blinking before the LED stays on. A count of 0 would skip the blink loop entirely (LED turns on immediately with no blinking). + +2. **Why is blink count in r1 and not r0? What does r0 hold at this point?** + The ARM calling convention (AAPCS) passes the first four function arguments in registers `r0`, `r1`, `r2`, `r3` in order. The `blink_led` function signature is `blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms)`. So `r0` = pin (the GPIO number of the LED to blink), `r1` = count (how many times to blink), and `r2` = delay_ms (the delay in milliseconds between on/off transitions). The blink count is the second parameter, hence `r1`. + +3. **If you wanted a blink count larger than 255 (e.g., 1000), what instruction sequence would the compiler generate instead of movs?** + For values exceeding 255, the compiler would use a 32-bit Thumb-2 `movw r1, #imm16` instruction, which can encode 0–65535. For example, `movw r1, #1000` would be 4 bytes: `40 F2 E8 31` (encoding `movw r1, #0x3E8`). For values exceeding 65535, the compiler would add `movt r1, #imm16` to set the upper 16 bits, or use a literal pool load (`ldr r1, [pc, #offset]`). The function parameter type (`uint8_t`) would still truncate to 0–255, so a count of 1000 would wrap to 232 (1000 mod 256) unless the function uses a wider type internally. + +4. **Is there one shared movs r1, #3 instruction for all three LEDs, or does each blink_led call have its own? How can you tell?** + Each `blink_led` call likely has its **own** `movs r1, #3` instruction. The compiler generates separate parameter setup sequences for each `bl blink_led` call site β€” the `movs r0, ` instruction before each call loads a different GPIO pin. You can verify by disassembling the full range (`disassemble 0x10000234,+300`) and counting how many `movs r1, #3` instructions appear before `bl blink_led` calls. If there are three separate call sites with three separate `movs r1, #3` instructions, you need to **patch all three** to change the blink count for every LED. If only one is patched, only that LED's blink count changes. diff --git a/WEEKS/WEEK11/WEEK11-02.md b/WEEKS/WEEK11/WEEK11-02.md new file mode 100644 index 0000000..74c60f1 --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-02.md @@ -0,0 +1,144 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +### Non-Credit Practice Exercise 2: Change Blink Count + +#### Objective +Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `#3` being loaded into `r1` (the blink count parameter), calculate the file offset, and patch it to `#5` so that each LED blinks 5 times instead of 3 when activated by the IR remote. + +#### Prerequisites +- Completed Week 11 tutorial (GDB and hex editor sections) +- `0x0026_functions.elf` and `0x0026_functions.bin` 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 IR remote and LEDs on GPIO 16, 17, 18 + +#### Task Description +The `blink_led` function is called with three parameters: the GPIO pin number, a blink count of `3`, and a delay of `50`ms. The blink count is loaded as a small immediate value (`movs r1, #3`) directly in the instruction before the `bl blink_led` call. You will locate this instruction, find the byte encoding the `#3` immediate, and patch it to `#5` so the LEDs blink 5 times per button press. + +#### 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\0x0026_functions.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the blink_led Call + +Disassemble main and look for the function call sequence: + +```gdb +(gdb) disassemble 0x10000234,+300 +``` + +Look for the parameter setup before `bl blink_led`: + +``` +movs r0, ; GPIO pin number (from get_led_pin) +movs r1, #3 ; blink count = 3 +movs r2, #0x32 ; delay = 50ms +bl blink_led +``` + +Note the address of the `movs r1, #3` instruction. + +##### Step 3: Examine the Instruction Encoding + +Look at the raw bytes of the `movs r1, #3` instruction: + +```gdb +(gdb) x/2bx +``` + +In Thumb encoding, `movs r1, #imm8` has the immediate in the lower byte. You should see a byte containing `03`. + +##### Step 4: Calculate the File Offset + +``` +file_offset = address - 0x10000000 +``` + +Note the file offset of the byte containing `03`. + +##### Step 5: Encode the New Value + +| Parameter | Original | New | Encoding | +| ---------- | -------- | ---- | -------------- | +| Blink count | `03` | `05` | `movs r1, #5` | + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` +2. Press **Ctrl+G** and enter the file offset +3. You should see: `03` +4. Replace with: `05` + +###### Question 1: The `movs r1, #3` is a 2-byte Thumb instruction. Which byte contains the immediate β€” the first or the second? + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0026_functions-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 behavior:** +- Press button 1 β†’ Red LED blinks **5 times** (was 3), then stays on +- Press button 2 β†’ Green LED blinks **5 times** (was 3), then stays on +- Press button 3 β†’ Yellow LED blinks **5 times** (was 3), then stays on +- Count carefully β€” you should see exactly 5 on/off cycles + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate small immediate values in Thumb `movs` instructions +- Understand Thumb instruction encoding for immediate operands +- Patch function parameters by modifying instruction immediates +- Verify behavioral changes by counting observable events + +#### Questions for Reflection + +###### Question 1: The `movs rN, #imm8` instruction can encode values 0-255. What is the maximum blink count you could set with a single byte patch? + +###### Question 2: Why is the blink count passed in `r1` and not `r0`? What does `r0` hold at this point in the calling convention? + +###### Question 3: If you wanted to set the blink count to 256 or higher, the `movs` immediate would not be enough. What instruction sequence would the compiler need to generate instead? + +###### Question 4: The same `blink_led` function is called for all three buttons. Does that mean there is only one `movs r1, #3` to patch, or could there be multiple call sites? + +#### Tips and Hints +- Small immediates (0-255) are encoded directly in the `movs` instruction β€” no literal pool needed +- The ARM calling convention uses `r0`, `r1`, `r2`, `r3` for the first four function parameters in order +- Look for `movs r1, #3` right before `bl blink_led` β€” there may be one shared call site or multiple per button +- If there are multiple `movs r1, #3` instructions (one per case), you need to patch all of them for consistent behavior diff --git a/WEEKS/WEEK11/WEEK11-03-S.md b/WEEKS/WEEK11/WEEK11-03-S.md new file mode 100644 index 0000000..891d47c --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-03-S.md @@ -0,0 +1,70 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control + +### Non-Credit Practice Exercise 3 Solution: Swap All Three LEDs + +#### Answers + +##### GPIO Rotation Patch + +| Struct Member | Original | Patched | Effect | +|--------------|-----------------|-----------------|---------------------| +| led1_pin | 0x10 (GPIO 16 Red) | 0x11 (GPIO 17 Green) | Button 1 β†’ Green | +| led2_pin | 0x11 (GPIO 17 Green) | 0x12 (GPIO 18 Yellow) | Button 2 β†’ Yellow | +| led3_pin | 0x12 (GPIO 18 Yellow) | 0x10 (GPIO 16 Red) | Button 3 β†’ Red | + +##### Assembly Patches + +Three single-byte patches in `movs` immediate fields: + +``` +Patch 1 (led1_pin): 10 β†’ 11 +Before: 10 20 (movs r0, #0x10) +After: 11 20 (movs r0, #0x11) + +Patch 2 (led2_pin): 11 β†’ 12 +Before: 11 20 (movs r0, #0x11) +After: 12 20 (movs r0, #0x12) + +Patch 3 (led3_pin): 12 β†’ 10 +Before: 12 20 (movs r0, #0x12) +After: 10 20 (movs r0, #0x10) +``` + +##### GDB Verification + +```gdb +(gdb) b *0x10000280 +(gdb) c +(gdb) x/6bx +``` + +Before patch: `10 11 12 00 00 00` +After patch: `11 12 10 00 00 00` + +##### Behavior After Patch + +| Button (IR) | NEC Code | Terminal Log | Actual LED | +|------------|----------|---------------------------|-----------| +| Button 1 | 0x0C | "LED 1 activated on GPIO 16" | Green (GPIO 17) | +| Button 2 | 0x18 | "LED 2 activated on GPIO 17" | Yellow (GPIO 18) | +| Button 3 | 0x5E | "LED 3 activated on GPIO 18" | Red (GPIO 16) | + +The terminal logs are **desynchronized** from actual behavior. + +#### Reflection Answers + +1. **Terminal log still says "LED 1 activated on GPIO 16" even though GPIO 17 (Green) is actually blinking. Why don't the logs update automatically?** + The `printf` format strings and their arguments are separate from the struct pin assignments. The log message "LED 1 activated on GPIO 16" is generated from hardcoded format strings or from reading the **original** pin value before our patch takes effect. The GPIO number in the log comes from a different code path β€” likely a format string like `"LED %d activated on GPIO %d\r\n"` where the GPIO value was loaded from the struct at a different point or is computed independently. Since we only patched the `movs` instructions that store pin values into the struct, the logging code still uses whatever values it computes independently. + +2. **If the struct initialization used ldr from a literal pool instead of movs immediates, how would the patching differ?** + With literal pool loads, the pin values would be stored as 32-bit words in a data area near the function code. You would need to: (1) find the `ldr r0, [pc, #offset]` instruction, (2) calculate the PC-relative offset to locate the literal pool entry, (3) navigate to the pool address in the hex editor, and (4) modify the 4-byte value there. For example, GPIO 16 would be `10 00 00 00` (little-endian) in the pool. This is more work than patching a 1-byte `movs` immediate, and you'd need to verify no other code shares the same pool entry. The `movs` approach is simpler because the value is encoded directly in the instruction. + +3. **Could you achieve the same LED rotation by patching gpio_init/gpio_put calls instead of the struct initialization? Which approach is cleaner?** + Patching `gpio_init` and `gpio_put` calls would require finding every call site that references each GPIO pin and modifying the pin argument. This is scattered throughout multiple functions (`process_ir_led_command`, `blink_led`, `leds_all_off`). The struct initialization approach is **far cleaner** β€” three adjacent `movs` instructions in one location control the entire mapping. By patching the struct data at its source, every function that reads from the struct automatically gets the new values. This demonstrates the power of data-driven design: changing the data at one point affects all code that uses it. + +4. **In a real attack, why is log desynchronization (display says one thing, hardware does another) dangerous for forensic analysis?** + Log desynchronization is dangerous because forensic investigators rely on logs to reconstruct what happened. If logs show "LED 1 on GPIO 16" but the hardware actually activated GPIO 17, investigators would draw incorrect conclusions about which physical device was controlled. In industrial systems, this could mask sabotage β€” operators see "normal" readings while equipment is being misused. In security systems, tampered firmware could log "door locked" while actually unlocking it. The logs become actively misleading, not just incomplete. This is a form of **anti-forensics** that makes post-incident analysis unreliable and can delay or prevent discovery of the actual attack. diff --git a/WEEKS/WEEK11/WEEK11-03.md b/WEEKS/WEEK11/WEEK11-03.md new file mode 100644 index 0000000..3d96833 --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-03.md @@ -0,0 +1,150 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +### Non-Credit Practice Exercise 3: Swap All Three LEDs + +#### Objective +Find the struct initialization instructions where `led1_pin` = 0x10 (GPIO 16, Red), `led2_pin` = 0x11 (GPIO 17, Green), and `led3_pin` = 0x12 (GPIO 18, Yellow) are written in the `0x0026_functions` binary using GDB, calculate the file offsets, and rotate the GPIO values so that button 1β†’Green (0x11), button 2β†’Yellow (0x12), and button 3β†’Red (0x10), then verify on hardware that the LED mapping has shifted. + +#### Prerequisites +- Completed Week 11 tutorial (GDB and hex editor sections) +- `0x0026_functions.elf` and `0x0026_functions.bin` 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 IR remote and LEDs on GPIO 16, 17, 18 + +#### Task Description +The struct initialization sets `led1_pin` = 16 (0x10), `led2_pin` = 17 (0x11), `led3_pin` = 18 (0x12) using `movs` instructions that store each value into the struct. By patching the immediate values in these three `movs` instructions, you can rotate the LED assignment: `led1_pin` = 17 (Green), `led2_pin` = 18 (Yellow), `led3_pin` = 16 (Red). This means button 1 will light the Green LED, button 2 the Yellow LED, and button 3 the Red LED. + +#### 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\0x0026_functions.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the Three movs Instructions + +Disassemble main and find the struct pin initialization: + +```gdb +(gdb) disassemble 0x10000234,+300 +``` + +Look for three consecutive `movs`/`strb` pairs: + +``` +movs r0, #0x10 ; led1_pin = 16 (Red) +strb r0, [r4, #0] +movs r0, #0x11 ; led2_pin = 17 (Green) +strb r0, [r4, #1] +movs r0, #0x12 ; led3_pin = 18 (Yellow) +strb r0, [r4, #2] +``` + +Note the address of each `movs` instruction. + +##### Step 3: Examine the Instruction Bytes + +Check the raw encoding of each `movs`: + +```gdb +(gdb) x/2bx +(gdb) x/2bx +(gdb) x/2bx +``` + +Each will have the GPIO pin number as the immediate byte. + +##### Step 4: Calculate the File Offsets + +``` +file_offset = address - 0x10000000 +``` + +Note the offset of the immediate byte in each of the three `movs` instructions. + +##### Step 5: Plan the Rotation + +| Struct Member | Original | New | Effect | +| ------------- | ----------------- | ----------------- | ---------------- | +| `led1_pin` | `10` (GPIO 16 Red) | `11` (GPIO 17 Green) | Button 1 β†’ Green | +| `led2_pin` | `11` (GPIO 17 Green) | `12` (GPIO 18 Yellow) | Button 2 β†’ Yellow | +| `led3_pin` | `12` (GPIO 18 Yellow) | `10` (GPIO 16 Red) | Button 3 β†’ Red | + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` +2. Go to the first `movs` immediate offset and change `10` to `11` +3. Go to the second `movs` immediate offset and change `11` to `12` +4. Go to the third `movs` immediate offset and change `12` to `10` + +###### Question 1: All three patches are single-byte changes in `movs` immediates. Why is this simpler than patching literal pool entries? + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0026_functions-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 behavior:** +- Press button 1 β†’ **Green** LED blinks (was Red) +- Press button 2 β†’ **Yellow** LED blinks (was Green) +- Press button 3 β†’ **Red** LED blinks (was Yellow) +- Terminal still says "LED 1 activated on GPIO 16" β€” but the actual LED is Green (GPIO 17) + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate struct initialization patterns in ARM Thumb assembly +- Patch multiple `movs` immediates to rotate data values +- Understand the disconnect between logged values and actual hardware behavior +- Recognize log desynchronization as a security concern + +#### Questions for Reflection + +###### Question 1: The terminal log still says "LED 1 activated on GPIO 16" even though GPIO 17 (Green) is actually blinking. Why don't the logs update automatically? + +###### Question 2: If the struct initialization used `ldr` from a literal pool instead of `movs` immediates, how would the patching approach differ? + +###### Question 3: Could you achieve the same LED rotation by patching the `gpio_init` and `gpio_put` calls instead of the struct? Which approach is cleaner and why? + +###### Question 4: In a real attack scenario, why is log desynchronization (logs say one thing, hardware does another) particularly dangerous for forensic analysis? + +#### Tips and Hints +- The three `movs` instructions are likely within 10-20 bytes of each other β€” use `x/20i` to see them all at once +- The `movs rN, #imm8` immediate is in the lower byte of the 2-byte Thumb instruction +- Make sure you patch the `movs` for the struct initialization, not any other `movs #0x10/0x11/0x12` that may exist elsewhere +- Verify by examining the struct in memory after initialization: `x/6bx ` should show `11 12 10` for the pin bytes diff --git a/WEEKS/WEEK11/WEEK11-04-S.md b/WEEKS/WEEK11/WEEK11-04-S.md new file mode 100644 index 0000000..17629b2 --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-04-S.md @@ -0,0 +1,60 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Functions in Embedded Systems: Debugging and Hacking Functions w/ IR Remote and Multi-LED Control + +### Non-Credit Practice Exercise 4 Solution: Change Blink Speed + +#### Answers + +##### Delay Parameter + +| Parameter | Original | Patched | +|----------|-------------|--------------| +| Delay | 50ms | 25ms | +| Hex | 0x32 | 0x19 | +| Register | r2 | r2 | +| Instruction | movs r2, #0x32 | movs r2, #0x19 | + +##### Assembly Context + +```asm +movs r0, ; r0 = GPIO pin +movs r1, #3 ; r1 = blink count +movs r2, #0x32 ; r2 = delay 50ms ← PATCH THIS +bl blink_led ; blink_led(pin, 3, 50) +``` + +##### Patch + +``` +Before: 32 22 (movs r2, #0x32 = 50ms) +After: 19 22 (movs r2, #0x19 = 25ms) +``` + +**Warning:** The byte `0x32` is also ASCII '2'. Verify you're patching the correct `movs r2` instruction by checking surrounding bytes β€” `movs r1, #3` (`03 21`) should appear immediately before, and `bl blink_led` immediately after. + +##### Timing Comparison + +| Metric | Original (50ms) | Patched (25ms) | +|-------------------|-----------------|----------------| +| On-time per blink | 50ms | 25ms | +| Off-time per blink| 50ms | 25ms | +| One blink cycle | 100ms | 50ms | +| 3 blinks total | 300ms | 150ms | +| Perceived speed | Normal | 2Γ— faster | + +#### Reflection Answers + +1. **If delay = 1ms (0x01), would you still see the LED blink, or would it appear constantly on?** + At 1ms on/off (2ms per cycle, 500Hz flicker), the LED would appear **constantly on** to the human eye. Human flicker fusion threshold is approximately 60Hz β€” anything above that appears as a steady light. At 500Hz, the LED is switching far too fast for the eye to perceive individual blinks. The LED would look like it's at roughly 50% brightness (since it's on half the time) compared to being fully on. The 3 blinks would complete in just 6ms total, appearing as a brief flash rather than distinct blinks. + +2. **0x32 appears as both the delay value (50ms) and potentially ASCII '2'. How would you systematically find ALL occurrences of 0x32 and determine which to patch?** + Search the binary for all `0x32` bytes, then examine the **context** of each occurrence: (1) Check the byte following `0x32` β€” if it's `0x22`, this is `movs r2, #0x32` (the delay parameter). If it's `0x2C`, it's `cmp r4, #0x32` (comparing against ASCII '2'). (2) Examine surrounding instructions: the delay `0x32` will be preceded by `movs r1, #3` (blink count) and followed by `bl blink_led`. A comparison `0x32` will be near `beq`/`bne` branches. (3) Use GDB to disassemble the region (`x/10i `) and read the instruction mnemonic. (4) Cross-reference with the function structure β€” delay patches are in `blink_led` call setup, comparisons are in `ir_to_led_number` or similar dispatcher functions. + +3. **For a delay of 500ms (0x1F4), the value won't fit in a movs immediate (max 255). How would the compiler handle it?** + For 500 (`0x1F4`), the compiler would use either: (1) A 32-bit `movw r2, #0x1F4` Thumb-2 instruction (4 bytes), which can encode any 16-bit immediate (0–65535). (2) A literal pool load: `ldr r2, [pc, #offset]` that reads `0x000001F4` from a nearby data word. The `movw` approach is preferred for values 256–65535 because it's a single instruction with no data dependency. For values exceeding 65535, a literal pool or `movw`+`movt` pair would be necessary. + +4. **The blink function uses the delay for both on-time and off-time (symmetrical blink). Could you make the LED stay on longer than off by patching only one instruction?** + Not with a single patch to the `movs r2` instruction, because `blink_led` uses the same delay parameter for both the on-phase and off-phase `sleep_ms` calls internally. To create asymmetric blink timing, you would need to patch **inside** the `blink_led` function itself β€” find the two `sleep_ms` calls within the blink loop and modify their delay arguments independently. For example, find the `ldr`/`movs` that sets up `r0` before each `bl sleep_ms` inside `blink_led`, and patch one to a different value. This would require disassembling `blink_led` to locate both `sleep_ms` call sites. diff --git a/WEEKS/WEEK11/WEEK11-04.md b/WEEKS/WEEK11/WEEK11-04.md new file mode 100644 index 0000000..1ce0b49 --- /dev/null +++ b/WEEKS/WEEK11/WEEK11-04.md @@ -0,0 +1,146 @@ +# Embedded Systems Reverse Engineering +[Repository](https://github.com/mytechnotalent/Embedded-Hacking) + +## Week 11 +Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +### Non-Credit Practice Exercise 4: Change Blink Speed + +#### Objective +Find the `blink_led(pin, 3, 50)` call in the `0x0026_functions` binary using GDB, identify the immediate value `0x32` (50) being loaded into `r2` (the delay parameter), calculate the file offset, and patch it to `0x19` (25) so that each LED blinks at double speed when activated by the IR remote. + +#### Prerequisites +- Completed Week 11 tutorial (GDB and hex editor sections) +- `0x0026_functions.elf` and `0x0026_functions.bin` 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 IR remote and LEDs on GPIO 16, 17, 18 + +#### Task Description +The `blink_led` function takes a delay parameter of `50`ms (`0x32`) in register `r2`. This value controls how long each LED stays on and off during the blink cycle. By patching this to `25`ms (`0x19`), the LEDs will blink twice as fast, creating a noticeably quicker flashing pattern when any IR remote button is pressed. + +#### 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\0x0026_functions.elf +``` + +**Connect to target:** + +```gdb +(gdb) target remote :3333 +(gdb) monitor reset halt +``` + +##### Step 2: Find the blink_led Call + +Disassemble main and look for the parameter setup before `bl blink_led`: + +```gdb +(gdb) disassemble 0x10000234,+300 +``` + +Look for: + +``` +movs r0, ; GPIO pin number +movs r1, #3 ; blink count +movs r2, #0x32 ; delay = 50ms +bl blink_led +``` + +Note the address of the `movs r2, #0x32` instruction. + +##### Step 3: Examine the Instruction Encoding + +Look at the raw bytes: + +```gdb +(gdb) x/2bx +``` + +The `movs r2, #0x32` instruction has `0x32` (50) as the immediate byte. + +##### Step 4: Calculate the File Offset + +``` +file_offset = address - 0x10000000 +``` + +Note the file offset of the byte containing `32`. + +##### Step 5: Encode the New Value + +| Parameter | Original | New | Effect | +| --------- | --------------- | --------------- | --------------- | +| Delay | `32` (50ms) | `19` (25ms) | 2x faster blink | + +**Be careful:** `0x32` is also the ASCII code for '2'. Make sure you are patching the `movs r2` instruction and not a comparison value like `cmp r4, #0x32`. + +##### Step 6: Patch with HxD + +1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions\build\0x0026_functions.bin` +2. Press **Ctrl+G** and enter the file offset +3. You should see: `32` +4. Replace with: `19` + +###### Question 1: How can you confirm you are patching the delay parameter and not some other `0x32` byte in the binary? + +##### Step 7: Save and Convert + +1. Click **File** β†’ **Save As** β†’ `0x0026_functions-h.bin` + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +##### Step 8: 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 behavior:** +- Press button 1 β†’ Red LED blinks 3 times but **noticeably faster** (25ms on/off vs 50ms) +- Press button 2 β†’ Green LED blinks 3 times at **double speed** +- Press button 3 β†’ Yellow LED blinks 3 times at **double speed** +- The total blink sequence should complete in roughly half the original time + +#### Expected Output + +After completing this exercise, you should be able to: +- Locate function parameters in ARM Thumb `movs` instructions +- Distinguish between identical byte values used in different contexts +- Patch timing parameters to change observable hardware behavior +- Understand the relationship between delay values and perceived blink speed + +#### Questions for Reflection + +###### Question 1: The `blink_led` function calls `sleep_ms` internally with the delay value. If you set the delay to `1`ms (0x01), would you still see the LED blink, or would it appear constantly on? + +###### Question 2: The value `0x32` appears in this binary as both a delay parameter (50ms) and potentially as an ASCII comparison ('2'). How would you systematically find ALL occurrences of `0x32` and determine which one to patch? + +###### Question 3: If you wanted a delay of 500ms (0x1F4), the value would not fit in a `movs` immediate. How would the compiler handle this larger delay value? + +###### Question 4: The blink function uses the delay for both the on-time and the off-time. Could you make the LED stay on longer than it stays off? What kind of patch would that require? + +#### Tips and Hints +- `25` decimal = `0x19` hex β€” fits in one byte, so the `movs` encoding works directly +- Verify location by checking the surrounding instructions: `movs r1, #3` should be right before and `bl blink_led` right after +- The total blink time for 3 blinks at 50ms = 3 Γ— (50 + 50) = 300ms; at 25ms = 3 Γ— (25 + 25) = 150ms +- If there are multiple call sites for `blink_led`, each may have its own `movs r2, #0x32` that needs patching diff --git a/WEEKS/WEEK11/WEEK11-SLIDES.pdf b/WEEKS/WEEK11/WEEK11-SLIDES.pdf new file mode 100644 index 0000000..0848920 Binary files /dev/null and b/WEEKS/WEEK11/WEEK11-SLIDES.pdf differ diff --git a/WEEKS/WEEK11/WEEK11.md b/WEEKS/WEEK11/WEEK11.md new file mode 100644 index 0000000..c0d1c9d --- /dev/null +++ b/WEEKS/WEEK11/WEEK11.md @@ -0,0 +1,1531 @@ +# Week 11: Structures and Functions in Embedded Systems: Debugging and Hacking w/ IR Remote Control and NEC Protocol Basics + +## 🎯 What You'll Learn This Week + +By the end of this tutorial, you will be able to: +- Understand C structures (structs) and how they organize related data +- Know how structs are represented in memory and assembly code +- Understand the NEC infrared (IR) protocol for remote control communication +- Create and use functions with parameters and return values +- Identify struct member access patterns in Ghidra +- Recognize how compilers "flatten" structs into individual operations +- Hack GPIO pin assignments to swap LED behavior +- Understand the security implications of log/behavior desynchronization +- Analyze .elf files in addition to .bin files in Ghidra + +--- + +## πŸ“š Part 1: Understanding C Structures (Structs) + +### What is a Struct? + +A **structure** (or **struct**) is a user-defined data type that groups related variables together under one name. Think of it like a form with multiple fields - each field can hold different types of data, but they all belong together. + +```c +// Define a struct type +typedef struct { + uint8_t led1_pin; // GPIO pin for LED 1 + uint8_t led2_pin; // GPIO pin for LED 2 + uint8_t led3_pin; // GPIO pin for LED 3 + bool led1_state; // Is LED 1 on? + bool led2_state; // Is LED 2 on? + bool led3_state; // Is LED 3 on? +} simple_led_ctrl_t; +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Structure as a Container β”‚ +β”‚ β”‚ +β”‚ simple_led_ctrl_t leds β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ led1_pin: 16 led2_pin: 17 led3_pin: 18 β”‚β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚β”‚ +β”‚ β”‚ β”‚ 16 β”‚ β”‚ 17 β”‚ β”‚ 18 β”‚ β”‚β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚β”‚ +β”‚ β”‚ β”‚β”‚ +β”‚ β”‚ led1_state: false led2_state: false led3_state: false β”‚β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚β”‚ +β”‚ β”‚ β”‚ false β”‚ β”‚ false β”‚ β”‚ false β”‚ β”‚β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β”‚ β”‚ +β”‚ All 6 members live together as ONE variable called "leds" β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Why Use Structs? + +| Without Structs (Messy!) | With Structs (Clean!) | +| -------------------------- | --------------------------- | +| `uint8_t led1_pin = 16;` | `simple_led_ctrl_t leds;` | +| `uint8_t led2_pin = 17;` | `leds.led1_pin = 16;` | +| `uint8_t led3_pin = 18;` | `leds.led2_pin = 17;` | +| `bool led1_state = false;` | `leds.led3_pin = 18;` | +| `bool led2_state = false;` | `leds.led1_state = false;` | +| `bool led3_state = false;` | ... (all in one container!) | + +**Benefits of Structs:** +1. **Organization** - Related data stays together +2. **Readability** - Code is easier to understand +3. **Maintainability** - Changes are easier to make +4. **Scalability** - Easy to add more LEDs or features +5. **Passing to Functions** - Pass one struct instead of many variables + +--- + +## πŸ“š Part 2: Struct Memory Layout + +### How Structs are Stored in Memory + +When you create a struct, the compiler places each member in consecutive memory locations: + +> πŸ“– **Datasheet Reference:** The RP2350 SRAM base address is `0x20000000` (Section 2.2, p. 31). SRAM banks SRAM0–SRAM7 provide 8 Γ— 64 KB = 512 KB of striped SRAM, with SRAM8 and SRAM9 providing an additional 2 Γ— 4 KB (Section 2.2.3, p. 32; Section 4.2, p. 338). Stack-allocated structs and local variables reside in this SRAM region. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Memory Layout of simple_led_ctrl_t β”‚ +β”‚ β”‚ +β”‚ Address Member Size Value β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ 0x20000000 led1_pin 1 byte 16 (0x10) β”‚ +β”‚ 0x20000001 led2_pin 1 byte 17 (0x11) β”‚ +β”‚ 0x20000002 led3_pin 1 byte 18 (0x12) β”‚ +β”‚ 0x20000003 led1_state 1 byte 0 (false) β”‚ +β”‚ 0x20000004 led2_state 1 byte 0 (false) β”‚ +β”‚ 0x20000005 led3_state 1 byte 0 (false) β”‚ +β”‚ β”‚ +β”‚ Total struct size: 6 bytes β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Accessing Struct Members + +Use the **dot operator** (`.`) to access members: + +```c +simple_led_ctrl_t leds; + +// Set values +leds.led1_pin = 16; +leds.led1_state = true; + +// Read values +printf("Pin: %d\n", leds.led1_pin); +``` + +### Pointer to Struct (Arrow Operator) + +When you have a **pointer** to a struct, use the **arrow operator** (`->`): + +```c +simple_led_ctrl_t leds; +simple_led_ctrl_t *ptr = &leds; // Pointer to the struct + +// These are equivalent: +leds.led1_pin = 16; // Using dot with struct variable +ptr->led1_pin = 16; // Using arrow with pointer +(*ptr).led1_pin = 16; // Dereferencing then dot (same thing) +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Dot vs Arrow Operator β”‚ +β”‚ β”‚ +β”‚ struct_variable.member ◄── Use with actual struct β”‚ +β”‚ β”‚ +β”‚ pointer_to_struct->member ◄── Use with pointer to struct β”‚ +β”‚ β”‚ +β”‚ The arrow (->) is shorthand for (*pointer).member β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 3: Designated Initializers + +### Clean Struct Initialization + +C allows you to initialize struct members by name using **designated initializers**: + +```c +simple_led_ctrl_t leds = { + .led1_pin = 16, + .led2_pin = 17, + .led3_pin = 18, + .led1_state = false, + .led2_state = false, + .led3_state = false +}; +``` + +**Benefits:** +- Clear which value goes to which member +- Order doesn't matter (can rearrange lines) +- Self-documenting code +- Easy to add new members later + +--- + +## πŸ“š Part 4: Understanding the NEC IR Protocol + +### What is Infrared (IR) Communication? + +**Infrared** communication uses invisible light pulses to send data. Your TV remote uses IR to send commands to your TV. The LED in the remote flashes on and off very quickly in specific patterns that represent different buttons. + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ IR Communication β”‚ +β”‚ β”‚ +β”‚ Remote Control IR Receiver β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Button β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 1 β”‚ ─── IR Light Pulses ──► β”‚ β–ˆβ–ˆβ–ˆβ–ˆ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β” β”‚ ~~~~~~~~~~~~β–Ί β”‚ Sensor β”‚ β”‚ +β”‚ β”‚ β”‚ ● β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ IR LED β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–Ό β”‚ +β”‚ GPIO Pin β”‚ +β”‚ (Digital signal) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### The NEC Protocol + +**NEC** is one of the most common IR protocols. When you press a button, the remote sends: + +1. **Leader pulse** - 9ms HIGH, 4.5ms LOW (says "attention!") +2. **Address** - 8 bits identifying the device +3. **Address Inverse** - 8 bits (for error checking) +4. **Command** - 8 bits for the button pressed +5. **Command Inverse** - 8 bits (for error checking) + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ NEC Protocol Frame β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Leader β”‚ Address β”‚ Address β”‚ Command β”‚ Command β”‚ Stop β”‚ β”‚ +β”‚ β”‚ Pulse β”‚ 8-bit β”‚ Inverse β”‚ 8-bit β”‚ Inverse β”‚ Bit β”‚ β”‚ +β”‚ β”‚ 9+4.5ms β”‚ β”‚ 8-bit β”‚ β”‚ 8-bit β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Total: 32 bits of data (+ leader + stop) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### NEC Command Codes for Our Remote + +| Button | NEC Command Code | Hex Value | +| ------ | ---------------- | --------- | +| 1 | 0x0C | 12 | +| 2 | 0x18 | 24 | +| 3 | 0x5E | 94 | + +**Note:** Different remotes have different codes. These are specific to our example remote. + +--- + +## πŸ“š Part 5: Understanding Functions in C + +### What is a Function? + +A **function** is a reusable block of code that performs a specific task. Functions help organize code and avoid repetition. + +```c +// Function definition +int add_numbers(int a, int b) { + return a + b; +} + +// Function call +int result = add_numbers(5, 3); // result = 8 +``` + +### Function Components + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Anatomy of a Function β”‚ +β”‚ β”‚ +β”‚ return_type function_name ( parameters ) { β”‚ +β”‚ // function body β”‚ +β”‚ return value; β”‚ +β”‚ } β”‚ +β”‚ β”‚ +β”‚ Example: β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ int ir_to_led_number ( int ir_command ) { β”‚β”‚ +β”‚ β”‚ ─── ─────────────── ─────────────── β”‚β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚β”‚ +β”‚ β”‚ β”‚ β”‚ └── Parameter (input) β”‚β”‚ +β”‚ β”‚ β”‚ └── Function name β”‚β”‚ +β”‚ β”‚ └── Return type (what it gives back) β”‚β”‚ +β”‚ β”‚ β”‚β”‚ +β”‚ β”‚ if (ir_command == 0x0C) return 1; ◄── Body β”‚β”‚ +β”‚ β”‚ if (ir_command == 0x18) return 2; β”‚β”‚ +β”‚ β”‚ return 0; ◄── Return value β”‚β”‚ +β”‚ β”‚ } β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Types of Functions + +| Type | Description | Example | +| ---------------------------- | ------------------------- | ---------------------------- | +| **No params, no return** | Just does something | `void leds_all_off(void)` | +| **With params, no return** | Takes input, no output | `void blink_led(pin, count)` | +| **No params, with return** | No input, gives output | `int ir_getkey(void)` | +| **With params, with return** | Takes input, gives output | `int ir_to_led_number(cmd)` | + +--- + +## πŸ“š Part 6: Functions with Struct Pointers + +### Passing Structs to Functions + +When passing a struct to a function, you usually pass a **pointer** to avoid copying all the data: + +```c +// Function takes a POINTER to the struct +void leds_all_off(simple_led_ctrl_t *leds) { + gpio_put(leds->led1_pin, false); // Use arrow operator! + gpio_put(leds->led2_pin, false); + gpio_put(leds->led3_pin, false); +} + +// Call with address-of operator +simple_led_ctrl_t my_leds; +leds_all_off(&my_leds); // Pass the ADDRESS of my_leds +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Passing Struct by Pointer β”‚ +β”‚ β”‚ +β”‚ main() { β”‚ +β”‚ simple_led_ctrl_t leds; ◄── Struct lives here β”‚ +β”‚ leds_all_off(&leds); ◄── Pass ADDRESS (pointer) β”‚ +β”‚ } β”‚ β”‚ +β”‚ β”‚ β”‚ +β”‚ β–Ό β”‚ +β”‚ leds_all_off(simple_led_ctrl_t *leds) { β”‚ +β”‚ gpio_put(leds->led1_pin, false); β”‚ +β”‚ ──── β”‚ +β”‚ β”‚ β”‚ +β”‚ └── Arrow because leds is a POINTER β”‚ +β”‚ } β”‚ +β”‚ β”‚ +β”‚ WHY use pointers? β”‚ +β”‚ β€’ Efficient: Only 4 bytes (address) instead of entire struct β”‚ +β”‚ β€’ Allows modification: Function can change the original β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ“š Part 7: How Compilers Handle Structs + +### Struct "Flattening" in Assembly + +When the compiler converts your C code to assembly, it "flattens" struct operations into individual memory accesses: + +**C Code:** +```c +gpio_init(leds.led1_pin); // leds.led1_pin = 16 +gpio_init(leds.led2_pin); // leds.led2_pin = 17 +gpio_init(leds.led3_pin); // leds.led3_pin = 18 +``` + +**Assembly (what the compiler produces):** +```assembly +movs r0, #0x10 ; r0 = 16 (led1_pin value) +bl gpio_init ; call gpio_init(16) + +movs r0, #0x11 ; r0 = 17 (led2_pin value) +bl gpio_init ; call gpio_init(17) + +movs r0, #0x12 ; r0 = 18 (led3_pin value) +bl gpio_init ; call gpio_init(18) +``` + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Struct Flattening β”‚ +β”‚ β”‚ +β”‚ C Level (High-level abstraction): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ gpio_init(leds.led1_pin); β”‚β”‚ +β”‚ β”‚ gpio_init(leds.led2_pin); β”‚β”‚ +β”‚ β”‚ gpio_init(leds.led3_pin); β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β”‚ β”‚ β”‚ +β”‚ β”‚ Compiler transforms β”‚ +β”‚ β–Ό β”‚ +β”‚ Assembly Level (Flattened): β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ +β”‚ β”‚ movs r0, #16 ; Just the VALUE, no struct reference β”‚β”‚ +β”‚ β”‚ bl gpio_init β”‚β”‚ +β”‚ β”‚ movs r0, #17 ; Next value directly β”‚β”‚ +β”‚ β”‚ bl gpio_init β”‚β”‚ +β”‚ β”‚ movs r0, #18 ; Next value directly β”‚β”‚ +β”‚ β”‚ bl gpio_init β”‚β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚ +β”‚ β”‚ +β”‚ The struct abstraction DISAPPEARS at the assembly level! β”‚ +β”‚ We just see individual values being loaded and used. β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Why This Matters for Reverse Engineering + +- In Ghidra, you won't always see "struct" - just individual values +- You must recognize PATTERNS (sequential values like 16, 17, 18) +- Understanding flattening helps you reconstruct the original struct + +--- + +## πŸ“š Part 8: Setting Up Your Environment + +### Prerequisites + +Before we start, make sure you have: +1. A Raspberry Pi Pico 2 board +2. A Raspberry Pi Pico Debug Probe +3. Ghidra installed (for static analysis) +4. Python installed (for UF2 conversion) +5. A serial monitor (PuTTY, minicom, or screen) +6. An IR receiver module (like VS1838B) +7. An IR remote control (any NEC-compatible remote) +8. Three LEDs (red, green, yellow) with resistors +9. The sample projects: `0x0023_structures` and `0x0026_functions` + +### Hardware Setup + +**IR Receiver Wiring:** + +| IR Receiver Pin | Pico 2 Pin | +| --------------- | ---------- | +| VCC | 3.3V | +| GND | GND | +| OUT/DATA | GPIO 5 | + +**LED Wiring:** + +| LED | GPIO Pin | Resistor | +| ------ | -------- | --------- | +| Red | GPIO 16 | 220Ξ©-330Ξ© | +| Green | GPIO 17 | 220Ξ©-330Ξ© | +| Yellow | GPIO 18 | 220Ξ©-330Ξ© | + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Complete Wiring Diagram β”‚ +β”‚ β”‚ +β”‚ Pico 2 Components β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ GPIO 5 │─────────────── IR Receiver β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ (VS1838B) β”‚ β”‚ +β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 16 │───[220Ξ©]───(RED LED)────┐ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 17 │───[220Ξ©]───(GRN LED)───── β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GPIO 18 │───[220Ξ©]───(YEL LED)───── β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ 3.3V │─────────────────────────┼── IR VCC β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ GND │─────────────────────────┴── All GNDs β”‚ +β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Project Structure + +``` +Embedded-Hacking/ +β”œβ”€β”€ 0x0023_structures/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x0023_structures.uf2 +β”‚ β”‚ └── 0x0023_structures.bin +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ └── 0x0023_structures.c +β”‚ └── ir.h +β”œβ”€β”€ 0x0026_functions/ +β”‚ β”œβ”€β”€ build/ +β”‚ β”‚ β”œβ”€β”€ 0x0026_functions.uf2 +β”‚ β”‚ β”œβ”€β”€ 0x0026_functions.bin +β”‚ β”‚ └── 0x0026_functions.elf +β”‚ β”œβ”€β”€ main/ +β”‚ β”‚ └── 0x0026_functions.c +β”‚ └── ir.h +└── uf2conv.py +``` + +--- + +## πŸ”¬ Part 9: Hands-On Tutorial - Structures Code + +### Step 1: Review the Source Code + +Let's examine the structures code: + +**File: `0x0023_structures.c`** + +```c +#include +#include +#include "pico/stdlib.h" +#include "ir.h" + +#define IR_PIN 5 + +typedef struct { + uint8_t led1_pin; + uint8_t led2_pin; + uint8_t led3_pin; + bool led1_state; + bool led2_state; + bool led3_state; +} simple_led_ctrl_t; + +int main(void) { + stdio_init_all(); + + simple_led_ctrl_t leds = { + .led1_pin = 16, + .led2_pin = 17, + .led3_pin = 18, + .led1_state = false, + .led2_state = false, + .led3_state = false + }; + + gpio_init(leds.led1_pin); gpio_set_dir(leds.led1_pin, GPIO_OUT); + gpio_init(leds.led2_pin); gpio_set_dir(leds.led2_pin, GPIO_OUT); + gpio_init(leds.led3_pin); gpio_set_dir(leds.led3_pin, GPIO_OUT); + + ir_init(IR_PIN); + printf("IR receiver on GPIO %d ready\n", IR_PIN); + + while (true) { + int key = ir_getkey(); + if (key >= 0) { + printf("NEC command: 0x%02X\n", key); + + // Turn all off first + leds.led1_state = false; + leds.led2_state = false; + leds.led3_state = false; + + // Check NEC codes + if (key == 0x0C) leds.led1_state = true; // GPIO16 + if (key == 0x18) leds.led2_state = true; // GPIO17 + if (key == 0x5E) leds.led3_state = true; // GPIO18 + + // Apply states + gpio_put(leds.led1_pin, leds.led1_state); + gpio_put(leds.led2_pin, leds.led2_state); + gpio_put(leds.led3_pin, leds.led3_state); + + sleep_ms(10); + } else { + sleep_ms(1); + } + } +} +``` + +### Step 2: Understand the Program Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Program Flow β”‚ +β”‚ β”‚ +β”‚ 1. Initialize UART (stdio_init_all) β”‚ +β”‚ 2. Create LED struct with pins 16, 17, 18 β”‚ +β”‚ 3. Initialize GPIO pins as outputs β”‚ +β”‚ 4. Initialize IR receiver on GPIO 5 β”‚ +β”‚ 5. Enter infinite loop: β”‚ +β”‚ a. Check for IR key press β”‚ +β”‚ b. If key received: β”‚ +β”‚ - Print the NEC command code β”‚ +β”‚ - Turn all LEDs off β”‚ +β”‚ - Check which button: 0x0C, 0x18, or 0x5E β”‚ +β”‚ - Turn on the matching LED β”‚ +β”‚ - Apply states to GPIO pins β”‚ +β”‚ c. Sleep briefly and repeat β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 3: 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 `0x0023_structures.uf2` onto the drive +5. The Pico will reboot and start running! + +### Step 4: Verify It's Working + +**Open PuTTY (115200 baud) and test:** +- Press "1" on remote β†’ Red LED lights, terminal shows `NEC command: 0x0C` +- Press "2" on remote β†’ Green LED lights, terminal shows `NEC command: 0x18` +- Press "3" on remote β†’ Yellow LED lights, terminal shows `NEC command: 0x5E` + +--- + +## πŸ”¬ Part 10: Debugging with GDB (Structures) + +### Step 5: Start OpenOCD (Terminal 1) + +Open a terminal and 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" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 6: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```powershell +arm-none-eabi-gdb build\0x0023_structures.elf +``` + +### Step 7: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target remote :3333 +``` + +### Step 8: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 9: Examine Main Function + +Disassemble around main to see struct initialization: + +```gdb +disassemble 0x10000234,+200 +``` + +Look for the struct member initialization sequence (mov instructions with values 16, 17, 18). + +### Step 10: Set a Breakpoint at Main + +```gdb +break *0x10000234 +``` + +Reset and run to hit the breakpoint: + +```gdb +monitor reset halt +continue +``` + +### Step 11: Examine the Struct on the Stack + +After stepping into main, the struct is initialized on the stack. Examine it: + +```gdb +stepi 20 +x/6xb $sp +``` + +You should see the struct layout: `10 11 12 00 00 00` (pins 16, 17, 18 and three false states). + +### Step 12: Watch GPIO Initialization + +Set a breakpoint on gpio_init and watch each LED pin get initialized: + +```gdb +break *0x10000260 +continue +info registers r0 +``` + +You should see `r0 = 0x10` (16), `0x11` (17), `0x12` (18) for each call. + +### Step 13: Examine IR Key Processing + +Set a breakpoint after ir_getkey returns: + +```gdb +break *0x10000290 +continue +``` + +Press a button on the remote, then check: + +```gdb +info registers r0 +``` + +You'll see the NEC code (0x0C, 0x18, or 0x5E). + +### Step 14: Watch the Conditional Checks + +Step through the NEC code comparisons: + +```gdb +stepi 10 +info registers +``` + +Watch for `cmp r0, #0x0c`, `cmp r0, #0x18`, `cmp r0, #0x5e` instructions. + +### Step 15: Examine gpio_put Arguments + +Before each gpio_put call, check the pin and state: + +```gdb +break *0x100002a0 +continue +info registers r0 r1 +``` + +`r0` = GPIO pin number, `r1` = state (0 or 1). + +### Step 16: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## πŸ”¬ Part 11: Setting Up Ghidra for Structures + +### Step 17: Start Ghidra + +Open a terminal and type: + +```powershell +ghidraRun +``` + +### Step 18: Create a New Project + +1. Click **File** β†’ **New Project** +2. Select **Non-Shared Project** +3. Click **Next** +4. Enter Project Name: `0x0023_structures` +5. Click **Finish** + +### Step 19: Import the Binary + +1. Navigate to the `0x0023_structures/build/` folder +2. **Drag and drop** the `.bin` file into Ghidra's project window + +### Step 20: Configure the Binary Format + +**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` +3. Click **OK** + +### Step 21: Analyze the Binary + +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. + +--- + +## πŸ”¬ Part 12: Resolving Functions - Structures Project + +### Step 22: Navigate to Main + +1. Press `G` (Go to address) and type `10000234` +2. Right-click β†’ **Edit Function Signature** +3. Change to: `int main(void)` +4. Click **OK** + +### Step 23: Resolve stdio_init_all + +At address `0x10000236`: + +1. Double-click on the called function +2. Right-click β†’ **Edit Function Signature** +3. Change to: `bool stdio_init_all(void)` +4. Click **OK** + +### Step 24: Identify gpio_init from Struct Pattern + +Look for three consecutive calls with values 16, 17, 18: + +```assembly +movs r0, #0x10 ; 16 = GPIO16 (led1_pin) +bl FUN_xxxxx ; gpio_init + +movs r0, #0x11 ; 17 = GPIO17 (led2_pin) +bl FUN_xxxxx ; gpio_init + +movs r0, #0x12 ; 18 = GPIO18 (led3_pin) +bl FUN_xxxxx ; gpio_init +``` + +This pattern reveals the struct members! Update the function signature: +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void gpio_init(uint gpio)` +3. Click **OK** + +### Step 25: Resolve ir_init + +Look for a function call with GPIO 5: + +```assembly +movs r0, #0x5 ; GPIO 5 for IR receiver +bl FUN_xxxxx ; ir_init +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void ir_init(uint pin)` +3. Click **OK** + +### Step 26: Resolve printf + +Right after ir_init, look for the "IR receiver on GPIO" string being loaded: + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `int printf(char *format, ...)` +3. Check the **Varargs** checkbox +4. Click **OK** + +### Step 27: Resolve ir_getkey + +Look for a function that returns a value checked against conditions: + +```assembly +bl FUN_xxxxx ; Call ir_getkey +cmp r0, #0 ; Check if >= 0 +blt no_key ; If negative, no key pressed +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `int ir_getkey(void)` +3. Click **OK** + +### Step 28: Resolve sleep_ms + +Look for calls with 10 (0x0A) or 1 (0x01): + +```assembly +movs r0, #0x0A ; 10 milliseconds +bl FUN_xxxxx ; sleep_ms +``` + +1. Right-click β†’ **Edit Function Signature** +2. Change to: `void sleep_ms(uint ms)` +3. Click **OK** + +--- + +## πŸ”¬ Part 13: Recognizing Struct Patterns in Assembly + +### Step 29: Identify GPIO Set Direction + +After each `gpio_init`, look for direction setting: + +```assembly +mov.w r4, #0x1 ; direction = output (1 = GPIO_OUT) +mcrr p0, 0x4, r3, r4 ; Configure GPIO direction register +``` + +This is the compiler's version of `gpio_set_dir(pin, GPIO_OUT)`. + +### Step 30: Map the Struct Members + +Create a mental (or written) map: + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Struct Member Mapping β”‚ +β”‚ β”‚ +β”‚ Assembly Value β†’ Struct Member β†’ Physical LED β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ 0x10 (16) β†’ led1_pin β†’ Red LED β”‚ +β”‚ 0x11 (17) β†’ led2_pin β†’ Green LED β”‚ +β”‚ 0x12 (18) β†’ led3_pin β†’ Yellow LED β”‚ +β”‚ β”‚ +β”‚ NEC Code β†’ State Member β†’ Action β”‚ +β”‚ ───────────────────────────────────────────────────────────── β”‚ +β”‚ 0x0C β†’ led1_state=true β†’ Red LED ON β”‚ +β”‚ 0x18 β†’ led2_state=true β†’ Green LED ON β”‚ +β”‚ 0x5E β†’ led3_state=true β†’ Yellow LED ON β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +--- + +## πŸ”¬ Part 14: Hacking Structures + +### Step 31: Open the Bytes Editor + +1. Click **Window** β†’ **Bytes** +2. Click the pencil icon to enable editing + +### Step 32: Swap LED Pin Assignments + +We'll swap the red and green LED pins to reverse their behavior! + +**Find the gpio_init calls:** + +1. Locate where `0x10` (16) is loaded for led1_pin +2. Change `0x10` to `0x11` (swap red to green's pin) +3. Locate where `0x11` (17) is loaded for led2_pin +4. Change `0x11` to `0x10` (swap green to red's pin) + +**Before:** +``` +LED 1 (0x0C) β†’ GPIO 16 β†’ Red LED +LED 2 (0x18) β†’ GPIO 17 β†’ Green LED +``` + +**After:** +``` +LED 1 (0x0C) β†’ GPIO 17 β†’ Green LED (SWAPPED!) +LED 2 (0x18) β†’ GPIO 16 β†’ Red LED (SWAPPED!) +``` + +### Step 33: Export and Flash + +1. Click **File** β†’ **Export Program** +2. Set **Format** to **Binary** +3. Name: `0x0023_structures-h.bin` +4. Click **OK** + +Convert and flash: + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0023_structures +python ..\uf2conv.py build\0x0023_structures-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 34: Verify the Hack + +**Open PuTTY and test:** +- Press "1" on remote β†’ **GREEN** LED lights (was red!) +- Terminal still shows `NEC command: 0x0C` +- Press "2" on remote β†’ **RED** LED lights (was green!) +- Terminal still shows `NEC command: 0x18` + +**The log says one thing, but the hardware does another!** + +--- + +## πŸ”¬ Part 15: Security Implications - Log Desynchronization + +### The Danger of Mismatched Logs + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Log vs Reality Desynchronization β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Terminal Log β”‚ β”‚ Physical LEDs β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ NEC: 0x0C β”‚ ◄─────── β”‚ GREEN LED on β”‚ ◄── Mismatch! β”‚ +β”‚ β”‚ (expects RED) β”‚ β”‚ (not red!) β”‚ β”‚ +β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ +β”‚ β”‚ NEC: 0x18 β”‚ ◄─────── β”‚ RED LED on β”‚ ◄── Mismatch! β”‚ +β”‚ β”‚ (expects GREEN) β”‚ β”‚ (not green!) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ The OPERATOR sees correct logs but WRONG physical behavior! β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Real-World Example: Stuxnet + +**Stuxnet** was a cyberweapon that: +- Attacked Iranian nuclear centrifuges +- Made centrifuges spin at dangerous speeds +- Fed FALSE "everything normal" data to operators +- Operators saw stable readings while equipment was destroyed + +Our LED example demonstrates the same principle: +- Logs show expected behavior +- Hardware performs different actions +- Attackers can hide malicious activity + +--- + +## πŸ”¬ Part 16: Functions Project - Advanced Code + +### Step 35: Review the Functions Code + +**File: `0x0026_functions.c`** (key functions shown) + +```c +// Map IR command to LED number +int ir_to_led_number(int ir_command) { + if (ir_command == 0x0C) return 1; + if (ir_command == 0x18) return 2; + if (ir_command == 0x5E) return 3; + return 0; +} + +// Get GPIO pin for LED number +uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) { + if (led_num == 1) return leds->led1_pin; + if (led_num == 2) return leds->led2_pin; + if (led_num == 3) return leds->led3_pin; + return 0; +} + +// Turn off all LEDs +void leds_all_off(simple_led_ctrl_t *leds) { + gpio_put(leds->led1_pin, false); + gpio_put(leds->led2_pin, false); + gpio_put(leds->led3_pin, false); +} + +// Blink an LED +void blink_led(uint8_t pin, uint8_t count, uint32_t delay_ms) { + for (uint8_t i = 0; i < count; i++) { + gpio_put(pin, true); + sleep_ms(delay_ms); + gpio_put(pin, false); + sleep_ms(delay_ms); + } +} + +// Main command processor +int process_ir_led_command(int ir_command, simple_led_ctrl_t *leds, uint8_t blink_count) { + if (!leds || ir_command < 0) return -1; + + leds_all_off(leds); + int led_num = ir_to_led_number(ir_command); + if (led_num == 0) return 0; + + uint8_t pin = get_led_pin(leds, led_num); + blink_led(pin, blink_count, 50); + gpio_put(pin, true); + + return led_num; +} +``` + +### Step 36: Understand the Function Call Chain + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function Call Chain β”‚ +β”‚ β”‚ +β”‚ main() β”‚ +β”‚ β”‚ β”‚ +β”‚ └──► process_ir_led_command(key, &leds, 3) β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β–Ί leds_all_off(&leds) β”‚ +β”‚ β”‚ └──► gpio_put() Γ— 3 β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β–Ί ir_to_led_number(ir_command) β”‚ +β”‚ β”‚ └──► returns 1, 2, or 3 β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β–Ί get_led_pin(&leds, led_num) β”‚ +β”‚ β”‚ └──► returns GPIO pin number β”‚ +β”‚ β”‚ β”‚ +β”‚ β”œβ”€β”€β–Ί blink_led(pin, 3, 50) β”‚ +β”‚ β”‚ └──► gpio_put() + sleep_ms() in loop β”‚ +β”‚ β”‚ β”‚ +β”‚ └──► gpio_put(pin, true) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Step 37: Flash and Test + +1. Flash `0x0026_functions.uf2` to your Pico 2 +2. Open PuTTY +3. Press remote buttons: + - "1" β†’ Red LED blinks 3 times, then stays on + - "2" β†’ Green LED blinks 3 times, then stays on + - "3" β†’ Yellow LED blinks 3 times, then stays on + +--- + +## πŸ”¬ Part 17: Debugging with GDB (Functions) + +### Step 38: Start OpenOCD (Terminal 1) + +Open a terminal and 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" +``` + +You should see output indicating OpenOCD connected successfully to your Pico 2 via the Debug Probe. + +### Step 39: Start GDB (Terminal 2) + +Open a **new terminal** and launch GDB with the binary: + +```powershell +arm-none-eabi-gdb build\0x0026_functions.elf +``` + +### Step 40: Connect to the Remote Target + +In GDB, connect to OpenOCD: + +```gdb +target remote :3333 +``` + +### Step 41: Halt the Running Binary + +Stop the processor: + +```gdb +monitor halt +``` + +### Step 42: Examine the Function Layout + +Disassemble to see the multiple functions: + +```gdb +disassemble 0x10000234,+300 +``` + +You'll see multiple function prologues (push) and epilogues (pop) for the helper functions. + +### Step 43: Set Breakpoints on Key Functions + +Set breakpoints on the helper functions: + +```gdb +break *0x10000234 +break *0x10000280 +break *0x100002a0 +``` + +Reset and run: + +```gdb +monitor reset halt +continue +``` + +### Step 44: Trace the Function Call Chain + +When you press a remote button, step through the calls: + +```gdb +stepi 50 +info registers +``` + +Watch the call chain: `process_ir_led_command` β†’ `leds_all_off` β†’ `ir_to_led_number` β†’ `get_led_pin` β†’ `blink_led`. + +### Step 45: Examine ir_to_led_number + +When the comparison function runs, check the return value: + +```gdb +info registers r0 +``` + +For button "1", you should see `r0 = 1`. For button "2", `r0 = 2`. + +### Step 46: Watch the blink_led Loop + +Set a breakpoint inside blink_led and watch it execute 3 times: + +```gdb +break *0x100002c0 +continue +info registers r0 r1 +``` + +`r0` = pin number, `r1` = state (alternates 0 and 1). + +### Step 47: Examine Pointer Dereference + +Watch how the struct pointer is used to get LED pins: + +```gdb +x/6xb $r0 +``` + +This shows the struct contents when `leds` pointer is in r0. + +### Step 48: Watch Return Values + +After function calls, check return values in r0: + +```gdb +stepi 20 +info registers r0 +``` + +### Step 49: Exit GDB + +When done exploring: + +```gdb +quit +``` + +--- + +## πŸ”¬ Part 18: Analyzing .ELF Files in Ghidra + +### Step 50: Create New Ghidra Project + +1. Create project: `0x0026_functions` +2. Import the `.elf` file (NOT the .bin this time!) + +### Why Use .ELF Instead of .BIN? + +| Feature | .BIN File | .ELF File | +| -------------- | -------------------- | --------------------------- | +| **Symbols** | None | Function/variable names | +| **Sections** | Raw bytes only | .text, .data, .rodata, etc. | +| **Debug info** | None | May include debug symbols | +| **Size** | Smaller | Larger | +| **Use case** | Flashing to hardware | Analysis and debugging | + +### Step 51: Import and Analyze the .ELF + +1. Drag and drop the `.elf` file into Ghidra +2. Ghidra automatically detects ARM format! +3. Click **Yes** to analyze +4. Wait for analysis to complete + +### Step 52: Explore the Symbol Tree + +With .ELF files, you get more information: +1. Look at the **Symbol Tree** panel +2. Expand **Functions** - you may see named functions! +3. Expand **Labels** - data labels may appear + +--- + +## πŸ”¬ Part 19: Hacking the Functions Project + +### Step 53: Find LED Pin Values + +Look for the struct initialization pattern: + +```assembly +movs r0, #0x10 ; led1_pin = 16 +movs r0, #0x11 ; led2_pin = 17 +movs r0, #0x12 ; led3_pin = 18 +``` + +### Step 54: Swap LED 1 and LED 3 + +We'll swap the red (GPIO 16) and yellow (GPIO 18) LEDs: + +**Find and patch in the .bin file:** +1. Change `0x10` (16) to `0x12` (18) +2. Change `0x12` (18) to `0x10` (16) + +**Before:** +``` +Button 1 β†’ LED 1 β†’ GPIO 16 β†’ Red +Button 3 β†’ LED 3 β†’ GPIO 18 β†’ Yellow +``` + +**After:** +``` +Button 1 β†’ LED 1 β†’ GPIO 18 β†’ Yellow (SWAPPED!) +Button 3 β†’ LED 3 β†’ GPIO 16 β†’ Red (SWAPPED!) +``` + +### Step 55: Export the Patched .BIN + +**Important:** Even though we analyzed the .elf, we patch the .bin! + +1. Open the original `.bin` file in Ghidra (or a hex editor) +2. Apply the patches +3. Export as `0x0026_functions-h.bin` + +### Step 56: Convert and Flash + +```powershell +cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0026_functions +python ..\uf2conv.py build\0x0026_functions-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 +``` + +### Step 57: Verify the Hack + +**Open PuTTY and test:** +- Press "1" β†’ **YELLOW** LED blinks (was red!) +- Terminal shows: `LED 1 activated on GPIO 16` (WRONG - it's actually GPIO 18!) +- Press "3" β†’ **RED** LED blinks (was yellow!) +- Terminal shows: `LED 3 activated on GPIO 18` (WRONG - it's actually GPIO 16!) + +**Again, logs don't match reality!** + +--- + +## πŸ“Š Part 20: Summary and Review + +### What We Accomplished + +1. **Learned C structures** - Grouping related data together +2. **Understood struct memory layout** - How members are stored consecutively +3. **Mastered dot and arrow operators** - Accessing struct members +4. **Learned the NEC IR protocol** - How remotes communicate +5. **Understood functions with parameters** - Passing data in and out +6. **Saw struct flattening in assembly** - How compilers transform structs +7. **Analyzed .ELF files** - Getting more symbol information +8. **Hacked GPIO assignments** - Swapping LED behavior +9. **Discovered log desynchronization** - Security implications + +### Struct Operations Summary + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Struct Operations β”‚ +β”‚ β”‚ +β”‚ Definition: β”‚ +β”‚ typedef struct { β”‚ +β”‚ uint8_t pin; β”‚ +β”‚ bool state; β”‚ +β”‚ } led_t; β”‚ +β”‚ β”‚ +β”‚ Creation: β”‚ +β”‚ led_t led = { .pin = 16, .state = false }; β”‚ +β”‚ β”‚ +β”‚ Access (variable): led.pin β”‚ +β”‚ Access (pointer): ptr->pin or (*ptr).pin β”‚ +β”‚ β”‚ +β”‚ Passing to function: void func(led_t *led) β”‚ +β”‚ Calling: func(&led) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Function Types Summary + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Function Patterns β”‚ +β”‚ β”‚ +β”‚ No params, no return: β”‚ +β”‚ void leds_all_off(void) β”‚ +β”‚ β”‚ +β”‚ With params, no return: β”‚ +β”‚ void blink_led(uint8_t pin, uint8_t count, uint32_t delay) β”‚ +β”‚ β”‚ +β”‚ No params, with return: β”‚ +β”‚ int ir_getkey(void) β”‚ +β”‚ β”‚ +β”‚ With params, with return: β”‚ +β”‚ int ir_to_led_number(int ir_command) β”‚ +β”‚ β”‚ +β”‚ With struct pointer: β”‚ +β”‚ uint8_t get_led_pin(simple_led_ctrl_t *leds, int led_num) β”‚ +β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Key Memory Addresses + +| Memory Address | Description | +| -------------- | ------------------------------- | +| `0x10000234` | main() function | +| `0x10` (16) | GPIO 16 - Red LED (led1_pin) | +| `0x11` (17) | GPIO 17 - Green LED (led2_pin) | +| `0x12` (18) | GPIO 18 - Yellow LED (led3_pin) | +| `0x05` | GPIO 5 - IR receiver | +| `0x0C` | NEC code for button 1 | +| `0x18` | NEC code for button 2 | +| `0x5E` | NEC code for button 3 | + +--- + +## βœ… Practice Exercises + +### Exercise 1: Add a Fourth LED +Modify the struct to include a fourth LED on GPIO 19. + +**Hint:** Add `led4_pin` and `led4_state` members. + +### Exercise 2: Change Blink Count +Find and modify the blink count from 3 to 5 blinks. + +**Hint:** Look for the value passed to `process_ir_led_command`. + +### Exercise 3: Swap All Three LEDs +Create a rotation where 1β†’Green, 2β†’Yellow, 3β†’Red. + +**Hint:** Patch all three GPIO values. + +### Exercise 4: Change Blink Speed +Make the LEDs blink faster by changing the delay from 50ms to 25ms. + +**Hint:** Find `0x32` (50) in the function parameters. + +### Exercise 5: Disable One LED +Make button 2 do nothing (LED stays off). + +**Hint:** NOP out the gpio_put call or change the NEC code comparison. + +--- + +## πŸŽ“ Key Takeaways + +1. **Structs group related data** - Better organization than separate variables + +2. **Dot operator for variables, arrow for pointers** - `.` vs `->` + +3. **Designated initializers are cleaner** - `.member = value` syntax + +4. **Compilers flatten structs** - You see values, not struct names, in assembly + +5. **NEC protocol uses 8-bit commands** - 0x0C, 0x18, 0x5E for our buttons + +6. **Functions separate concerns** - Each function does one job + +7. **.ELF files contain more info than .BIN** - Symbols, sections, debug data + +8. **Log desynchronization is dangerous** - Logs can lie about real behavior + +9. **Pattern recognition is key** - Consecutive values like 16, 17, 18 reveal structs + +10. **Always patch the .bin for flashing** - .elf is for analysis only + +--- + +## πŸ“– Glossary + +| Term | Definition | +| -------------------------- | -------------------------------------------------- | +| **Arrow Operator (->)** | Accesses struct member through a pointer | +| **Designated Initializer** | Syntax `.member = value` for struct initialization | +| **Dot Operator (.)** | Accesses struct member from a struct variable | +| **.ELF File** | Executable and Linkable Format - contains symbols | +| **Flattening** | Compiler converting structs to individual values | +| **IR (Infrared)** | Invisible light used for remote control | +| **Log Desynchronization** | When logs don't match actual system behavior | +| **Member** | A variable inside a struct | +| **NEC Protocol** | Common IR communication standard | +| **Struct** | User-defined type grouping related variables | +| **typedef** | Creates an alias for a type | + +--- + +## πŸ”— Additional Resources + +### NEC IR Command Reference + +| Button | Command | Binary | +| ------ | ------- | --------- | +| 1 | 0x0C | 0000 1100 | +| 2 | 0x18 | 0001 1000 | +| 3 | 0x5E | 0101 1110 | + +### GPIO Pin Quick Reference + +| GPIO | Default Function | Our Usage | +| ---- | ---------------- | ----------- | +| 5 | General I/O | IR Receiver | +| 16 | General I/O | Red LED | +| 17 | General I/O | Green LED | +| 18 | General I/O | Yellow LED | + +### Struct Size Calculation + +| Type | Size (bytes) | +| ---------- | ------------ | +| `uint8_t` | 1 | +| `bool` | 1 | +| `uint16_t` | 2 | +| `uint32_t` | 4 | +| `int` | 4 | +| `float` | 4 | +| `pointer` | 4 (on ARM32) | + +--- + +## 🚨 Real-World Implications + +### What You've Learned in This Course + +Over these weeks, you've built skills that few people possess: + +1. **Hardware fundamentals** - GPIO, I2C, PWM, IR protocols +2. **Reverse engineering** - Ghidra, disassembly, function identification +3. **Binary patching** - Modifying compiled code +4. **Security awareness** - Understanding vulnerabilities + +### The Power and Responsibility + +The techniques you've learned can be used for: + +**Good:** +- Security research +- Debugging proprietary systems +- Understanding how things work +- Career in cybersecurity + +**Danger:** +- Unauthorized system access +- Sabotage of critical infrastructure +- Fraud and deception + +**Always use your skills ethically and legally!** + +### Keep Learning + +This is just the beginning: +- Explore more complex protocols (SPI, CAN bus) +- Learn dynamic analysis with debuggers +- Study cryptographic implementations +- Practice on CTF challenges + +--- + +**Congratulations on completing this course! You now have the curiosity, persistence, and skills that embedded systems engineers and security researchers thrive on. Keep experimenting, documenting, and sharing your work. The world needs more builders and defenders like you!** + +Happy hacking! πŸ”§ diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG00.svg b/WEEKS/WEEK11/slides/WEEK11-IMG00.svg new file mode 100644 index 0000000..0469f90 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG00.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + 4F 70 65 6E 4F 43 44 + 10 00 02 34 08 B5 01 + 47 44 42 20 52 45 56 + 20 08 20 00 FF AA 00 + 52 50 32 33 35 30 00 + 0A 0A 0F 12 12 1A 1A + 41 52 4D 76 38 2D 4D + 00 FF 41 00 D4 FF 88 + 47 48 49 44 52 41 00 + FF 00 40 C0 C0 C0 00 + + + + + + + + + + + + +Embedded Systems +Reverse Engineering + + + + + +// WEEK 11 + + +Structures and Functions in +Embedded Systems: Debugging and Hacking +w/ IR Remote Control & NEC Protocol + + + + + +George Mason University + + + +RP2350 // ARM Cortex-M33 + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG01.svg b/WEEKS/WEEK11/slides/WEEK11-IMG01.svg new file mode 100644 index 0000000..6701ba4 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG01.svg @@ -0,0 +1,70 @@ + + + + +C Structures (Structs) +Grouping Related Data Together + + + +What is a Struct? +A user-defined type that groups +related variables under one name +Like a form with multiple fields +-- each field holds different data + + + +Struct Definition + +typedef struct { +uint8_t led1_pin; +uint8_t led2_pin; +uint8_t led3_pin; +bool led1_state; +bool led2_state; +bool led3_state; +} simple_led_ctrl_t; + + + +Why Use Structs? +1. +Organization +Related data stays together +2. +Readability +Code easier to understand +3. +Scalability +Easy to add more features +4. +Pass to Functions + + + +simple_led_ctrl_t leds + +pin1: 16 + +pin2: 17 + +pin3: 18 + +state1: 0 + +state2: 0 + +state3: 0 + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG02.svg b/WEEKS/WEEK11/slides/WEEK11-IMG02.svg new file mode 100644 index 0000000..6157da6 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG02.svg @@ -0,0 +1,85 @@ + + + + +Struct Memory Layout +How Structs Are Stored and Accessed + + + +Memory Layout (6 bytes total) + +Address +Member +Size +Value + + +0x20000000 +led1_pin +1 byte +16 (0x10) + +0x20000001 +led2_pin +1 byte +17 (0x11) + +0x20000002 +led3_pin +1 byte +18 (0x12) + +0x20000003 +led1_state +1 byte +0 (false) + +0x20000004 +led2_state +1 byte +0 (false) + +0x20000005 +led3_state +1 byte +0 (false) + + + +Dot Operator ( . ) +Use with struct variable + +leds.led1_pin = 16; +leds.led1_state = true; + + + +Arrow Operator ( -> ) +Use with pointer to struct + +ptr->led1_pin = 16; +// same as (*ptr).led1_pin + + + +Designated Initializers + +simple_led_ctrl_t leds = { +.led1_pin = 16, .led2_pin = 17, .led3_pin = 18 +}; +Clear which value goes +to which member +Order doesn't matter + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG03.svg b/WEEKS/WEEK11/slides/WEEK11-IMG03.svg new file mode 100644 index 0000000..c44ccd0 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG03.svg @@ -0,0 +1,88 @@ + + + + +NEC IR Protocol +Infrared Remote Control Communication + + + +How IR Works +Remote sends invisible light pulses +IR receiver on GPIO 5 reads signal +IR LED flashes in specific patterns +Each pattern = different button + + + +NEC Protocol Frame (32 bits) + + +Leader + +Address + +Addr Inv + +Command + +Cmd Inv + +Stop + +9ms+4.5ms +8-bit +8-bit check +8-bit +8-bit check + +Leader says "attention!", address identifies device, command is the button pressed + + + +NEC Command Codes + +Button +NEC Code +LED + + +1 +0x0C +Red (GP16) + +2 +0x18 +Green (GP17) + +3 +0x5E +Yellow (GP18) + + + +Hardware Wiring +GPIO 5 +IR Receiver (VS1838B) +GPIO 16 +Red LED + 220 ohm +GPIO 17 +Green LED + 220 ohm +GPIO 18 +Yellow LED + 220 ohm + + + +Projects: 0x0023_structures and 0x0026_functions + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG04.svg b/WEEKS/WEEK11/slides/WEEK11-IMG04.svg new file mode 100644 index 0000000..719946b --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG04.svg @@ -0,0 +1,91 @@ + + + + +Functions in C +Reusable Blocks of Code + + + +Anatomy of a Function + +int ir_to_led_number(int ir_command) { + +^^^ +^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^ +ret +function name +parameter + + +if (ir_command == 0x0C) return 1; +// body + return value + + + +Function Types + +Type +Example + + +No params, no ret +leds_all_off() + +Params, no return +blink_led(..) + +No params, return +ir_getkey() + +Params + return +ir_to_led_num() + +Struct pointer +get_led_pin() + + + +Key Functions + +ir_to_led_number(cmd) +Maps NEC code to LED 1/2/3 + +get_led_pin(leds, num) +Returns GPIO pin for LED + +blink_led(pin, cnt, ms) +Blinks LED cnt times + + + +Function Call Chain + +main() +--> +process_ir_led_command() + +1. leds_all_off() +Turn all LEDs off + +2. ir_to_led_number() +Map NEC to LED + +3. get_led_pin() +Get GPIO pin + +4. blink_led() +Blink + stay on + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG05.svg b/WEEKS/WEEK11/slides/WEEK11-IMG05.svg new file mode 100644 index 0000000..9058acc --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG05.svg @@ -0,0 +1,66 @@ + + + + +Struct Pointers in Functions +Passing Data Efficiently + + + +Why Pass by Pointer? +Efficient +4 bytes (address) not 6 +Modifiable +Function can change original +Standard +Embedded systems practice + + + +Arrow Operator +leds->led1_pin +Same as (*leds).led1_pin +Use -> when leds is a pointer + + + +leds_all_off() + +void leds_all_off( +simple_led_ctrl_t *leds) { +gpio_put(leds->led1_pin, 0); +gpio_put(leds->led2_pin, 0); + + + +blink_led() + +void blink_led(uint8_t pin, +uint8_t count, uint32_t ms){ +gpio_put(pin, true); +sleep_ms(ms); + + + +process_ir_led_command() -- Main Command Processor + +int process_ir_led_command(int cmd, +simple_led_ctrl_t *leds, uint8_t blink_count) { +leds_all_off(leds); +// turn all off first +int num = ir_to_led_number(cmd); +// map NEC to LED +blink_led(get_led_pin(leds, num), +// blink then stay on + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG06.svg b/WEEKS/WEEK11/slides/WEEK11-IMG06.svg new file mode 100644 index 0000000..84c7359 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG06.svg @@ -0,0 +1,57 @@ + + + + +Structures Source Code +0x0023_structures.c + + + +Full Source + + +#include <stdio.h> +#include "pico/stdlib.h" +#include "ir.h" + +typedef struct { +uint8_t led1_pin, led2_pin, led3_pin; +bool led1_state, led2_state, led3_state; +} simple_led_ctrl_t; + +int main(void) { +stdio_init_all(); +simple_led_ctrl_t leds = { +.led1_pin=16, .led2_pin=17, .led3_pin=18 +}; + +gpio_init(leds.led1_pin); +// init 16, 17, 18 +ir_init(5); +// IR on GPIO 5 +while (true) { +// main loop + + + +Main Loop Flow +ir_getkey() +--> +check NEC code +--> +set state +--> +gpio_put() +0x0C=LED1(red) 0x18=LED2(green) 0x5E=LED3(yellow) + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG07.svg b/WEEKS/WEEK11/slides/WEEK11-IMG07.svg new file mode 100644 index 0000000..514e9f0 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG07.svg @@ -0,0 +1,73 @@ + + + + +Struct Flattening +How Compilers Transform Structs + + + +C Code (High Level) + +gpio_init(leds.led1_pin); +// leds.led1_pin = 16 +gpio_init(leds.led2_pin); +// leds.led2_pin = 17 + + + +Assembly (Flattened) + +movs r0, #0x10 +// 16 +bl gpio_init +movs r0, #0x11 +// 17 +bl gpio_init + + + +The Key Insight +Struct abstraction DISAPPEARS +at assembly level +You see individual values (16, 17, 18) not struct names + + + +Struct Member Mapping + +Assembly +Struct Member +Physical +NEC Code + + +0x10 (16) +led1_pin +Red LED +0x0C + +0x11 (17) +led2_pin +Green LED +0x18 + +0x12 (18) +led3_pin +Yellow LED +0x5E + +Sequential values (16,17,18) reveal the struct pattern +Recognize patterns to reconstruct original structs in Ghidra + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG08.svg b/WEEKS/WEEK11/slides/WEEK11-IMG08.svg new file mode 100644 index 0000000..8eb24ae --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG08.svg @@ -0,0 +1,77 @@ + + + + +Hacking Structures +Swapping GPIO Pin Assignments + + + +Swap LED 1 and LED 2 +Find gpio_init values: +0x10 (16) +--> +0x11 (17) +0x11 (17) +--> +0x10 (16) +Swap the two byte values + + + +Result After Hack + +Before: +Btn 1 --> GPIO 16 --> Red +Btn 2 --> GPIO 17 --> Green + +After: +SWAPPED! + + + +Before (Normal) +Btn 1 (0x0C) --> GPIO 16 +Red +Btn 2 (0x18) --> GPIO 17 +Green +Log and LED match correctly + + +After (Hacked) +Btn 1 (0x0C) --> GPIO 17 +Green! +Btn 2 (0x18) --> GPIO 16 +Red! +Log says RED but GREEN lights + + + +Log Desynchronization + +Terminal Log: +NEC command: 0x0C +(expects Red) + +Physical LED: +GREEN LED on +MISMATCH! + +Operator sees correct logs but WRONG behavior + + + +Stuxnet: +False "normal" data to operators, equipment destroyed + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG09.svg b/WEEKS/WEEK11/slides/WEEK11-IMG09.svg new file mode 100644 index 0000000..e6ae7a2 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG09.svg @@ -0,0 +1,73 @@ + + + + +.ELF vs .BIN Analysis +Ghidra Analysis of 0x0026_functions + + + +.ELF vs .BIN Comparison + +Feature +.BIN File +.ELF File + + +Symbols +None +Function names + +Sections +Raw bytes only +.text .data .rodata + +Debug info +None +May include debug + +Use case +Flash to device +Analysis + debug + + + +Importing .BIN +Manual setup required: +ARM Cortex 32 little endian +Block: .text +Base: 10000000 +No function names + + +Importing .ELF +Auto-detected by Ghidra: +ARM format recognized +Sections auto-loaded +Symbol tree populated +Named functions visible + + + +Important Rule +Analyze the .ELF +for symbol information +Patch the .BIN +for flashing + + + +Export Workflow +Patch .bin in Ghidra --> uf2conv.py --> flash to Pico 2 + diff --git a/WEEKS/WEEK11/slides/WEEK11-IMG10.svg b/WEEKS/WEEK11/slides/WEEK11-IMG10.svg new file mode 100644 index 0000000..3906d05 --- /dev/null +++ b/WEEKS/WEEK11/slides/WEEK11-IMG10.svg @@ -0,0 +1,85 @@ + + + + +Structs & IR Protocol +Structs, Functions, IR, and Hacking + + + +C Structures +Group related data together +Dot (.) for variables +Arrow (->) for pointers +Designated init: .pin = 16 + + + +NEC IR Protocol +32-bit frame: addr + cmd +0x0C +Button 1 +0x18 +Button 2 +0x5E +Button 3 + + + +Functions +Reusable blocks, one job each +ir_to_led_number() +get_led_pin() / blink_led() +process_ir_led_command() + + + +Assembly Flattening +Structs vanish in assembly +Only see values: 0x10 0x11 0x12 +Pattern recognition is key +.ELF has symbols, .BIN doesn't + + + +Key Values +0x10000234 +main() +GPIO 5 +IR receiver +GPIO 16/17/18 +Red/Green/Yellow + + + +Hacking Techniques +GPIO swap +0x10 <--> 0x11 +Log desync +Logs lie! +Stuxnet +Same concept + + + +Projects +0x0023_structures +0x0026_functions + + + +Key Takeaway +Patch bytes, mislead logs +hardware does what YOU say +