Added WEEK03

This commit is contained in:
Kevin Thomas
2026-01-18 21:50:56 -05:00
parent 5ffb12f551
commit c7469eda98
6 changed files with 2116 additions and 0 deletions
+10
View File
@@ -84,6 +84,16 @@ This chapter covers the hacking of our firmware for the Pico 2 MCU hello, world
## Week 3
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md)
#### Exercise 1: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01.md)
#### Exercise 2: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02.md)
#### Exercise 3: Examine All Vectors [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-03.md)
#### Exercise 4: Find Your Main Function [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-04.md)
### Chapter 4: Embedded System Analysis
This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools.
+130
View File
@@ -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
### 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
```bash
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
```bash
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.
+167
View File
@@ -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
### 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
```
+209
View File
@@ -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
### 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 <isr_nmi>: bkpt 0x0000
0x1000011c <isr_hardfault>: bkpt 0x0000
0x1000011e <isr_svcall>: 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
```
+238
View File
@@ -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
### 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 <main>: push {r7, lr}
0x10000236 <main+2>: sub sp, #8
0x10000238 <main+4>: add r7, sp, #0
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
0x1000023e <main+10>: movw r0, #404 @ 0x194
0x10000242 <main+14>: movt r0, #4096 @ 0x1000
0x10000246 <main+18>: bl 0x1000023c <__wrap_puts>
0x1000024a <main+22>: b.n 0x1000023e <main+10>
0x1000024c <runtime_init>: push {r3, r4, r5, r6, r7, lr}
```
##### Step 4: Identify the First Function Call
The first function call in `main()` is:
```
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
```
**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 <platform_entry>: ldr r1, [pc, #80]
0x10000188 <platform_entry+2>: blx r1
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] ← LR points here
0x1000018c <platform_entry+6>: blx r1 ← This called main
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
0x10000190 <platform_entry+10>: blx r1
0x10000192 <platform_entry+12>: 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 <data_cpy_table+60>: 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.
+1362
View File
File diff suppressed because it is too large Load Diff