OPERATION IRON VAULT - Requirements & Grading Criteria

September 24, 2026 ยท View on GitHub

+--------------------------------------------------------------------------------+
|                                                                                |
|                 OPERATION IRON VAULT                                           |
|                                                                                |
|                 REQUIREMENTS & GRADING CRITERIA                                |
|                                                                                |
|   TARGET: NorthPharma datacenter vent controller (server hall cooling node)    |
|   ARTIFACT: ACT-VIII.bin / ACT-VIII.uf2 (compromised)                          |
|   CREW: FROSTLINE            OPERATIVE: NIGHTINGALE                           |
|                                                                                |
+--------------------------------------------------------------------------------+

Project Overview

NorthPharma runs the buildings that keep the state's data alive, and its datacenter vent controller built on a Pico 2 is the node on the edge of the cooling plant. A contractor called FROSTLINE planted a locker in the node image: an inverted vent lock that forces the damper closed, an inverted display mask that renders ST:MAINT, a reserved-sector lock marker that re-arms the lock on every boot, and an inverted vent command authorization verdict. Operative NIGHTINGALE recovered the compromised image as ACT-VIII.bin.

Students are the reverse-engineering reserve. They reverse engineer ACT-VIII.bin with Ghidra, find and patch all four defects, defeat the CoreDebug DHCSR anti-debug under GDB to observe the marker write, export a corrected image, flash it to a real Pico 2, and prove the corrected behavior on the breadboard. The machine check is scripts/verify_ctf.py.

The challenge is a standalone capstone exercise and contains no answer, constant, address, bug, or patch belonging to any other course assignment.


Learning Objectives

  • Decode an ARM Cortex-M33 vector and boot table and identify the reset handler and initial stack pointer.
  • Map a stripped firmware image into modules by tracing calls from main and the monitor loop.
  • Locate four corrupted bytes: a forced-close lock gate, a display mask gate, a reserved-sector marker gate, and an authorization verdict branch.
  • Analyze cbz and cbnz condition semantics and branch inversion.
  • Explain why a local lock that overrides the output makes an authenticated command path irrelevant, and why an availability attack does not need the cipher.
  • Explain why a device that misreports its own state hides a lockout behind the appearance of routine maintenance.
  • Explain why reserved-flash state survives a firmware reflash.
  • Read CoreDebug DHCSR, explain the anti-debug trap, and defeat it under GDB.
  • Explain why authentication is not authorization and why a verdict must be verified before the command is applied.

Students must use only the course concepts: ARM registers, stack behavior, USB-CDC and UART consoles, GDB, Ghidra static analysis and binary patching, vector tables, reset startup, XIP, Thumb addressing, condition-code analysis, stateful security, and the Argon2id plus XChaCha20-Poly1305 authenticated envelope.


Deliverables Checklist

#DeliverableFormatCriterion
1Ghidra project screenshotPNG/JPGTask 1
2Vector table and boot tableInside ACT-VIII-Answers.mdTask 1
3main and monitor-loop tableInside ACT-VIII-Answers.mdTask 1
4Module mapInside ACT-VIII-Answers.mdTask 1
5Vent lock evidence and patchInside ACT-VIII-Answers.mdTask 2
6LCD mask evidence and patchInside ACT-VIII-Answers.mdTask 3
7Anti-debug GDB proof, reserved-sector evidence, and patchInside ACT-VIII-Answers.mdTask 4
8Vent command authorization evidence and patchInside ACT-VIII-Answers.mdTask 5
9ACT-VIII_fixed.binBIN fileTask 6
10ACT-VIII_fixed.uf2UF2 fileTask 6
11Hardware proof and reflectionInside ACT-VIII-Answers.mdTask 6

Required Tools and Equipment

