Add 0x0001b_ctf challenge with companion PDFs

This commit is contained in:
Kevin Thomas
2026-09-09 09:08:36 -04:00
parent 58880fd18d
commit be5ef8fa46
8 changed files with 920 additions and 0 deletions
Binary file not shown.
+387
View File
@@ -0,0 +1,387 @@
# CTF Challenge - Operation Black Start
## ⚠ WORLDGRID EMERGENCY INCIDENT ⚠
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N B L A C K S T A R T |
| |
| *** PRIORITY RED *** |
| |
+----------------------------------------------------------------------------------------+
```
---
## INCIDENT BRIEFING
### Background
**WorldGrid Compact** is the emergency interconnection standard shared by
three allied national grid operators. When any member's primary SCADA
network goes dark, a fleet of small embedded relay nodes — call sign
**GRID-7** — is the only thing standing between an orderly recovery and an
uncontrolled cascade. Each relay node watches the last known grid frequency
deviation, decides whether conditions are safe, and either **holds** the
automatic black-start dispatch or **authorizes** it.
At 03:11 UTC, a coordinated cyberattack severed the primary SCADA uplink
across the GRID-7 corridor and the WATER-3 aqueduct pumping stations that
depend on it. With the network coordination center offline and three
continents' worth of hospitals, rail systems, and water treatment plants
running on backup power, WorldGrid's engineering team did the only thing
they could: they rushed an emergency firmware build for the relay fleet and
pushed it within **eleven minutes** of the attack being detected.
### The Disaster
The engineer who built that emergency image, **Dr. Elias Renner**, has not
slept in thirty-one hours. He compiled the fix, ran a five-second bench
test, and shipped it — because the alternative was leaving the relay fleet
completely blind. It appears to work. The relay boots. It prints a status
report. It reports **GRID STATUS: STABLE** and **DISPATCH PATH:
AUTHORIZED**.
There is a problem: the frozen frequency reading latched at the moment
communications were cut shows a deviation of **0.87 Hz** — nearly *50%
beyond* WorldGrid's hard engineering limit of **0.60 Hz**. A deviation this
large, if trusted, means the grid is nowhere near stable enough for an
automatic black-start dispatch. If the fleet authorizes dispatch on a false
"STABLE" reading, cascading generator trips will follow within minutes,
and GRID-7 and WATER-3 will go dark for the second time — this time with no
backup plan.
**Dr. Renner's rushed build has a bug. Multiple relay nodes are already
reporting the same false-safe status. Nobody has found where in the
compiled firmware the error lives, because the source code used for that
emergency compile was overwritten by the next build fifteen minutes later
and cannot be recovered.**
### The Only Surviving Evidence
One relay node — the training/verification unit — still holds the exact
miscompiled image that shipped to the fleet. This binary, and this binary
alone, is the only remaining copy of the emergency build. There is no
source code. There is no build log. There is only the compiled image, a
UART cable, and whatever a skilled embedded reverse engineer can prove by
reading machine code.
### The Human Stakes
| Consequence if the false "STABLE" reading is trusted | Scale |
|---|---|
| Hospitals on generator backup past their fuel reserve | 214 facilities |
| Water treatment and pumping stations losing pressure | 3 aqueduct systems |
| Rail corridors stranded mid-route | 6 national rail networks |
| Estimated population affected by cascading failure | 40+ million people |
**The options are:**
1.**Trust the fleet's reported status** — dispatch fires on a false
reading, cascading failure follows within the hour.
2.**Shut the entire relay fleet down** — buys time, but leaves 40
million people with no automated recovery path at all.
3. **REVERSE ENGINEER THE EMERGENCY BUILD** — find the exact
miscompiled bytes, patch them, verify the corrected image on real
hardware, and hand the fix to the field team so the *rest of the fleet*
can be safely repatched before the next attempt.
### THE SHORTAGE
For years, the world treated embedded systems as invisible infrastructure.
The engineers who could read a vector table, decode a Thumb branch, or
patch a miscompiled constant directly in a stripped binary were never
numerous enough. Tonight almost all of them are already in the field
chasing other failures. **You are the reserve team.**
You were called in because you can do something Dr. Renner's exhausted
team cannot do right now: read what the processor is actually doing, with
no source code, no time for a rewrite, and no room for a guess.
> **⏰ TIME PRESSURE:** The field team is standing by to push your verified
> patch to the rest of the GRID-7 fleet. Every relay node still reporting
> a false "STABLE" status is one dispatch cycle away from disaster.
> **AUTHORIZED LAB ONLY:** This challenge uses a supplied Pico 2 training
> relay and its exact miscompiled firmware image. Do not connect this
> exercise to a public network, an operational grid, a water utility, or
> any device you do not own or have explicit written authorization to test.
---
## What This CTF Tests
| Week | Concepts Tested |
|------|-----------------|
| 1 | RP2350 architecture, ARM Cortex-M33 registers, stack, flash/RAM, Thumb assembly, Ghidra static analysis |
| 2 | GDB connection, breakpoints, disassembly, register and memory inspection, UART observation |
| 3 | Bootrom handoff, vector table, reset handler, startup code, XIP, Thumb-bit addressing |
---
## Part 1: Understanding the Relay Node
### GRID-7 Relay Hardware
| Component | Connection | Purpose |
|-----------|------------|---------|
| Raspberry Pi Pico 2 | RP2350 | Runs the miscompiled emergency firmware |
| UART TX | GPIO 0 | Relay telemetry output |
| UART RX | GPIO 1 | Reserved (no command parser is implemented) |
| SWD debug interface | Supplied probe | Authorized GDB inspection |
No LED, relay output, sensor, display, or other peripheral is part of this
CTF. Every graded finding lives in flash (`.rodata`/`.text`) or SRAM, and is
reachable with only the Weeks 1-3 toolset: Ghidra, GDB, and a UART monitor.
### UART Configuration
- Baud: `115200`
- Data: `8 bits`
- Parity: `none`
- Stop: `1`
- Logic: `3.3 V`
### Normal (Intended) Behavior
The relay should latch the frozen deviation reading, compare it against the
**real** WorldGrid safety limit of **60** (0.60 Hz, encoded as an integer
`x100`), and report honestly:
```
+-----------------------------------------------------------------+
| Intended Relay Behavior |
| |
| 1. Boot and initialize UART |
| 2. Print the boot identity and a signal-quality banner |
| 3. Compare the frozen 87 (0.87 Hz) reading against the 60 |
| (0.60 Hz) safety limit |
| 4. 87 exceeds 60, so the grid is NOT stable |
| 5. Report GRID STATUS: CRITICAL and DISPATCH PATH: HELD |
| 6. Repeat the report once per second until conditions change |
+-----------------------------------------------------------------+
```
### Observed (Buggy) Behavior — What You Will See When You First Flash `CTF-01.uf2`
```text
GLOBAL EMBEDDED RESPONSE NETWORK
BLACK START WINDOW: 27 MINUTES
UART0 115200 8N1 | AUTHORIZED LAB CONSOLE
SIGNAL: NORMAL
RESPONSE> GRID STATUS: STABLE
DISPATCH PATH: AUTHORIZED
LAST FRAME: QUARANTINED
RESPONSE>
```
This is exactly what Dr. Renner's team is seeing on the deployed fleet. It
is wrong, and it is wrong in **two independent ways** inside the compiled
binary. Do not assume the first readable sentence is the full truth —
treat every printed line as evidence to be checked against the machine
code, not as a fact on its own.
---
## How To Connect the Training Relay
- Pico 2 **GPIO 0 / UART TX** -> USB-UART adapter **RX**
- Pico 2 **GPIO 1 / UART RX** -> USB-UART adapter **TX**
- Pico 2 **GND** -> USB-UART adapter **GND**
- Use **3.3 V logic only**. Never connect a 5 V line to a Pico GPIO.
- Connect the supplied SWD probe according to its documented pinout.
The supplied image is `CTF-01.bin` (for Ghidra analysis) and `CTF-01.uf2`
(for flashing). If your instructor supplies different filenames, record the
actual filenames in your report.
---
## Part 2: The Miscompiled Firmware
You do not have the source code. It was overwritten fifteen minutes after
the emergency build shipped. You have only the compiled image. Your job is
to reverse engineer it with Ghidra, locate the defects, and patch the
binary directly — exactly the way Dr. Renner's field team will need to
patch the rest of the deployed fleet.
### What The Firmware Does
1. Initializes UART0 and stdio.
2. Reads a frozen grid-frequency-deviation reading that was latched in
memory before communications were severed.
3. Compares that reading against a compiled-in safety threshold — **twice**,
once for each independent status line it reports.
4. Prints a boot banner containing an unconditional signal-quality line.
5. Enters an infinite loop printing the grid classification and dispatch
decision once per second.
### Bug Summary — What You Are Graded On
| Bug # | Category | Severity | Description | Hint |
|-------|----------|----------|--------------|------|
| **Bug #1** | Miscompiled safety constant | **CRITICAL** | The safety threshold used to classify the frozen reading was compiled far too permissive. It is used **twice** — once for the operator-facing status and once for the automated dispatch decision — and **both** copies must be corrected. | The real WorldGrid safety limit is 60 (0.60 Hz). Search for the wrong immediate value used in the comparison. |
| **Bug #2** | Hardcoded string literal | **HIGH** | The boot banner unconditionally prints a signal-quality word that does not reflect the actual reading, regardless of what the relay later reports. | The correct word describes the true state of a 0.87 Hz deviation against a 0.60 Hz limit — not "NORMAL". |
**Important:** The replacement text for Bug #2 **must be the same length**
as the original — patching a shorter or longer string will corrupt
adjacent flash data.
### A Third Finding — Not a Bug, a Recovery Task
Somewhere in this image is the **quarantined black-start authorization
frame** — the exact frame the relay is supposed to transmit to the
regional dispatcher once a human operator confirms it is safe to proceed.
It is never printed by the firmware. Recovering it (without patching
anything) is required evidence for your final report.
---
## Part 3: Your Assignment
### Submission Document
Whenever a task asks you to **Document** or **answer**, write your answers
in a single file named `CTF-01-Answers.md` (or `.txt`).
### Task 1: Setup and Initial Analysis
1. Create a new Ghidra project named `Black_Start_Investigation`.
2. Import `CTF-01.bin`.
3. Configure the language as **ARM Cortex 32-bit, little endian**.
4. Set the base address to `0x10000000`.
5. Run auto-analysis.
**Document:**
- A screenshot of the Ghidra **Import Results** or **Program Information**
window showing the project name, processor settings, and base address.
- The address of `main()`.
- The address of the recurring status loop (the branch target that repeats
once per second).
- The vector-table base, the initial stack pointer, and the reset-handler
pointer as stored (note its Thumb bit) versus the actual instruction
address.
### Task 2: Find and Patch Bug #1 — The Miscalibrated Safety Threshold
1. Find **both** locations where the frozen reading is compared against
the miscompiled safety constant.
2. Document the exact address, the original instruction, and the original
immediate value at each location.
3. Determine the correct immediate value. **Caution:** the compiler may
not have encoded the raw threshold you expect — a strict "less than"
comparison against an unsigned value is often optimized into a
"less-or-equal" comparison against one less than the threshold. Show
your reasoning.
4. Patch **both** locations in Ghidra.
**Questions to answer:**
- Why must both locations be patched? What happens if you only patch one?
- Why is a false "STABLE" classification on an 0.87 Hz reading dangerous
for an automated black-start dispatch?
### Task 3: Find and Patch Bug #2 — The False Signal Banner
1. Find the boot-banner string that unconditionally reports the wrong
signal quality.
2. Document its address and the exact bytes that must change.
3. Patch the string, preserving its exact length.
**Questions to answer:**
- Document the original vs. patched bytes, character by character.
- Why is a hardcoded, unconditional status word more dangerous than one
that is at least computed from a (miscalibrated) reading?
### Task 4: Recover the Quarantined Dispatch Frame
1. Use Ghidra's Defined Strings (or a raw string search) to locate the
hidden black-start authorization frame.
2. Document its address and explain why it is never transmitted by the
current firmware.
3. Do **not** attempt to patch this value — it is evidence, not a bug.
### Task 5: Export and Verify
1. Export your patched binary as `CTF-01_fixed.bin`.
2. Convert it to UF2 format for the RP2350:
```bash
python uf2conv.py CTF-01_fixed.bin --base 0x10000000 --family 0xe48bff59 --output CTF-01_fixed.uf2
```
3. Flash `CTF-01_fixed.uf2` to your Pico 2 and capture the corrected UART
output.
4. Confirm that the corrected image now reports **GRID STATUS: CRITICAL**,
**DISPATCH PATH: HELD**, and the corrected signal-quality word — an
honest, safe report instead of a false "all clear."
5. Build a summary table of every patch: address, original bytes, patched
bytes, and a one-line description.
### Task 6: Written Reflection (short answers, 150 words or less each)
1. Why is "the build was rushed under emergency pressure" not an
acceptable excuse for shipping a firmware defect that could trigger a
cascading grid failure?
2. Name one concrete engineering practice (code review, static analysis,
hardware-in-the-loop test, etc.) that would have caught **each** of the
two graded bugs before this image ever reached the fleet.
---
## Submission Format
Submit a folder containing:
- `CTF-01-Answers.md`;
- screenshots or terminal transcripts;
- `CTF-01_fixed.bin` and `CTF-01_fixed.uf2`;
- the original image hash.
---
## Success Criteria
You complete the challenge when you can prove all of the following:
- You can explain how the RP2350 reaches the relay's code from reset.
- You can locate and patch both copies of the miscalibrated threshold.
- You can locate and patch the false signal-quality string without
corrupting adjacent data.
- You can export, convert, and flash a corrected image.
- You can prove on real hardware that the corrected image reports the
true, dangerous state instead of the false "all clear."
- You can recover the quarantined dispatch frame as evidence.
---
## Academic Integrity and Safety
By submitting this CTF work, you certify that:
1. You used only the supplied training relay, image, and lab interface.
2. You did not connect the challenge to a public network, an operational
grid, a water utility, or any third-party device.
3. You understand that embedded reverse engineering and binary patching
require explicit authorization in any real-world context.
4. You will report any discovered weakness responsibly to the course
instructor.
The world is short on people who can do this work. Treat that
responsibility seriously: verify before you patch, patch before you trust,
and never confuse a clean-looking status line with a safe system.
---
## Reference Material
- ARM Cortex-M33 Technical Reference Manual
- RP2350 datasheet
- GDB documentation
- Ghidra documentation: [https://ghidra-sre.org/](https://ghidra-sre.org/)
Binary file not shown.
+216
View File
@@ -0,0 +1,216 @@
# CTF Challenge: Operation Black Start
## Project Requirements & Grading Criteria
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N B L A C K S T A R T |
| |
| REQUIREMENTS & GRADING CRITERIA |
| |
+----------------------------------------------------------------------------------------+
```
---
## Project Overview
Students are the reverse-engineering reserve team called in after WorldGrid
Compact's emergency firmware build shipped a miscompiled safety threshold and
a false status string to its GRID-7 relay fleet. Students reverse engineer
the supplied RP2350 image with Ghidra, locate two real defects, patch them
directly in the binary, export a corrected image, flash it to real hardware,
and verify the corrected behavior on a physical Pico 2 — the same workflow
used in the FINAL projects.
Students must use only Weeks 1-3 concepts: ARM registers and stack behavior,
UART output, GDB, Ghidra static analysis and binary patching, vector tables,
reset startup, XIP, and Thumb addressing. No LED, relay, sensor, display, or
later-week peripheral-control task is part of this CTF.
The challenge is separate from all FINAL projects and contains no FINAL-project
answer, constant, address, bug, or patch.
---
## Learning Objectives
Students will demonstrate the ability to:
1. Capture a clean UART baseline before modifying the binary.
2. Decode an RP2350 vector table and initial stack pointer.
3. Explain the Thumb bit in a reset-handler pointer.
4. Trace a flash string into the UART output call and argument register.
5. Locate a miscompiled immediate value used in **multiple** locations and
understand why every occurrence must be patched.
6. Correctly compute a patched immediate value even when the compiler has
transformed the original comparison (e.g., `<` optimized to `<=`).
7. Patch a string literal in flash without corrupting adjacent data.
8. Export a patched binary, convert it to UF2, and verify the fix on real
hardware.
9. Recover a quarantined data frame from flash as static evidence.
---
## Deliverables Checklist
| # | Deliverable | Format | Task |
|---|-------------|--------|------|
| 1 | Ghidra project screenshot (project name, processor, base address) | PNG/JPG | Task 1 |
| 2 | `main()`, status-loop, and vector-table address table | Inside `CTF-01-Answers.md` | Task 1 |
| 3 | Bug #1 analysis: both addresses, original/patched bytes, immediate-value reasoning | Inside `CTF-01-Answers.md` | Task 2 |
| 4 | Bug #2 analysis: string address, original/patched bytes, character-by-character mapping | Inside `CTF-01-Answers.md` | Task 3 |
| 5 | Recovered dispatch frame and its address | Inside `CTF-01-Answers.md` | Task 4 |
| 6 | `CTF-01_fixed.bin` — exported patched binary | BIN file | Task 5 |
| 7 | `CTF-01_fixed.uf2` — UF2-converted binary | UF2 file | Task 5 |
| 8 | Verification transcript: corrected UART output on real hardware | Inside `CTF-01-Answers.md` | Task 5 |
| 9 | Summary table of all patches (address, original bytes, patched bytes) | Inside `CTF-01-Answers.md` | Task 5 |
| 10 | Written reflection (two short answers) | Inside `CTF-01-Answers.md` | Task 6 |
---
## Required Tools and Equipment
| Tool | Purpose | Required For |
|------|---------|-------------|
| Raspberry Pi Pico 2 | Isolated target | All tasks |
| 3.3 V USB-UART adapter | UART capture | Tasks 1, 5 |
| Serial monitor | Observe output | Tasks 1, 5 |
| Ghidra | Static analysis and binary patching | Tasks 1-4 |
| Python (`uf2conv.py`) | UF2 conversion | Task 5 |
| `CTF-01.bin` and `CTF-01.uf2` | Supplied artifacts | All tasks |
UART settings: **115200 baud, 8 data bits, no parity, 1 stop bit**.
The instructor-issued artifact hashes are:
```text
CTF-01.bin 6FD296F7A85F243FB26BF6BFFCBEAB26815FD8915101A81F72069063A5635E5A
CTF-01.uf2 980F04369C23AD32A063DFE18DE5AF08DF3830138FC7E7898B1F011B4F5E1D9D
```
---
## Task 1: Setup and Initial Analysis — 15 points
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| Ghidra project setup | 3 | Screenshot shows correct project name, ARM Cortex 32-bit little endian, base `0x10000000` | One item off | Not set up |
| `main()` and status-loop addresses | 4 | Both addresses correctly documented | One correct | Neither found |
| Vector table | 4 | Correct base, initial SP, reset pointer | One missing | Not found |
| Thumb addressing | 4 | Correctly clears bit 0 to identify the real instruction address | General explanation | Incorrect |
---
## Task 2: Find and Patch Bug #1 — Miscalibrated Safety Threshold — 30 points
**What to find:** the frozen reading is compared against a miscompiled
safety constant at **two separate addresses** — once for the operator-facing
status and once for the automated dispatch decision.
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| Found location A | 5 | Correct address and original instruction/bytes documented | Address off | Not found |
| Found location B | 5 | Correct address and original instruction/bytes documented | Address off | Not found |
| Correct immediate-value reasoning | 8 | Explains the `<` vs `<=` compiler transform and derives the correct patched immediate | Correct value, no reasoning | Wrong value |
| Patched location A | 4 | Grader verifies the byte change | Wrong byte | Not patched |
| Patched location B | 4 | Grader verifies the byte change | Wrong byte | Not patched |
| Explained why both must be patched | 4 | Clear explanation of duplicated/independent comparisons | Vague | Missing |
---
## Task 3: Find and Patch Bug #2 — The False Signal Banner — 20 points
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| Found the string | 5 | Correct address, found via Ghidra Defined Strings or hex inspection | Approximate | Not found |
| Patched correctly | 8 | All required bytes changed, string length preserved, grader verifies boot output | Correct text but wrong bytes documented | Wrong length or corrupted data |
| Character-by-character documentation | 4 | Original vs. patched bytes for every changed character | Partial | Missing |
| Explained the danger of a hardcoded status word | 3 | Clear, specific reasoning | Generic | Missing |
---
## Task 4: Recover the Quarantined Dispatch Frame — 10 points
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| Found the hidden frame | 6 | Correct address and full recovered text | Partial text | Not found |
| Explained why it is not transmitted | 4 | Clear static-analysis explanation | Vague | Missing |
Students must **not** patch this value; it is evidence only.
---
## Task 5: Export and Verify — 20 points
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| Exported patched binary | 4 | Valid `CTF-01_fixed.bin` submitted | Corrupted | Not submitted |
| Converted to UF2 correctly | 4 | Valid `CTF-01_fixed.uf2`, correct base/family flags | Wrong flags | Not submitted |
| Hardware verification | 8 | Grader confirms corrected UART output: `GRID STATUS: CRITICAL`, `DISPATCH PATH: HELD`, corrected signal word | Only some lines corrected | No verification |
| Summary table of all patches | 4 | Complete table with addresses and before/after bytes | Missing entries | No table |
---
## Task 6: Written Reflection — 5 points
| Criterion | Points | Full Credit | Partial Credit | No Credit |
|-----------|--------|-------------|----------------|-----------|
| "Rushed build" is not an excuse | 2 | Specific, grounded reasoning | Generic | Missing |
| Engineering practice per bug | 3 | Names one concrete, relevant practice for **each** bug | Names one for only one bug | Missing |
---
## Recommended Answer File Structure
```text
# Operation Black Start - Incident Report
## 1. Scope and Artifact Integrity
## 2. Ghidra Setup and Boot/Vector Table
## 3. Bug #1 - Miscalibrated Safety Threshold
## 4. Bug #2 - False Signal Banner
## 5. Recovered Dispatch Frame
## 6. Export and Hardware Verification
## 7. Patch Summary Table
## 8. Written Reflection
```
---
## ⚠ Common Pitfalls
| Pitfall | Consequence | Avoidance |
|---------|-------------|-----------|
| Patching only one of the two threshold locations | Half the fleet's telemetry still lies | Search for **every** occurrence of the wrong immediate |
| Assuming the threshold immediate equals the safety limit directly | Off-by-one patch, wrong behavior | Check whether the compiler used `<` or `<=` semantics |
| Replacing a string with a different length | Corrupts adjacent flash data | Count bytes before patching |
| Treating an odd vector address as invalid | Thumb analysis fails | Explain bit 0 |
| Skipping hardware verification | Patch is unproven | Flash and capture real UART output |
| Modifying the quarantined dispatch frame | Destroys required evidence | Recover it, do not patch it |
---
## Reference Memory Map
| Region | Address | Purpose |
|--------|---------|---------|
| Bootrom | `0x00000000` | Immutable boot code |
| Flash/XIP | `0x10000000` | Vector table, code, constant strings |
| SRAM | `0x20000000` | Stack and writable state |
---
## Safety and Academic Integrity
Use only the supplied Pico 2 and firmware. Do not connect the exercise to an
operational grid, water plant, public network, military system, or third-party
device. This is a controlled, isolated educational exercise.
Binary file not shown.
+311
View File
@@ -0,0 +1,311 @@
# CTF Challenge - Operation Black Start
## Instructor Solution Key
```
+----------------------------------------------------------------------------------------+
| |
| ██████╗ ██╗ █████╗ ██████╗██╗ ██╗███████╗████████╗ █████╗ ██████╗ ████████╗ |
| ██╔══██╗██║ ██╔══██╗██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝██╔══██╗██╔══██╗╚══██╔══╝ |
| ██████╔╝██║ ███████║██║ █████╔╝ ███████╗ ██║ ███████║██████╔╝ ██║ |
| ██╔══██╗██║ ██╔══██║██║ ██╔═██╗ ╚════██║ ██║ ██╔══██║██╔══██╗ ██║ |
| ██████╔╝███████╗██║ ██║╚██████╗██║ ██╗███████╗ ██║ ██║ ██║██║ ██║ ██║ |
| ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝██║ ██║╚══════╝ ╚═╝ ╚═╝ ╚═╝██║ ██║ ██║ |
| |
| |
| O P E R A T I O N B L A C K S T A R T |
| |
| *** INSTRUCTOR SOLUTION KEY - RESTRICTED *** |
| |
+----------------------------------------------------------------------------------------+
```
> This key is for Operation Black Start only. It contains no FINAL-project
> answers, constants, addresses, bugs, or patches.
---
## Artifact Identity
| Artifact | Value |
|----------|-------|
| Student image | `CTF-01.bin` |
| Flash image | `CTF-01.uf2` |
| Target | Raspberry Pi Pico 2 / RP2350 ARM |
| Image base | `0x10000000` |
| UART | UART0, GPIO 0 TX / GPIO 1 RX, 115200 8N1 |
```text
CTF-01.bin 6FD296F7A85F243FB26BF6BFFCBEAB26815FD8915101A81F72069063A5635E5A
CTF-01.uf2 980F04369C23AD32A063DFE18DE5AF08DF3830138FC7E7898B1F011B4F5E1D9D
```
Every address and byte value below was independently verified against the
compiled ELF (`arm-none-eabi-nm`, `arm-none-eabi-objdump`) and the raw bytes
of the delivered `CTF-01.bin` (direct hex read at each file offset). This
image contains **no** LED, relay, sensor, display, or GPIO-control logic.
---
## Instructor Scenario
WorldGrid Compact's emergency firmware build for the GRID-7 relay fleet was
compiled and shipped eleven minutes after a cyberattack severed the primary
SCADA uplink. The engineer (Dr. Elias Renner) had no time to review the
build; the source used for that compile no longer exists. The training
relay supplied to students holds the exact miscompiled image that shipped
to the fleet, containing two real, independently patchable defects.
Students must reconstruct the boot path, locate both defects with Ghidra,
patch the binary directly, export and convert it, and prove the corrected
behavior on real hardware — the same workflow used in the FINAL projects.
---
## Task 1: Setup and Initial Analysis Solution
### Vector Table (first 32 bytes of `CTF-01.bin`)
```text
00 20 08 20 5B 01 00 10 1B 01 00 10 1D 01 00 10
11 01 00 10 11 01 00 10 11 01 00 10 11 01 00 10
```
| Evidence | Answer |
|----------|--------|
| Vector table base | `0x10000000` |
| Initial SP | `0x20082000` |
| Reset pointer (as stored) | `0x1000015b` |
| Reset instruction address (bit 0 cleared) | `0x1000015a` |
| `main()` | `0x100001e0` |
| Recurring status loop start | `0x10000234` |
| Loop branch (`b.n` back to loop start) | `0x10000246` |
### Data Symbols
| Symbol | Address | Notes |
|--------|---------|-------|
| `grid_deviation` | `0x200005d8` | Initialized to `87` (0.87 Hz x100), lives in `.data` |
| `operator_state` | `0x20000844` | Zero-initialized, `.bss` |
| `dispatch_state` | `0x20000834` | Zero-initialized, `.bss` |
| `dispatch_frame` | `0x100037a0` | Hidden black-start frame, `.rodata` |
---
## Task 2: Bug #1 Solution — Miscalibrated Safety Threshold
`grid_deviation` is declared `volatile`, so the compiler cannot reuse one
comparison for both output paths — it emits **two independent** compare
instructions, one for `operator_state` and one for `dispatch_state`:
```text
100001f8: 6813 ldr r3, [r2, #0] ; r3 = grid_deviation
100001fc: 2b5e cmp r3, #94 @ 0x5e ; <-- PATCH LOCATION A
100001fe: bf8c ite hi
10000200: 2300 movhi r3, #0 ; operator_state = 0 (CRITICAL)
10000202: 2301 movls r3, #1 ; operator_state = 1 (STABLE)
10000204: 6033 str r3, [r6, #0]
10000206: 6813 ldr r3, [r2, #0] ; r3 = grid_deviation (2nd read)
1000020a: 2b5e cmp r3, #94 @ 0x5e ; <-- PATCH LOCATION B
1000020c: bf8c ite hi
1000020e: 2300 movhi r3, #0 ; dispatch_state = 0 (HELD)
10000210: 2301 movls r3, #1 ; dispatch_state = 1 (AUTHORIZED)
10000212: 602b str r3, [r5, #0]
```
### Why the immediate is 94, not 95
The source-level constant is `SAFE_THRESHOLD = 95`, and the intended test is
`grid_deviation < 95`. For an **unsigned** comparison, GCC legally rewrites
`x < 95` as `x <= 94` (`ite hi`/`movhi`/`movls` on the `hi`/`ls` unsigned
condition codes), which lets it use a single `cmp` + `ite` sequence instead
of a separate branch. The compiled immediate is therefore **one less** than
the source constant.
The correct engineering limit is **60**. Applying the same compiler
transform, `x < 60` becomes `x <= 59`, so the **correct patched immediate is
`0x3B` (59), not `0x3C` (60)**. A student who patches to `0x3C` without
understanding this transform will get a binary that misclassifies a
`grid_deviation` of exactly 60.
### Exact Byte Patch (both locations, identical change)
| Location | File Offset | Address | Original Bytes | Patched Bytes | Instruction Before | Instruction After |
|----------|-------------|---------|-----------------|----------------|---------------------|---------------------|
| A | `0x1FC` | `0x100001fc` | `5E 2B` | `3B 2B` | `cmp r3, #0x5e` | `cmp r3, #0x3b` |
| B | `0x20A` | `0x1000020a` | `5E 2B` | `3B 2B` | `cmp r3, #0x5e` | `cmp r3, #0x3b` |
Both changes independently verified against the raw bytes of `CTF-01.bin`.
### Why both must be patched
`operator_state` (GRID STATUS line) and `dispatch_state` (DISPATCH PATH
line) are each computed from their **own** re-read of `grid_deviation`
against their **own** copy of the compiled threshold. Patching only
location A fixes what is *displayed* to a human operator while leaving the
*automated dispatch decision* (location B) still authorizing a black start
on a dangerous reading — the worst possible partial fix, because it makes
the display look trustworthy while the machine still does the wrong thing.
### Grid math with the frozen reading (87)
| Threshold used | Comparison | Result |
|---|---|---|
| Miscompiled: `<= 94` | `87 <= 94` -> true | `STABLE` / `AUTHORIZED` (false-safe) |
| Corrected: `<= 59` | `87 <= 59` -> false | `CRITICAL` / `HELD` (true, safe) |
---
## Task 3: Bug #2 Solution — The False Signal Banner
The unconditional boot-banner string lives in `.rodata`:
| String | Address |
|--------|---------|
| `"SIGNAL: NORMAL\r"` (printed via `puts`, which appends `\n`) | `0x10003678` |
| `"NORMAL"` substring to patch | `0x10003680` |
Call site: `0x10000224` loads `r0 = 0x10003678`; `0x10000226` calls
`__wrap_puts`. This line prints once, at boot, and is never re-evaluated —
it does not depend on `grid_deviation` at all.
### Exact Byte Patch (6 bytes, same length: `NORMAL` -> `DANGER`)
| Address | Original Char | Original Byte | Patched Char | Patched Byte |
|---------|----------------|----------------|----------------|----------------|
| `0x10003680` | N | `4E` | D | `44` |
| `0x10003681` | O | `4F` | A | `41` |
| `0x10003682` | R | `52` | N | `4E` |
| `0x10003683` | M | `4D` | G | `47` |
| `0x10003684` | A | `41` | E | `45` |
| `0x10003685` | L | `4C` | R | `52` |
Verified directly against the delivered `CTF-01.bin` (ASCII read at
`0x10003678` returns exactly `SIGNAL: NORMAL`).
---
## Task 4: Recovered Dispatch Frame Solution
```text
WORLDGRID:BLACKSTART:GRID-7:WATER-3
```
Address: `0x100037a0` (flash `.rodata`). It is retained in the image through
a non-disclosing runtime anchor (`retain_dispatch_frame()`), so it survives
linker garbage collection but is never passed to a print routine. Full
credit requires a Ghidra Defined-Strings or cross-reference explanation, not
a guess from the narrative. Students must not patch this value.
---
## Task 5: Export and Verify Solution
### Expected UART Transcript — Before Patching
```text
GLOBAL EMBEDDED RESPONSE NETWORK
BLACK START WINDOW: 27 MINUTES
UART0 115200 8N1 | AUTHORIZED LAB CONSOLE
SIGNAL: NORMAL
RESPONSE> GRID STATUS: STABLE
DISPATCH PATH: AUTHORIZED
LAST FRAME: QUARANTINED
RESPONSE>
```
### Expected UART Transcript — After All Three Byte Patches
```text
GLOBAL EMBEDDED RESPONSE NETWORK
BLACK START WINDOW: 27 MINUTES
UART0 115200 8N1 | AUTHORIZED LAB CONSOLE
SIGNAL: DANGER
RESPONSE> GRID STATUS: CRITICAL
DISPATCH PATH: HELD
LAST FRAME: QUARANTINED
RESPONSE>
```
### Complete Patch Summary Table
| # | What | Address(es) | Original | Patched |
|---|------|--------------|----------|---------|
| 1a | GRID STATUS threshold compare | `0x100001fc` | `5E 2B` | `3B 2B` |
| 1b | DISPATCH PATH threshold compare | `0x1000020a` | `5E 2B` | `3B 2B` |
| 2 | Boot banner signal word | `0x10003680`-`0x10003685` | `4E 4F 52 4D 41 4C` (NORMAL) | `44 41 4E 47 45 52` (DANGER) |
Total: **8 bytes changed** to correct a false-safe reading on a fleet
responsible for tens of millions of people.
### UF2 Conversion
```bash
python uf2conv.py CTF-01_fixed.bin --base 0x10000000 --family 0xe48bff59 --output CTF-01_fixed.uf2
```
### Hardware Verification Note
The vector table, boot path, and every byte offset above were verified by
direct inspection of the compiled ELF and the delivered `CTF-01.bin` (two
independent cross-checks: disassembly-derived addresses and raw hex-dump
addresses agree exactly). A live UART capture on physical hardware is the
final confirmation step and should be performed with the Pico in BOOTSEL
mode before grading a submission that claims hardware verification.
---
## Grading Notes
Accept equivalent addresses when a student's Ghidra auto-analysis produces
slightly different intermediate labels, provided the byte-level patch
locations and values match this key. Do not award credit for a `0x3C` patch
to the threshold immediates without a correct explanation of the `<`/`<=`
compiler transform — that is a coincidentally-close but technically
incorrect answer for the boundary case `grid_deviation == 60`.
A complete answer finds both threshold locations, explains the compiler's
comparison transform, patches all three locations, and proves the corrected
behavior on real hardware.
---
## Task 6: Written Reflection Solution Guidance
There is no single "correct" essay for either question. Grade for specific,
grounded reasoning tied to *this* incident, not generic statements.
**Question 1 — Why "rushed under emergency pressure" is not an excuse:**
An acceptable answer names the actual failure mode: an eleven-minute compile
with no review path shipped an integer threshold that was never checked
against the documented 60-unit engineering limit, and a hardcoded status
string that was never wired to the real reading at all. "We were under
pressure" explains *why* the review step was skipped; it does not change
the fact that the skipped step is what caused the false-safe report. Full
credit requires the student to connect the excuse to the specific missing
safeguard (code review or automated bounds-checking), not just assert that
pressure is never an excuse.
**Question 2 — One practice per bug:**
- Bug #1 (miscalibrated, duplicated threshold): a unit test or static
analysis rule that checks every comparison against `SAFE_THRESHOLD`
matches a single source of truth, or a code review that would have asked
"why is this threshold checked in two places instead of one shared
function?"
- Bug #2 (hardcoded status string): a hardware-in-the-loop smoke test that
compares the boot banner's signal word against the actual latched
reading, which would have caught a string that never changes regardless
of input.
Award full credit only when the named practice is specific enough that it
would plausibly have caught that exact bug, not a generic "more testing"
answer.
---
## ⚠ Safety
Use only the supplied Pico 2, 3.3 V UART adapter, and firmware. Never
connect the exercise to an operational grid, water plant, public network,
military system, or third-party device.
Binary file not shown.
+6
View File
@@ -118,6 +118,12 @@ Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive
### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md)
### CTF Instructions [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/0x0001b_ctf/CTF-I.md)
### CTF Rubric [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/0x0001b_ctf/CTF-R.md)
### CTF Solution [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/0x0001b_ctf/CTF-S.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.