Course update: lessons, CTF 0x0011a_cb, and documentation

- 0x0011a_cb (Operation Dark Vector): nation-state CTF redesign with an
  AES-128-ECB sealed target and a plaintext launch origin; RP2350 firmware with
  bearing-driven servo, tri-color LEDs, GSV stats, and a realistic no-fix path
- docs: story-driven classified brief, GDB and Ghidra tutorials with deep
  step-throughs, regenerated artifacts and PDFs
- scripts: docstring standard, AES per-student randomizer, telemetry monitor
- week 3 to week 5 lessons: Ghidra patching tutorial, CMSIS-SVD hardware RE,
  double floating-point and GPIO architecture chapters, README structure
This commit is contained in:
Kevin Thomas committed 2026-09-27 14:18:56 -04:00
1 parent 5201ee4b6b
commit 35eacd2c0e
162 files changed
+125658 -232

No files matched your search

+131 -52
View File
@@ -345,14 +345,20 @@ MEMORY
**What this means:**
| Region | Start Address | Size | Purpose |
| --------- | ------------- | -------- | --------------------- |
| Flash | `0x10000000` | (varies) | Your code (XIP) |
| RAM | `0x20000000` | 512 KB | Main RAM |
| SCRATCH_X | `0x20080000` | 4 KB | Core 0 scratch memory |
| SCRATCH_Y | `0x20081000` | 4 KB | Core 0 stack |
| Region | Start Address | Size | Purpose |
| --------- | ------------- | -------- | -------------------------------------------- |
| Flash | `0x10000000` | (varies) | Your code (XIP) |
| RAM | `0x20000000` | 512 KB | Main striped SRAM |
| SCRATCH_X | `0x20080000` | 4 KB | SRAM8: shared, non-striped SRAM |
| SCRATCH_Y | `0x20081000` | 4 KB | SRAM9: shared, non-striped SRAM |
### Where Does the Stack Come From?
`SCRATCH_X` and `SCRATCH_Y` are linker-script names for SRAM8 and SRAM9. They
are **not hardware-assigned to Core 0 or Core 1**: both cores can access both
banks. Software may reserve either bank for per-core data to reduce bank
contention. This particular linker script selects `SCRATCH_Y` for the Core 0
stack; that is a software allocation choice, not a property of SRAM9.
### Where Does This Build's Core 0 Stack Come From?
The linker script calculates the initial stack pointer:
@@ -366,7 +372,9 @@ Let's do the math:
- `LENGTH(SCRATCH_Y)` = `0x1000` (4 KB)
- `__StackTop` = `0x20081000` + `0x1000` = **`0x20082000`**
This value (`0x20082000`) is what we see at offset `0x00` in the vector table!
This value (`0x20082000`) is what we see at offset `0x00` in the vector table.
It is the initial Core 0 stack pointer for this build because its linker script
chose `SCRATCH_Y`; it does not reserve `SCRATCH_Y` for Core 0 in hardware.
---
@@ -791,44 +799,96 @@ Each type of exception has its own handler:
## Part 13: Finding Where Main is Called
### Step 12: Look at Platform Entry
### Step 12: Trace Reset Handler to `main`
After all the setup, the code finally calls `main()`. Let's find it:
**Type this command:**
Start at the **application** Cortex-M vector table in XIP flash. The RP2350
bootrom occupies `0x00000000`; it is not this firmware's vector table. Word
`0x10000000` is this image's initial stack pointer, and word `0x10000004` is
the **reset-handler pointer** loaded into `pc` when the bootrom enters the
application:
```gdb
(gdb) x/10i 0x10000186
(gdb) x/2wx 0x10000000
0x10000000 <__vectors>: 0x20082000 0x1000015d
```
**You should see:**
`0x1000015d` is a Thumb function pointer: bit 0 is set to indicate Thumb
state. Clear that bit before disassembly, so the reset handler's first
instruction address is `0x1000015c`. Then continue through startup until
`platform_entry`:
```
0x10000186 <platform_entry>:
ldr r1, [pc, #80] @ (0x100001d8 <data_cpy_table+56>)
0x10000188 <platform_entry+2>: blx r1
0x1000018a <platform_entry+4>:
ldr r1, [pc, #80] @ (0x100001dc <data_cpy_table+60>)
0x1000018c <platform_entry+6>: blx r1
0x1000018e <platform_entry+8>:
ldr r1, [pc, #80] @ (0x100001e0 <data_cpy_table+64>)
0x10000190 <platform_entry+10>: blx r1
0x10000192 <platform_entry+12>: bkpt 0x0000
0x10000194 <platform_entry+14>:
b.n 0x10000192 <platform_entry+12>
0x10000196 <data_cpy_loop>: ldmia r1!, {r0}
0x10000198 <data_cpy_loop+2>: stmia r2!, {r0}
```gdb
(gdb) x/10i 0x1000015c
(gdb) x/x 0x10000004
0x10000004 <__vectors+4>: 0x1000015d
(gdb) b platform_entry
(gdb) c
```
### Understanding Platform Entry
At `platform_entry`, the three `ldr r1` / `blx r1` pairs are indirect calls:
The platform entry code makes **three function calls** using `ldr` + `blx`:
```text
0x10000186 <platform_entry+0>: ldr r1, [pc, #80]
0x10000188 <platform_entry+2>: blx r1 (first call)
0x1000018a <platform_entry+4>: ldr r1, [pc, #80]
0x1000018c <platform_entry+6>: blx r1 (second call)
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
0x10000190 <platform_entry+10>: blx r1 (third call)
```
1. **First call**: `runtime_init()` - SDK initialization
2. **Second call**: `main()` - YOUR CODE!
3. **Third call**: `exit()` - Called when main returns
### Prove That the Second Call Is `main`
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.
Stop at the second `blx r1`. `r1` holds the target; inspect it and then
disassemble that address:
```gdb
(gdb) b *0x1000018c
(gdb) c
Thread 1 "rp2350.dap.core0" hit Breakpoint 1, platform_entry ()
at C:/Users/assem.KEVINTHOMAS/.pico-sdk/sdk/2.2.0/src/rp2_common/pico_crt0/crt0.S:515
515 blx r1
(gdb) x/x 0x1000018c
0x1000018c <platform_entry+6>: 0x49144788
(gdb) disas
Dump of assembler code for function platform_entry:
0x10000186 <+0>: ldr r1, [pc, #80] @ (0x100001d8 <data_cpy_table+56>)
0x10000188 <+2>: blx r1
0x1000018a <+4>: ldr r1, [pc, #80] @ (0x100001dc <data_cpy_table+60>)
=> 0x1000018c <+6>: blx r1
0x1000018e <+8>: ldr r1, [pc, #80] @ (0x100001e0 <data_cpy_table+64>)
0x10000190 <+10>: blx r1
0x10000192 <+12>: bkpt 0x0000
0x10000194 <+14>: b.n 0x10000192 <platform_entry+12>
End of assembler dump.
(gdb) x/x 0x1000018c
0x1000018c <platform_entry+6>: 0x49144788
(gdb) x/x $r1
0x10000235 <main>: 0x99f001b5
(gdb) disas $r1
Dump of assembler code for function main:
0x10000234 <+0>: push {r3, lr}
0x10000236 <+2>: bl 0x1000156c <stdio_init_all>
0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 <main+16>)
0x1000023c <+8>: bl 0x100015fc <__wrap_puts>
0x10000240 <+12>: b.n 0x1000023a <main+6>
0x10000242 <+14>: nop
0x10000244 <+16>: adds r4, r1, r7
0x10000246 <+18>: asrs r0, r0, #32
End of assembler dump.
(gdb) x/x 0x100001dc
0x100001dc <data_cpy_table+60>: 0x10000235
```
`x/x $r1` reports `0x10000235 <main>` because Thumb function pointers have
bit 0 set. The actual instruction starts at `0x10000234`, as `disas $r1`
shows. The preceding `ldr r1, [pc, #80]` reads the literal-pool word at
`0x100001dc`; `x/x 0x100001dc` confirms that word is `0x10000235`.
This proves that the **second** indirect call enters `main()`.
The first call performs runtime initialization. When `main()` returns, the
third call enters the SDK exit path; the following `bkpt` catches the
unexpected case where that exit path returns.
### Step 13: Set a Breakpoint at Main
@@ -1031,11 +1091,14 @@ void _reset_handler(void)
### Step 18: Trace the Path to Main
Let's find how the boot code eventually calls `main()`:
Use the same evidence chain as GDB, but statically in the Listing view:
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!
1. In the Symbol Tree, find the `main` function at `0x10000234`.
2. Right-click `main` and select **References -> Show References to main**.
3. Double-click the reference at `0x1000018c` to jump to the second `blx r1`.
4. Select the instruction immediately above it: `ldr r1,[DAT_100001dc]` at
`0x1000018a`.
5. Double-click `DAT_100001dc`, or press **G** and enter `0x100001dc`.
**You should see:**
@@ -1043,7 +1106,16 @@ Let's find how the boot code eventually calls `main()`:
| ------------------------- | ---- | ------------------ |
| `1000018c` | CALL | `blx r1` (to main) |
4. Double-click on the reference to jump to `1000018c`
At `0x100001dc`, Ghidra shows the literal-pool value `0x10000235`. That is
the Thumb function pointer loaded into `r1` immediately before the call.
Clear bit 0 to obtain the actual first instruction address:
```text
0x10000235 (Thumb function pointer; bit 0 is set)
0x10000234 (main's first instruction; bit 0 cleared)
```
This proves the second indirect call at `0x1000018c` reaches `main`.
### Step 19: Examine Platform Entry
@@ -1071,7 +1143,14 @@ In Ghidra, look at `platform_entry`:
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()`.
The `DAT_100001dc = 10000235h` annotation is the static proof. The preceding
`ldr` loads that Thumb function pointer into `r1`; the following `blx r1` at
`0x1000018c` calls it. Ghidra clears the Thumb bit and labels the target
`main` at `0x10000234`.
> **Key Insight:** The reset vector identifies the reset handler, the reset
> handler reaches `platform_entry`, and this literal-pool entry proves that
> `platform_entry`'s second indirect call reaches `main`.
### Step 20: Create a Boot Sequence Graph
@@ -1115,8 +1194,8 @@ Ghidra can visualize the call flow:
| - 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 |
| - 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) |
@@ -1551,20 +1630,20 @@ well within the 4 KB scan window the bootrom uses (Datasheet 5.9.5, p. 429).
| PROVEN BOOT SEQUENCE (0x0001_hello-world) |
+-----------------------------------------------------------------+
| 1. Bootrom reads 0x10000000 |
| -> SP = 0x20082000 (offset +0x00 of vector table) |
| -> RST = 0x1000015d (offset +0x04, Thumb -> 0x1000015c) |
| -> SP = 0x20082000 (offset +0x00 of vector table) |
| -> RST = 0x1000015d (offset +0x04, Thumb -> 0x1000015c) |
+-----------------------------------------------------------------+
| 2. Bootrom scans first 4 kB for IMAGE_DEF |
| -> Found at 0x10000138 (this build) |
| -> Start marker: d3 de ff ff |
| -> End marker: 79 35 12 ab |
| -> Found at 0x10000138 (this build) |
| -> Start marker: d3 de ff ff |
| -> End marker: 79 35 12 ab |
+-----------------------------------------------------------------+
| 3. Bootrom jumps to reset handler at 0x1000015c |
| -> _reset_handler (crt0.S) runs |
| -> Checks CPUID - Core 1 sent back to bootrom |
| -> Core 0: .data copied, .bss zeroed, platform_entry called |
| -> _reset_handler (crt0.S) runs |
| -> Checks CPUID - Core 1 sent back to bootrom |
| -> Core 0: .data copied, .bss zeroed, platform_entry called |
+-----------------------------------------------------------------+
| 4. platform_entry calls runtime_init -> main -> exit |
| 4. platform_entry calls runtime_init -> main -> exit |
+-----------------------------------------------------------------+
```