ToolPurpose
Raspberry Pi Pico 2Isolated target node
Debug Probe (OpenOCD)SWD connection for GDB inspection and the anti-debug work
arm-none-eabi-gdbRuntime breakpoints, DHCSR clearing, and reserved-sector observation
GhidraStatic analysis and binary patching
Python 3 with uf2conv.pyUF2 conversion and artifact checks
DHT11, 1602 I2C LCD, RYLR998, IR receiver, SG90 servo, 3 LEDs, manual purge buttonBreadboard hardware proof
ACT-VIII.bin and ACT-VIII.uf2Supplied compromised artifacts

Console settings: USB-CDC virtual COM port, 115200 baud, 8 data bits, no parity, 1 stop bit. Radio UART settings: UART1, 115200, network ID 18.


Artifact Identity

The instructor-issued artifact hashes are:

ACT-VIII.bin        3eb95ef74dcc11bc5a9b77b48645390f17d969c1832a3a7f765ebad7ebd0e120
ACT-VIII.uf2        b653b2b3c1542caf82a7c03d26dd290dc8859882780c2dfdf799d2d69872238d
ACT-VIII_fixed.bin  59411f4bf07809ada695315d75f43c47b7a6efa7c055c8c46841cc6ae0caa234
ACT-VIII_fixed.uf2  4443ce43225991d358e20ab409310209427da83977f6bdf59135a13701ba4a2b

The verifier checks the ACT-VIII.bin and ACT-VIII_fixed.bin hashes specifically, asserts the four fixed bytes, and requires that only those four offsets differ between the two .bin images. Both .bin images are 50,860 bytes and both .uf2 images are 102,400 bytes.


Grading Rubric - Detailed Breakdown

Task 1: Setup and Initial Analysis (10 points)

CriterionPointsFull creditPartial creditNo credit
[DOCUMENT] Ghidra project created with the correct name and settings2Project IronVault_Investigation, raw binary importOne item offNot set up
[DOCUMENT] Processor configured as ARM Cortex 32 little endian default2Screenshot shows the correct processorWrong languageMissing
[DOCUMENT] Base address set to 0x100000002Base 0x10000000Wrong baseMissing
[DOCUMENT] Vector table, initial stack pointer, and reset handler identified2Base 0x10000000, initial SP 0x20082000, reset handler 0x1000015DOne missingNot found
[DOCUMENT] main and the vent monitor state machine (monitor_step) addresses identified1main 0x10000234, monitor_step 0x10006648One correctNeither
[DOCUMENT] Module map identifies the damper, control, vault_auth, implant, and monitor anchors1At least one correct anchor per modulePartialMissing

Task 2: Bug #1 The Vent Lock (20 points)

CriterionPointsFull creditPartial creditNo credit
[DOCUMENT] Located the vent lock gate at 0x1000A33B5Address and function (implant_init, inlined lock gate) identifiedApproximateNot found
[DOCUMENT] Documented the forced close that holds the vent shut and lights LOCKED5Lock gate 0x20013CF2, forced closed damper, yellow LOCKED lampPartialWrong
[DOCUMENT & PATCH] Patched 0xBB to 0xB3 so the vent lock is not armed7Byte 0xBB changed to 0xB3Wrong byteNot patched
[DOCUMENT] Explained why the lock forces the vent closed regardless of the authorized state3Local override beside the authenticated command path, availability as a policy controlVagueMissing

Task 3: Bug #2 The LCD Mask (20 points)

CriterionPointsFull creditPartial creditNo credit
[DOCUMENT] Located the mask gate at 0x1000A3115Address and function (implant_mask_active) identifiedApproximateNot found
[DOCUMENT] Documented the ST:MAINT maintenance mask that hides the true vent state5Mask gate 0x20013CF5, monitor_state_text returns MAINT while lockedPartialWrong
[DOCUMENT & PATCH] Patched 0xB9 to 0xB1 so the true state is never masked7Byte 0xB9 changed to 0xB1Wrong byteNot patched
[DOCUMENT] Explained why a masked state hides an availability failure3The readout is part of the attack surface and the truth is a controlVagueMissing

Task 4: Bug #3 The Lock Marker (20 points)

