mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-24 16:34:46 +02:00
Added WEEK03
This commit is contained in:
@@ -84,6 +84,16 @@ This chapter covers the hacking of our firmware for the Pico 2 MCU hello, world
|
|||||||
## Week 3
|
## Week 3
|
||||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
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
|
### Chapter 4: Embedded System Analysis
|
||||||
This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools.
|
This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user