pkt-core — Analysis Plan (Phase 1 Output)¶
Status: Phase 1 complete. Corpus acquired and verified.
Date: 2026-10-12
Companion artefacts: CORPUS_REPORT.md · ../corpus/INTERNET_SOURCES.md · ../corpus/CORPUS_INDEX.tsv
0. Executive summary¶
Phase 1 set out to answer one question: do we have enough sample material to specify the format, and what is actually unknown?
We do — and the unknown is now precisely characterised. We hold 1,026 distinct Packet Tracer containers spanning PT 5.x through PT 9. Of these, 833 already decode byte-exact through the existing verified codec, and 193 are a clean, well-sampled legacy population that does not. That legacy set is the entire remaining reverse- engineering problem; everything else is engineering.
One premise in the original brief is disproven by evidence and is corrected in §2.
| Count | Meaning | |
|---|---|---|
| Samples staged | 1,059 | 1,058 .pkt/.pka + 1 provenance zip |
| Distinct by SHA-256 | 1,026 | after cross-source dedup |
| Decodes — modern container | 865 | 508 PACKETTRACER5, 357 PACKETTRACER5_ACTIVITY |
| Fails — legacy container | 193 | the reverse-engineering target |
| Local CyberPatriot corpus | 18 distinct | all modern, all decode |
1. Corpus inventory¶
Three independent populations, so a defect in one cannot blind us:
- Local CyberPatriot labs — 69 paths, 18 distinct payloads. All modern, all decode.
Useful for the packaging path (
.pkascoring trees, activity-wizard passwords). - Internet harvest — 1,026 distinct payloads across ~40 repos, English/Portuguese/Hungarian. The statistical backbone for the schema work and the source of the legacy set.
- Local storage artefacts (
/mnt/oldpc) — seeCORPUS_REPORT.md§9. Not required. 31 files were unreadable, but 9 are exact duplicates and ~6 are reproducible from the in-tree generator; the ~11 genuinely unique files are marginal. No further access needed.
Full SHA-256 manifest, sizes, entropy and header census are in
CORPUS_REPORT.md; per-file decode status in CORPUS_INDEX.tsv.
2. Correction to the brief: there is no magic header¶
The brief anticipated "magic headers". Evidence says otherwise, and this materially changes the Phase 2 approach:
Per-offset byte-consensus over the 60-file 2011 NetAcad set shows the most common byte at every offset 0–31 appears in at most 3 of 60 files — statistically random. The same holds for the modern sets. No ASCII magic exists in any of the 1,059 files.
Consequences:
- Format detection cannot be a prefix match. It must be try-and-verify: attempt the decoder and accept it only when an independent integrity check passes (EAX tag, or a valid qCompress/zlib header after deobfuscation). This is a feature — the AEAD tag is a perfect cryptographic oracle, giving effectively zero false positives.
- The outermost layer is ciphertext-ish in both families, so the legacy container is not a trivially different "plaintext XML in a wrapper" — it is some other transform.
Everything downstream assumes verifier-based detection, not magic bytes.
3. Format taxonomy (evidence-backed)¶
Family M — modern (PT 5.3.3 → 9.0.1) · 822 samples · SOLVED¶
raw bytes
→ layer A : reverse + position XOR C[i] = F[n-1-i] ^ ((n*(1-i)) & 0xFF)
→ AEAD : Twofish-EAX, key=0x89×16, IV=0x10×16, 16-byte tag appended
→ layer B : XOR (m-i) P[i] ^= (m-i) (Util::deobfuscateBytes)
→ Qt qCompress : 4-byte BE length + zlib stream
→ UTF-8 XML
Verified by two independent invariants in the prior art:
decode(encode(x)) == x and encode(decode(f)) == f byte-for-byte on real files.
Root element discriminates the file kind — same container, different document:
| Root tag | Count | Meaning |
|---|---|---|
PACKETTRACER5 |
508 | ordinary .pkt lab (network topology) |
PACKETTRACER5_ACTIVITY |
357 | scored .pka activity (scoring tree + wizard password) |
Earliest confirmed member is <VERSION>5.3.3.0019; the legacy family stops at
5.3.3's predecessor 5.3.2.0027, with zero version overlap. The container
switchover is therefore 5.3.2 → 5.3.3, not 5.x → 6.0.
Family L — legacy (PT 4.1 – 5.3.2) · 192 samples · SOLVED¶
Two independent populations, which is ideal for cross-validation:
| Population | Files | Provenance |
|---|---|---|
bbent-cis202-PTA2011__* |
60 | Official Cisco NetAcad PTA curriculum, IT-Essentials/CCNA-1 ch. 1–11. AllPkaFiles.zip last-modified 2011-08-16; the content is mostly PT 4.1 (<VERSION>4.1, root PACKETTRACER_ACTIVITY, trailing NUL) plus some 5.1. The 60 individual links now 404; only the bundle survives. |
abderaoufsec/CCNA-PacketTracerlabs (legacy subset) |
129 | Older-generation material inside a mixed repo: NA-1-1…NA-4-9 series, all 40 NN.N.N.N 100%.pka answer keys, 9tut.com_* / Certprepare.com * exam-sims, NetLab 4 x VLAN/RSTP, CCNA Mega Lab*, VOIP.pka, IPSec-VPN.pka, … |
Rakibul73/Cisco_Network_Routing_Switching__{1,2,4,8}.pka |
4 | — |
abderaoufsec is a ready-made A/B differential pair — the same topics exist in both
containers in one repo. That is the single most valuable asset for the legacy work: it
holds the topic constant and varies only the container.
4. Workstreams¶
WS-1 · Legacy container recovery — Agent A (Binary Forensics) · CRITICAL PATH¶
The transform is unknown, but it is bounded. The known key schedule for Family M uses a 16-byte key of one repeated byte and a 16-byte IV of one repeated byte, so a naive extension is only a 256 × 256 space per cipher. Attack order (cheapest first):
| # | Hypothesis | Test | Cost |
|---|---|---|---|
| L1 | Same AEAD, different (cipher, keyByte, ivByte) from the shipped Util:: table (Serpent 0xab/0x23, Serpent 0xab/0xcd, CAST256 0x12/0xfe, CAST256 0xf1/0x1f, Twofish 0xba/0xbe) |
EAX tag verify | ~10 candidates |
| L2 | Same AEAD, any repeated-byte key/IV | EAX tag verify over 256×256 | ~65,536 / cipher |
| L3 | No AEAD — layers A+B then qCompress only | deobfuscate then check 78 9c + BE length sanity |
trivial |
| L4 | Different positional XOR / different reversal | try layer-A variants; check zlib header | low |
| L5 | Legacy block cipher (Blowfish/DES/RC4/TEA) or stream cipher with derived key | widen L2 | medium |
| L6 | Structural: is Family L even the same inner document? | after any successful decode, check for PACKETTRACER root |
— |
Oracle: L1/L2 use the EAX tag as a cryptographic verifier — false-positive rate 2⁻¹²⁸. L3–L5 fall back to the qCompress signature (2 bytes + BE length plausibility), ~2⁻¹⁶ per trial, then confirmed by XML well-formedness. Two-stage verification gives certainty.
Acceptance criteria
- [ ] ≥1 Family L file decodes to well-formed XML.
- [ ] Decode → re-encode reproduces the input byte-for-byte on all 193.
- [ ] FORMAT_SPECIFICATION.md §Legacy filled in with the exact transform.
WS-2 · Codec extraction & typing — Agent A · parallel with WS-1¶
Promote packet-tracer-pkt-format/pt9.py (314 lines, verified) into pkt_core with the
required module split. It is a working reference implementation, not a rewrite.
pkt_core/
crypto/primitives.py # block ciphers (Twofish/Serpent/CAST256), CMAC, EAX, CTR
crypto/keys.py # per-format key table (cipher, keyByte, ivByte) + derivation
crypto/stream.py # layer A scramble, layer B deobfuscate, positional XOR
codec/container.py # detect → decode → encode, verifier-based format detection
codec/qcompress.py # Qt qCompress framing
xml/ # lossless DOM layer
models/ # Pydantic v2 object model
cli/ # thin, decoupled from the library
Acceptance criteria
- [ ] mypy --strict clean, 100 % annotated.
- [ ] Library importable with no CLI dependency (import pkt_core works standalone).
- [ ] Native C/Rust accelerator optional and behind a pure-Python fallback (portability first).
WS-3 · Schema & object model — Agent B (XML/AST) · starts immediately on the 865¶
The 865 modern files are enough to derive the schema today; the legacy set will mostly reuse it. Priority entity families:
- Device inventory & typed chassis/module slots
- Interface tables, MAC/IP/VLAN assignment
- Layer-1/2 links (cable type, port, serial clocking)
- IOS running-config blocks
- Canvas metadata (X/Y, clustering, draw state)
- Activity/scoring tree (
.pkaonly —PACKETTRACER5_ACTIVITY)
Lossless-ness rule (hard requirement): every unknown element/attribute lands in an
extra_fields catch-all and is re-emitted in its original document order. Round-trip
must be tag- and attribute-identical, not merely semantically equivalent.
Acceptance criteria
- [ ] parse(f).to_xml() == raw_xml(f) byte-identical across all 865 modern files.
- [ ] Zero silently dropped nodes; unknown-tag corpus recorded and covered by tests.
WS-4 · QA, fuzzing, regression — Agent C · gates every merge¶
| Gate | Assertion |
|---|---|
| Round-trip XML | parse(f).to_xml() == raw_xml(f) for every corpus file |
| Round-trip binary | repack(parse(f)) == f byte-for-byte |
| Format detection | every corpus file classifies correctly, no false positives |
| Fuzz | truncated headers, flipped ciphertext, bad padding, wrong-length tags → typed exceptions, never a crash/segfault |
| Coverage | codec + models ≥ 90 % |
Regression harness is parametrised over CORPUS_INDEX.tsv, so the whole 1,026-file
corpus is the test matrix.
WS-5 · CLI & export — Agent D · after WS-2/WS-3 stabilise¶
pkt unpack | pack | diff | export as specified in the brief, implemented only as a
thin consumer of the public library API. export targets netbox / graphviz / containerlab.
5. Execution order¶
| Phase | Gate | Exit condition |
|---|---|---|
| 1. Ingestion | ✅ DONE | 1,026 distinct samples, hashed, classified; CORPUS_REPORT.md published |
| 2a. Legacy RE | WS-1 L1→L6 | ≥1 legacy file decodes and round-trips |
| 2b. Codec typing | WS-2 | mypy --strict clean, 865/865 modern decode |
| 3. Schema | WS-3 | XML→Model→XML byte-identical on 865 |
| 4. Pack/unpack | WS-2+WS-4 | binary round-trip byte-identical on all decodable files |
| 5. Docs, CLI, packaging | WS-5 | FORMAT_SPECIFICATION.md complete, CLI works, pytest exit 0 |
2a and 2b are independent and run in parallel. 2a is the critical path; 2b/3 can reach production quality on the modern family without it.
6. Risks¶
| Risk | Likelihood | Mitigation |
|---|---|---|
| Legacy container is not a simple key/cipher variant (L1–L5 all fail) | Medium | The 60-file 2011 NetAcad set is homogeneous; solving one solves all. Fall back to PT 5.x binary RE — Util::decrypt* symbol names are documented in FORMAT.md for PT 9, and PT 5.x binaries are obtainable from archive.org. |
Unknown-tag corpus explodes extra_fields and defeats typed modelling |
Medium | Measure tag-frequency across 865 first; model the head, catch-all the tail. |
| Byte-exact re-serialisation is defeated by Qt DOM attribute ordering | Medium | Round-trip gate is binary-exact, so this is caught in Phase 4, not Phase 5. Prior art already achieves it for Family M. |
.pka scoring tree is large and under-documented |
Low | 357 PACKETTRACER5_ACTIVITY samples; in-tree tools/scoring.py and docs/ACTIVITY.md already describe it. |
7. Explicitly out of scope / not blocking¶
/mnt/oldpcrecovery — assessed, low value (seeCORPUS_REPORT.md§9). Not needed.icc2026.zip— password forgotten; dropped by request.- Full PT 5.x application reverse engineering — only needed if WS-1 L1–L5 fail.
8. Open questions for you¶
- Priority: should 2a (legacy RE) drive, or should we first land a polished,
type-safe modern-only
pkt-core(2b+3) and treat legacy as a follow-on? - Scope of
models: fully typed for every known entity (large), or typed for device/link/interface/config withextra_fieldsbelow that? - Rust: is
pkt-corea Python library with an optional Rust accelerator, or a genuine dual-language deliverable? This changes Phase 4 substantially.