CriterionPointsFull creditPartial creditNo credit
[DOCUMENT] Located the lock marker gate at 0x1000A3535Address and inlined implant_init path identifiedApproximateNot found
[DOCUMENT] Documented the CoreDebug DHCSR anti-debug and how it is defeated under GDB50xE000EDF0, C_DEBUGEN and C_HALT, and a real defeat methodPartialWrong
[DOCUMENT & PATCH] Patched 0xB9 to 0xB1 so no lock marker is programmed to 0x103FF0007Byte 0xB9 changed to 0xB1Wrong byteNot patched
[DOCUMENT] Explained the reserved sector 0x103FF000 and the lock marker byte 0x4C3Marker, reserved sector, write-once first runVagueMissing

Task 5: Bug #4 The Vent Command Authorization (20 points)

CriterionPointsFull creditPartial creditNo credit
[DOCUMENT] Located the vent command authorization branch at 0x100075955Address and function (control_handle_frame) identifiedApproximateNot found
[DOCUMENT] Documented the authorization verdict inversion and the branch condition5Reject when the verdict is falsePartialWrong
[DOCUMENT & PATCH] Patched 0xB9 to 0xB1 so failed and replayed authorizations are rejected7Byte 0xB9 changed to 0xB1Wrong byteNot patched
[DOCUMENT] Explained why an unauthenticated or replayed vent command must be rejected3The applied command must see only an authorized verdictVagueMissing

Task 6: Export and Verify (10 points)

CriterionPointsFull creditPartial creditNo credit
[PATCH] Exported ACT-VIII_fixed.bin from Ghidra2Valid patched binaryCorruptNot submitted
[PATCH] Converted to ACT-VIII_fixed.uf2 with the correct base and family2--base 0x10000000 --family 0xe48bff59Wrong flagsNot submitted
[DOCUMENT] scripts/verify_ctf.py passes and hardware proves the correct behavior3Verifier passes and the hardware proof is shownPartial proofNo proof
[DOCUMENT] Reflection maps each of the four defects to a real-world control-system failure3Specific mapping for all fourPartialMissing

Common Pitfalls

PitfallConsequenceAvoidance
Reading the vent lock gate backwardsThe vent is still forced closed and the LOCKED lamp is litNeutralize only on the clear-gate branch (cbz, 0xB3)
Reading the mask gate backwardsThe readout still shows ST:MAINTNeutralize only when the gate is clear (cbz, 0xB1)
Confusing cbz and cbnz at 0xA311 or 0xA353The mask still lies, or the marker is still writtenNeutralize only when the gate is clear (cbz, 0xB1)
Patching the low byte at 0xA33A, 0xA310, 0xA352, or 0x7594The condition code never changesPatch the high byte at 0xA33B, 0xA311, 0xA353, 0x7595
Searching for a standalone implant_infect symbolCannot find the inlined gateLook inside implant_init at 0xA353
Confusing the lock gate with the marker gateBoth sit in implant_init at 0xA33B and 0xA353Patch the lock gate first, then the marker gate
Patching the shipped image before observing the writeYou never prove the lock marker writeDefeat DHCSR under GDB first, then patch the artifact
Fabricating the GDB sessionVerification failsShow the command sequence and the real observed code path
Treating the anti-debug as a defect to patchWasted effort; it is identical in both imagesDefeat it in a scratch copy or with GDB, then patch the real defect
Missing that the authorization branch is a verdictUnauthenticated commands still reach the applied command and zoneAccept only when the verdict is true (cbz to reject, 0xB1)
Forgetting that the fix is also a policyThe device still fails closed on a lost linkMake the vent fail open when the link is lost
Forgetting UF2 conversionRaw binary will not flashUse uf2conv.py with family 0xe48bff59

How To Breadboard

