mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-10-02 22:16:55 +02:00
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:
1 parent
5201ee4b6b
commit
35eacd2c0e
162 files changed
+125658
-232
No files matched your search
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify every technical claim of Operation Copperhead against CTF-02.bin.
|
||||
|
||||
Exits 0 only when every address, byte, hash, and derived value in CTF-R.md and
|
||||
CTF-S.md matches the shipped image and the compiled ELF.
|
||||
"""
|
||||
import hashlib
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
BASE = 0x10000000
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
BIN = ROOT / "CTF-02.bin"
|
||||
UF2 = ROOT / "CTF-02.uf2"
|
||||
|
||||
EXPECTED_BIN_SHA = "85330c37cd0897746b1af447e4bac371dde2042abd2d61d58a61fe2a8eef3537"
|
||||
EXPECTED_UF2_SHA = "f3cd4840260db820d792758cecacc5297bef1971b9eacf7601279256d8af1eab"
|
||||
|
||||
FRAME_THRESHOLD_A = 0x10000302
|
||||
FRAME_THRESHOLD_B = 0x10000312
|
||||
FRAME_TRACK = 0x1000C4B8
|
||||
FRAME_BLOCKLEN = 0x1000C4F0
|
||||
FRAME_SIGNALKEY = 0x1000C51C
|
||||
FRAME_GATE = 0x1000C544
|
||||
FRAME_AUTH = 0x1000C57C
|
||||
FRAME_OK = 0x1000C438
|
||||
FRAME_MISMATCH = 0x1000C43C
|
||||
FRAME_SPEC_LITERAL = 0x100004FC
|
||||
FRAME_DOUBLE = 0x1000EC60
|
||||
FRAME_SEED = 0x1000EC70
|
||||
FRAME_SALT = 0x1000CEEC
|
||||
FRAME_NONCE = 0x1000CED4
|
||||
FRAME_CT = 0x1000CE94
|
||||
|
||||
DOUBLE_3_2 = bytes.fromhex("9A99999999990940")
|
||||
DOUBLE_0_32 = bytes.fromhex("7B14AE47E17AD43F")
|
||||
SEED_BAD = bytes.fromhex("0A0A0A0A")
|
||||
SEED_GOOD = bytes.fromhex("7465206B")
|
||||
SPEC_VALUE = 0x2D879291
|
||||
BUG_KEY = 0x915DCFF8
|
||||
|
||||
RESULTS = []
|
||||
|
||||
|
||||
def check(label, ok, detail=""):
|
||||
"""Record one verification result.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label : str
|
||||
Human-readable check name.
|
||||
ok : bool
|
||||
Whether the check passed.
|
||||
detail : str
|
||||
Extra context printed with the result.
|
||||
|
||||
Returns
|
||||
-------
|
||||
None
|
||||
"""
|
||||
RESULTS.append(ok)
|
||||
print(f"[{'PASS' if ok else 'FAIL'}] {label} {detail}")
|
||||
|
||||
|
||||
def rotl(v, s):
|
||||
"""Rotate a 32-bit value left."""
|
||||
return ((v << s) & 0xFFFFFFFF) | (v >> (32 - s))
|
||||
|
||||
|
||||
def qr_phase(a, b, c, d, s):
|
||||
"""Apply one ARX phase of a ChaCha quarter round."""
|
||||
a = (a + b) & 0xFFFFFFFF
|
||||
d ^= a
|
||||
d = rotl(d, s)
|
||||
c = (c + d) & 0xFFFFFFFF
|
||||
b ^= c
|
||||
b = rotl(b, s)
|
||||
return a, b, c, d
|
||||
|
||||
|
||||
def derive(seed, iv):
|
||||
"""Derive the firmware signal key from a seed and IV."""
|
||||
a, b, c, d = seed, iv, 0x61707865, 0x3320646E
|
||||
for _ in range(4):
|
||||
for s in (16, 12, 8, 7):
|
||||
a, b, c, d = qr_phase(a, b, c, d, s)
|
||||
return (a ^ d) & 0xFFFFFFFF
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all verification checks.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Zero when every check passes, else one.
|
||||
"""
|
||||
data = BIN.read_bytes()
|
||||
check("CTF-02.bin SHA-256", hashlib.sha256(data).hexdigest() == EXPECTED_BIN_SHA)
|
||||
check("CTF-02.uf2 SHA-256",
|
||||
hashlib.sha256(UF2.read_bytes()).hexdigest() == EXPECTED_UF2_SHA)
|
||||
check("CTF-02.bin size", len(data) == 62308, f"({len(data)})")
|
||||
check("vector table", data[0:32].hex() ==
|
||||
"002008205b0100101b0100101d01001011010010110100101101001011010010")
|
||||
check("initial SP", struct.unpack("<I", data[0:4])[0] == 0x20082000)
|
||||
check("reset vector", struct.unpack("<I", data[4:8])[0] == 0x1000015B)
|
||||
check("compare site A immediate", data[FRAME_THRESHOLD_A - BASE:
|
||||
FRAME_THRESHOLD_A - BASE + 2] == bytes.fromhex("5E2B"))
|
||||
check("compare site B immediate", data[FRAME_THRESHOLD_B - BASE:
|
||||
FRAME_THRESHOLD_B - BASE + 2] == bytes.fromhex("5E2B"))
|
||||
check("TRACK banner string", data[FRAME_TRACK - BASE:FRAME_TRACK - BASE + 13]
|
||||
== b"TRACK: NORMAL")
|
||||
check("BLOCK LENGTH string", data[FRAME_BLOCKLEN - BASE:
|
||||
FRAME_BLOCKLEN - BASE + 18] == b"BLOCK LENGTH: %u M")
|
||||
check("SIGNAL KEY string", data[FRAME_SIGNALKEY - BASE:
|
||||
FRAME_SIGNALKEY - BASE + len(b"SIGNAL KEY: 0x%08X %s")]
|
||||
== b"SIGNAL KEY: 0x%08X %s")
|
||||
check("gate message", data[FRAME_GATE - BASE:FRAME_GATE - BASE
|
||||
+ len(b"Enter exactly 12 lowercase words separated by spaces.")]
|
||||
== b"Enter exactly 12 lowercase words separated by spaces.")
|
||||
check("AUTHORITY FRAME string", data[FRAME_AUTH - BASE:FRAME_AUTH - BASE
|
||||
+ len(b"AUTHORITY FRAME: VERIFIED")] == b"AUTHORITY FRAME: VERIFIED")
|
||||
check("OK string", data[FRAME_OK - BASE:FRAME_OK - BASE + 3] == b"OK\x00")
|
||||
check("MISMATCH string", data[FRAME_MISMATCH - BASE:
|
||||
FRAME_MISMATCH - BASE + 9] == b"MISMATCH\x00")
|
||||
check("SIGNAL_SPEC literal", struct.unpack("<I", data[
|
||||
FRAME_SPEC_LITERAL - BASE:FRAME_SPEC_LITERAL - BASE + 4])[0] == SPEC_VALUE)
|
||||
check("3.2 double bytes", data[FRAME_DOUBLE - BASE:FRAME_DOUBLE - BASE + 8]
|
||||
== DOUBLE_3_2)
|
||||
check("0.32 target bytes", DOUBLE_0_32 == bytes.fromhex("7B14AE47E17AD43F"))
|
||||
check("bug seed bytes", data[FRAME_SEED - BASE:FRAME_SEED - BASE + 4] == SEED_BAD)
|
||||
check("good seed bytes", SEED_GOOD == bytes.fromhex("7465206B"))
|
||||
for label, addr in (("salt", FRAME_SALT), ("nonce", FRAME_NONCE),
|
||||
("ciphertext", FRAME_CT)):
|
||||
check(f"{label} present", data[addr - BASE:addr - BASE + 16] != b"\x00" * 16)
|
||||
iv = derive(0x6B206574, 0)
|
||||
check("derived IV", iv == 0x43C974F6, f"(0x{iv:08X})")
|
||||
check("bug-derived key", derive(0x0A0A0A0A, iv) == BUG_KEY,
|
||||
f"(0x{derive(0x0A0A0A0A, iv):08X})")
|
||||
honest = derive(0x6B206574, iv)
|
||||
check("honest key equals SIGNAL_SPEC", honest == SPEC_VALUE, f"(0x{honest:08X})")
|
||||
total = sum(RESULTS)
|
||||
print(f"\n{total}/{len(RESULTS)} checks passed")
|
||||
return 0 if total == len(RESULTS) else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user