docs(06-obfuscation-hardening): create phase plan
This commit is contained in:
186
.planning/phases/06-obfuscation-hardening/06-01-PLAN.md
Normal file
186
.planning/phases/06-obfuscation-hardening/06-01-PLAN.md
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
phase: 06-obfuscation-hardening
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/format.rs
|
||||
- src/archive.rs
|
||||
- src/crypto.rs
|
||||
- tests/golden_vectors.rs
|
||||
autonomous: true
|
||||
requirements:
|
||||
- FMT-06
|
||||
- FMT-07
|
||||
- FMT-08
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Rust archiver pack() produces archives with XOR-obfuscated headers (magic bytes not visible in raw hex)"
|
||||
- "Rust archiver pack() encrypts the TOC with AES-256-CBC using a random toc_iv stored in header"
|
||||
- "Rust archiver pack() inserts random decoy padding between data blocks"
|
||||
- "Rust unpack() and inspect() correctly decode obfuscated archives (XOR de-obfuscation + TOC decryption)"
|
||||
- "All existing cargo test pass (unit tests + integration tests + golden vectors)"
|
||||
- "Flags byte is 0x0F when compression + all 3 obfuscation features are active"
|
||||
artifacts:
|
||||
- path: "src/format.rs"
|
||||
provides: "XOR_KEY constant, xor_header_buf() function, read_header_auto() with XOR bootstrapping"
|
||||
contains: "XOR_KEY"
|
||||
- path: "src/archive.rs"
|
||||
provides: "Updated pack() with TOC encryption + decoy padding + XOR header; updated unpack()/inspect() with de-obfuscation"
|
||||
contains: "xor_header_buf"
|
||||
- path: "src/crypto.rs"
|
||||
provides: "generate_iv (unchanged) used for toc_iv"
|
||||
key_links:
|
||||
- from: "src/archive.rs pack()"
|
||||
to: "src/format.rs xor_header_buf()"
|
||||
via: "XOR applied to 40-byte header buffer after write_header"
|
||||
pattern: "xor_header_buf"
|
||||
- from: "src/archive.rs pack()"
|
||||
to: "src/crypto.rs encrypt_data()"
|
||||
via: "TOC plaintext buffer encrypted with toc_iv"
|
||||
pattern: "encrypt_data.*toc"
|
||||
- from: "src/archive.rs unpack()/inspect()"
|
||||
to: "src/format.rs"
|
||||
via: "XOR bootstrapping on header read, then TOC decryption"
|
||||
pattern: "xor_header_buf|decrypt_data"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Implement all three obfuscation features (XOR headers, encrypted TOC, decoy padding) in the Rust archiver and unpacker, with all existing tests passing.
|
||||
|
||||
Purpose: Make the archive format resist casual analysis by hiding the header structure, encrypting all metadata, and inserting random noise between data blocks. This is the encoder-side implementation that the Kotlin and Shell decoders will build against.
|
||||
|
||||
Output: Updated src/format.rs, src/archive.rs with full obfuscation pipeline. All `cargo test` pass including existing unit, golden vector, and round-trip integration tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/home/nick/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/home/nick/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/06-obfuscation-hardening/06-RESEARCH.md
|
||||
@docs/FORMAT.md (Sections 9.1-9.3 and Section 10 for decode order)
|
||||
@src/format.rs
|
||||
@src/crypto.rs
|
||||
@src/archive.rs
|
||||
@src/key.rs
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add XOR header obfuscation and TOC encryption to format.rs</name>
|
||||
<files>src/format.rs</files>
|
||||
<action>
|
||||
Add the following to format.rs:
|
||||
|
||||
1. **XOR_KEY constant** (FORMAT.md Section 9.1):
|
||||
```rust
|
||||
pub const XOR_KEY: [u8; 8] = [0xA5, 0x3C, 0x96, 0x0F, 0xE1, 0x7B, 0x4D, 0xC8];
|
||||
```
|
||||
|
||||
2. **xor_header_buf()** function that XORs a mutable byte slice (first 40 bytes) with the cyclic 8-byte key. XOR is its own inverse, so the same function encodes and decodes.
|
||||
|
||||
3. **read_header_auto()** function (replaces or wraps read_header for external use):
|
||||
- Read 40 raw bytes.
|
||||
- Check bytes 0-3 against MAGIC.
|
||||
- If match: parse header normally from the buffer.
|
||||
- If NO match: apply xor_header_buf to all 40 bytes, re-check magic. If still wrong, return error.
|
||||
- Parse header fields from the (possibly de-XORed) buffer.
|
||||
- This function should accept `&mut impl (Read + Seek)` or work from a `[u8; 40]` buffer passed in. The simplest approach: accept a `[u8; 40]` buffer and return a Header (factoring out the parsing from read_header into a parse_header_from_buf helper).
|
||||
|
||||
4. **write_header_to_buf()** helper that serializes header to a `[u8; 40]` buffer (instead of directly to writer), so the caller can XOR it before writing.
|
||||
|
||||
5. **write_toc_entry_to_vec() / serialize_toc()** helper that serializes all TOC entries to a `Vec<u8>` buffer, so the caller can encrypt the buffer. This can reuse write_toc_entry with a Vec writer.
|
||||
|
||||
6. **read_toc_from_buf()** helper that parses TOC entries from a byte slice (using a Cursor), so the caller can pass in the decrypted TOC buffer.
|
||||
|
||||
Keep the existing read_header() and write_header() functions for backward compatibility with existing tests, but the new pack/unpack code will use the _buf variants.
|
||||
|
||||
Add unit tests:
|
||||
- XOR round-trip: write header to buf, XOR, XOR again, verify identical to original.
|
||||
- XOR changes magic: write header to buf, XOR, verify bytes 0-3 are NOT 0x00 0xEA 0x72 0x63.
|
||||
- read_header_auto works with both plain and XOR'd headers.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo test --lib format -- --nocapture 2>&1 | tail -5</automated>
|
||||
<manual>Verify XOR_KEY constant matches FORMAT.md Section 9.1 exactly</manual>
|
||||
</verify>
|
||||
<done>format.rs has XOR_KEY, xor_header_buf(), read_header_auto() with bootstrapping, and helper functions for buffer-based header/TOC serialization/parsing. All format unit tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Update pack/unpack/inspect with full obfuscation pipeline</name>
|
||||
<files>src/archive.rs</files>
|
||||
<action>
|
||||
Update archive.rs to implement all three obfuscation features. Follow the encoder order from 06-RESEARCH.md:
|
||||
|
||||
**pack() changes:**
|
||||
|
||||
1. **Generate decoy padding** for each file: `let padding_after: u16 = rng.random_range(64..=4096);` using `rand::Rng`. Generate the random bytes too: `let mut padding_bytes = vec![0u8; padding_after as usize]; rand::Fill::fill(&mut padding_bytes[..], &mut rng);`. Store padding_after and padding_bytes in ProcessedFile struct (add fields).
|
||||
|
||||
2. **Compute data offsets accounting for padding**: After computing toc_offset + toc_size (which will now be the ENCRYPTED toc size), compute data offsets as `current_offset += pf.encrypted_size + pf.padding_after as u32` for each file.
|
||||
|
||||
3. **Serialize TOC entries to a buffer**: Use the new serialize_toc helper. Include padding_after values in entries.
|
||||
|
||||
4. **Encrypt serialized TOC**: Generate `toc_iv = crypto::generate_iv()`. Call `crypto::encrypt_data(&toc_plaintext, &KEY, &toc_iv)`. The `toc_size` in the header becomes `encrypted_toc.len() as u32`.
|
||||
|
||||
5. **Build header**: Set flags bits 1-3 in addition to bit 0 (compression). When all obfuscation is active and files are compressed, flags = 0x0F. Set toc_iv in header.
|
||||
|
||||
6. **Compute toc_offset and data offsets**: `toc_offset = HEADER_SIZE`. Data block start = `toc_offset + encrypted_toc_size`. Then compute per-file data_offset accounting for preceding files' `encrypted_size + padding_after`.
|
||||
|
||||
7. **Serialize header to buffer and XOR**: Use write_header_to_buf, then xor_header_buf on the resulting 40-byte buffer.
|
||||
|
||||
8. **Write archive**: XOR'd header bytes || encrypted TOC bytes || (for each file: ciphertext || padding_bytes).
|
||||
|
||||
**unpack() changes:**
|
||||
|
||||
1. Read 40 bytes raw. Use read_header_auto (XOR bootstrapping).
|
||||
2. Check flags bit 1 (0x02) for TOC encryption. If set: seek to toc_offset, read toc_size bytes, decrypt with `crypto::decrypt_data(&encrypted_toc, &KEY, &header.toc_iv)`. Parse TOC from decrypted buffer using read_toc_from_buf.
|
||||
3. If TOC not encrypted (backward compat): read TOC directly as before.
|
||||
4. Rest of unpack is unchanged -- each file uses data_offset from TOC entries, which already accounts for padding.
|
||||
|
||||
**inspect() changes:**
|
||||
|
||||
Apply the same header and TOC de-obfuscation as unpack. Factor out a shared `read_archive_metadata()` helper that returns (Header, Vec<TocEntry>) with all de-obfuscation applied. Both unpack() and inspect() call this helper.
|
||||
|
||||
**Important notes:**
|
||||
- Use `use rand::Rng;` for `random_range()`.
|
||||
- Padding range 64..=4096 bytes per file.
|
||||
- The `--no-compress` flag behavior is unchanged.
|
||||
- Do NOT add a `--no-obfuscate` flag yet (always obfuscate).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/nick/Projects/Rust/encrypted_archive && cargo test 2>&1 | tail -10</automated>
|
||||
<manual>Run `cargo run -- pack test_file.txt -o /tmp/test.bin && xxd /tmp/test.bin | head -3` and verify first 4 bytes are NOT 00 ea 72 63</manual>
|
||||
</verify>
|
||||
<done>pack() produces fully obfuscated archives (XOR header + encrypted TOC + decoy padding). unpack() and inspect() correctly de-obfuscate. All `cargo test` pass including existing integration tests and round-trip tests (which now exercise the full obfuscation pipeline end-to-end).</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `cargo test` -- all existing unit, golden, and integration tests pass
|
||||
2. `cargo run -- pack <files> -o /tmp/obf.bin` produces an archive where `xxd /tmp/obf.bin | head -3` shows no recognizable magic bytes
|
||||
3. `cargo run -- inspect /tmp/obf.bin` correctly displays metadata after de-obfuscation
|
||||
4. `cargo run -- unpack /tmp/obf.bin -o /tmp/obf_out/` extracts files byte-identically to originals
|
||||
5. `binwalk /tmp/obf.bin` and `file /tmp/obf.bin` show no recognized signatures
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All three obfuscation features (FMT-06, FMT-07, FMT-08) are implemented in Rust archiver
|
||||
- Flags byte is 0x0F for archives with compression + all obfuscation
|
||||
- XOR bootstrapping allows decoders to detect both plain and obfuscated archives
|
||||
- All `cargo test` pass (0 failures)
|
||||
- Archives are unrecognizable by file/binwalk/strings
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/06-obfuscation-hardening/06-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user