DevicePin on devicePico 2 GPIONotes
DHT11 rack temperature sensorDATAGP410 kOhm pull-up to 3.3 V if the module needs it
1602 LCDSDAGP2I2C1, backpack address 0x27
1602 LCDSCLGP3I2C1, 100 kHz
1602 LCDVCC / GNDVBUS 5 V / GNDThe backpack needs 5 V, not 3.3 V
RYLR998RXGP8 (Pico TX)UART1, 115200, network ID 18
RYLR998TXGP9 (Pico RX)UART1
IR receiverOUTGP5VS1838B, internal pull-up enabled
Vent damper servosignalGP14PWM 50 Hz; 1000 uF bulk cap across servo 5 V and GND
Red LEDanodeGP16HALL HOT, 220 to 330 ohm to GND
Yellow LEDanodeGP17LOCKED, 220 to 330 ohm to GND
Green LEDanodeGP18COOLING OK, 220 to 330 ohm to GND
Manual purge buttonleg 1GP15Internal pull-up; leg 2 to GND, never to 3.3 V
Onboard LEDbuilt inGP25Heartbeat
Debug ProbeSWCLK / SWDIO / GNDdebug headerFor GDB only

Use 3.3 V logic on every GPIO. The only 5 V connection is the LCD backpack supply and the servo rail. Keep the 1000 uF capacitor on the servo rail to absorb the SG90 current spike.


Memory Map Reference

RegionAddressPurpose
Bootrom0x00000000Immutable boot code
Flash/XIP0x10000000Vector table, code, rodata, data image
SRAM0x20000000Stack and writable state
CoreDebug DHCSR0xE000EDF0Anti-debug register read by the locker
Locker reserved sector0x103FF000Lock marker target (last flash sector)
Locker tick counter0x200136F4Incremented once per implant_tick
Locker lock count0x200136F0Number of lock operations this boot
Locker active flag0x20013CF1Set when the locker arms
Locker locked flag0x20013CF3True while the vent is held closed
Locker lock gate0x20013CF2Gates the ransom vent lock
Locker mask gate0x20013CF5Gates the ST:MAINT display mask
Locker marker gate0x20013CF4Gates the reserved-sector lock marker write
Control ready gate0x20013CEDGates the sealed vent command path
Applied command0x20013CECCommand after a true verdict
Applied zone0x20013CE2Zone after a true verdict
Authorization ready gate0x20013CFFGates the authorization check
Auth state record0x200136ACAnti-replay and state-tag record
Control field key0x20013848Derived field key for the envelope
Envelope workspace0x200136C8Sealed frame open workspace

The VA of any file offset is the file offset plus 0x10000000.


Deadline & Submission

  • Create a folder containing the Ghidra screenshot, ACT-VIII_fixed.bin, and ACT-VIII_fixed.uf2.
  • Write all written answers in ACT-VIII-Answers.md inside that folder.
  • Include the output of python scripts/verify_ctf.py.
  • ZIP the folder as lastname-firstname-ACT-VIII.zip.
  • Submit the ZIP before the posted deadline; late submissions lose 10 percent per day.

Grade Scale

GradePercentagePoints
A+97-100%97-100
A93-96%93-96
A-90-92%90-92
B+87-89%87-89
B84-86%84-86
B-80-83%80-83
C70-79%70-79
F0-69%0-69%

Academic Integrity

Use only the supplied Pico 2 and firmware. Do not connect the exercise to an operational datacenter network, a building-management system, a cooling plant control system, a public network, a military system, or a third-party device. This is a controlled, isolated educational exercise. All analysis and patches must be your own work; sharing binaries, addresses, keys, passphrases, or answers is a violation of the academic integrity policy.


Reference Material

TopicReference
ARM Cortex-M33 registers and stackCourse block 1
USB-CDC and UART console captureCourse block 2
Vector tables, reset startup, and XIPCourse block 3
Ghidra static analysis and binary patchingCourse block 4
Availability attacks, lockout logic, and ransom logicCourse block 5
Display integrity and misleading annunciationCourse block 6
Reserved-flash persistence and boot re-installCourse block 7
CoreDebug DHCSR and anti-debugCourse block 8
Argon2id and XChaCha20-Poly1305 authenticated envelopeCourse block 9