feat(06-02): add XOR header bootstrapping and encrypted TOC support to Shell decoder

- Add XOR_KEY_HEX constant and hex_to_bin() helper (xxd + od fallback)
- Replace magic check with XOR bootstrapping: read 40 bytes, XOR if mismatch
- Write de-XORed header to temp file for field parsing
- Add TOC decryption via openssl enc when flags bit 1 is set
- Switch TOC parsing loop from $ARCHIVE to $TOC_FILE variable
- Update HMAC verification to construct IV from parsed hex (not archive position)
- All 7 cross-validation tests pass (Rust pack -> Shell decode -> SHA-256 match)
This commit is contained in:
NikitolProject
2026-02-25 02:26:05 +03:00
parent cef681fd13
commit ac51cc70aa

View File

@@ -102,17 +102,69 @@ if ! printf 'test' | openssl dgst -sha256 -mac HMAC -macopt hexkey:00 >/dev/null
fi
# -------------------------------------------------------
# Header parsing (FORMAT.md Section 4)
# XOR obfuscation key (FORMAT.md Section 9.1)
# -------------------------------------------------------
# Header is 40 bytes at offset 0x00
magic_hex=$(read_hex "$ARCHIVE" 0 4)
XOR_KEY_HEX="a53c960fe17b4dc8"
# -------------------------------------------------------
# hex_to_bin <hex_string> <output_file>
# Write binary data from a hex string to a file.
# -------------------------------------------------------
hex_to_bin() {
if [ "$HAS_XXD" = "1" ]; then
printf '%s' "$1" | xxd -r -p > "$2"
else
_htb_hex="$1"
_htb_i=0
_htb_len=${#_htb_hex}
: > "$2"
while [ "$_htb_i" -lt "$_htb_len" ]; do
_htb_byte=$(printf '%s' "$_htb_hex" | cut -c$((_htb_i + 1))-$((_htb_i + 2)))
printf "\\$(printf '%03o' "0x$_htb_byte")" >> "$2"
_htb_i=$((_htb_i + 2))
done
fi
}
# -------------------------------------------------------
# Header parsing with XOR bootstrapping (FORMAT.md Section 9.1, Section 10)
# -------------------------------------------------------
# Read 40-byte header as hex string (80 hex chars)
raw_header_hex=$(read_hex "$ARCHIVE" 0 40)
magic_hex=$(printf '%.8s' "$raw_header_hex")
if [ "$magic_hex" != "00ea7263" ]; then
printf 'Invalid archive: bad magic bytes (got %s)\n' "$magic_hex" >&2
exit 1
# Attempt XOR de-obfuscation
header_hex=""
byte_idx=0
while [ "$byte_idx" -lt 40 ]; do
hex_pos=$((byte_idx * 2))
# Extract this byte from raw header (2 hex chars)
raw_byte=$(printf '%s' "$raw_header_hex" | cut -c$((hex_pos + 1))-$((hex_pos + 2)))
# Extract key byte (cyclic)
key_pos=$(( (byte_idx % 8) * 2 ))
key_byte=$(printf '%s' "$XOR_KEY_HEX" | cut -c$((key_pos + 1))-$((key_pos + 2)))
# XOR
xored=$(printf '%02x' "$(( 0x$raw_byte ^ 0x$key_byte ))")
header_hex="${header_hex}${xored}"
byte_idx=$((byte_idx + 1))
done
# Verify magic after XOR
magic_hex=$(printf '%.8s' "$header_hex")
if [ "$magic_hex" != "00ea7263" ]; then
printf 'Invalid archive: bad magic bytes (got %s)\n' "$magic_hex" >&2
exit 1
fi
else
header_hex="$raw_header_hex"
fi
version_hex=$(read_hex "$ARCHIVE" 4 1)
# Write de-XORed header to temp file for field parsing
hex_to_bin "$header_hex" "$TMPDIR/header.bin"
# Parse header fields from de-XORed temp file
version_hex=$(read_hex "$TMPDIR/header.bin" 4 1)
version=$(printf '%d' "0x${version_hex}")
if [ "$version" -ne 1 ]; then
@@ -120,66 +172,87 @@ if [ "$version" -ne 1 ]; then
exit 1
fi
flags_hex=$(read_hex "$ARCHIVE" 5 1)
flags_hex=$(read_hex "$TMPDIR/header.bin" 5 1)
flags=$(printf '%d' "0x${flags_hex}")
file_count=$(read_le_u16 "$ARCHIVE" 6)
toc_offset=$(read_le_u32 "$ARCHIVE" 8)
toc_size=$(read_le_u32 "$ARCHIVE" 12)
file_count=$(read_le_u16 "$TMPDIR/header.bin" 6)
toc_offset=$(read_le_u32 "$TMPDIR/header.bin" 8)
toc_size=$(read_le_u32 "$TMPDIR/header.bin" 12)
toc_iv_hex=$(read_hex "$TMPDIR/header.bin" 16 16)
printf 'Archive: %d files\n' "$file_count"
# -------------------------------------------------------
# TOC decryption (FORMAT.md Section 9.2)
# -------------------------------------------------------
toc_encrypted=$(( flags & 2 ))
if [ "$toc_encrypted" -ne 0 ]; then
# Extract encrypted TOC to temp file
dd if="$ARCHIVE" bs=1 skip="$toc_offset" count="$toc_size" of="$TMPDIR/toc_enc.bin" 2>/dev/null
# Decrypt TOC
openssl enc -d -aes-256-cbc -nosalt \
-K "$KEY_HEX" -iv "$toc_iv_hex" \
-in "$TMPDIR/toc_enc.bin" -out "$TMPDIR/toc_dec.bin"
TOC_FILE="$TMPDIR/toc_dec.bin"
TOC_BASE_OFFSET=0
else
TOC_FILE="$ARCHIVE"
TOC_BASE_OFFSET=$toc_offset
fi
# -------------------------------------------------------
# TOC parsing loop (FORMAT.md Section 5)
# -------------------------------------------------------
pos=$toc_offset
pos=$TOC_BASE_OFFSET
extracted=0
i=0
while [ "$i" -lt "$file_count" ]; do
# -- name_length (u16 LE) --
name_length=$(read_le_u16 "$ARCHIVE" "$pos")
name_length=$(read_le_u16 "$TOC_FILE" "$pos")
pos=$((pos + 2))
# -- filename (raw UTF-8 bytes) --
filename=$(dd if="$ARCHIVE" bs=1 skip="$pos" count="$name_length" 2>/dev/null)
filename=$(dd if="$TOC_FILE" bs=1 skip="$pos" count="$name_length" 2>/dev/null)
pos=$((pos + name_length))
# -- original_size (u32 LE) --
original_size=$(read_le_u32 "$ARCHIVE" "$pos")
original_size=$(read_le_u32 "$TOC_FILE" "$pos")
pos=$((pos + 4))
# -- compressed_size (u32 LE) --
compressed_size=$(read_le_u32 "$ARCHIVE" "$pos")
compressed_size=$(read_le_u32 "$TOC_FILE" "$pos")
pos=$((pos + 4))
# -- encrypted_size (u32 LE) --
encrypted_size=$(read_le_u32 "$ARCHIVE" "$pos")
encrypted_size=$(read_le_u32 "$TOC_FILE" "$pos")
pos=$((pos + 4))
# -- data_offset (u32 LE) --
data_offset=$(read_le_u32 "$ARCHIVE" "$pos")
data_offset=$(read_le_u32 "$TOC_FILE" "$pos")
pos=$((pos + 4))
# -- iv (16 bytes as hex) --
iv_toc_pos=$pos
iv_hex=$(read_hex "$ARCHIVE" "$pos" 16)
iv_hex=$(read_hex "$TOC_FILE" "$pos" 16)
pos=$((pos + 16))
# -- hmac (32 bytes as hex) --
hmac_hex=$(read_hex "$ARCHIVE" "$pos" 32)
hmac_hex=$(read_hex "$TOC_FILE" "$pos" 32)
pos=$((pos + 32))
# -- sha256 (32 bytes as hex) --
sha256_hex=$(read_hex "$ARCHIVE" "$pos" 32)
sha256_hex=$(read_hex "$TOC_FILE" "$pos" 32)
pos=$((pos + 32))
# -- compression_flag (1 byte as hex) --
compression_flag=$(read_hex "$ARCHIVE" "$pos" 1)
compression_flag=$(read_hex "$TOC_FILE" "$pos" 1)
pos=$((pos + 1))
# -- padding_after (u16 LE) --
padding_after=$(read_le_u16 "$ARCHIVE" "$pos")
padding_after=$(read_le_u16 "$TOC_FILE" "$pos")
pos=$((pos + 2))
# =======================================================
@@ -190,9 +263,13 @@ while [ "$i" -lt "$file_count" ]; do
dd if="$ARCHIVE" bs=1 skip="$data_offset" count="$encrypted_size" of="$TMPDIR/ct.bin" 2>/dev/null
# b. Verify HMAC (if available)
# HMAC input = IV (16 bytes) || ciphertext
# IV comes from the parsed TOC entry (iv_hex), not from an archive position
if [ "$SKIP_HMAC" = "0" ]; then
# Write IV bytes to temp file from parsed hex
hex_to_bin "$iv_hex" "$TMPDIR/iv.bin"
computed_hmac=$( {
dd if="$ARCHIVE" bs=1 skip="$iv_toc_pos" count=16 2>/dev/null
cat "$TMPDIR/iv.bin"
cat "$TMPDIR/ct.bin"
} | openssl dgst -sha256 -mac HMAC -macopt "hexkey:${KEY_HEX}" -hex 2>/dev/null | awk '{print $NF}' )
@@ -239,7 +316,7 @@ while [ "$i" -lt "$file_count" ]; do
extracted=$((extracted + 1))
# Clean up temp files for next iteration
rm -f "$TMPDIR/ct.bin" "$TMPDIR/dec.bin"
rm -f "$TMPDIR/ct.bin" "$TMPDIR/dec.bin" "$TMPDIR/iv.bin"
i=$((i + 1))
done