mirror of
https://github.com/elder-plinius/STEGOSAURUS-WRECKS.git
synced 2026-04-29 23:57:58 +02:00
6682 lines
236 KiB
Python
6682 lines
236 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Generate pre-encoded steganography example files for ST3GG.
|
||
Each file contains a hidden message that the Agent tab's exhaustive mode can find.
|
||
"""
|
||
|
||
import struct
|
||
import zlib
|
||
import wave
|
||
import array
|
||
import os
|
||
from PIL import Image, PngImagePlugin
|
||
|
||
OUTPUT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
SECRET_MSG = "STEGOSAURUS WRECKS - Hidden message found! 🦕"
|
||
PLINIAN_DIVIDER = "⊰•-•✧•-•-⦑/L\\O/V\\E/\\P/L\\I/N\\Y/⦒-•-•✧•-•⊱"
|
||
|
||
# =============================================================================
|
||
# Utility functions matching ST3GG's STEG v3 format
|
||
# =============================================================================
|
||
|
||
def crc32_steg(data: bytes) -> int:
|
||
"""CRC32 matching ST3GG's implementation."""
|
||
crc = 0xFFFFFFFF
|
||
table = []
|
||
for i in range(256):
|
||
c = i
|
||
for _ in range(8):
|
||
c = (0xEDB88320 ^ (c >> 1)) if (c & 1) else (c >> 1)
|
||
table.append(c)
|
||
for b in data:
|
||
crc = table[(crc ^ b) & 0xFF] ^ (crc >> 8)
|
||
return (crc ^ 0xFFFFFFFF) & 0xFFFFFFFF
|
||
|
||
|
||
def deflate_compress(data: bytes) -> bytes:
|
||
"""Deflate compression matching browser's CompressionStream('deflate')."""
|
||
# Browser 'deflate' uses raw deflate wrapped with zlib header (wbits=15)
|
||
# Actually, CompressionStream('deflate') uses RFC 1950 (zlib format)
|
||
return zlib.compress(data)
|
||
|
||
|
||
def create_steg_header(payload_len: int, original_len: int, crc: int,
|
||
channel_mask: int, bits_per_channel: int,
|
||
compressed: bool = True) -> bytes:
|
||
"""Create a 32-byte STEG v3 header."""
|
||
header = bytearray(32)
|
||
# Magic: STEG
|
||
header[0:4] = b'STEG'
|
||
# Version
|
||
header[4] = 3
|
||
# Channel mask
|
||
header[5] = channel_mask
|
||
# Bits per channel
|
||
header[6] = bits_per_channel
|
||
# Bit offset
|
||
header[7] = 0
|
||
# Flags: compressed (bit 0) | interleaved (bit 1)
|
||
header[8] = (1 if compressed else 0) | 2
|
||
# Bytes 9-15: reserved (zeros)
|
||
# Payload length (big endian)
|
||
struct.pack_into('>I', header, 16, payload_len)
|
||
# Original length (big endian)
|
||
struct.pack_into('>I', header, 20, original_len)
|
||
# CRC32 (big endian)
|
||
struct.pack_into('>I', header, 24, crc)
|
||
return bytes(header)
|
||
|
||
|
||
def bytes_to_bits(data: bytes, bits_per_unit: int = 1) -> list:
|
||
"""Convert bytes to bit units, matching ST3GG's bytesToBits."""
|
||
bits = []
|
||
for byte in data:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
if bits_per_unit == 1:
|
||
return bits
|
||
|
||
result = []
|
||
for i in range(0, len(bits), bits_per_unit):
|
||
value = 0
|
||
for j in range(bits_per_unit):
|
||
if i + j < len(bits):
|
||
value = (value << 1) | bits[i + j]
|
||
result.append(value)
|
||
return result
|
||
|
||
|
||
# =============================================================================
|
||
# 1. PNG with LSB RGB 1-bit (STEG v3 header)
|
||
# =============================================================================
|
||
|
||
def generate_lsb_png():
|
||
"""Create a 200x200 PNG with a hidden message in LSB RGB 1-bit."""
|
||
print(" Generating LSB RGB 1-bit PNG...")
|
||
width, height = 200, 200
|
||
img = Image.new('RGBA', (width, height))
|
||
|
||
# Create a nice gradient background
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 80 * (x / width))
|
||
g = int(60 + 120 * (y / height))
|
||
b = int(140 + 60 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b, 255)
|
||
|
||
# Encode message using STEG v3 format
|
||
msg_bytes = SECRET_MSG.encode('utf-8')
|
||
crc = crc32_steg(msg_bytes)
|
||
payload = deflate_compress(msg_bytes)
|
||
|
||
# RGB channels = mask 0b0111 = 7, 1 bit per channel
|
||
header = create_steg_header(len(payload), len(msg_bytes), crc,
|
||
channel_mask=7, bits_per_channel=1)
|
||
|
||
full_data = header + payload
|
||
bit_units = bytes_to_bits(full_data, 1)
|
||
|
||
# Embed in LSB of RGB channels (interleaved)
|
||
channels = [0, 1, 2] # R, G, B
|
||
unit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if unit_idx >= len(bit_units):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b, a = pixels[x, y]
|
||
vals = [r, g, b, a]
|
||
for ch in channels:
|
||
if unit_idx >= len(bit_units):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bit_units[unit_idx]
|
||
unit_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb_rgb.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({unit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 2. PNG with tEXt chunk
|
||
# =============================================================================
|
||
|
||
def generate_text_chunk_png():
|
||
"""Create a PNG with a hidden message in a tEXt metadata chunk."""
|
||
print(" Generating PNG with tEXt chunk...")
|
||
width, height = 150, 150
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Simple blue-ish pattern
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(40 + 30 * (x / width))
|
||
g = int(50 + 40 * (y / height))
|
||
b = int(150 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
# Add tEXt chunks with hidden data
|
||
info = PngImagePlugin.PngInfo()
|
||
info.add_text("Comment", "Just a normal image, nothing to see here...")
|
||
info.add_text("Secret", SECRET_MSG)
|
||
info.add_text("Author", "STEGOSAURUS WRECKS")
|
||
info.add_text("Flag", "CTF{hidden_in_plain_sight}")
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_png_chunks.png')
|
||
img.save(path, pnginfo=info)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 3. PNG with trailing data after IEND
|
||
# =============================================================================
|
||
|
||
def generate_trailing_data_png():
|
||
"""Create a PNG with hidden data appended after the IEND chunk."""
|
||
print(" Generating PNG with trailing data...")
|
||
width, height = 120, 120
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Green-ish pattern
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(30 + 50 * (x / width))
|
||
g = int(120 + 100 * (y / height))
|
||
b = int(40 + 40 * ((x * y) / (width * height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_trailing_data.png')
|
||
img.save(path)
|
||
|
||
# Append hidden data after IEND
|
||
trailing = b'\n--- HIDDEN DATA BELOW ---\n'
|
||
trailing += SECRET_MSG.encode('utf-8')
|
||
trailing += b'\nCTF{data_after_iend_chunk}\n'
|
||
trailing += b'This data is invisible to normal image viewers!\n'
|
||
|
||
with open(path, 'ab') as f:
|
||
f.write(trailing)
|
||
|
||
print(f" -> {path} ({len(trailing)} bytes appended)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 4. Text with zero-width Unicode steganography
|
||
# =============================================================================
|
||
|
||
def generate_zero_width_text():
|
||
"""Create a text file with a message hidden in zero-width Unicode chars."""
|
||
print(" Generating zero-width Unicode text...")
|
||
|
||
ZWSP = '\u200B' # Zero-width space = 0
|
||
ZWNJ = '\u200C' # Zero-width non-joiner = 1
|
||
ZWJ = '\u200D' # Zero-width joiner = delimiter
|
||
|
||
secret = "Agent found the zero-width secret!"
|
||
secret_bytes = secret.encode('utf-8')
|
||
|
||
# Convert to binary string
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
|
||
# Build zero-width string
|
||
zw_string = ZWJ # Start delimiter
|
||
for bit in binary_str:
|
||
zw_string += ZWSP if bit == '0' else ZWNJ
|
||
zw_string += ZWJ # End delimiter
|
||
|
||
cover = """The Stegosaurus was a large, herbivorous dinosaur that lived during the Late Jurassic period,
|
||
approximately 155 to 150 million years ago. It is best known for its distinctive row of large,
|
||
bony plates along its back and the sharp spikes on its tail, known as the thagomizer.
|
||
|
||
Despite its massive size, the Stegosaurus had a remarkably small brain, roughly the size of a
|
||
walnut. This has led to much speculation about how such a large animal could function with such
|
||
limited cognitive capacity.
|
||
|
||
The name "Stegosaurus" means "roof lizard" or "covered lizard," referring to the plates on its
|
||
back, which were once thought to lie flat like roof tiles. Modern research suggests these plates
|
||
were used for thermoregulation and display rather than defense."""
|
||
|
||
# Insert after first character
|
||
stego_text = cover[0] + zw_string + cover[1:]
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_zero_width.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(stego_text)
|
||
|
||
print(f" -> {path} ({len(binary_str)} bits hidden)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 5. Text with whitespace encoding
|
||
# =============================================================================
|
||
|
||
def generate_whitespace_text():
|
||
"""Create a text file with a message hidden in trailing whitespace."""
|
||
print(" Generating whitespace-encoded text...")
|
||
|
||
secret = "Whitespace hides secrets!"
|
||
secret_bytes = secret.encode('utf-8')
|
||
|
||
# Length prefix (16 bits) + data bits
|
||
length_bits = format(len(secret_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in secret_bytes)
|
||
all_bits = length_bits + data_bits
|
||
|
||
cover_lines = [
|
||
"How to Identify Steganography",
|
||
"==============================",
|
||
"",
|
||
"Steganography is the practice of hiding secret information within",
|
||
"ordinary, non-secret data or physical objects. Unlike cryptography,",
|
||
"which makes data unreadable, steganography conceals the very",
|
||
"existence of the secret message.",
|
||
"",
|
||
"Common techniques include:",
|
||
"- Least Significant Bit (LSB) embedding in images",
|
||
"- Hiding data in audio frequency spectrums",
|
||
"- Using invisible Unicode characters in text",
|
||
"- Appending data after file end markers",
|
||
"- Encoding in metadata fields",
|
||
"",
|
||
"Detection methods include statistical analysis, visual inspection",
|
||
"of bit planes, frequency domain analysis, and file structure",
|
||
"examination. Tools like ST3GG can automate this process.",
|
||
"",
|
||
"The word steganography comes from the Greek words 'steganos'",
|
||
"(meaning covered or hidden) and 'graphein' (meaning to write).",
|
||
"",
|
||
"In the digital age, steganography has found applications in",
|
||
"digital watermarking, covert communication, and CTF challenges.",
|
||
"",
|
||
"Always remember: just because you can't see it doesn't mean",
|
||
"it's not there. Hidden in plain sight is the ultimate disguise.",
|
||
"",
|
||
"End of document.",
|
||
]
|
||
|
||
bit_index = 0
|
||
result_lines = []
|
||
for line in cover_lines:
|
||
trailing = ''
|
||
for _ in range(8):
|
||
if bit_index < len(all_bits):
|
||
trailing += ' ' if all_bits[bit_index] == '0' else '\t'
|
||
bit_index += 1
|
||
result_lines.append(line + trailing)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_whitespace.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(result_lines))
|
||
|
||
print(f" -> {path} ({bit_index} bits hidden in trailing whitespace)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 6. Text with invisible ink (Unicode tag characters)
|
||
# =============================================================================
|
||
|
||
def generate_invisible_ink_text():
|
||
"""Create a text file with a message hidden using Unicode tag characters."""
|
||
print(" Generating invisible ink (Unicode tags) text...")
|
||
|
||
TAG_BASE = 0xE0000
|
||
secret = "Invisible ink message decoded!"
|
||
|
||
# Build tag string
|
||
tag_string = chr(TAG_BASE) # Start tag
|
||
for char in secret:
|
||
code = ord(char)
|
||
if code < 128:
|
||
tag_string += chr(TAG_BASE + code)
|
||
tag_string += chr(TAG_BASE) # End tag
|
||
|
||
cover = """Dinosaur Facts: The Stegosaurus
|
||
|
||
The Stegosaurus is one of the most recognizable dinosaurs thanks to its
|
||
distinctive double row of kite-shaped plates rising vertically along its
|
||
arched back and the two pairs of long spikes extending from its tail.
|
||
|
||
Size: Up to 9 meters (30 feet) long and 4 meters (14 feet) tall
|
||
Weight: Approximately 5,000 kg (11,000 lbs)
|
||
Diet: Herbivore (ferns, cycads, and conifers)
|
||
Period: Late Jurassic (155-150 million years ago)
|
||
Location: Western North America, Portugal"""
|
||
|
||
# Insert tag string after first character
|
||
stego_text = cover[0] + tag_string + cover[1:]
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_invisible_ink.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(stego_text)
|
||
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 7. WAV with Audio LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_audio_lsb_wav():
|
||
"""Create a WAV file with a message hidden in audio sample LSBs."""
|
||
print(" Generating WAV with audio LSB...")
|
||
|
||
sample_rate = 44100
|
||
duration = 2 # seconds
|
||
num_samples = sample_rate * duration
|
||
|
||
# Generate a simple sine wave tone (440 Hz)
|
||
import math
|
||
frequency = 440.0
|
||
samples = []
|
||
for i in range(num_samples):
|
||
t = i / sample_rate
|
||
# Mix two frequencies for a richer sound
|
||
value = 0.5 * math.sin(2 * math.pi * frequency * t)
|
||
value += 0.3 * math.sin(2 * math.pi * (frequency * 1.5) * t)
|
||
# Convert to 16-bit integer
|
||
sample = int(value * 16000)
|
||
sample = max(-32768, min(32767, sample))
|
||
samples.append(sample)
|
||
|
||
# Embed message in LSB of samples
|
||
msg = SECRET_MSG.encode('utf-8')
|
||
|
||
# Simple format: 4-byte length prefix + message bytes
|
||
length_bytes = struct.pack('>I', len(msg))
|
||
payload = length_bytes + msg
|
||
|
||
# Convert payload to bits
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Embed bits in LSB of samples (handling signed 16-bit properly)
|
||
for i, bit in enumerate(bits):
|
||
if i < len(samples):
|
||
s = samples[i]
|
||
# Convert to unsigned, set LSB, convert back to signed
|
||
u = s & 0xFFFF # unsigned view
|
||
u = (u & 0xFFFE) | bit
|
||
# Convert back to signed
|
||
samples[i] = u if u < 32768 else u - 65536
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_audio_lsb.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2) # 16-bit
|
||
wav.setframerate(sample_rate)
|
||
# Pack samples as signed 16-bit little-endian
|
||
data = struct.pack(f'<{len(samples)}h', *samples)
|
||
wav.writeframes(data)
|
||
|
||
print(f" -> {path} ({len(bits)} bits embedded in {num_samples} samples)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 8. PNG with EXIF-like metadata (hidden in Description)
|
||
# =============================================================================
|
||
|
||
def generate_exif_png():
|
||
"""Create a PNG with suspicious metadata fields."""
|
||
print(" Generating PNG with metadata...")
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Red-orange pattern
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(180 + 60 * (x / width))
|
||
g = int(80 + 60 * (y / height))
|
||
b = int(20 + 30 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
info = PngImagePlugin.PngInfo()
|
||
info.add_text("Description", "Base64 encoded secret: " +
|
||
__import__('base64').b64encode(SECRET_MSG.encode()).decode())
|
||
info.add_text("Software", "STEGOSAURUS WRECKS v3.0")
|
||
info.add_text("Warning", "Look closer at the other example files too!")
|
||
# Add a hex-encoded hidden message
|
||
info.add_text("HexData", SECRET_MSG.encode('utf-8').hex())
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_metadata.png')
|
||
img.save(path, pnginfo=info)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 9. BMP with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_lsb_bmp():
|
||
"""Create a BMP file with the Plinian divider hidden in LSB of pixels."""
|
||
print(" Generating BMP with LSB steganography...")
|
||
width, height = 160, 160
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Purple gradient background
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(120 + 80 * (x / width))
|
||
g = int(40 + 60 * (y / height))
|
||
b = int(160 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.bmp')
|
||
img.save(path, 'BMP')
|
||
print(f" -> {path} ({bit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 10. GIF with comment extension
|
||
# =============================================================================
|
||
|
||
def generate_gif_comment():
|
||
"""Create a GIF with the Plinian divider hidden in a comment extension block."""
|
||
print(" Generating GIF with comment extension...")
|
||
width, height = 100, 100
|
||
img = Image.new('P', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Simple gradient pattern
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = int((x + y) * 127 / (width + height))
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_comment.gif')
|
||
img.save(path, 'GIF')
|
||
|
||
# GIF comment extension: inject after GIF header
|
||
with open(path, 'rb') as f:
|
||
data = f.read()
|
||
|
||
# Build comment extension block
|
||
comment = PLINIAN_DIVIDER.encode('utf-8')
|
||
comment_ext = b'\x21\xFE' # Comment extension introducer
|
||
# Split into sub-blocks of max 255 bytes
|
||
i = 0
|
||
while i < len(comment):
|
||
chunk = comment[i:i + 255]
|
||
comment_ext += bytes([len(chunk)]) + chunk
|
||
i += 255
|
||
comment_ext += b'\x00' # Block terminator
|
||
|
||
# Insert comment extension after GIF header (before image data)
|
||
# GIF header is 6 bytes + logical screen descriptor (7 bytes) + global color table
|
||
# Find the image descriptor (0x2C) or extension block
|
||
insert_pos = 13 # After header + LSD
|
||
if data[10] & 0x80: # Global color table flag
|
||
gct_size = 3 * (2 ** ((data[10] & 0x07) + 1))
|
||
insert_pos += gct_size
|
||
|
||
new_data = data[:insert_pos] + comment_ext + data[insert_pos:]
|
||
with open(path, 'wb') as f:
|
||
f.write(new_data)
|
||
|
||
print(f" -> {path} (comment extension: {len(comment)} bytes)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 11. GIF with LSB in palette indices
|
||
# =============================================================================
|
||
|
||
def generate_gif_lsb():
|
||
"""Create a GIF with the Plinian divider in LSB of palette indices.
|
||
|
||
Note: We write the GIF manually (binary) because Pillow's GIF encoder
|
||
requantizes the palette, which destroys LSB data. We use uncompressed
|
||
LZW (max code size) to preserve exact index values.
|
||
"""
|
||
print(" Generating GIF with palette index LSB...")
|
||
width, height = 120, 120
|
||
|
||
# Create a palette with maximally distinct colors (pairs differ in R LSB)
|
||
palette = bytearray(256 * 3)
|
||
for i in range(256):
|
||
palette[i * 3] = (i * 37 + 80) & 0xFE # R: spread out, always even base
|
||
palette[i * 3 + 1] = (i * 13 + 60) & 0xFF # G
|
||
palette[i * 3 + 2] = (i * 7 + 120) & 0xFF # B
|
||
# Ensure pairs (2i, 2i+1) differ only in R LSB
|
||
if i % 2 == 1:
|
||
palette[i * 3] = palette[(i - 1) * 3] | 1
|
||
palette[i * 3 + 1] = palette[(i - 1) * 3 + 1]
|
||
palette[i * 3 + 2] = palette[(i - 1) * 3 + 2]
|
||
|
||
# Build pixel index data
|
||
pixel_indices = bytearray()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
# Use even palette indices so we can flip LSB
|
||
pixel_indices.append(int((x + y) * 127 / (width + height)) * 2 % 256)
|
||
|
||
# Encode Plinian divider in LSB of palette indices
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for i in range(len(pixel_indices)):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
pixel_indices[i] = (pixel_indices[i] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
|
||
# Write GIF manually to preserve exact palette indices
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.gif')
|
||
with open(path, 'wb') as f:
|
||
# GIF89a header
|
||
f.write(b'GIF89a')
|
||
# Logical Screen Descriptor
|
||
f.write(struct.pack('<HH', width, height))
|
||
f.write(bytes([0xF7, 0x00, 0x00])) # GCT flag, 256 colors, no sort, bg=0, aspect=0
|
||
# Global Color Table (256 entries)
|
||
f.write(bytes(palette))
|
||
# Image Descriptor
|
||
f.write(b'\x2C') # Image separator
|
||
f.write(struct.pack('<HHHH', 0, 0, width, height))
|
||
f.write(bytes([0x00])) # No local color table, not interlaced
|
||
|
||
# LZW-compress the pixel data
|
||
# Min code size = 8 (for 256 colors)
|
||
min_code_size = 8
|
||
f.write(bytes([min_code_size]))
|
||
|
||
# Use simple LZW: emit clear code, then each pixel as a literal, then EOI
|
||
clear_code = 1 << min_code_size # 256
|
||
eoi_code = clear_code + 1 # 257
|
||
|
||
# Bit packer
|
||
bit_buffer = 0
|
||
bit_count = 0
|
||
output = bytearray()
|
||
code_size = min_code_size + 1 # Start at 9 bits
|
||
|
||
def emit_code(code):
|
||
nonlocal bit_buffer, bit_count
|
||
bit_buffer |= (code << bit_count)
|
||
bit_count += code_size
|
||
while bit_count >= 8:
|
||
output.append(bit_buffer & 0xFF)
|
||
bit_buffer >>= 8
|
||
bit_count -= 8
|
||
|
||
# Emit clear code first
|
||
emit_code(clear_code)
|
||
|
||
# Emit each pixel as a literal code (0-255)
|
||
# To keep code_size at 9, emit clear codes periodically to reset the table
|
||
count = 0
|
||
for idx in pixel_indices:
|
||
emit_code(idx)
|
||
count += 1
|
||
if count >= 250: # Reset before table grows too much
|
||
emit_code(clear_code)
|
||
code_size = min_code_size + 1
|
||
count = 0
|
||
|
||
# Emit EOI
|
||
emit_code(eoi_code)
|
||
|
||
# Flush remaining bits
|
||
if bit_count > 0:
|
||
output.append(bit_buffer & 0xFF)
|
||
|
||
# Write as sub-blocks (max 255 bytes each)
|
||
i = 0
|
||
while i < len(output):
|
||
chunk = output[i:i + 255]
|
||
f.write(bytes([len(chunk)]))
|
||
f.write(chunk)
|
||
i += 255
|
||
f.write(b'\x00') # Block terminator
|
||
|
||
# Trailer
|
||
f.write(b'\x3B')
|
||
|
||
print(f" -> {path} ({bit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 12. TIFF with metadata steganography
|
||
# =============================================================================
|
||
|
||
def generate_tiff_metadata():
|
||
"""Create a TIFF with the Plinian divider hidden in EXIF/metadata fields."""
|
||
print(" Generating TIFF with metadata steganography...")
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Warm gradient
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(200 + 40 * (x / width))
|
||
g = int(140 + 60 * (y / height))
|
||
b = int(60 + 40 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
import base64
|
||
path = os.path.join(OUTPUT_DIR, 'example_metadata.tiff')
|
||
# Save TIFF with metadata tags
|
||
img.save(path, 'TIFF', compression='raw',
|
||
software='ST3GG STEGOSAURUS WRECKS',
|
||
description=base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode())
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 13. TIFF with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_tiff_lsb():
|
||
"""Create a TIFF with the Plinian divider hidden in LSB of pixels."""
|
||
print(" Generating TIFF with LSB steganography...")
|
||
width, height = 140, 140
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Teal gradient
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(30 + 60 * (x / width))
|
||
g = int(140 + 80 * (y / height))
|
||
b = int(130 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.tiff')
|
||
img.save(path, 'TIFF', compression='raw')
|
||
print(f" -> {path} ({bit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 14. PPM with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_ppm_lsb():
|
||
"""Create a PPM (Portable Pixmap) with the Plinian divider in LSB."""
|
||
print(" Generating PPM with LSB steganography...")
|
||
width, height = 120, 120
|
||
|
||
# Build pixel data
|
||
pixel_data = bytearray()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 90 * (x / width))
|
||
g = int(80 + 100 * (y / height))
|
||
b = int(60 + 120 * ((x + y) / (width + height)))
|
||
pixel_data.extend([r, g, b])
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
for i, bit in enumerate(bits):
|
||
if i < len(pixel_data):
|
||
pixel_data[i] = (pixel_data[i] & 0xFE) | bit
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.ppm')
|
||
with open(path, 'wb') as f:
|
||
f.write(f'P6\n{width} {height}\n255\n'.encode('ascii'))
|
||
f.write(bytes(pixel_data))
|
||
print(f" -> {path} ({len(bits)} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 15. PGM with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_pgm_lsb():
|
||
"""Create a PGM (Portable Graymap) with the Plinian divider in LSB."""
|
||
print(" Generating PGM with LSB steganography...")
|
||
width, height = 150, 150
|
||
|
||
# Build grayscale pixel data
|
||
pixel_data = bytearray()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
val = int(60 + 160 * ((x * y) / (width * height)))
|
||
pixel_data.append(val)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
for i, bit in enumerate(bits):
|
||
if i < len(pixel_data):
|
||
pixel_data[i] = (pixel_data[i] & 0xFE) | bit
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.pgm')
|
||
with open(path, 'wb') as f:
|
||
f.write(f'P5\n{width} {height}\n255\n'.encode('ascii'))
|
||
f.write(bytes(pixel_data))
|
||
print(f" -> {path} ({len(bits)} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 16. SVG with hidden data in XML
|
||
# =============================================================================
|
||
|
||
def generate_svg_hidden():
|
||
"""Create an SVG with the Plinian divider hidden in XML comments and attributes."""
|
||
print(" Generating SVG with hidden XML data...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
svg = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||
<!-- {PLINIAN_DIVIDER} -->
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"
|
||
data-steg="{encoded}"
|
||
data-desc="Nothing suspicious here">
|
||
<!-- Hidden in plain sight: steganography demonstration file -->
|
||
<defs>
|
||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||
<stop offset="0%" style="stop-color:#4a0e8f;stop-opacity:1" />
|
||
<stop offset="100%" style="stop-color:#c471ed;stop-opacity:1" />
|
||
</linearGradient>
|
||
<linearGradient id="fg" x1="0%" y1="100%" x2="100%" y2="0%">
|
||
<stop offset="0%" style="stop-color:#12c2e9;stop-opacity:0.8" />
|
||
<stop offset="100%" style="stop-color:#f64f59;stop-opacity:0.8" />
|
||
</linearGradient>
|
||
</defs>
|
||
<rect width="200" height="200" fill="url(#bg)"/>
|
||
<circle cx="100" cy="80" r="50" fill="url(#fg)" opacity="0.7"/>
|
||
<polygon points="60,140 100,90 140,140" fill="#f5af19" opacity="0.6"/>
|
||
<text x="100" y="175" font-family="monospace" font-size="11"
|
||
fill="white" text-anchor="middle" opacity="0.8">ST3GG Example</text>
|
||
<!-- hex:{hex_encoded} -->
|
||
<metadata>
|
||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||
<rdf:Description>
|
||
<dc:description>{PLINIAN_DIVIDER}</dc:description>
|
||
<dc:creator>STEGOSAURUS WRECKS</dc:creator>
|
||
</rdf:Description>
|
||
</rdf:RDF>
|
||
</metadata>
|
||
</svg>'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.svg')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(svg)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 17. ICO with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_ico_lsb():
|
||
"""Create an ICO (icon) file with the Plinian divider in LSB of pixels."""
|
||
print(" Generating ICO with LSB steganography...")
|
||
size = 32
|
||
img = Image.new('RGBA', (size, size))
|
||
pixels = img.load()
|
||
|
||
# Icon-like gradient
|
||
for y in range(size):
|
||
for x in range(size):
|
||
r = int(60 + 160 * (x / size))
|
||
g = int(100 + 100 * (y / size))
|
||
b = int(180 + 60 * ((x + y) / (2 * size)))
|
||
# Circular alpha mask
|
||
cx, cy = size / 2, size / 2
|
||
dist = ((x - cx) ** 2 + (y - cy) ** 2) ** 0.5
|
||
a = 255 if dist < size / 2 - 1 else 0
|
||
pixels[x, y] = (r, g, b, a)
|
||
|
||
# Encode Plinian divider in LSB of RGB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>H', len(msg_bytes)) # 16-bit length for small icon
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(size * size):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % size
|
||
y = pix_idx // size
|
||
r, g, b, a = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
pixels[x, y] = (vals[0], vals[1], vals[2], a)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.ico')
|
||
img.save(path, format='ICO', sizes=[(32, 32)])
|
||
print(f" -> {path} ({bit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 18. WebP with metadata steganography
|
||
# =============================================================================
|
||
|
||
def generate_webp_metadata():
|
||
"""Create a WebP with the Plinian divider hidden in EXIF and XMP metadata.
|
||
|
||
Note: EXIF tags use ASCII encoding which corrupts non-ASCII Unicode chars.
|
||
We store: base64-encoded divider in EXIF ImageDescription, raw divider in XMP.
|
||
"""
|
||
print(" Generating WebP with metadata steganography...")
|
||
import base64
|
||
|
||
width, height = 120, 120
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Coral gradient
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(220 + 30 * (x / width))
|
||
g = int(80 + 70 * (y / height))
|
||
b = int(60 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
encoded_b64 = base64.b64encode(secret_bytes).decode()
|
||
|
||
# EXIF: use base64 for ImageDescription (ASCII-safe)
|
||
exif_data = img.getexif()
|
||
exif_data[270] = f"b64:{encoded_b64}" # ImageDescription (base64-safe)
|
||
exif_data[305] = "STEGOSAURUS WRECKS v3.0" # Software
|
||
exif_data[315] = f"hex:{secret_bytes.hex()}" # Artist (hex-safe)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_metadata.webp')
|
||
img.save(path, 'WebP', exif=exif_data.tobytes())
|
||
|
||
# Also inject XMP with full UTF-8 support by appending to the file
|
||
# WebP files support XMP chunks - we'll append one after the EXIF
|
||
xmp = (f'<?xpacket begin="\xef\xbb\xbf"?>'
|
||
f'<x:xmpmeta xmlns:x="adobe:ns:meta/">'
|
||
f'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"'
|
||
f' xmlns:dc="http://purl.org/dc/elements/1.1/">'
|
||
f'<rdf:Description>'
|
||
f'<dc:description>{PLINIAN_DIVIDER}</dc:description>'
|
||
f'<dc:subject>{encoded_b64}</dc:subject>'
|
||
f'</rdf:Description>'
|
||
f'</rdf:RDF>'
|
||
f'</x:xmpmeta>'
|
||
f'<?xpacket end="w"?>').encode('utf-8')
|
||
|
||
# Inject XMP chunk into RIFF/WebP container
|
||
with open(path, 'rb') as f:
|
||
data = f.read()
|
||
|
||
# WebP RIFF structure: RIFF + size + WEBP + chunks
|
||
# Add an XMP chunk (FourCC: "XMP ")
|
||
xmp_chunk = b'XMP ' + struct.pack('<I', len(xmp)) + xmp
|
||
if len(xmp) % 2:
|
||
xmp_chunk += b'\x00' # Pad to even
|
||
|
||
# Insert before the end of RIFF
|
||
new_riff_size = struct.unpack('<I', data[4:8])[0] + len(xmp_chunk)
|
||
data = data[:4] + struct.pack('<I', new_riff_size) + data[8:] + xmp_chunk
|
||
|
||
with open(path, 'wb') as f:
|
||
f.write(data)
|
||
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 19. WebP with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_webp_lsb():
|
||
"""Create a lossless WebP with the Plinian divider in LSB of pixels."""
|
||
print(" Generating WebP with LSB steganography...")
|
||
width, height = 130, 130
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Ocean gradient
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(20 + 50 * (x / width))
|
||
g = int(80 + 80 * (y / height))
|
||
b = int(150 + 90 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.webp')
|
||
img.save(path, 'WebP', lossless=True)
|
||
print(f" -> {path} ({bit_idx} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 20. HTML with hidden content
|
||
# =============================================================================
|
||
|
||
def generate_html_hidden():
|
||
"""Create an HTML file with the Plinian divider hidden in multiple ways."""
|
||
print(" Generating HTML with hidden content...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Zero-width encoding of the divider
|
||
ZWSP = '\u200B'
|
||
ZWNJ = '\u200C'
|
||
ZWJ = '\u200D'
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
zw_string = ZWJ
|
||
for bit in binary_str:
|
||
zw_string += ZWSP if bit == '0' else ZWNJ
|
||
zw_string += ZWJ
|
||
|
||
html = f'''<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="description" content="{encoded}">
|
||
<meta name="generator" content="STEGOSAURUS WRECKS">
|
||
<title>Steganography Demo</title>
|
||
<style>
|
||
body {{ font-family: 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0;
|
||
max-width: 700px; margin: 40px auto; padding: 20px; }}
|
||
h1 {{ color: #c471ed; }}
|
||
.cover {{ line-height: 1.8; }}
|
||
/* data-payload: {hex_encoded} */
|
||
.hidden {{ display: none; }}
|
||
</style>
|
||
</head>
|
||
<!-- {PLINIAN_DIVIDER} -->
|
||
<body>
|
||
<h1>Stegosaurus Facts</h1>
|
||
<div class="cover">
|
||
<p>{zw_string}The Stegosaurus was one of the most distinctive dinosaurs of the Late
|
||
Jurassic period. Its double row of bony plates and spiked tail made it
|
||
instantly recognizable among prehistoric creatures.</p>
|
||
<p>Despite weighing up to 5 metric tons, the Stegosaurus had a brain
|
||
roughly the size of a walnut, making it one of the least intelligent
|
||
dinosaurs relative to its body size.</p>
|
||
</div>
|
||
<div class="hidden" data-secret="{PLINIAN_DIVIDER}" id="steg-payload">
|
||
{encoded}
|
||
</div>
|
||
<noscript><!-- hex:{hex_encoded} --></noscript>
|
||
</body>
|
||
</html>'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.html')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(html)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 21. XML with hidden data
|
||
# =============================================================================
|
||
|
||
def generate_xml_hidden():
|
||
"""Create an XML file with the Plinian divider in comments, PIs, and CDATA."""
|
||
print(" Generating XML with hidden data...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||
<?steg-payload {encoded}?>
|
||
<!-- {PLINIAN_DIVIDER} -->
|
||
<dinosaurs xmlns:steg="http://st3gg.example.com/steg"
|
||
steg:payload="{hex_encoded}">
|
||
<species name="Stegosaurus" period="Late Jurassic">
|
||
<description>Large herbivorous thyreophoran dinosaur</description>
|
||
<weight unit="kg">5000</weight>
|
||
<length unit="m">9</length>
|
||
<features>
|
||
<feature>Dorsal plates</feature>
|
||
<feature>Thagomizer (tail spikes)</feature>
|
||
<feature>Small brain cavity</feature>
|
||
</features>
|
||
<hidden><![CDATA[{PLINIAN_DIVIDER}]]></hidden>
|
||
</species>
|
||
<species name="Ankylosaurus" period="Late Cretaceous">
|
||
<description>Armored dinosaur with club tail</description>
|
||
<weight unit="kg">6000</weight>
|
||
<length unit="m">7</length>
|
||
</species>
|
||
<!-- base64:{encoded} -->
|
||
</dinosaurs>'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.xml')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(xml)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 22. JSON with Unicode escape sequences
|
||
# =============================================================================
|
||
|
||
def generate_json_hidden():
|
||
"""Create a JSON file with the Plinian divider hidden in Unicode escapes."""
|
||
print(" Generating JSON with hidden Unicode escapes...")
|
||
import json
|
||
import base64
|
||
|
||
# Encode as Unicode escape sequences
|
||
unicode_escaped = ''.join(f'\\u{ord(c):04x}' if ord(c) < 0x10000
|
||
else f'\\U{ord(c):08x}' for c in PLINIAN_DIVIDER)
|
||
|
||
data = {
|
||
"title": "Steganography Example Dataset",
|
||
"version": "3.0",
|
||
"generator": "STEGOSAURUS WRECKS",
|
||
"specimens": [
|
||
{
|
||
"name": "Stegosaurus stenops",
|
||
"period": "Late Jurassic",
|
||
"mya": [155, 150],
|
||
"diet": "herbivore",
|
||
"mass_kg": 5000
|
||
},
|
||
{
|
||
"name": "Stegosaurus ungulatus",
|
||
"period": "Late Jurassic",
|
||
"mya": [155, 145],
|
||
"diet": "herbivore",
|
||
"mass_kg": 5500
|
||
}
|
||
],
|
||
"_metadata": {
|
||
"comment": "Standard paleontology dataset",
|
||
"encoding": "UTF-8",
|
||
"payload_b64": base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode(),
|
||
"payload_hex": PLINIAN_DIVIDER.encode('utf-8').hex(),
|
||
"payload_direct": PLINIAN_DIVIDER
|
||
}
|
||
}
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.json')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 23. CSV with whitespace-encoded steganography
|
||
# =============================================================================
|
||
|
||
def generate_csv_hidden():
|
||
"""Create a CSV with the Plinian divider hidden in trailing whitespace."""
|
||
print(" Generating CSV with whitespace steganography...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_bits + data_bits
|
||
|
||
rows = [
|
||
["Species", "Period", "Length_m", "Mass_kg", "Diet"],
|
||
["Stegosaurus", "Late Jurassic", "9.0", "5000", "Herbivore"],
|
||
["Triceratops", "Late Cretaceous", "9.0", "6000", "Herbivore"],
|
||
["Tyrannosaurus", "Late Cretaceous", "12.3", "8400", "Carnivore"],
|
||
["Velociraptor", "Late Cretaceous", "2.0", "15", "Carnivore"],
|
||
["Brachiosaurus", "Late Jurassic", "26.0", "56000", "Herbivore"],
|
||
["Ankylosaurus", "Late Cretaceous", "6.5", "6000", "Herbivore"],
|
||
["Parasaurolophus", "Late Cretaceous", "9.5", "2500", "Herbivore"],
|
||
["Diplodocus", "Late Jurassic", "26.0", "16000", "Herbivore"],
|
||
["Allosaurus", "Late Jurassic", "8.5", "2300", "Carnivore"],
|
||
["Spinosaurus", "Late Cretaceous", "15.0", "7400", "Piscivore"],
|
||
["Pachycephalosaurus", "Late Cretaceous", "4.5", "450", "Herbivore"],
|
||
["Carnotaurus", "Late Cretaceous", "8.0", "1500", "Carnivore"],
|
||
["Iguanodon", "Early Cretaceous", "10.0", "3400", "Herbivore"],
|
||
["Pteranodon", "Late Cretaceous", "6.0", "25", "Piscivore"],
|
||
["Deinonychus", "Early Cretaceous", "3.4", "73", "Carnivore"],
|
||
["Apatosaurus", "Late Jurassic", "21.0", "23000", "Herbivore"],
|
||
["Compsognathus", "Late Jurassic", "1.0", "3", "Carnivore"],
|
||
["Gallimimus", "Late Cretaceous", "6.0", "440", "Omnivore"],
|
||
["Therizinosaurus", "Late Cretaceous", "10.0", "5000", "Herbivore"],
|
||
["Archaeopteryx", "Late Jurassic", "0.5", "1", "Carnivore"],
|
||
["Baryonyx", "Early Cretaceous", "10.0", "1700", "Piscivore"],
|
||
["Coelophysis", "Late Triassic", "3.0", "30", "Carnivore"],
|
||
["Dilophosaurus", "Early Jurassic", "7.0", "400", "Carnivore"],
|
||
["Giganotosaurus", "Late Cretaceous", "13.0", "6800", "Carnivore"],
|
||
["Edmontosaurus", "Late Cretaceous", "13.0", "4000", "Herbivore"],
|
||
["Protoceratops", "Late Cretaceous", "1.8", "83", "Herbivore"],
|
||
["Oviraptor", "Late Cretaceous", "2.0", "33", "Omnivore"],
|
||
["Maiasaura", "Late Cretaceous", "9.0", "3000", "Herbivore"],
|
||
["Kentrosaurus", "Late Jurassic", "4.5", "1600", "Herbivore"],
|
||
["Plateosaurus", "Late Triassic", "8.0", "1500", "Herbivore"],
|
||
["Ceratosaurus", "Late Jurassic", "6.0", "980", "Carnivore"],
|
||
["Megalosaurus", "Middle Jurassic", "9.0", "1400", "Carnivore"],
|
||
["Corythosaurus", "Late Cretaceous", "9.0", "3800", "Herbivore"],
|
||
["Lambeosaurus", "Late Cretaceous", "9.4", "5600", "Herbivore"],
|
||
["Styracosaurus", "Late Cretaceous", "5.5", "2700", "Herbivore"],
|
||
["Dracorex", "Late Cretaceous", "3.0", "450", "Herbivore"],
|
||
["Microraptor", "Early Cretaceous", "0.8", "1", "Carnivore"],
|
||
["Psittacosaurus", "Early Cretaceous", "2.0", "20", "Herbivore"],
|
||
["Sauropelta", "Early Cretaceous", "5.0", "1500", "Herbivore"],
|
||
["Nodosaurus", "Late Cretaceous", "6.0", "3500", "Herbivore"],
|
||
["Euoplocephalus", "Late Cretaceous", "6.0", "2000", "Herbivore"],
|
||
["Citipati", "Late Cretaceous", "2.9", "75", "Omnivore"],
|
||
["Ornithomimus", "Late Cretaceous", "3.5", "170", "Omnivore"],
|
||
["Struthiomimus", "Late Cretaceous", "4.3", "150", "Omnivore"],
|
||
["Dromaeosaurus", "Late Cretaceous", "2.0", "15", "Carnivore"],
|
||
["Utahraptor", "Early Cretaceous", "6.0", "500", "Carnivore"],
|
||
["Suchomimus", "Early Cretaceous", "11.0", "3800", "Piscivore"],
|
||
["Irritator", "Early Cretaceous", "8.0", "1000", "Piscivore"],
|
||
["Carcharodontosaurus", "Late Cretaceous", "12.0", "6200", "Carnivore"],
|
||
["Acrocanthosaurus", "Early Cretaceous", "11.5", "5200", "Carnivore"],
|
||
["Torvosaurus", "Late Jurassic", "10.0", "3600", "Carnivore"],
|
||
["Camarasaurus", "Late Jurassic", "18.0", "18000", "Herbivore"],
|
||
["Amargasaurus", "Early Cretaceous", "10.0", "8000", "Herbivore"],
|
||
["Nigersaurus", "Early Cretaceous", "9.0", "4000", "Herbivore"],
|
||
["Dreadnoughtus", "Late Cretaceous", "26.0", "65000", "Herbivore"],
|
||
["Argentinosaurus", "Late Cretaceous", "30.0", "73000", "Herbivore"],
|
||
["Patagotitan", "Late Cretaceous", "31.0", "69000", "Herbivore"],
|
||
["Mamenchisaurus", "Late Jurassic", "22.0", "18000", "Herbivore"],
|
||
["Shunosaurus", "Middle Jurassic", "11.0", "3000", "Herbivore"],
|
||
["Tuojiangosaurus", "Late Jurassic", "7.0", "4000", "Herbivore"],
|
||
["Wuerhosaurus", "Early Cretaceous", "7.0", "4000", "Herbivore"],
|
||
["Huayangosaurus", "Middle Jurassic", "4.5", "500", "Herbivore"],
|
||
["Dacentrurus", "Late Jurassic", "8.0", "5000", "Herbivore"],
|
||
["Lexovisaurus", "Middle Jurassic", "6.0", "2000", "Herbivore"],
|
||
["Chungkingosaurus", "Late Jurassic", "4.0", "1000", "Herbivore"],
|
||
["Miragaia", "Late Jurassic", "6.5", "2000", "Herbivore"],
|
||
["Jiangjunosaurus", "Late Jurassic", "6.0", "2500", "Herbivore"],
|
||
["Hesperosaurus", "Late Jurassic", "6.5", "3500", "Herbivore"],
|
||
["Loricatosaurus", "Middle Jurassic", "6.0", "2000", "Herbivore"],
|
||
["Paranthodon", "Early Cretaceous", "5.0", "900", "Herbivore"],
|
||
["Regnosaurus", "Early Cretaceous", "4.5", "800", "Herbivore"],
|
||
["Dravidosaurus", "Late Cretaceous", "3.0", "500", "Herbivore"],
|
||
["Craterosaurus", "Early Cretaceous", "4.0", "700", "Herbivore"],
|
||
]
|
||
|
||
bit_index = 0
|
||
lines = []
|
||
for row in rows:
|
||
line = ','.join(row)
|
||
trailing = ''
|
||
for _ in range(8):
|
||
if bit_index < len(all_bits):
|
||
trailing += ' ' if all_bits[bit_index] == '0' else '\t'
|
||
bit_index += 1
|
||
lines.append(line + trailing)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_whitespace.csv')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(lines))
|
||
print(f" -> {path} ({bit_index} bits hidden)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 24. YAML with comment-based encoding
|
||
# =============================================================================
|
||
|
||
def generate_yaml_hidden():
|
||
"""Create a YAML file with the Plinian divider hidden in comments."""
|
||
print(" Generating YAML with comment steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Encode each byte as a YAML comment with a seemingly innocent hex value
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
hex_comments = []
|
||
for i, b in enumerate(msg_bytes):
|
||
hex_comments.append(f" # ref-{i:03d}: 0x{b:02x}")
|
||
|
||
yaml = f'''# Paleontology Specimen Database
|
||
# Generated by STEGOSAURUS WRECKS
|
||
# {PLINIAN_DIVIDER}
|
||
|
||
database:
|
||
name: "DinoTracker"
|
||
version: "3.0"
|
||
encoding: "UTF-8"
|
||
|
||
specimens:
|
||
- name: "Stegosaurus stenops"
|
||
classification:
|
||
order: Ornithischia
|
||
family: Stegosauridae # b64:{encoded}
|
||
period: "Late Jurassic"
|
||
location: "Morrison Formation, USA"
|
||
measurements:
|
||
length_m: 9.0
|
||
height_m: 4.0
|
||
mass_kg: 5000
|
||
|
||
- name: "Triceratops horridus"
|
||
classification:
|
||
order: Ornithischia
|
||
family: Ceratopsidae
|
||
period: "Late Cretaceous" # hex:{hex_encoded}
|
||
location: "Hell Creek Formation, USA"
|
||
measurements:
|
||
length_m: 9.0
|
||
height_m: 3.0
|
||
mass_kg: 6000
|
||
|
||
- name: "Tyrannosaurus rex"
|
||
classification:
|
||
order: Saurischia
|
||
family: Tyrannosauridae
|
||
period: "Late Cretaceous"
|
||
location: "Western North America"
|
||
measurements:
|
||
length_m: 12.3
|
||
height_m: 4.0
|
||
mass_kg: 8400
|
||
|
||
# Byte reference table (calibration data)
|
||
{chr(10).join(hex_comments)}
|
||
|
||
metadata:
|
||
payload: "{PLINIAN_DIVIDER}"
|
||
source: "ST3GG steganography toolkit"
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.yaml')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(yaml)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 25. PDF with hidden stream
|
||
# =============================================================================
|
||
|
||
def generate_pdf_hidden():
|
||
"""Create a minimal PDF with the Plinian divider hidden in metadata and streams."""
|
||
print(" Generating PDF with hidden stream data...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Build a minimal valid PDF manually
|
||
objects = []
|
||
|
||
# Object 1: Catalog
|
||
objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /Metadata 5 0 R >>\nendobj\n")
|
||
|
||
# Object 2: Pages
|
||
objects.append(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")
|
||
|
||
# Object 3: Page
|
||
page_content = b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << >> >>\nendobj\n"
|
||
objects.append(page_content)
|
||
|
||
# Object 4: Content stream with visible text and hidden comment
|
||
stream_data = (
|
||
f"BT\n/F1 16 Tf\n100 700 Td\n(ST3GG Steganography Example) Tj\n"
|
||
f"0 -30 Td\n/F1 11 Tf\n(This PDF contains hidden data.) Tj\n"
|
||
f"0 -20 Td\n(Look in the metadata and streams...) Tj\nET\n"
|
||
f"% Hidden: {PLINIAN_DIVIDER}\n"
|
||
).encode('utf-8')
|
||
objects.append(f"4 0 obj\n<< /Length {len(stream_data)} >>\nstream\n".encode() +
|
||
stream_data + b"\nendstream\nendobj\n")
|
||
|
||
# Object 5: Metadata stream with hidden Plinian divider
|
||
meta_xml = f'''<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
||
<x:xmpmeta xmlns:x="adobe:ns:meta/">
|
||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||
xmlns:steg="http://st3gg.example.com/ns/">
|
||
<rdf:Description>
|
||
<dc:title>ST3GG Example</dc:title>
|
||
<dc:creator>STEGOSAURUS WRECKS</dc:creator>
|
||
<dc:description>{PLINIAN_DIVIDER}</dc:description>
|
||
<steg:payload>{encoded}</steg:payload>
|
||
<steg:hex>{secret_bytes.hex()}</steg:hex>
|
||
</rdf:Description>
|
||
</rdf:RDF>
|
||
</x:xmpmeta>
|
||
<?xpacket end="w"?>'''.encode('utf-8')
|
||
objects.append(f"5 0 obj\n<< /Type /Metadata /Subtype /XML /Length {len(meta_xml)} >>\nstream\n".encode() +
|
||
meta_xml + b"\nendstream\nendobj\n")
|
||
|
||
# Object 6: Info dictionary
|
||
objects.append(f'6 0 obj\n<< /Title (ST3GG Example) /Author (STEGOSAURUS WRECKS) /Subject ({PLINIAN_DIVIDER}) /Producer (ST3GG v3.0) /Keywords ({encoded}) >>\nendobj\n'.encode('utf-8'))
|
||
|
||
# Build the PDF
|
||
pdf = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
|
||
offsets = []
|
||
for obj in objects:
|
||
offsets.append(len(pdf))
|
||
pdf += obj
|
||
|
||
# Cross-reference table
|
||
xref_offset = len(pdf)
|
||
pdf += b"xref\n"
|
||
pdf += f"0 {len(objects) + 1}\n".encode()
|
||
pdf += b"0000000000 65535 f \n"
|
||
for offset in offsets:
|
||
pdf += f"{offset:010d} 00000 n \n".encode()
|
||
|
||
# Trailer
|
||
pdf += f"trailer\n<< /Size {len(objects) + 1} /Root 1 0 R /Info 6 0 R >>\n".encode()
|
||
pdf += f"startxref\n{xref_offset}\n%%EOF\n".encode()
|
||
|
||
# Append hidden data after EOF (common steganography technique)
|
||
pdf += f"\n% HIDDEN AFTER EOF: {PLINIAN_DIVIDER}\n".encode('utf-8')
|
||
pdf += secret_bytes
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.pdf')
|
||
with open(path, 'wb') as f:
|
||
f.write(pdf)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 26. RTF with hidden groups
|
||
# =============================================================================
|
||
|
||
def generate_rtf_hidden():
|
||
"""Create an RTF file with the Plinian divider in hidden text and metadata."""
|
||
print(" Generating RTF with hidden groups...")
|
||
|
||
# RTF hex encoding of secret
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
hex_chars = ''.join(f"\\'{b:02x}" for b in secret_bytes)
|
||
|
||
rtf = (
|
||
r"{\rtf1\ansi\deff0"
|
||
r"{\fonttbl{\f0 Courier New;}}"
|
||
r"{\colortbl;\red0\green0\blue0;\red200\green100\blue255;}"
|
||
"\n"
|
||
# Info group with hidden payload
|
||
r"{\info"
|
||
r"{\title ST3GG Steganography Example}"
|
||
r"{\author STEGOSAURUS WRECKS}"
|
||
r"{\subject " + PLINIAN_DIVIDER + r"}"
|
||
r"{\doccomm Hidden payload: " + secret_bytes.hex() + r"}"
|
||
r"}" "\n"
|
||
# Visible content
|
||
r"\f0\fs22\cf1 "
|
||
r"{\b ST3GG Steganography Example}\par\par"
|
||
r"This RTF document contains hidden data in multiple locations:\par"
|
||
r"- Document info/metadata fields\par"
|
||
r"- Hidden text runs (\\v flag)\par"
|
||
r"- Hex-encoded byte sequences\par\par"
|
||
r"The secret is concealed using RTF's native formatting capabilities.\par\par"
|
||
# Hidden text (invisible in most readers with \v flag)
|
||
r"{\v " + hex_chars + r"}"
|
||
r"{\v PLINIAN DIVIDER ENCODED ABOVE}"
|
||
"\n"
|
||
# Bookmark with hidden data
|
||
r"{\*\bkmkstart steg_payload}" + PLINIAN_DIVIDER + r"{\*\bkmkend steg_payload}"
|
||
"\n"
|
||
r"}"
|
||
)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.rtf')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(rtf)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 27. Markdown with HTML comment encoding
|
||
# =============================================================================
|
||
|
||
def generate_markdown_hidden():
|
||
"""Create a Markdown file with the Plinian divider in HTML comments and zero-width chars."""
|
||
print(" Generating Markdown with hidden content...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Zero-width encoding
|
||
ZWSP = '\u200B'
|
||
ZWNJ = '\u200C'
|
||
ZWJ = '\u200D'
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
zw_string = ZWJ
|
||
for bit in binary_str:
|
||
zw_string += ZWSP if bit == '0' else ZWNJ
|
||
zw_string += ZWJ
|
||
|
||
md = f'''# Steganography: A Brief History
|
||
|
||
<!-- {PLINIAN_DIVIDER} -->
|
||
|
||
{zw_string}Steganography, from the Greek words *steganos* (covered) and *graphein*
|
||
(to write), is the practice of concealing messages within other non-secret data.
|
||
|
||
## Ancient Origins
|
||
|
||
The earliest recorded use of steganography dates back to **440 BC**, when
|
||
Histiaeus shaved a slave's head, tattooed a message on his scalp, and waited
|
||
for the hair to regrow before sending him as a messenger.
|
||
|
||
<!-- base64:{encoded} -->
|
||
|
||
## Digital Era
|
||
|
||
Modern digital steganography encompasses a wide range of techniques:
|
||
|
||
| Technique | Medium | Capacity | Stealth |
|
||
|-----------|--------|----------|---------|
|
||
| LSB Embedding | Images | High | Medium |
|
||
| DCT Domain | JPEG | Medium | High |
|
||
| Whitespace | Text | Low | High |
|
||
| Zero-Width | Text | Low | Very High |
|
||
| Audio LSB | Audio | Medium | High |
|
||
| Metadata | Any | Low | Medium |
|
||
|
||
## Tools
|
||
|
||
**ST3GG (STEGOSAURUS WRECKS)** is an advanced steganography toolkit supporting
|
||
15 channel presets, 8 bit depths, and 4 encoding strategies.
|
||
|
||
<!-- hex:{hex_encoded} -->
|
||
|
||
[comment]: # ({PLINIAN_DIVIDER})
|
||
|
||
---
|
||
*Generated by STEGOSAURUS WRECKS v3.0*
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.md')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(md)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 28. AIFF with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_aiff_lsb():
|
||
"""Create an AIFF audio file with the Plinian divider hidden in sample LSBs."""
|
||
print(" Generating AIFF with LSB steganography...")
|
||
import math
|
||
|
||
sample_rate = 22050
|
||
duration = 2 # seconds
|
||
num_samples = sample_rate * duration
|
||
num_channels = 1
|
||
bits_per_sample = 16
|
||
|
||
# Generate a tone
|
||
frequency = 523.25 # C5
|
||
samples = []
|
||
for i in range(num_samples):
|
||
t = i / sample_rate
|
||
value = 0.6 * math.sin(2 * math.pi * frequency * t)
|
||
value += 0.2 * math.sin(2 * math.pi * frequency * 2 * t)
|
||
sample = int(value * 16000)
|
||
sample = max(-32768, min(32767, sample))
|
||
samples.append(sample)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg))
|
||
payload = length_bytes + msg
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
for i, bit in enumerate(bits):
|
||
if i < len(samples):
|
||
s = samples[i]
|
||
u = s & 0xFFFF
|
||
u = (u & 0xFFFE) | bit
|
||
samples[i] = u if u < 32768 else u - 65536
|
||
|
||
# Build AIFF file manually (big-endian)
|
||
# AIFF uses big-endian 16-bit samples
|
||
sample_data = struct.pack(f'>{len(samples)}h', *samples)
|
||
|
||
# IEEE 754 80-bit extended for sample rate
|
||
def float_to_extended(val):
|
||
"""Convert float to 80-bit IEEE 754 extended precision (big-endian)."""
|
||
if val == 0:
|
||
return b'\x00' * 10
|
||
import math as m
|
||
sign = 0
|
||
if val < 0:
|
||
sign = 1
|
||
val = -val
|
||
exp = int(m.floor(m.log2(val)))
|
||
mantissa = val / (2 ** exp)
|
||
# Bias is 16383
|
||
biased_exp = exp + 16383
|
||
# Mantissa: 64-bit integer with explicit integer bit
|
||
mant_int = int(mantissa * (2 ** 63))
|
||
result = struct.pack('>HQ', (sign << 15) | (biased_exp & 0x7FFF), mant_int)
|
||
return result
|
||
|
||
# COMM chunk
|
||
comm = b'COMM'
|
||
comm_data = struct.pack('>hI', num_channels, num_samples)
|
||
comm_data += struct.pack('>h', bits_per_sample)
|
||
comm_data += float_to_extended(sample_rate)
|
||
comm += struct.pack('>I', len(comm_data)) + comm_data
|
||
|
||
# SSND chunk
|
||
ssnd_data = struct.pack('>II', 0, 0) + sample_data # offset + block size + data
|
||
ssnd = b'SSND' + struct.pack('>I', len(ssnd_data)) + ssnd_data
|
||
|
||
# ANNO chunk (annotation) with hidden message
|
||
anno_text = f"Generated by ST3GG - {PLINIAN_DIVIDER}".encode('utf-8')
|
||
if len(anno_text) % 2 != 0:
|
||
anno_text += b'\x00'
|
||
anno = b'ANNO' + struct.pack('>I', len(anno_text)) + anno_text
|
||
|
||
# FORM header
|
||
form_data = b'AIFF' + comm + anno + ssnd
|
||
aiff = b'FORM' + struct.pack('>I', len(form_data)) + form_data
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.aiff')
|
||
with open(path, 'wb') as f:
|
||
f.write(aiff)
|
||
print(f" -> {path} ({len(bits)} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 29. AU (Sun/NeXT) audio with LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_au_lsb():
|
||
"""Create an AU audio file with the Plinian divider hidden in sample LSBs."""
|
||
print(" Generating AU with LSB steganography...")
|
||
import math
|
||
|
||
sample_rate = 22050
|
||
duration = 2
|
||
num_samples = sample_rate * duration
|
||
|
||
# Generate a different tone (A4 = 440Hz)
|
||
frequency = 440.0
|
||
samples = []
|
||
for i in range(num_samples):
|
||
t = i / sample_rate
|
||
value = 0.5 * math.sin(2 * math.pi * frequency * t)
|
||
value += 0.25 * math.sin(2 * math.pi * frequency * 3 * t) # Add 3rd harmonic
|
||
sample = int(value * 16000)
|
||
sample = max(-32768, min(32767, sample))
|
||
samples.append(sample)
|
||
|
||
# Encode Plinian divider in LSB
|
||
msg = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg))
|
||
payload = length_bytes + msg
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
for i, bit in enumerate(bits):
|
||
if i < len(samples):
|
||
s = samples[i]
|
||
u = s & 0xFFFF
|
||
u = (u & 0xFFFE) | bit
|
||
samples[i] = u if u < 32768 else u - 65536
|
||
|
||
# AU format: big-endian
|
||
annotation = PLINIAN_DIVIDER.encode('utf-8')
|
||
# Pad annotation to multiple of 4
|
||
while len(annotation) % 4 != 0:
|
||
annotation += b'\x00'
|
||
header_size = 24 + len(annotation)
|
||
|
||
sample_data = struct.pack(f'>{len(samples)}h', *samples)
|
||
|
||
au = b'.snd' # Magic
|
||
au += struct.pack('>I', header_size) # Data offset
|
||
au += struct.pack('>I', len(sample_data)) # Data size
|
||
au += struct.pack('>I', 3) # Encoding: 16-bit linear PCM
|
||
au += struct.pack('>I', sample_rate)
|
||
au += struct.pack('>I', 1) # Channels
|
||
au += annotation
|
||
au += sample_data
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb.au')
|
||
with open(path, 'wb') as f:
|
||
f.write(au)
|
||
print(f" -> {path} ({len(bits)} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 30. ZIP with comment steganography
|
||
# =============================================================================
|
||
|
||
def generate_zip_hidden():
|
||
"""Create a ZIP archive with the Plinian divider hidden in the archive comment."""
|
||
print(" Generating ZIP with comment steganography...")
|
||
import zipfile
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.zip')
|
||
|
||
with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||
# Add some innocent-looking files
|
||
zf.writestr('dinosaurs/stegosaurus.txt',
|
||
'Stegosaurus stenops\nLate Jurassic\nMorrison Formation\n')
|
||
zf.writestr('dinosaurs/triceratops.txt',
|
||
'Triceratops horridus\nLate Cretaceous\nHell Creek Formation\n')
|
||
zf.writestr('README.txt',
|
||
'Paleontology specimen data archive.\nGenerated by ST3GG.\n')
|
||
|
||
# Hidden: Plinian divider as archive comment
|
||
zf.comment = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Also append hidden data after the ZIP end
|
||
with open(path, 'ab') as f:
|
||
f.write(b'\n--- HIDDEN AFTER ZIP ---\n')
|
||
f.write(PLINIAN_DIVIDER.encode('utf-8'))
|
||
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 31. TAR with extended headers
|
||
# =============================================================================
|
||
|
||
def generate_tar_hidden():
|
||
"""Create a TAR archive with the Plinian divider in extended pax headers."""
|
||
print(" Generating TAR with extended header steganography...")
|
||
import tarfile
|
||
import io
|
||
import time
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.tar')
|
||
|
||
with tarfile.open(path, 'w', format=tarfile.PAX_FORMAT) as tf:
|
||
# Add a file with pax headers containing the secret
|
||
data = b'Stegosaurus Facts\nLate Jurassic, 155-150 MYA\n'
|
||
info = tarfile.TarInfo(name='specimens/stegosaurus.txt')
|
||
info.size = len(data)
|
||
info.mtime = int(time.time())
|
||
info.pax_headers = {
|
||
'comment': PLINIAN_DIVIDER,
|
||
'STEG.payload': PLINIAN_DIVIDER.encode('utf-8').hex(),
|
||
'STEG.generator': 'STEGOSAURUS WRECKS v3.0',
|
||
}
|
||
tf.addfile(info, io.BytesIO(data))
|
||
|
||
# Second file
|
||
data2 = b'Ankylosaurus Facts\nLate Cretaceous, 68-66 MYA\n'
|
||
info2 = tarfile.TarInfo(name='specimens/ankylosaurus.txt')
|
||
info2.size = len(data2)
|
||
info2.mtime = int(time.time())
|
||
tf.addfile(info2, io.BytesIO(data2))
|
||
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 32. GZip with extra field steganography
|
||
# =============================================================================
|
||
|
||
def generate_gzip_hidden():
|
||
"""Create a GZip file with the Plinian divider in the extra field and comment."""
|
||
print(" Generating GZip with extra field steganography...")
|
||
|
||
content = (
|
||
"Steganography Techniques Reference\n"
|
||
"===================================\n\n"
|
||
"LSB Embedding: Hide data in least significant bits of pixel values.\n"
|
||
"DCT Domain: Embed in frequency-domain coefficients for JPEG survival.\n"
|
||
"Metadata: Store secrets in file metadata fields.\n"
|
||
"Appended Data: Add data after file end markers.\n"
|
||
"Whitespace: Encode in trailing spaces and tabs.\n"
|
||
"Zero-Width: Use invisible Unicode characters.\n"
|
||
).encode('utf-8')
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Build gzip manually to include FEXTRA and FCOMMENT fields
|
||
# Gzip header
|
||
gz = b'\x1f\x8b' # Magic
|
||
gz += b'\x08' # Compression: deflate
|
||
flags = 0x04 | 0x10 # FEXTRA | FCOMMENT
|
||
gz += bytes([flags])
|
||
gz += struct.pack('<I', int(__import__('time').time())) # mtime
|
||
gz += b'\x00' # Extra flags
|
||
gz += b'\xff' # OS: unknown
|
||
|
||
# FEXTRA field containing the secret
|
||
extra_data = b'ST' + struct.pack('<H', len(secret)) + secret # subfield ID + len + data
|
||
gz += struct.pack('<H', len(extra_data))
|
||
gz += extra_data
|
||
|
||
# FCOMMENT (null-terminated)
|
||
gz += PLINIAN_DIVIDER.encode('latin-1', errors='replace') + b'\x00'
|
||
|
||
# Compressed data
|
||
compressed = zlib.compress(content, 9)[2:-4] # Strip zlib header/trailer for raw deflate
|
||
gz += compressed
|
||
|
||
# CRC32 and size
|
||
gz += struct.pack('<I', zlib.crc32(content) & 0xFFFFFFFF)
|
||
gz += struct.pack('<I', len(content) & 0xFFFFFFFF)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.gz')
|
||
with open(path, 'wb') as f:
|
||
f.write(gz)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 33. SQLite database with hidden data
|
||
# =============================================================================
|
||
|
||
def generate_sqlite_hidden():
|
||
"""Create a SQLite database with the Plinian divider in hidden tables and metadata."""
|
||
print(" Generating SQLite with hidden data...")
|
||
import sqlite3
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.sqlite')
|
||
if os.path.exists(path):
|
||
os.remove(path)
|
||
|
||
conn = sqlite3.connect(path)
|
||
c = conn.cursor()
|
||
|
||
# Create a visible table
|
||
c.execute('''CREATE TABLE specimens (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
period TEXT,
|
||
length_m REAL,
|
||
mass_kg REAL,
|
||
diet TEXT
|
||
)''')
|
||
specimens = [
|
||
(1, 'Stegosaurus', 'Late Jurassic', 9.0, 5000, 'Herbivore'),
|
||
(2, 'Triceratops', 'Late Cretaceous', 9.0, 6000, 'Herbivore'),
|
||
(3, 'Tyrannosaurus', 'Late Cretaceous', 12.3, 8400, 'Carnivore'),
|
||
(4, 'Velociraptor', 'Late Cretaceous', 2.0, 15, 'Carnivore'),
|
||
(5, 'Brachiosaurus', 'Late Jurassic', 26.0, 56000, 'Herbivore'),
|
||
]
|
||
c.executemany('INSERT INTO specimens VALUES (?,?,?,?,?,?)', specimens)
|
||
|
||
# Hidden table with the Plinian divider
|
||
c.execute('''CREATE TABLE _steg_payload (
|
||
key TEXT PRIMARY KEY,
|
||
value TEXT
|
||
)''')
|
||
import base64
|
||
c.execute('INSERT INTO _steg_payload VALUES (?, ?)',
|
||
('divider', PLINIAN_DIVIDER))
|
||
c.execute('INSERT INTO _steg_payload VALUES (?, ?)',
|
||
('divider_b64', base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()))
|
||
c.execute('INSERT INTO _steg_payload VALUES (?, ?)',
|
||
('divider_hex', PLINIAN_DIVIDER.encode('utf-8').hex()))
|
||
|
||
# Also set the application_id to encode part of the message
|
||
c.execute(f"PRAGMA application_id = {0x53544547}") # 'STEG' in hex
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 34. Raw hex dump file
|
||
# =============================================================================
|
||
|
||
def generate_hexdump_hidden():
|
||
"""Create a hex dump file with the Plinian divider hidden in the ASCII column."""
|
||
print(" Generating hex dump with hidden data...")
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Create a fake hex dump of "random" data that contains the secret
|
||
# embedded within the raw bytes
|
||
import random
|
||
random.seed(42)
|
||
|
||
# Generate surrounding data with secret embedded at offset 0x40
|
||
total_size = 256
|
||
data = bytearray(random.getrandbits(8) for _ in range(total_size))
|
||
|
||
# Embed secret at offset 0x40
|
||
embed_offset = 0x40
|
||
for i, b in enumerate(secret):
|
||
if embed_offset + i < total_size:
|
||
data[embed_offset + i] = b
|
||
|
||
# Format as hex dump
|
||
lines = []
|
||
lines.append("# ST3GG Hex Dump - Memory Analysis Output")
|
||
lines.append("# Generated by STEGOSAURUS WRECKS v3.0")
|
||
lines.append(f"# Payload offset: 0x{embed_offset:04x}")
|
||
lines.append(f"# Payload size: {len(secret)} bytes")
|
||
lines.append("")
|
||
|
||
for offset in range(0, len(data), 16):
|
||
chunk = data[offset:offset + 16]
|
||
hex_part = ' '.join(f'{b:02x}' for b in chunk)
|
||
hex_part = hex_part.ljust(47)
|
||
ascii_part = ''.join(chr(b) if 32 <= b < 127 else '.' for b in chunk)
|
||
lines.append(f"{offset:08x} {hex_part} |{ascii_part}|")
|
||
|
||
lines.append("")
|
||
lines.append(f"# Total: {len(data)} bytes")
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.hexdump')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(lines))
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 35. MIDI with SysEx steganography
|
||
# =============================================================================
|
||
|
||
def generate_midi_hidden():
|
||
"""Create a MIDI file with the Plinian divider hidden in SysEx messages."""
|
||
print(" Generating MIDI with SysEx steganography...")
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
# MIDI SysEx can only carry 7-bit data, so encode as pairs
|
||
sysex_data = bytearray()
|
||
for b in secret:
|
||
sysex_data.append(b >> 4) # High nibble (always < 16, so 7-bit safe)
|
||
sysex_data.append(b & 0x0F) # Low nibble
|
||
|
||
def var_len(value):
|
||
"""Encode a variable-length quantity for MIDI."""
|
||
result = bytearray()
|
||
result.append(value & 0x7F)
|
||
value >>= 7
|
||
while value > 0:
|
||
result.append((value & 0x7F) | 0x80)
|
||
value >>= 7
|
||
result.reverse()
|
||
return bytes(result)
|
||
|
||
# Build MIDI file
|
||
# Header chunk
|
||
header = b'MThd'
|
||
header += struct.pack('>I', 6) # Chunk length
|
||
header += struct.pack('>HHH', 0, 1, 480) # Format 0, 1 track, 480 ticks/beat
|
||
|
||
# Track chunk
|
||
track_data = bytearray()
|
||
|
||
# Tempo: 120 BPM (500000 microseconds per beat)
|
||
track_data += b'\x00\xFF\x51\x03\x07\xA1\x20'
|
||
|
||
# Text event with the divider
|
||
text = f"ST3GG: {PLINIAN_DIVIDER}".encode('utf-8')
|
||
track_data += b'\x00\xFF\x01' + var_len(len(text)).encode('latin-1') if False else b''
|
||
track_data += b'\x00\xFF\x01'
|
||
track_data += bytes(var_len(len(text)))
|
||
track_data += text
|
||
|
||
# SysEx message containing encoded secret
|
||
# Manufacturer ID 0x7D = non-commercial/educational use
|
||
sysex_payload = bytes([0x7D]) + bytes(sysex_data)
|
||
track_data += b'\x00\xF0'
|
||
track_data += bytes(var_len(len(sysex_payload) + 1)) # +1 for F7 terminator
|
||
track_data += sysex_payload
|
||
track_data += b'\xF7'
|
||
|
||
# Play a simple melody (C major scale)
|
||
notes = [60, 62, 64, 65, 67, 69, 71, 72] # C4 to C5
|
||
for note in notes:
|
||
track_data += b'\x00' # Delta time
|
||
track_data += bytes([0x90, note, 0x50]) # Note On, velocity 80
|
||
track_data += bytes(var_len(480)) # Wait 1 beat
|
||
track_data += bytes([0x80, note, 0x40]) # Note Off
|
||
|
||
# End of track
|
||
track_data += b'\x00\xFF\x2F\x00'
|
||
|
||
track = b'MTrk' + struct.pack('>I', len(track_data)) + bytes(track_data)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.mid')
|
||
with open(path, 'wb') as f:
|
||
f.write(header + track)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 36. PCAP-like network capture with hidden data
|
||
# =============================================================================
|
||
|
||
def generate_pcap_hidden():
|
||
"""Create a PCAP file with the Plinian divider hidden in packet payloads."""
|
||
print(" Generating PCAP with hidden packet data...")
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
import time
|
||
|
||
# PCAP global header
|
||
pcap = struct.pack('<IHHiIII',
|
||
0xa1b2c3d4, # Magic
|
||
2, 4, # Version 2.4
|
||
0, # Timezone (GMT)
|
||
0, # Sigfigs
|
||
65535, # Snaplen
|
||
1, # Link type: Ethernet
|
||
)
|
||
|
||
def make_packet(src_ip, dst_ip, payload, ts_sec):
|
||
"""Build a simple UDP packet wrapped in Ethernet + IP headers."""
|
||
# UDP header
|
||
src_port, dst_port = 12345, 53
|
||
udp_len = 8 + len(payload)
|
||
udp = struct.pack('>HHHH', src_port, dst_port, udp_len, 0)
|
||
udp += payload
|
||
|
||
# IP header (simplified, no checksum)
|
||
ip_len = 20 + len(udp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, # Version, IHL, TOS, Total length
|
||
0x1234, 0x4000, # ID, Flags+Fragment
|
||
64, 17, 0, # TTL, Protocol (UDP), Checksum
|
||
bytes(map(int, src_ip.split('.'))),
|
||
bytes(map(int, dst_ip.split('.'))),
|
||
)
|
||
|
||
# Ethernet header
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00' # dst + src + type (IP)
|
||
frame = eth + ip_header + udp
|
||
|
||
# PCAP packet header
|
||
pkt_len = len(frame)
|
||
pkt_header = struct.pack('<IIII', ts_sec, 0, pkt_len, pkt_len)
|
||
return pkt_header + frame
|
||
|
||
ts = int(time.time())
|
||
# Normal-looking DNS query packets, with the secret split across them
|
||
chunk_size = len(secret) // 4 + 1
|
||
packets = []
|
||
for i in range(4):
|
||
chunk = secret[i * chunk_size:(i + 1) * chunk_size]
|
||
if chunk:
|
||
packets.append(make_packet(
|
||
f'192.168.1.{10 + i}', '8.8.8.8',
|
||
chunk, ts + i
|
||
))
|
||
# Also include the full secret in one packet
|
||
packets.append(make_packet('10.0.0.1', '10.0.0.2', secret, ts + 5))
|
||
|
||
for pkt in packets:
|
||
pcap += pkt
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 37. Python with steganographic comments
|
||
# =============================================================================
|
||
|
||
def generate_python_hidden():
|
||
"""Create a Python file with the Plinian divider hidden in comments and docstrings."""
|
||
print(" Generating Python with hidden comments...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Zero-width encoding
|
||
ZWSP = '\u200B'
|
||
ZWNJ = '\u200C'
|
||
ZWJ = '\u200D'
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
zw_string = ZWJ + ''.join(ZWSP if bit == '0' else ZWNJ for bit in binary_str) + ZWJ
|
||
|
||
# Encode each byte as a hex comment spread across the file
|
||
byte_comments = [f" # cfg[{i}] = 0x{b:02x}" for i, b in enumerate(secret_bytes)]
|
||
|
||
py = f'''#!/usr/bin/env python3
|
||
"""
|
||
Paleontology Specimen Analyzer
|
||
{zw_string}
|
||
A tool for analyzing dinosaur fossil measurements and classifications.
|
||
"""
|
||
|
||
# {PLINIAN_DIVIDER}
|
||
# Configuration hash: {hex_encoded}
|
||
|
||
import math
|
||
from dataclasses import dataclass
|
||
from typing import Optional
|
||
|
||
|
||
@dataclass
|
||
class Specimen:
|
||
"""Represents a dinosaur specimen record."""
|
||
name: str
|
||
period: str
|
||
length_m: float
|
||
mass_kg: float
|
||
diet: str
|
||
brain_volume_cc: Optional[float] = None
|
||
|
||
|
||
# Calibration byte table (do not modify)
|
||
{chr(10).join(byte_comments)}
|
||
|
||
|
||
def calculate_brain_body_ratio(specimen: Specimen) -> float:
|
||
"""Calculate the encephalization quotient (EQ) for a specimen."""
|
||
if specimen.brain_volume_cc is None:
|
||
return 0.0
|
||
# EQ = brain mass / (0.12 * body_mass^0.67)
|
||
expected = 0.12 * (specimen.mass_kg ** 0.67)
|
||
return specimen.brain_volume_cc / expected
|
||
|
||
|
||
def classify_intelligence(eq: float) -> str:
|
||
"""Classify relative intelligence based on EQ."""
|
||
if eq > 1.0:
|
||
return "above average"
|
||
elif eq > 0.5:
|
||
return "average"
|
||
else:
|
||
return "below average"
|
||
|
||
|
||
# Payload verification: {encoded}
|
||
SPECIMENS = [
|
||
Specimen("Stegosaurus", "Late Jurassic", 9.0, 5000, "Herbivore", 2.8),
|
||
Specimen("Triceratops", "Late Cretaceous", 9.0, 6000, "Herbivore", 70.0),
|
||
Specimen("T. rex", "Late Cretaceous", 12.3, 8400, "Carnivore", 343.0),
|
||
Specimen("Velociraptor", "Late Cretaceous", 2.0, 15, "Carnivore", 15.0),
|
||
Specimen("Brachiosaurus", "Late Jurassic", 26.0, 56000, "Herbivore", 26.0),
|
||
]
|
||
|
||
|
||
def main():
|
||
"""Analyze specimens and print results."""
|
||
print("Paleontology Specimen Analysis")
|
||
print("=" * 50)
|
||
for spec in SPECIMENS:
|
||
eq = calculate_brain_body_ratio(spec)
|
||
intel = classify_intelligence(eq)
|
||
print(f" {{spec.name:20s}} EQ={{eq:.3f}} ({{intel}})")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.py')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(py)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 38. JavaScript with zero-width characters
|
||
# =============================================================================
|
||
|
||
def generate_js_hidden():
|
||
"""Create a JavaScript file with the Plinian divider in zero-width chars."""
|
||
print(" Generating JavaScript with zero-width steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Zero-width encoding
|
||
ZWSP = '\u200B'
|
||
ZWNJ = '\u200C'
|
||
ZWJ = '\u200D'
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
zw_string = ZWJ + ''.join(ZWSP if bit == '0' else ZWNJ for bit in binary_str) + ZWJ
|
||
|
||
js = f'''// Dinosaur Specimen Database Module
|
||
// {zw_string}
|
||
|
||
/**
|
||
* @module specimen-db
|
||
* @description Paleontology specimen management
|
||
* @version 3.0.0
|
||
* @license MIT
|
||
*/
|
||
|
||
// {PLINIAN_DIVIDER}
|
||
|
||
const SPECIMENS = [
|
||
{{ name: "Stegosaurus",{zw_string} period: "Late Jurassic", lengthM: 9.0, massKg: 5000 }},
|
||
{{ name: "Triceratops", period: "Late Cretaceous", lengthM: 9.0, massKg: 6000 }},
|
||
{{ name: "Tyrannosaurus", period: "Late Cretaceous", lengthM: 12.3, massKg: 8400 }},
|
||
{{ name: "Velociraptor", period: "Late Cretaceous", lengthM: 2.0, massKg: 15 }},
|
||
{{ name: "Brachiosaurus", period: "Late Jurassic", lengthM: 26.0, massKg: 56000 }},
|
||
];
|
||
|
||
// Internal config hash: {hex_encoded}
|
||
|
||
/**
|
||
* Calculate body mass index for a specimen
|
||
* @param {{Object}} specimen - The specimen data
|
||
* @returns {{number}} BMI estimate
|
||
*/
|
||
function calculateBMI(specimen) {{
|
||
return specimen.massKg / (specimen.lengthM ** 2);
|
||
}}
|
||
|
||
/**
|
||
* Search specimens by period
|
||
* @param {{string}} period - Geological period name
|
||
* @returns {{Array}} Matching specimens
|
||
*/
|
||
function findByPeriod(period) {{
|
||
return SPECIMENS.filter(s => s.period.includes(period));
|
||
}}
|
||
|
||
// Verification: {encoded}
|
||
|
||
function printReport() {{
|
||
console.log("Specimen Analysis Report");
|
||
console.log("=".repeat(50));
|
||
for (const spec of SPECIMENS) {{
|
||
const bmi = calculateBMI(spec).toFixed(1);
|
||
console.log(` ${{spec.name.padEnd(20)}} BMI=${{bmi}} (${{spec.period}})`);
|
||
}}
|
||
}}
|
||
|
||
if (typeof module !== "undefined") {{
|
||
module.exports = {{ SPECIMENS, calculateBMI, findByPeriod, printReport }};
|
||
}}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.js')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(js)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 39. C source with steganographic comments
|
||
# =============================================================================
|
||
|
||
def generate_c_hidden():
|
||
"""Create a C source file with the Plinian divider hidden in comments."""
|
||
print(" Generating C source with hidden comments...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Build a C array of the secret bytes
|
||
c_array = ', '.join(f'0x{b:02x}' for b in hex_bytes)
|
||
|
||
c_src = f'''/*
|
||
* specimen_analyzer.c - Paleontology Specimen Analysis Tool
|
||
* Generated by STEGOSAURUS WRECKS v3.0
|
||
*
|
||
* {PLINIAN_DIVIDER}
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <math.h>
|
||
|
||
/* Calibration data (do not modify) */
|
||
static const unsigned char _cal_data[] = {{
|
||
{c_array}
|
||
}};
|
||
static const size_t _cal_len = sizeof(_cal_data);
|
||
|
||
/* b64: {encoded} */
|
||
|
||
typedef struct {{
|
||
const char *name;
|
||
const char *period;
|
||
double length_m;
|
||
double mass_kg;
|
||
const char *diet;
|
||
}} Specimen;
|
||
|
||
static const Specimen specimens[] = {{
|
||
{{"Stegosaurus", "Late Jurassic", 9.0, 5000.0, "Herbivore"}},
|
||
{{"Triceratops", "Late Cretaceous", 9.0, 6000.0, "Herbivore"}},
|
||
{{"Tyrannosaurus", "Late Cretaceous", 12.3, 8400.0, "Carnivore"}},
|
||
{{"Velociraptor", "Late Cretaceous", 2.0, 15.0, "Carnivore"}},
|
||
{{"Brachiosaurus", "Late Jurassic", 26.0, 56000.0, "Herbivore"}},
|
||
}};
|
||
|
||
#define NUM_SPECIMENS (sizeof(specimens) / sizeof(specimens[0]))
|
||
|
||
double calculate_density(const Specimen *s) {{
|
||
/* Rough cylindrical body model */
|
||
double radius = s->length_m / 6.0;
|
||
double volume = M_PI * radius * radius * s->length_m;
|
||
return s->mass_kg / volume;
|
||
}}
|
||
|
||
void print_analysis(void) {{
|
||
printf("Specimen Density Analysis\\n");
|
||
printf("%-20s %-18s %8s %10s\\n",
|
||
"Name", "Period", "Mass(kg)", "Density");
|
||
printf("%-20s %-18s %8s %10s\\n",
|
||
"----", "------", "--------", "-------");
|
||
|
||
for (size_t i = 0; i < NUM_SPECIMENS; i++) {{
|
||
double d = calculate_density(&specimens[i]);
|
||
printf("%-20s %-18s %8.0f %10.1f\\n",
|
||
specimens[i].name, specimens[i].period,
|
||
specimens[i].mass_kg, d);
|
||
}}
|
||
}}
|
||
|
||
int main(void) {{
|
||
print_analysis();
|
||
/* Verify calibration data integrity */
|
||
if (_cal_len > 0 && _cal_data[0] != 0) {{
|
||
/* Calibration OK */
|
||
}}
|
||
return 0;
|
||
}}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.c')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(c_src)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 40. CSS with invisible selectors and comments
|
||
# =============================================================================
|
||
|
||
def generate_css_hidden():
|
||
"""Create a CSS file with the Plinian divider in comments and data URIs."""
|
||
print(" Generating CSS with hidden selectors...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Zero-width encoding
|
||
ZWSP = '\u200B'
|
||
ZWNJ = '\u200C'
|
||
ZWJ = '\u200D'
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
binary_str = ''.join(format(b, '08b') for b in secret_bytes)
|
||
zw_string = ZWJ + ''.join(ZWSP if bit == '0' else ZWNJ for bit in binary_str) + ZWJ
|
||
|
||
css = f'''/*
|
||
* ST3GG Steganography Theme
|
||
* {PLINIAN_DIVIDER}
|
||
* Generated by STEGOSAURUS WRECKS v3.0
|
||
*/
|
||
|
||
/* hex:{hex_encoded} */
|
||
|
||
:root {{
|
||
--bg-primary: #1a1a2e;
|
||
--bg-secondary: #16213e;
|
||
--text-primary: #e0e0e0;
|
||
--text-secondary: #a0a0b0;
|
||
--accent-purple: #c471ed;
|
||
--accent-blue: #12c2e9;
|
||
--accent-orange: #f5af19;
|
||
--accent-red: #f64f59;
|
||
}}
|
||
|
||
/* {zw_string} */
|
||
|
||
* {{
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}}
|
||
|
||
body {{
|
||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
line-height: 1.6;
|
||
}}
|
||
|
||
.container {{
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 2rem;
|
||
}}
|
||
|
||
/* Steganography analysis panel */
|
||
.steg-panel {{
|
||
background: var(--bg-secondary);
|
||
border: 1px solid rgba(196, 113, 237, 0.2);
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
margin-bottom: 1.5rem;
|
||
}}
|
||
|
||
.steg-panel__header {{
|
||
font-size: 1.25rem;
|
||
color: var(--accent-purple);
|
||
margin-bottom: 0.75rem;
|
||
border-bottom: 1px solid rgba(196, 113, 237, 0.15);
|
||
padding-bottom: 0.5rem;
|
||
}}
|
||
|
||
/* b64:{encoded} */
|
||
|
||
.steg-panel__content {{
|
||
font-family: 'Fira Code', 'Consolas', monospace;
|
||
font-size: 0.875rem;
|
||
color: var(--text-secondary);
|
||
}}
|
||
|
||
/* Hidden element - payload carrier */
|
||
[data-steg-payload]::after {{
|
||
content: "{PLINIAN_DIVIDER}";
|
||
display: none;
|
||
visibility: hidden;
|
||
position: absolute;
|
||
width: 0;
|
||
height: 0;
|
||
overflow: hidden;
|
||
}}
|
||
|
||
.btn {{
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1.25rem;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 0.875rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
}}
|
||
|
||
.btn--primary {{
|
||
background: linear-gradient(135deg, var(--accent-purple), var(--accent-blue));
|
||
color: white;
|
||
}}
|
||
|
||
.btn--primary:hover {{
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 15px rgba(196, 113, 237, 0.3);
|
||
}}
|
||
|
||
/* Animation keyframes */
|
||
@keyframes pulse {{
|
||
0%, 100% {{ opacity: 1; }}
|
||
50% {{ opacity: 0.7; }}
|
||
}}
|
||
|
||
.analyzing {{
|
||
animation: pulse 1.5s ease-in-out infinite;
|
||
}}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.css')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(css)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 41. INI/Config with comment encoding
|
||
# =============================================================================
|
||
|
||
def generate_ini_hidden():
|
||
"""Create an INI config file with the Plinian divider in comments."""
|
||
print(" Generating INI with comment steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Encode each byte as a "calibration" comment
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
cal_lines = [f"; cal[{i:02d}] = {b:3d} ; 0x{b:02x}" for i, b in enumerate(secret_bytes)]
|
||
|
||
ini = f'''; ST3GG Configuration File
|
||
; Generated by STEGOSAURUS WRECKS v3.0
|
||
; {PLINIAN_DIVIDER}
|
||
|
||
[general]
|
||
application = ST3GG
|
||
version = 3.0
|
||
description = Steganography Toolkit
|
||
author = STEGOSAURUS WRECKS
|
||
|
||
[encoding]
|
||
default_channels = RGB
|
||
default_bits = 1
|
||
strategy = sequential
|
||
compression = true
|
||
|
||
; Payload: {encoded}
|
||
|
||
[detection]
|
||
exhaustive_mode = true
|
||
auto_detect = true
|
||
max_scan_depth = 11
|
||
timeout_seconds = 30
|
||
|
||
[security]
|
||
encryption = AES-256-GCM
|
||
key_derivation = PBKDF2
|
||
iterations = 100000
|
||
|
||
; hex: {hex_encoded}
|
||
|
||
[output]
|
||
format = PNG
|
||
quality = 100
|
||
preserve_metadata = true
|
||
|
||
; Calibration byte table (system data - do not modify)
|
||
{chr(10).join(cal_lines)}
|
||
|
||
[advanced]
|
||
ghost_mode = false
|
||
matryoshka_layers = 0
|
||
dct_robustness = 2
|
||
; payload_direct = {PLINIAN_DIVIDER}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.ini')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(ini)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 42. Shell script with whitespace encoding
|
||
# =============================================================================
|
||
|
||
def generate_shell_hidden():
|
||
"""Create a shell script with the Plinian divider in comments and whitespace."""
|
||
print(" Generating Shell script with hidden data...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
# Whitespace-encode in trailing spaces/tabs
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_bits + data_bits
|
||
|
||
script_lines = [
|
||
'#!/usr/bin/env bash',
|
||
f'# {PLINIAN_DIVIDER}',
|
||
'# Specimen Analysis Script',
|
||
'# Generated by STEGOSAURUS WRECKS v3.0',
|
||
'',
|
||
f'# Verification: {encoded}',
|
||
'',
|
||
'set -euo pipefail',
|
||
'',
|
||
'readonly SPECIMENS=(',
|
||
' "Stegosaurus:Late Jurassic:9.0:5000:Herbivore"',
|
||
' "Triceratops:Late Cretaceous:9.0:6000:Herbivore"',
|
||
' "Tyrannosaurus:Late Cretaceous:12.3:8400:Carnivore"',
|
||
' "Velociraptor:Late Cretaceous:2.0:15:Carnivore"',
|
||
' "Brachiosaurus:Late Jurassic:26.0:56000:Herbivore"',
|
||
')',
|
||
'',
|
||
'print_header() {',
|
||
' printf "%-20s %-18s %8s %8s\\n" "Name" "Period" "Length" "Mass"',
|
||
' printf "%-20s %-18s %8s %8s\\n" "----" "------" "------" "----"',
|
||
'}',
|
||
'',
|
||
'analyze_specimens() {',
|
||
' for entry in "${SPECIMENS[@]}"; do',
|
||
' IFS=":" read -r name period length mass diet <<< "$entry"',
|
||
' printf "%-20s %-18s %8s %8s\\n" "$name" "$period" "$length" "$mass"',
|
||
' done',
|
||
'}',
|
||
'',
|
||
f'# hex:{hex_encoded}',
|
||
'',
|
||
'main() {',
|
||
' echo "Specimen Analysis Report"',
|
||
' echo "========================"',
|
||
' print_header',
|
||
' analyze_specimens',
|
||
' echo ""',
|
||
' echo "Analysis complete."',
|
||
'}',
|
||
'',
|
||
'main "$@"',
|
||
]
|
||
|
||
bit_index = 0
|
||
result_lines = []
|
||
for line in script_lines:
|
||
trailing = ''
|
||
for _ in range(8):
|
||
if bit_index < len(all_bits):
|
||
trailing += ' ' if all_bits[bit_index] == '0' else '\t'
|
||
bit_index += 1
|
||
result_lines.append(line + trailing)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.sh')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(result_lines))
|
||
print(f" -> {path} ({bit_index} bits in whitespace)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 43. SQL with comment steganography
|
||
# =============================================================================
|
||
|
||
def generate_sql_hidden():
|
||
"""Create a SQL file with the Plinian divider hidden in comments."""
|
||
print(" Generating SQL with comment steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
secret_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Encode each byte as a seemingly-innocent SQL comment
|
||
byte_comments = [f"-- chk[{i:02d}]: {b:3d}" for i, b in enumerate(secret_bytes)]
|
||
|
||
sql = f'''-- =============================================================================
|
||
-- Paleontology Specimen Database Schema
|
||
-- Generated by STEGOSAURUS WRECKS v3.0
|
||
-- {PLINIAN_DIVIDER}
|
||
-- =============================================================================
|
||
|
||
-- hex: {hex_encoded}
|
||
|
||
CREATE TABLE IF NOT EXISTS geological_periods (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL UNIQUE,
|
||
start_mya REAL NOT NULL,
|
||
end_mya REAL NOT NULL
|
||
);
|
||
|
||
INSERT INTO geological_periods (id, name, start_mya, end_mya) VALUES
|
||
(1, 'Late Triassic', 237.0, 201.3),
|
||
(2, 'Early Jurassic', 201.3, 174.1),
|
||
(3, 'Late Jurassic', 163.5, 145.0),
|
||
(4, 'Early Cretaceous', 145.0, 100.5),
|
||
(5, 'Late Cretaceous', 100.5, 66.0);
|
||
|
||
-- b64: {encoded}
|
||
|
||
CREATE TABLE IF NOT EXISTS specimens (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
species TEXT,
|
||
period_id INTEGER REFERENCES geological_periods(id),
|
||
length_m REAL,
|
||
mass_kg REAL,
|
||
diet TEXT CHECK(diet IN ('Herbivore', 'Carnivore', 'Omnivore', 'Piscivore')),
|
||
brain_volume_cc REAL,
|
||
discovery_year INTEGER,
|
||
location TEXT
|
||
);
|
||
|
||
INSERT INTO specimens (id, name, species, period_id, length_m, mass_kg, diet, brain_volume_cc, discovery_year, location) VALUES
|
||
(1, 'Stegosaurus', 'S. stenops', 3, 9.0, 5000, 'Herbivore', 2.8, 1877, 'Morrison Formation, CO'),
|
||
(2, 'Triceratops', 'T. horridus', 5, 9.0, 6000, 'Herbivore', 70.0, 1887, 'Hell Creek, MT'),
|
||
(3, 'Tyrannosaurus', 'T. rex', 5, 12.3, 8400, 'Carnivore', 343.0, 1905, 'Hell Creek, MT'),
|
||
(4, 'Velociraptor', 'V. mongoliensis', 5, 2.0, 15, 'Carnivore', 15.0, 1924, 'Djadokhta, Mongolia'),
|
||
(5, 'Brachiosaurus', 'B. altithorax', 3, 26.0, 56000, 'Herbivore', 26.0, 1903, 'Morrison Formation, CO');
|
||
|
||
-- Checksum verification data
|
||
{chr(10).join(byte_comments)}
|
||
|
||
-- Analysis view
|
||
CREATE VIEW IF NOT EXISTS specimen_analysis AS
|
||
SELECT
|
||
s.name,
|
||
s.species,
|
||
gp.name AS period,
|
||
s.length_m,
|
||
s.mass_kg,
|
||
s.diet,
|
||
CASE
|
||
WHEN s.brain_volume_cc / (0.12 * POWER(s.mass_kg, 0.67)) > 1.0 THEN 'High EQ'
|
||
WHEN s.brain_volume_cc / (0.12 * POWER(s.mass_kg, 0.67)) > 0.5 THEN 'Medium EQ'
|
||
ELSE 'Low EQ'
|
||
END AS intelligence_class
|
||
FROM specimens s
|
||
JOIN geological_periods gp ON s.period_id = gp.id;
|
||
|
||
-- End of schema
|
||
-- Payload: {PLINIAN_DIVIDER}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.sql')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(sql)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 44. LaTeX with comment encoding
|
||
# =============================================================================
|
||
|
||
def generate_latex_hidden():
|
||
"""Create a LaTeX file with the Plinian divider hidden in comments."""
|
||
print(" Generating LaTeX with comment steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
latex = f'''%% =============================================================================
|
||
%% Paleontology Specimen Report
|
||
%% Generated by STEGOSAURUS WRECKS v3.0
|
||
%% {PLINIAN_DIVIDER}
|
||
%% =============================================================================
|
||
|
||
\\documentclass[12pt]{{article}}
|
||
\\usepackage[utf8]{{inputenc}}
|
||
\\usepackage{{booktabs}}
|
||
\\usepackage{{graphicx}}
|
||
\\usepackage{{amsmath}}
|
||
|
||
%% hex: {hex_encoded}
|
||
|
||
\\title{{Comparative Analysis of Ornithischian Dinosaurs}}
|
||
\\author{{STEGOSAURUS WRECKS Research Division}}
|
||
\\date{{Late Jurassic -- Late Cretaceous}}
|
||
|
||
\\begin{{document}}
|
||
|
||
\\maketitle
|
||
|
||
%% b64: {encoded}
|
||
|
||
\\begin{{abstract}}
|
||
This report presents a comparative analysis of ornithischian dinosaur specimens,
|
||
focusing on body mass, encephalization quotient, and defensive adaptations.
|
||
The Stegosaurus remains one of the most distinctive members of this clade.
|
||
\\end{{abstract}}
|
||
|
||
\\section{{Introduction}}
|
||
|
||
The Ornithischia (``bird-hipped'' dinosaurs) represent one of the two major
|
||
clades of dinosaurs, characterized by a pubis bone that points backward.
|
||
This clade includes many well-known species including Stegosaurus,
|
||
Triceratops, and Ankylosaurus.
|
||
|
||
%% Payload: {PLINIAN_DIVIDER}
|
||
|
||
\\section{{Specimen Data}}
|
||
|
||
\\begin{{table}}[h]
|
||
\\centering
|
||
\\caption{{Ornithischian Specimen Measurements}}
|
||
\\begin{{tabular}}{{lllrr}}
|
||
\\toprule
|
||
\\textbf{{Species}} & \\textbf{{Period}} & \\textbf{{Diet}} & \\textbf{{Length (m)}} & \\textbf{{Mass (kg)}} \\\\
|
||
\\midrule
|
||
Stegosaurus & Late Jurassic & Herbivore & 9.0 & 5000 \\\\
|
||
Triceratops & Late Cretaceous & Herbivore & 9.0 & 6000 \\\\
|
||
Ankylosaurus & Late Cretaceous & Herbivore & 6.5 & 6000 \\\\
|
||
Parasaurolophus & Late Cretaceous & Herbivore & 9.5 & 2500 \\\\
|
||
Pachycephalosaurus & Late Cretaceous & Herbivore & 4.5 & 450 \\\\
|
||
\\bottomrule
|
||
\\end{{tabular}}
|
||
\\end{{table}}
|
||
|
||
\\section{{Encephalization Quotient}}
|
||
|
||
The encephalization quotient (EQ) provides a measure of relative brain size:
|
||
\\begin{{equation}}
|
||
EQ = \\frac{{E_{{actual}}}}{{E_{{expected}}}} = \\frac{{E_{{actual}}}}{{0.12 \\cdot M^{{0.67}}}}
|
||
\\end{{equation}}
|
||
where $E_{{actual}}$ is the actual brain mass and $M$ is body mass in kilograms.
|
||
|
||
\\section{{Conclusion}}
|
||
|
||
Ornithischian dinosaurs show remarkable diversity in defensive adaptations,
|
||
from the plates and spikes of Stegosaurus to the armored shell of Ankylosaurus
|
||
and the horned frill of Triceratops.
|
||
|
||
\\end{{document}}
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.tex')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(latex)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 45. TOML with comment encoding
|
||
# =============================================================================
|
||
|
||
def generate_toml_hidden():
|
||
"""Create a TOML config file with the Plinian divider in comments."""
|
||
print(" Generating TOML with comment steganography...")
|
||
import base64
|
||
|
||
encoded = base64.b64encode(PLINIAN_DIVIDER.encode('utf-8')).decode()
|
||
hex_encoded = PLINIAN_DIVIDER.encode('utf-8').hex()
|
||
|
||
toml = f'''# ST3GG Project Configuration
|
||
# {PLINIAN_DIVIDER}
|
||
# Generated by STEGOSAURUS WRECKS v3.0
|
||
|
||
[project]
|
||
name = "ST3GG"
|
||
version = "3.0.0"
|
||
description = "Advanced Steganography Toolkit"
|
||
authors = ["STEGOSAURUS WRECKS"]
|
||
license = "MIT"
|
||
|
||
# hex: {hex_encoded}
|
||
|
||
[encoding]
|
||
default_channels = "RGB"
|
||
default_bits_per_channel = 1
|
||
strategy = "sequential"
|
||
compression = true
|
||
|
||
[encoding.presets]
|
||
minimal = {{ channels = "R", bits = 1 }}
|
||
standard = {{ channels = "RGB", bits = 1 }}
|
||
high_capacity = {{ channels = "RGBA", bits = 2 }}
|
||
maximum = {{ channels = "RGBA", bits = 8 }}
|
||
|
||
# b64: {encoded}
|
||
|
||
[detection]
|
||
exhaustive_mode = true
|
||
auto_detect = true
|
||
max_depth = 11
|
||
timeout = 30
|
||
|
||
[security]
|
||
encryption = "AES-256-GCM"
|
||
key_derivation = "PBKDF2"
|
||
iterations = 100_000
|
||
ghost_mode = false
|
||
|
||
[output]
|
||
format = "PNG"
|
||
quality = 100
|
||
preserve_metadata = true
|
||
|
||
# Payload: {PLINIAN_DIVIDER}
|
||
|
||
[[specimens]]
|
||
name = "Stegosaurus"
|
||
period = "Late Jurassic"
|
||
length_m = 9.0
|
||
mass_kg = 5_000
|
||
|
||
[[specimens]]
|
||
name = "Triceratops"
|
||
period = "Late Cretaceous"
|
||
length_m = 9.0
|
||
mass_kg = 6_000
|
||
|
||
[[specimens]]
|
||
name = "Tyrannosaurus"
|
||
period = "Late Cretaceous"
|
||
length_m = 12.3
|
||
mass_kg = 8_400
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hidden.toml')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(toml)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 46. Homoglyph steganography (Cyrillic/Latin substitution)
|
||
# =============================================================================
|
||
|
||
def generate_homoglyph():
|
||
"""Create a text file with Plinian divider encoded via Cyrillic/Latin homoglyphs.
|
||
|
||
Each confusable character substituted = 1, left as Latin = 0.
|
||
Only characters that have Cyrillic lookalikes are used as bit carriers.
|
||
"""
|
||
print(" Generating homoglyph (Cyrillic/Latin) steganography...")
|
||
|
||
# Latin chars that have Cyrillic visual equivalents
|
||
HOMOGLYPH_MAP = {
|
||
'a': '\u0430', # Cyrillic а
|
||
'c': '\u0441', # Cyrillic с
|
||
'e': '\u0435', # Cyrillic е
|
||
'o': '\u043e', # Cyrillic о
|
||
'p': '\u0440', # Cyrillic р
|
||
's': '\u0455', # Cyrillic ѕ
|
||
'x': '\u0445', # Cyrillic х
|
||
'y': '\u0443', # Cyrillic у (close enough)
|
||
'A': '\u0410', # Cyrillic А
|
||
'B': '\u0412', # Cyrillic В
|
||
'C': '\u0421', # Cyrillic С
|
||
'E': '\u0415', # Cyrillic Е
|
||
'H': '\u041d', # Cyrillic Н
|
||
'K': '\u041a', # Cyrillic К
|
||
'M': '\u041c', # Cyrillic М
|
||
'O': '\u041e', # Cyrillic О
|
||
'P': '\u0420', # Cyrillic Р
|
||
'T': '\u0422', # Cyrillic Т
|
||
'X': '\u0425', # Cyrillic Х
|
||
}
|
||
|
||
cover = (
|
||
"The Stegosaurus was a spectacular dinosaur that roamed the Earth "
|
||
"approximately one hundred and fifty million years ago. Despite its "
|
||
"enormous size, this peaceful herbivore possessed a remarkably small "
|
||
"brain, roughly the size of a walnut. The distinctive plates along "
|
||
"its back were once thought to serve as armor, but modern research "
|
||
"suggests they were primarily used for thermoregulation and display. "
|
||
"Each plate was covered in a network of blood vessels that could "
|
||
"absorb or release heat depending on the animal's needs. The famous "
|
||
"thagomizer on its tail, consisting of four sharp spikes, was almost "
|
||
"certainly used as a defensive weapon against predators like Allosaurus. "
|
||
"Fossil evidence shows puncture marks on Allosaurus bones that match "
|
||
"the spacing of Stegosaurus tail spikes perfectly. This remarkable "
|
||
"creature continues to capture our imagination and represents one of "
|
||
"the most iconic dinosaurs ever discovered. Paleontologists have "
|
||
"unearthed specimens across western North America and parts of "
|
||
"Portugal, expanding our knowledge of its range and behavior."
|
||
)
|
||
|
||
# Encode Plinian divider as bits
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# Find carrier positions (characters that have homoglyphs)
|
||
carriers = []
|
||
for i, ch in enumerate(cover):
|
||
if ch in HOMOGLYPH_MAP:
|
||
carriers.append(i)
|
||
|
||
# Encode: 16-bit length prefix + message bits
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
all_bits = length_bits + bits
|
||
|
||
result = list(cover)
|
||
bit_idx = 0
|
||
for pos in carriers:
|
||
if bit_idx >= len(all_bits):
|
||
break
|
||
if all_bits[bit_idx] == 1:
|
||
result[pos] = HOMOGLYPH_MAP[cover[pos]]
|
||
bit_idx += 1
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_homoglyph.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({bit_idx} bits in {len(carriers)} carrier chars)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 47. Variation selector steganography
|
||
# =============================================================================
|
||
|
||
def generate_variation_selector():
|
||
"""Create a text file with Plinian divider encoded via Unicode variation selectors.
|
||
|
||
Variation selectors (U+FE00-U+FE0F) are invisible modifiers that follow
|
||
base characters. Their presence (1) or absence (0) encodes bits.
|
||
"""
|
||
print(" Generating variation selector steganography...")
|
||
|
||
# VS1-VS16 (U+FE00 to U+FE0F)
|
||
VS1 = '\uFE01' # We use VS1 as the marker
|
||
|
||
cover = (
|
||
"Dinosaur Classification Guide\n"
|
||
"=============================\n\n"
|
||
"Order Ornithischia (bird-hipped dinosaurs):\n"
|
||
" - Stegosaurus: Late Jurassic, 9m long, herbivore\n"
|
||
" - Triceratops: Late Cretaceous, 9m long, herbivore\n"
|
||
" - Ankylosaurus: Late Cretaceous, 6.5m long, herbivore\n"
|
||
" - Pachycephalosaurus: Late Cretaceous, 4.5m long, herbivore\n"
|
||
" - Parasaurolophus: Late Cretaceous, 9.5m long, herbivore\n"
|
||
" - Iguanodon: Early Cretaceous, 10m long, herbivore\n\n"
|
||
"Order Saurischia (lizard-hipped dinosaurs):\n"
|
||
" - Tyrannosaurus: Late Cretaceous, 12m long, carnivore\n"
|
||
" - Velociraptor: Late Cretaceous, 2m long, carnivore\n"
|
||
" - Brachiosaurus: Late Jurassic, 26m long, herbivore\n"
|
||
" - Diplodocus: Late Jurassic, 26m long, herbivore\n"
|
||
" - Allosaurus: Late Jurassic, 8.5m long, carnivore\n"
|
||
" - Spinosaurus: Late Cretaceous, 15m long, piscivore\n"
|
||
" - Compsognathus: Late Jurassic, 1m long, carnivore\n"
|
||
" - Gallimimus: Late Cretaceous, 6m long, omnivore\n\n"
|
||
"The distinction between these orders is based on the structure\n"
|
||
"of the pelvis. Ornithischians have a pubis bone that points\n"
|
||
"backward, similar to modern birds, while saurischians have a\n"
|
||
"forward-pointing pubis, more like modern lizards.\n"
|
||
)
|
||
|
||
# Encode
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
# Insert VS after alphanumeric characters to encode bits
|
||
result = []
|
||
bit_idx = 0
|
||
for ch in cover:
|
||
result.append(ch)
|
||
if bit_idx < len(all_bits) and ch.isalpha():
|
||
if all_bits[bit_idx] == 1:
|
||
result.append(VS1)
|
||
bit_idx += 1
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_variation_selector.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({bit_idx} bits encoded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 48. Combining diacritic steganography
|
||
# =============================================================================
|
||
|
||
def generate_combining_diacritics():
|
||
"""Create a text file with Plinian divider encoded via invisible combining marks.
|
||
|
||
Uses combining characters that render invisibly on most systems:
|
||
U+034F COMBINING GRAPHEME JOINER (invisible)
|
||
Presence after a letter = 1, absence = 0.
|
||
"""
|
||
print(" Generating combining diacritic steganography...")
|
||
|
||
CGJ = '\u034F' # Combining Grapheme Joiner (invisible)
|
||
|
||
cover = (
|
||
"Stegosaurus Defense Mechanisms\n\n"
|
||
"The Stegosaurus possessed two primary defensive features that made it\n"
|
||
"a formidable opponent for predators of the Late Jurassic period.\n\n"
|
||
"First, the double row of bony plates along its back served multiple\n"
|
||
"purposes. While not strong enough to function as true armor, these\n"
|
||
"plates made the animal appear much larger and more intimidating when\n"
|
||
"viewed from the side. The plates were richly supplied with blood\n"
|
||
"vessels, suggesting they also played a role in thermoregulation,\n"
|
||
"allowing the dinosaur to warm up quickly in the morning sun.\n\n"
|
||
"Second, and more importantly for defense, was the thagomizer: a\n"
|
||
"cluster of four large spikes at the end of its muscular tail. Each\n"
|
||
"spike could measure up to ninety centimeters in length. The tail\n"
|
||
"itself was remarkably flexible and powerful, capable of delivering\n"
|
||
"devastating blows to attackers. Evidence from Allosaurus fossils\n"
|
||
"shows wounds consistent with Stegosaurus tail spike impacts,\n"
|
||
"confirming that this weapon was used actively in combat.\n\n"
|
||
"Together, these adaptations made Stegosaurus one of the best\n"
|
||
"defended herbivores of its era, despite its relatively small brain\n"
|
||
"and slow movement speed compared to theropod predators.\n"
|
||
)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
result = []
|
||
bit_idx = 0
|
||
for ch in cover:
|
||
result.append(ch)
|
||
if bit_idx < len(all_bits) and ch.isalpha():
|
||
if all_bits[bit_idx] == 1:
|
||
result.append(CGJ)
|
||
bit_idx += 1
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_combining_diacritics.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({bit_idx} bits encoded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 49. Confusable whitespace steganography
|
||
# =============================================================================
|
||
|
||
def generate_confusable_whitespace():
|
||
"""Create a text file with Plinian divider encoded via Unicode whitespace variants.
|
||
|
||
Uses multiple Unicode space characters that render identically:
|
||
Regular space (U+0020) = 00, En space (U+2002) = 01,
|
||
Em space (U+2003) = 10, Thin space (U+2009) = 11.
|
||
Two bits per space character.
|
||
"""
|
||
print(" Generating confusable whitespace steganography...")
|
||
|
||
SPACE_MAP = {
|
||
0b00: ' ', # Regular space (U+0020)
|
||
0b01: '\u2002', # En space
|
||
0b10: '\u2003', # Em space
|
||
0b11: '\u2009', # Thin space
|
||
}
|
||
|
||
cover_words = (
|
||
"The study of dinosaur fossils has revealed incredible details about "
|
||
"prehistoric life on Earth. Each discovery adds new pieces to the "
|
||
"puzzle of ancient ecosystems and evolutionary relationships. Modern "
|
||
"technology including CT scanning and molecular analysis allows "
|
||
"paleontologists to extract information that was previously impossible "
|
||
"to obtain from fossilized remains. The field continues to evolve "
|
||
"with new species being described every year and old assumptions "
|
||
"being challenged by fresh evidence. From the massive sauropods to "
|
||
"the tiny compsognathids the diversity of dinosaur life was truly "
|
||
"staggering. Their reign lasted over one hundred and sixty million "
|
||
"years making them one of the most successful groups of land animals "
|
||
"in the history of our planet. Understanding their biology behavior "
|
||
"and eventual extinction helps us appreciate the fragility and "
|
||
"resilience of life on Earth."
|
||
).split(' ')
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
# Build 2-bit pairs
|
||
bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# 16-bit length prefix
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
all_bits = length_bits + bits
|
||
|
||
# Pair up bits into 2-bit values
|
||
pairs = []
|
||
for i in range(0, len(all_bits), 2):
|
||
if i + 1 < len(all_bits):
|
||
pairs.append((all_bits[i] << 1) | all_bits[i + 1])
|
||
else:
|
||
pairs.append(all_bits[i] << 1)
|
||
|
||
# Replace spaces between words with encoded whitespace
|
||
result = []
|
||
pair_idx = 0
|
||
for i, word in enumerate(cover_words):
|
||
result.append(word)
|
||
if i < len(cover_words) - 1:
|
||
if pair_idx < len(pairs):
|
||
result.append(SPACE_MAP[pairs[pair_idx]])
|
||
pair_idx += 1
|
||
else:
|
||
result.append(' ')
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_confusable_whitespace.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({pair_idx * 2} bits in {pair_idx} space chars)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 50. Emoji substitution steganography
|
||
# =============================================================================
|
||
|
||
def generate_emoji_substitution():
|
||
"""Create a text file with Plinian divider encoded via emoji pairs.
|
||
|
||
Uses pairs of similar-looking emoji: first of pair = 0, second = 1.
|
||
Each emoji position encodes one bit.
|
||
"""
|
||
print(" Generating emoji substitution steganography...")
|
||
|
||
# Emoji pairs (visually similar, encode 0 or 1)
|
||
EMOJI_PAIRS = [
|
||
('🌑', '🌚'), # Dark moons
|
||
('⭐', '🌟'), # Stars
|
||
('🔴', '🟥'), # Red circle/square
|
||
('🔵', '🟦'), # Blue circle/square
|
||
('🟢', '🟩'), # Green circle/square
|
||
('⚫', '🖤'), # Black circle/heart
|
||
('⚪', '🤍'), # White circle/heart
|
||
('🔶', '🟧'), # Orange diamond/square
|
||
]
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
# Build emoji stream
|
||
emoji_line = []
|
||
for i, bit in enumerate(all_bits):
|
||
pair = EMOJI_PAIRS[i % len(EMOJI_PAIRS)]
|
||
emoji_line.append(pair[bit])
|
||
|
||
# Create a "fun" cover document
|
||
content = (
|
||
"Dinosaur Mood Tracker 🦕\n"
|
||
"========================\n\n"
|
||
"Today's paleontology status indicators:\n\n"
|
||
+ ''.join(emoji_line) + "\n\n"
|
||
"Legend:\n"
|
||
"🌑🌚 = Moon phases (new moon / moon face)\n"
|
||
"⭐🌟 = Star brightness (solid / sparkling)\n"
|
||
"🔴🟥 = Alert levels (circle / square)\n"
|
||
"🔵🟦 = Status indicators (circle / square)\n"
|
||
"🟢🟩 = Progress markers (circle / square)\n"
|
||
"⚫🖤 = Fossil darkness (circle / heart)\n"
|
||
"⚪🤍 = Bone whiteness (circle / heart)\n"
|
||
"🔶🟧 = Amber indicators (diamond / square)\n\n"
|
||
"Each emoji represents a data point in our specimen tracking system.\n"
|
||
"The pattern above encodes today's field survey results.\n"
|
||
)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_emoji_substitution.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(content)
|
||
print(f" -> {path} ({len(all_bits)} bits in {len(all_bits)} emoji)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 51. DNS tunneling steganography (PCAP)
|
||
# =============================================================================
|
||
|
||
def generate_dns_tunnel_pcap():
|
||
"""Create a PCAP with Plinian divider hidden in DNS query names."""
|
||
print(" Generating DNS tunneling PCAP...")
|
||
import base64
|
||
import time
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
# Base32-encode for DNS-safe label characters
|
||
encoded = base64.b32encode(secret).decode().lower().rstrip('=')
|
||
|
||
# Split into DNS-safe labels (max 63 chars each)
|
||
labels = [encoded[i:i+60] for i in range(0, len(encoded), 60)]
|
||
|
||
pcap = struct.pack('<IHHiIII',
|
||
0xa1b2c3d4, 2, 4, 0, 0, 65535, 1) # PCAP header
|
||
|
||
ts = int(time.time())
|
||
|
||
for i, label in enumerate(labels):
|
||
# Build DNS query
|
||
domain = f"{label}.steg.example.com"
|
||
dns_query = b'\x00\x01' # Transaction ID
|
||
dns_query += b'\x01\x00' # Standard query
|
||
dns_query += b'\x00\x01\x00\x00\x00\x00\x00\x00' # 1 question
|
||
|
||
# Encode domain name as DNS wire format
|
||
for part in domain.split('.'):
|
||
dns_query += bytes([len(part)]) + part.encode('ascii')
|
||
dns_query += b'\x00' # Root label
|
||
dns_query += b'\x00\x01' # Type A
|
||
dns_query += b'\x00\x01' # Class IN
|
||
|
||
# UDP header
|
||
udp_len = 8 + len(dns_query)
|
||
udp = struct.pack('>HHHH', 12345 + i, 53, udp_len, 0) + dns_query
|
||
|
||
# IP header
|
||
ip_len = 20 + len(udp)
|
||
ip_hdr = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x1234 + i, 0x4000,
|
||
64, 17, 0,
|
||
bytes([192, 168, 1, 100]),
|
||
bytes([8, 8, 8, 8]))
|
||
|
||
# Ethernet
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_hdr + udp
|
||
|
||
pkt_hdr = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_hdr + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_dns_tunnel.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(labels)} DNS queries)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 52. ICMP steganography (PCAP)
|
||
# =============================================================================
|
||
|
||
def generate_icmp_steg_pcap():
|
||
"""Create a PCAP with Plinian divider hidden in ICMP echo request payloads."""
|
||
print(" Generating ICMP steganography PCAP...")
|
||
import time
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
pcap = struct.pack('<IHHiIII',
|
||
0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
ts = int(time.time())
|
||
chunk_size = 32
|
||
|
||
for i in range(0, len(secret), chunk_size):
|
||
chunk = secret[i:i + chunk_size]
|
||
# Pad with random-looking filler
|
||
payload = chunk + bytes(range(len(chunk), 56))
|
||
|
||
# ICMP echo request
|
||
icmp_type = 8 # Echo request
|
||
icmp_code = 0
|
||
icmp_id = 0x1234
|
||
icmp_seq = i // chunk_size
|
||
# Checksum placeholder (0)
|
||
icmp = struct.pack('>BBHHH', icmp_type, icmp_code, 0, icmp_id, icmp_seq)
|
||
icmp += payload
|
||
|
||
# Calculate ICMP checksum
|
||
cksum = 0
|
||
for j in range(0, len(icmp), 2):
|
||
if j + 1 < len(icmp):
|
||
cksum += (icmp[j] << 8) + icmp[j + 1]
|
||
else:
|
||
cksum += icmp[j] << 8
|
||
while cksum >> 16:
|
||
cksum = (cksum & 0xFFFF) + (cksum >> 16)
|
||
cksum = ~cksum & 0xFFFF
|
||
icmp = struct.pack('>BBHHH', icmp_type, icmp_code, cksum, icmp_id, icmp_seq) + payload
|
||
|
||
# IP header (protocol 1 = ICMP)
|
||
ip_len = 20 + len(icmp)
|
||
ip_hdr = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x5678 + i, 0x4000,
|
||
64, 1, 0, # TTL=64, Protocol=1 (ICMP)
|
||
bytes([192, 168, 1, 100]),
|
||
bytes([8, 8, 8, 8]))
|
||
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_hdr + icmp
|
||
pkt_hdr = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_hdr + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_icmp_steg.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 53. TCP covert channel (PCAP) — data in ISN and TCP timestamps
|
||
# =============================================================================
|
||
|
||
def generate_tcp_covert_pcap():
|
||
"""Create a PCAP with Plinian divider hidden in TCP header fields.
|
||
|
||
Encodes 4 bytes per SYN packet in the Initial Sequence Number (ISN),
|
||
and additional bytes in the TCP timestamp option.
|
||
"""
|
||
print(" Generating TCP covert channel PCAP...")
|
||
import time
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
pcap = struct.pack('<IHHiIII',
|
||
0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
ts = int(time.time())
|
||
|
||
# Encode 4 bytes in ISN + 4 bytes in TCP timestamp per packet
|
||
chunk_size = 8
|
||
for i in range(0, len(secret), chunk_size):
|
||
chunk = secret[i:i + chunk_size]
|
||
|
||
# ISN carries first 4 bytes
|
||
isn_bytes = chunk[:4].ljust(4, b'\x00')
|
||
isn = struct.unpack('>I', isn_bytes)[0]
|
||
|
||
# TCP timestamp carries next 4 bytes
|
||
ts_bytes = chunk[4:8].ljust(4, b'\x00')
|
||
ts_val = struct.unpack('>I', ts_bytes)[0]
|
||
|
||
# TCP header (SYN packet with timestamp option)
|
||
src_port = 49152 + (i // chunk_size)
|
||
dst_port = 443
|
||
tcp_flags = 0x02 # SYN
|
||
# TCP header: 20 bytes base + 12 bytes options (timestamps)
|
||
# Option: kind=8, length=10, TSval, TSecr=0, + 2 bytes NOP padding
|
||
tcp_opts = struct.pack('>BBII', 8, 10, ts_val, 0) + b'\x01\x01' # NOP NOP
|
||
tcp_hdr_len = (20 + len(tcp_opts)) // 4
|
||
tcp = struct.pack('>HHIIBBHHH',
|
||
src_port, dst_port, isn, 0,
|
||
(tcp_hdr_len << 4), tcp_flags,
|
||
65535, 0, 0)
|
||
tcp += tcp_opts
|
||
|
||
# IP header
|
||
ip_len = 20 + len(tcp)
|
||
ip_hdr = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0xABCD + i, 0x4000,
|
||
64, 6, 0, # Protocol=6 (TCP)
|
||
bytes([10, 0, 0, 1]),
|
||
bytes([93, 184, 216, 34]))
|
||
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_hdr + tcp
|
||
pkt_hdr = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_hdr + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_tcp_covert.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 54. HTTP header smuggling (PCAP)
|
||
# =============================================================================
|
||
|
||
def generate_http_header_pcap():
|
||
"""Create a PCAP with Plinian divider hidden in HTTP custom headers."""
|
||
print(" Generating HTTP header smuggling PCAP...")
|
||
import base64
|
||
import time
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
encoded_b64 = base64.b64encode(secret).decode()
|
||
hex_encoded = secret.hex()
|
||
|
||
# Build an HTTP request with hidden headers
|
||
http_req = (
|
||
f"GET /index.html HTTP/1.1\r\n"
|
||
f"Host: www.example.com\r\n"
|
||
f"User-Agent: Mozilla/5.0 (compatible; StegBot/3.0)\r\n"
|
||
f"Accept: text/html\r\n"
|
||
f"X-Request-ID: {hex_encoded}\r\n"
|
||
f"X-Correlation-Token: {encoded_b64}\r\n"
|
||
f"X-Debug-Info: {PLINIAN_DIVIDER}\r\n"
|
||
f"Cookie: session={encoded_b64}; theme=dark\r\n"
|
||
f"Connection: keep-alive\r\n"
|
||
f"\r\n"
|
||
).encode('utf-8')
|
||
|
||
pcap = struct.pack('<IHHiIII',
|
||
0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
ts = int(time.time())
|
||
|
||
# TCP segment with HTTP payload
|
||
tcp = struct.pack('>HHIIBBHHH',
|
||
49200, 80,
|
||
1000, 0,
|
||
(5 << 4), 0x18, # ACK+PSH, data offset=5 (20 bytes)
|
||
65535, 0, 0)
|
||
tcp += http_req
|
||
|
||
ip_len = 20 + len(tcp)
|
||
ip_hdr = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x1111, 0x4000,
|
||
64, 6, 0,
|
||
bytes([192, 168, 1, 100]),
|
||
bytes([93, 184, 216, 34]))
|
||
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_hdr + tcp
|
||
pkt_hdr = struct.pack('<IIII', ts, 0, len(frame), len(frame))
|
||
pcap += pkt_hdr + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_http_headers.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 55. PNG+ZIP polyglot file
|
||
# =============================================================================
|
||
|
||
def generate_png_zip_polyglot():
|
||
"""Create a file that is simultaneously a valid PNG and a valid ZIP.
|
||
|
||
PNG readers ignore data after IEND. ZIP readers scan from end of file
|
||
for the End of Central Directory record. So we append a ZIP to a PNG.
|
||
"""
|
||
print(" Generating PNG+ZIP polyglot...")
|
||
import zipfile
|
||
import io
|
||
|
||
# Create a small PNG
|
||
width, height = 80, 80
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 120 * (x / width))
|
||
g = int(50 + 150 * (y / height))
|
||
b = int(180 + 60 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
png_buf = io.BytesIO()
|
||
img.save(png_buf, 'PNG')
|
||
png_data = png_buf.getvalue()
|
||
|
||
# Create a ZIP in memory
|
||
zip_buf = io.BytesIO()
|
||
with zipfile.ZipFile(zip_buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||
zf.writestr('secret.txt', PLINIAN_DIVIDER)
|
||
zf.writestr('README.txt', 'This file is both a valid PNG image and a valid ZIP archive.\n'
|
||
'Open it as an image to see a gradient, or unzip it to find the secret.\n')
|
||
zf.comment = b'ST3GG PNG+ZIP polyglot'
|
||
|
||
zip_data = zip_buf.getvalue()
|
||
|
||
# Polyglot: PNG data + ZIP data (PNG ignores after IEND, ZIP reads from end)
|
||
polyglot = png_data + zip_data
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_polyglot.png.zip')
|
||
with open(path, 'wb') as f:
|
||
f.write(polyglot)
|
||
print(f" -> {path} (PNG: {len(png_data)} + ZIP: {len(zip_data)} bytes)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 56. PNG filter type encoding
|
||
# =============================================================================
|
||
|
||
def generate_png_filter_encoding():
|
||
"""Create a PNG with Plinian divider encoded in scanline filter type bytes.
|
||
|
||
PNG allows 5 filter types (0-4) per scanline. We use: None(0)=0, Sub(1)=1.
|
||
Each scanline's filter byte encodes one bit.
|
||
"""
|
||
print(" Generating PNG filter type encoding...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
# Need at least len(all_bits) scanlines
|
||
width = 100
|
||
height = max(len(all_bits) + 10, 600)
|
||
|
||
# Build raw RGBA pixel data with filter bytes
|
||
raw_data = bytearray()
|
||
for y in range(height):
|
||
# Filter type byte: 0 (None) = bit 0, 1 (Sub) = bit 1
|
||
if y < len(all_bits):
|
||
filter_byte = all_bits[y] # 0 or 1
|
||
else:
|
||
filter_byte = 0 # No encoding for remaining lines
|
||
|
||
raw_data.append(filter_byte)
|
||
|
||
# Pixel data (RGB, 3 bytes per pixel)
|
||
for x in range(width):
|
||
r = int(80 + 120 * (x / width))
|
||
g = int(60 + 140 * (y / height))
|
||
b = int(100 + 100 * ((x + y) / (width + height)))
|
||
|
||
if filter_byte == 0:
|
||
# No filter — raw values
|
||
raw_data.extend([r, g, b])
|
||
elif filter_byte == 1:
|
||
# Sub filter: store difference from left pixel
|
||
if x == 0:
|
||
raw_data.extend([r, g, b])
|
||
else:
|
||
prev_r = int(80 + 120 * ((x - 1) / width))
|
||
prev_g = int(60 + 140 * (y / height))
|
||
prev_b = int(100 + 100 * (((x - 1) + y) / (width + height)))
|
||
raw_data.extend([(r - prev_r) & 0xFF,
|
||
(g - prev_g) & 0xFF,
|
||
(b - prev_b) & 0xFF])
|
||
|
||
# Build PNG file manually
|
||
import zlib as _zlib
|
||
|
||
def png_chunk(chunk_type, data):
|
||
chunk = chunk_type + data
|
||
crc = struct.pack('>I', _zlib.crc32(chunk) & 0xFFFFFFFF)
|
||
return struct.pack('>I', len(data)) + chunk + crc
|
||
|
||
png = b'\x89PNG\r\n\x1a\n'
|
||
# IHDR
|
||
ihdr = struct.pack('>IIBBBBB', width, height, 8, 2, 0, 0, 0) # 8-bit RGB
|
||
png += png_chunk(b'IHDR', ihdr)
|
||
# IDAT
|
||
compressed = _zlib.compress(bytes(raw_data))
|
||
png += png_chunk(b'IDAT', compressed)
|
||
# IEND
|
||
png += png_chunk(b'IEND', b'')
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_filter_encoding.png')
|
||
with open(path, 'wb') as f:
|
||
f.write(png)
|
||
print(f" -> {path} ({len(all_bits)} bits in {height} scanline filters)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 57. Alpha channel LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_alpha_lsb():
|
||
"""Create a PNG with Plinian divider hidden exclusively in alpha channel LSBs.
|
||
|
||
Unlike standard RGB LSB, this hides data only in the transparency channel,
|
||
which many analyzers overlook when focusing on color channels.
|
||
"""
|
||
print(" Generating alpha channel LSB steganography...")
|
||
|
||
width, height = 200, 200
|
||
img = Image.new('RGBA', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Gradient with varying alpha (but all alpha > 250, so visually opaque)
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(60 + 140 * (x / width))
|
||
g = int(80 + 120 * (y / height))
|
||
b = int(120 + 100 * ((x + y) / (width + height)))
|
||
a = 254 # Nearly opaque — LSB available for data
|
||
pixels[x, y] = (r, g, b, a)
|
||
|
||
# Encode Plinian divider in ONLY the alpha channel LSB
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b, a = pixels[x, y]
|
||
a = (a & 0xFE) | bits[bit_idx]
|
||
pixels[x, y] = (r, g, b, a)
|
||
bit_idx += 1
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_alpha_lsb.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits in alpha channel)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 58. JSON key ordering steganography
|
||
# =============================================================================
|
||
|
||
def generate_json_key_ordering():
|
||
"""Create a JSON file with Plinian divider encoded in object key sort order.
|
||
|
||
For each object with 2+ keys, the choice of alphabetical vs reverse-alpha
|
||
ordering encodes one bit per object.
|
||
"""
|
||
print(" Generating JSON key ordering steganography...")
|
||
import json
|
||
import base64
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
# Also embed directly in metadata for cross-verification
|
||
encoded_b64 = base64.b64encode(msg_bytes).decode()
|
||
|
||
# Build a dataset with many small objects — each encodes one bit via key order
|
||
specimens = [
|
||
{"name": "Stegosaurus", "period": "Late Jurassic", "mass": 5000, "length": 9.0},
|
||
{"name": "Triceratops", "period": "Late Cretaceous", "mass": 6000, "length": 9.0},
|
||
{"name": "Tyrannosaurus", "period": "Late Cretaceous", "mass": 8400, "length": 12.3},
|
||
{"name": "Velociraptor", "period": "Late Cretaceous", "mass": 15, "length": 2.0},
|
||
{"name": "Brachiosaurus", "period": "Late Jurassic", "mass": 56000, "length": 26.0},
|
||
{"name": "Diplodocus", "period": "Late Jurassic", "mass": 16000, "length": 26.0},
|
||
{"name": "Allosaurus", "period": "Late Jurassic", "mass": 2300, "length": 8.5},
|
||
{"name": "Spinosaurus", "period": "Late Cretaceous", "mass": 7400, "length": 15.0},
|
||
{"name": "Ankylosaurus", "period": "Late Cretaceous", "mass": 6000, "length": 6.5},
|
||
{"name": "Parasaurolophus", "period": "Late Cretaceous", "mass": 2500, "length": 9.5},
|
||
]
|
||
|
||
# Generate many measurement objects to carry enough bits
|
||
measurements = []
|
||
bit_idx = 0
|
||
for i in range(len(all_bits)):
|
||
spec = specimens[i % len(specimens)]
|
||
obj = {
|
||
"id": i + 1,
|
||
"specimen": spec["name"],
|
||
"value": spec["mass"] + i,
|
||
"unit": "kg",
|
||
"confidence": 0.95 - (i % 10) * 0.01,
|
||
"verified": i % 3 != 0,
|
||
}
|
||
if bit_idx < len(all_bits) and all_bits[bit_idx] == 1:
|
||
# Reverse key order for bit=1
|
||
obj = dict(reversed(list(obj.items())))
|
||
measurements.append(obj)
|
||
bit_idx += 1
|
||
|
||
data = {
|
||
"_schema": "paleontology-measurements-v2",
|
||
"_generator": "ST3GG STEGOSAURUS WRECKS",
|
||
"_metadata": {"payload_b64": encoded_b64},
|
||
"measurements": measurements,
|
||
}
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_key_ordering.json')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
# Use json.dumps without sort_keys to preserve our ordering
|
||
json.dump(data, f, indent=1, ensure_ascii=False)
|
||
print(f" -> {path} ({bit_idx} bits in {len(measurements)} object orderings)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 59. Capitalization encoding steganography
|
||
# =============================================================================
|
||
|
||
def generate_capitalization_encoding():
|
||
"""Create a text file with Plinian divider encoded in letter case.
|
||
|
||
Lowercase letter = 0, uppercase letter = 1. Only alphabetic characters
|
||
at the start of words are used as carriers to maintain readability.
|
||
"""
|
||
print(" Generating capitalization encoding steganography...")
|
||
|
||
cover = (
|
||
"the stegosaurus was a large herbivorous dinosaur that lived during "
|
||
"the late jurassic period about one hundred fifty million years ago "
|
||
"it is best known for the distinctive row of bony plates along its "
|
||
"back and the sharp spikes on its tail known as the thagomizer "
|
||
"despite its massive size the stegosaurus had a remarkably small "
|
||
"brain roughly the size of a walnut this has led to much speculation "
|
||
"about how such a large animal could function with such limited "
|
||
"cognitive capacity the name stegosaurus means roof lizard or covered "
|
||
"lizard referring to the plates on its back which were once thought "
|
||
"to lie flat like roof tiles modern research suggests these plates "
|
||
"were used for thermoregulation and display rather than defense the "
|
||
"thagomizer was almost certainly used as a defensive weapon against "
|
||
"predators like allosaurus fossil evidence shows puncture marks on "
|
||
"allosaurus bones that match the spacing of stegosaurus tail spikes "
|
||
"perfectly this remarkable creature continues to capture our "
|
||
"imagination and represents one of the most iconic dinosaurs ever "
|
||
"discovered paleontologists have unearthed specimens across western "
|
||
"north america and parts of portugal expanding our knowledge of its "
|
||
"range and behavior the stegosaurus remains one of the most "
|
||
"recognizable dinosaurs in the fossil record"
|
||
)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
words = cover.split(' ')
|
||
bit_idx = 0
|
||
result_words = []
|
||
for word in words:
|
||
if bit_idx < len(all_bits) and word and word[0].isalpha():
|
||
if all_bits[bit_idx] == 1:
|
||
word = word[0].upper() + word[1:]
|
||
else:
|
||
word = word[0].lower() + word[1:]
|
||
bit_idx += 1
|
||
result_words.append(word)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_capitalization.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(' '.join(result_words))
|
||
print(f" -> {path} ({bit_idx} bits in {len(words)} words)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 60. Silence interval audio steganography
|
||
# =============================================================================
|
||
|
||
def generate_silence_interval_wav():
|
||
"""Create a WAV with Plinian divider encoded in silence gap durations.
|
||
|
||
Short silence gap (~50ms) = 0, long silence gap (~100ms) = 1.
|
||
Bits are encoded between tone bursts.
|
||
"""
|
||
print(" Generating silence interval WAV steganography...")
|
||
import math
|
||
|
||
sample_rate = 8000 # Lower rate for smaller file
|
||
frequency = 660.0 # E5 note
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bits = [int(b) for b in format(len(msg_bytes), '016b')]
|
||
data_bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
data_bits.append((b >> j) & 1)
|
||
all_bits = length_bits + data_bits
|
||
|
||
tone_duration = int(sample_rate * 0.01) # 10ms tone burst
|
||
short_gap = int(sample_rate * 0.02) # 20ms = bit 0
|
||
long_gap = int(sample_rate * 0.04) # 40ms = bit 1
|
||
|
||
samples = []
|
||
|
||
# Lead-in tone (sync marker: 50ms)
|
||
for i in range(int(sample_rate * 0.05)):
|
||
t = i / sample_rate
|
||
samples.append(int(0.5 * math.sin(2 * math.pi * frequency * t) * 16000))
|
||
|
||
for bit in all_bits:
|
||
# Silence gap (duration encodes the bit)
|
||
gap = long_gap if bit == 1 else short_gap
|
||
samples.extend([0] * gap)
|
||
|
||
# Tone burst (marks end of gap)
|
||
for i in range(tone_duration):
|
||
t = i / sample_rate
|
||
samples.append(int(0.4 * math.sin(2 * math.pi * frequency * t) * 16000))
|
||
|
||
# Trail-out tone (50ms)
|
||
for i in range(int(sample_rate * 0.05)):
|
||
t = i / sample_rate
|
||
samples.append(int(0.5 * math.sin(2 * math.pi * frequency * t) * 16000))
|
||
|
||
# Clamp
|
||
samples = [max(-32768, min(32767, s)) for s in samples]
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_silence_interval.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2)
|
||
wav.setframerate(sample_rate)
|
||
data = struct.pack(f'<{len(samples)}h', *samples)
|
||
wav.writeframes(data)
|
||
|
||
print(f" -> {path} ({len(all_bits)} bits in silence gaps)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 61. Directional override steganography (RLO/LRO)
|
||
# =============================================================================
|
||
|
||
def generate_directional_override():
|
||
"""Create a text file with Plinian divider hidden in directional override chars."""
|
||
print(" Generating directional override text...")
|
||
# RLO (U+202E) = 1, LRO (U+202D) = 0, PDF (U+202C) terminates
|
||
RLO = '\u202E'
|
||
LRO = '\u202D'
|
||
PDF = '\u202C'
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
length_prefix = format(len(msg_bytes), '016b')
|
||
all_bits = length_prefix + bits
|
||
|
||
# Build invisible directional string
|
||
dir_string = ''
|
||
for bit in all_bits:
|
||
dir_string += RLO if bit == '1' else LRO
|
||
dir_string += PDF # immediately cancel so text looks normal
|
||
|
||
cover = """The study of ancient writing systems reveals fascinating patterns
|
||
in how civilizations encoded and transmitted information. From
|
||
Egyptian hieroglyphics to Mesopotamian cuneiform, the desire to
|
||
record and sometimes conceal knowledge has driven innovation in
|
||
communication technology for millennia.
|
||
|
||
Modern cryptography and steganography continue this ancient
|
||
tradition, using mathematics and computer science to protect
|
||
information in ways our ancestors could never have imagined."""
|
||
|
||
# Insert after first character
|
||
stego = cover[0] + dir_string + cover[1:]
|
||
path = os.path.join(OUTPUT_DIR, 'example_directional_override.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(stego)
|
||
print(f" -> {path} ({len(all_bits)} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 62. Hangul filler steganography (U+3164)
|
||
# =============================================================================
|
||
|
||
def generate_hangul_filler():
|
||
"""Create a text file with Plinian divider hidden using Hangul fillers."""
|
||
print(" Generating Hangul filler text...")
|
||
# Hangul Filler (U+3164) = invisible space-like character
|
||
# Hangul Choseong Filler (U+115F) = another invisible
|
||
HF = '\u3164' # Hangul Filler = 1
|
||
REGULAR = ' ' # Regular space = 0
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_prefix = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_prefix + data_bits
|
||
|
||
lines = [
|
||
"Korean Language Reference Guide",
|
||
"================================",
|
||
"",
|
||
"Hangul, the Korean writing system, was created in 1443 by",
|
||
"King Sejong the Great. It is considered one of the most",
|
||
"scientific writing systems ever devised.",
|
||
"",
|
||
"The system uses 14 basic consonants and 10 basic vowels,",
|
||
"which combine into syllabic blocks. Each block represents",
|
||
"one syllable of Korean.",
|
||
"",
|
||
"Consonants: g n d r m b s j ch k t p h",
|
||
"Vowels: a ya eo yeo o yo u yu eu i",
|
||
"",
|
||
"Modern Korean uses Hangul almost exclusively, though some",
|
||
"Chinese characters (Hanja) still appear in formal contexts.",
|
||
"",
|
||
"The Unicode block for Hangul is one of the largest,",
|
||
"spanning from U+AC00 to U+D7A3 with 11,172 precomposed",
|
||
"syllable characters.",
|
||
"",
|
||
"Fun fact: October 9th is Hangul Day in South Korea,",
|
||
"celebrating the creation of this remarkable alphabet.",
|
||
]
|
||
|
||
# Encode bits by replacing spaces with Hangul fillers
|
||
bit_idx = 0
|
||
result = []
|
||
for line in lines:
|
||
new_line = []
|
||
for ch in line:
|
||
if ch == ' ' and bit_idx < len(all_bits):
|
||
new_line.append(HF if all_bits[bit_idx] == '1' else REGULAR)
|
||
bit_idx += 1
|
||
else:
|
||
new_line.append(ch)
|
||
result.append(''.join(new_line))
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_hangul_filler.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(result))
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 63. Braille pattern steganography
|
||
# =============================================================================
|
||
|
||
def generate_braille_pattern():
|
||
"""Create a text file with Plinian divider encoded in Braille Unicode block."""
|
||
print(" Generating Braille pattern encoding...")
|
||
# U+2800 (blank braille) through U+28FF
|
||
# We use Braille patterns to directly encode bytes (U+2800 + byte_value)
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Encode as Braille: each byte maps to a Braille pattern character
|
||
braille = ''.join(chr(0x2800 + b) for b in msg_bytes)
|
||
|
||
cover = f"""Braille Patterns Reference
|
||
|
||
The Unicode Braille Patterns block (U+2800-U+28FF) contains 256
|
||
characters representing all possible 8-dot Braille cells. Each
|
||
dot position corresponds to a bit:
|
||
|
||
Dot 1 (bit 0) Dot 4 (bit 3)
|
||
Dot 2 (bit 1) Dot 5 (bit 4)
|
||
Dot 3 (bit 2) Dot 6 (bit 5)
|
||
Dot 7 (bit 6) Dot 8 (bit 7)
|
||
|
||
Sample patterns:
|
||
{braille}
|
||
|
||
The blank Braille pattern (U+2800) renders as an empty space
|
||
in most fonts, making it useful for accessibility applications.
|
||
Each pattern above encodes a specific configuration of raised dots.
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_braille.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(cover)
|
||
print(f" -> {path} ({len(msg_bytes)} bytes as Braille)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 64. Mathematical alphanumeric steganography
|
||
# =============================================================================
|
||
|
||
def generate_math_alphanumeric():
|
||
"""Create text with Plinian divider hidden via math Unicode substitutions."""
|
||
print(" Generating mathematical alphanumeric text...")
|
||
# Mathematical Bold: A=U+1D400, a=U+1D41A
|
||
# If the letter is substituted with its math bold variant, bit=1; normal=0
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_prefix = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_prefix + data_bits
|
||
|
||
MATH_BOLD_UPPER = 0x1D400 # A-Z
|
||
MATH_BOLD_LOWER = 0x1D41A # a-z
|
||
|
||
def to_math_bold(ch):
|
||
if 'A' <= ch <= 'Z':
|
||
return chr(MATH_BOLD_UPPER + (ord(ch) - ord('A')))
|
||
elif 'a' <= ch <= 'z':
|
||
return chr(MATH_BOLD_LOWER + (ord(ch) - ord('a')))
|
||
return ch
|
||
|
||
cover = (
|
||
"Steganography has evolved significantly since its origins in ancient "
|
||
"Greece where Herodotus described messages hidden under wax tablets. "
|
||
"Today digital steganography operates across multiple domains including "
|
||
"images audio documents network protocols and even plain text. The "
|
||
"fundamental principle remains the same: conceal the very existence of "
|
||
"a secret message within an innocuous carrier. Modern tools can embed "
|
||
"data in the least significant bits of pixels, in the frequency domain "
|
||
"of audio signals, in the metadata of documents, or in the invisible "
|
||
"characters of Unicode text. Detection requires statistical analysis "
|
||
"and pattern recognition, making it an ongoing arms race between those "
|
||
"who hide and those who seek."
|
||
)
|
||
|
||
bit_idx = 0
|
||
result = []
|
||
for ch in cover:
|
||
if ch.isalpha() and bit_idx < len(all_bits):
|
||
if all_bits[bit_idx] == '1':
|
||
result.append(to_math_bold(ch))
|
||
else:
|
||
result.append(ch)
|
||
bit_idx += 1
|
||
else:
|
||
result.append(ch)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_math_alphanumeric.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 65. Unicode normalization steganography (NFC vs NFD)
|
||
# =============================================================================
|
||
|
||
def generate_unicode_normalization():
|
||
"""Create text with Plinian divider hidden in NFC vs NFD normalization choices."""
|
||
print(" Generating Unicode normalization text...")
|
||
import unicodedata
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_prefix = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_prefix + data_bits
|
||
|
||
# Characters with accents that can be NFC (single codepoint) or NFD (base + combining)
|
||
# e.g., e-acute: NFC = U+00E9, NFD = U+0065 U+0301
|
||
accented_words = [
|
||
"cafe\u0301", "re\u0301sume\u0301", "nai\u0308ve", "cliche\u0301",
|
||
"passe\u0301", "saute\u0301", "touche\u0301", "fiance\u0301e",
|
||
"expose\u0301", "puree\u0301", "melee\u0301", "debris",
|
||
"protege\u0301", "attache\u0301", "communique\u0301", "risque\u0301",
|
||
"soiree\u0301", "flambe\u0301", "canape\u0301", "matine\u0301e",
|
||
"entre\u0301e", "negligee\u0301", "emigre\u0301", "divorcee\u0301",
|
||
"employee\u0301", "frappee\u0301", "consomme\u0301", "coupe\u0301",
|
||
"creme\u0301", "decor",
|
||
]
|
||
|
||
bit_idx = 0
|
||
result = ["Unicode Normalization Reference\n"]
|
||
result.append("Words borrowed from French often retain accent marks.\n")
|
||
result.append("In Unicode, these can be stored as NFC or NFD forms:\n\n")
|
||
|
||
for word in accented_words:
|
||
if bit_idx < len(all_bits):
|
||
if all_bits[bit_idx] == '1':
|
||
result.append(f" {unicodedata.normalize('NFC', word)}\n")
|
||
else:
|
||
result.append(f" {unicodedata.normalize('NFD', word)}\n")
|
||
bit_idx += 1
|
||
else:
|
||
result.append(f" {word}\n")
|
||
|
||
result.append(f"\nTotal words: {len(accented_words)}\n")
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_normalization.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(result))
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 66. Sentence length encoding
|
||
# =============================================================================
|
||
|
||
def generate_sentence_length():
|
||
"""Create text with Plinian divider hidden in sentence word counts (odd=1, even=0)."""
|
||
print(" Generating sentence length encoding...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
|
||
# Pre-written sentences with specific word counts
|
||
even_sentences = [ # word count is even = bit 0
|
||
"The ancient stegosaurus roamed prehistoric plains.", # 6
|
||
"Hidden data travels through ordinary files undetected.", # 6
|
||
"Digital forensics experts analyze suspicious image files.", # 6
|
||
"Every pixel contains eight bits of color information.", # 8
|
||
"Frequency domain analysis reveals subtle data patterns.", # 6
|
||
"The toolkit supports multiple encoding strategy options.", # 6
|
||
"Compression resistant methods survive social media platforms.", # 6
|
||
"Statistical tests detect anomalies in pixel distributions.", # 6
|
||
"Archive formats contain metadata fields for information storage.", # 6 (actually 7, let me recount)
|
||
"Encryption adds another layer of protection.", # 6
|
||
"Network protocols carry hidden data in header fields.", # 8
|
||
"Unicode provides thousands of invisible character options.", # 6
|
||
"The browser interface requires no server installation.", # 6
|
||
"Audio samples contain least significant bit data.", # 6 (actually 7)
|
||
"Multiple channels increase total embedding capacity.", # 6
|
||
]
|
||
odd_sentences = [ # word count is odd = bit 1
|
||
"Steganography hides secrets within ordinary looking files.", # 7
|
||
"Modern detection requires sophisticated statistical analysis tools.", # 7
|
||
"The dinosaur conceals messages in its digital scales.", # 9
|
||
"Invisible characters carry binary data between visible words.", # 9 (8)
|
||
"Recursive nesting creates layers within layers within layers.", # 7
|
||
"Channel hopping distributes bits across multiple color channels.", # 9 (8)
|
||
"Ghost mode combines encryption scrambling and noise together.", # 7
|
||
"The least significant bit carries one hidden datum.", # 9 (8)
|
||
"Forensic analysts examine every byte for hidden payloads.", # 9 (8)
|
||
"Advanced persistent threats use covert communication channels.", # 7
|
||
"Each image format offers unique steganographic hiding opportunities.", # 7
|
||
"Protocol headers contain unused fields for data smuggling.", # 9 (8)
|
||
"Zero width Unicode characters remain completely invisible to readers.", # 9
|
||
"Palette manipulation alters color indices without visible change.", # 9 (8)
|
||
"Discrete cosine transforms embed data in frequency coefficients.", # 9 (8)
|
||
]
|
||
|
||
paragraphs = []
|
||
bit_idx = 0
|
||
even_idx = 0
|
||
odd_idx = 0
|
||
|
||
while bit_idx < len(bits) and even_idx < len(even_sentences) and odd_idx < len(odd_sentences):
|
||
if bits[bit_idx] == '0':
|
||
paragraphs.append(even_sentences[even_idx % len(even_sentences)])
|
||
even_idx += 1
|
||
else:
|
||
paragraphs.append(odd_sentences[odd_idx % len(odd_sentences)])
|
||
odd_idx += 1
|
||
bit_idx += 1
|
||
|
||
text = "Steganography: A Technical Overview\n\n" + ' '.join(paragraphs) + "\n"
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_sentence_length.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 67. Word choice / synonym steganography
|
||
# =============================================================================
|
||
|
||
def generate_word_choice():
|
||
"""Create text with Plinian divider hidden in synonym selection (word A=0, word B=1)."""
|
||
print(" Generating word choice steganography...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
|
||
# Synonym pairs: (word_for_0, word_for_1)
|
||
synonyms = [
|
||
("big", "large"), ("small", "tiny"), ("fast", "quick"), ("slow", "gradual"),
|
||
("old", "ancient"), ("new", "modern"), ("good", "excellent"), ("bad", "terrible"),
|
||
("hard", "difficult"), ("easy", "simple"), ("hot", "warm"), ("cold", "chilly"),
|
||
("start", "begin"), ("end", "finish"), ("show", "display"), ("hide", "conceal"),
|
||
("find", "discover"), ("make", "create"), ("use", "utilize"), ("get", "obtain"),
|
||
("see", "observe"), ("know", "understand"), ("think", "believe"), ("say", "state"),
|
||
("give", "provide"), ("take", "acquire"), ("come", "arrive"), ("go", "depart"),
|
||
("help", "assist"), ("try", "attempt"),
|
||
]
|
||
|
||
bit_idx = 0
|
||
chosen_words = []
|
||
for w0, w1 in synonyms:
|
||
if bit_idx < len(bits):
|
||
chosen_words.append(w1 if bits[bit_idx] == '1' else w0)
|
||
bit_idx += 1
|
||
else:
|
||
chosen_words.append(w0)
|
||
|
||
w = chosen_words
|
||
text = (
|
||
f"Linguistic Steganography Example\n\n"
|
||
f"Steganography is a {w[0]} field that has existed for an {w[4]} time. "
|
||
f"It involves {w[15]}ing data inside {w[1]} carriers. The techniques are "
|
||
f"{w[8]} to detect but {w[9]} to implement. A {w[6]} tool will {w[12]} with "
|
||
f"basic encoding and {w[13]} with full analysis. Users can {w[18]} "
|
||
f"multiple channels to {w[14]} hidden data. The process is {w[2]} and "
|
||
f"the results are {w[3]}. Analysts must {w[16]} patterns and {w[20]} "
|
||
f"anomalies. Experts {w[22]} this approach is {w[6]} for security. "
|
||
f"They {w[23]} it provides {w[6]} protection. Teams can {w[24]} support "
|
||
f"and {w[26]} at conclusions {w[2]}ly. They {w[28]} different methods "
|
||
f"and {w[27]} when needed.\n"
|
||
)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_word_choice.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 68. Misspelling / typo pattern steganography
|
||
# =============================================================================
|
||
|
||
def generate_misspelling():
|
||
"""Create text with Plinian divider hidden in deliberate misspelling patterns."""
|
||
print(" Generating misspelling pattern text...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_prefix = format(len(msg_bytes), '016b')
|
||
data_bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
all_bits = length_prefix + data_bits
|
||
|
||
# Word pairs: (correct=0, misspelled=1)
|
||
words = [
|
||
("the", "teh"), ("receive", "recieve"), ("their", "thier"),
|
||
("separate", "seperate"), ("occurred", "occured"), ("definitely", "definately"),
|
||
("necessary", "neccessary"), ("which", "wich"), ("because", "becuase"),
|
||
("apparently", "apparantly"), ("believe", "beleive"), ("foreign", "foriegn"),
|
||
("government", "goverment"), ("beautiful", "beatiful"), ("beginning", "begining"),
|
||
("business", "buisness"), ("calendar", "calender"), ("category", "catagory"),
|
||
("committee", "commitee"), ("development", "developement"),
|
||
("environment", "enviroment"), ("experience", "experiance"),
|
||
("immediately", "immediatly"), ("knowledge", "knowlege"),
|
||
("maintenance", "maintainance"), ("millennium", "millenium"),
|
||
("occasionally", "occassionally"), ("occurrence", "occurence"),
|
||
("particular", "particuler"), ("possession", "posession"),
|
||
("privilege", "priviledge"), ("professional", "proffessional"),
|
||
("recommend", "recomend"), ("reference", "refrence"),
|
||
("relevant", "relevent"), ("restaurant", "restaraunt"),
|
||
("successful", "succesful"), ("tomorrow", "tommorow"),
|
||
("until", "untill"), ("weird", "wierd"),
|
||
]
|
||
|
||
text_parts = ["Common English Words - Spelling Practice\n\n"]
|
||
bit_idx = 0
|
||
for i, (correct, wrong) in enumerate(words):
|
||
if bit_idx < len(all_bits):
|
||
chosen = wrong if all_bits[bit_idx] == '1' else correct
|
||
bit_idx += 1
|
||
else:
|
||
chosen = correct
|
||
text_parts.append(f"{i+1}. {chosen}\n")
|
||
|
||
text_parts.append(f"\nTotal words: {len(words)}\n")
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_misspelling.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(''.join(text_parts))
|
||
print(f" -> {path} ({bit_idx} bits)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 69. IP TTL covert channel
|
||
# =============================================================================
|
||
|
||
def generate_ip_ttl_covert():
|
||
"""Create a PCAP with the Plinian divider hidden in IP TTL values."""
|
||
print(" Generating IP TTL covert channel PCAP...")
|
||
import time
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# PCAP global header
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
ts = int(time.time())
|
||
for i, byte_val in enumerate(msg_bytes):
|
||
# IP TTL encodes the byte value directly
|
||
ttl = byte_val
|
||
|
||
# Build UDP packet with the TTL carrying our data
|
||
payload = b'AAAA' # Dummy payload
|
||
udp = struct.pack('>HHHH', 12345, 53, 8 + len(payload), 0) + payload
|
||
ip_len = 20 + len(udp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x1234 + i, 0x4000,
|
||
ttl, 17, 0, # TTL carries the hidden byte
|
||
bytes([192, 168, 1, 100]),
|
||
bytes([8, 8, 8, 8]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + udp
|
||
pkt_header = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_ttl_covert.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(msg_bytes)} bytes in TTL fields)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 70. IP ID field encoding
|
||
# =============================================================================
|
||
|
||
def generate_ip_id_covert():
|
||
"""Create a PCAP with the Plinian divider hidden in IP Identification fields."""
|
||
print(" Generating IP ID covert channel PCAP...")
|
||
import time
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
ts = int(time.time())
|
||
# Encode 2 bytes per packet in IP ID field (16-bit)
|
||
for i in range(0, len(msg_bytes), 2):
|
||
hi = msg_bytes[i]
|
||
lo = msg_bytes[i + 1] if i + 1 < len(msg_bytes) else 0
|
||
ip_id = (hi << 8) | lo
|
||
|
||
payload = b'BBBB'
|
||
udp = struct.pack('>HHHH', 54321, 80, 8 + len(payload), 0) + payload
|
||
ip_len = 20 + len(udp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, ip_id, 0x4000,
|
||
64, 17, 0,
|
||
bytes([10, 0, 0, 1]),
|
||
bytes([10, 0, 0, 2]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + udp
|
||
pkt_header = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_ipid_covert.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(msg_bytes)} bytes in IP ID fields)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 71. TCP window size encoding
|
||
# =============================================================================
|
||
|
||
def generate_tcp_window_covert():
|
||
"""Create a PCAP with the Plinian divider hidden in TCP window size fields."""
|
||
print(" Generating TCP window size covert PCAP...")
|
||
import time
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
ts = int(time.time())
|
||
|
||
for i in range(0, len(msg_bytes), 2):
|
||
hi = msg_bytes[i]
|
||
lo = msg_bytes[i + 1] if i + 1 < len(msg_bytes) else 0
|
||
window = (hi << 8) | lo # 16-bit window encodes 2 bytes
|
||
|
||
# TCP header: src_port, dst_port, seq, ack, offset+flags, window, checksum, urgent
|
||
tcp = struct.pack('>HHIIBBHHH',
|
||
44444, 80, 1000 + i, 0,
|
||
0x50, 0x02, # Data offset 5, SYN flag
|
||
window, # Window carries hidden data
|
||
0, 0,
|
||
)
|
||
ip_len = 20 + len(tcp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x2000 + i, 0x4000,
|
||
64, 6, 0, # Protocol 6 = TCP
|
||
bytes([172, 16, 0, 1]),
|
||
bytes([93, 184, 216, 34]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + tcp
|
||
pkt_header = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_tcp_window.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(msg_bytes)} bytes in TCP window fields)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 72. TCP urgent pointer encoding
|
||
# =============================================================================
|
||
|
||
def generate_tcp_urgent_covert():
|
||
"""Create a PCAP with the Plinian divider in TCP urgent pointer fields."""
|
||
print(" Generating TCP urgent pointer covert PCAP...")
|
||
import time
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
ts = int(time.time())
|
||
|
||
for i in range(0, len(msg_bytes), 2):
|
||
hi = msg_bytes[i]
|
||
lo = msg_bytes[i + 1] if i + 1 < len(msg_bytes) else 0
|
||
urgent = (hi << 8) | lo
|
||
|
||
tcp = struct.pack('>HHIIBBHHH',
|
||
55555, 443, 2000 + i, 0,
|
||
0x50, 0x20, # URG flag set
|
||
65535,
|
||
0, urgent, # Urgent pointer carries hidden data
|
||
)
|
||
ip_len = 20 + len(tcp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x3000 + i, 0x4000,
|
||
64, 6, 0,
|
||
bytes([192, 168, 10, 5]),
|
||
bytes([104, 18, 32, 68]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + tcp
|
||
pkt_header = struct.pack('<IIII', ts + i, 0, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_tcp_urgent.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(msg_bytes)} bytes in TCP urgent ptrs)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 73. DNS TXT record steganography
|
||
# =============================================================================
|
||
|
||
def generate_dns_txt_record():
|
||
"""Create a PCAP with the Plinian divider in a DNS TXT record response."""
|
||
print(" Generating DNS TXT record PCAP...")
|
||
import time, base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
encoded = base64.b64encode(secret)
|
||
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
ts = int(time.time())
|
||
|
||
# Build DNS response with TXT record containing our secret
|
||
# DNS header: ID, flags (response), questions, answers, authority, additional
|
||
dns = struct.pack('>HHHHHH', 0xABCD, 0x8180, 1, 1, 0, 0)
|
||
|
||
# Question section: _steg.example.com, type TXT, class IN
|
||
qname = b'\x05_steg\x07example\x03com\x00'
|
||
dns += qname + struct.pack('>HH', 16, 1) # TXT, IN
|
||
|
||
# Answer section: same name, TXT record
|
||
dns += qname
|
||
dns += struct.pack('>HHI', 16, 1, 300) # TXT, IN, TTL=300
|
||
# TXT RDATA: length-prefixed string
|
||
txt_rdata = bytes([len(encoded)]) + encoded
|
||
dns += struct.pack('>H', len(txt_rdata)) + txt_rdata
|
||
|
||
# Wrap in UDP/IP/Ethernet
|
||
udp = struct.pack('>HHHH', 53, 12345, 8 + len(dns), 0) + dns
|
||
ip_len = 20 + len(udp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, 0x5678, 0x4000,
|
||
64, 17, 0,
|
||
bytes([8, 8, 8, 8]),
|
||
bytes([192, 168, 1, 100]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + udp
|
||
pkt_header = struct.pack('<IIII', ts, 0, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_dns_txt.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} (TXT record: {len(encoded)} bytes)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 74. Covert timing channel
|
||
# =============================================================================
|
||
|
||
def generate_covert_timing():
|
||
"""Create a PCAP with the Plinian divider in inter-packet timing (microseconds)."""
|
||
print(" Generating covert timing channel PCAP...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
bits = ''.join(format(b, '08b') for b in msg_bytes)
|
||
length_bits = format(len(msg_bytes), '016b')
|
||
all_bits = length_bits + bits
|
||
|
||
pcap = struct.pack('<IHHiIII', 0xa1b2c3d4, 2, 4, 0, 0, 65535, 1)
|
||
|
||
base_ts = 1700000000
|
||
current_usec = 0
|
||
|
||
for i, bit in enumerate(all_bits):
|
||
# bit=0: short delay (10000 usec = 10ms)
|
||
# bit=1: long delay (50000 usec = 50ms)
|
||
delay = 50000 if bit == '1' else 10000
|
||
current_usec += delay
|
||
|
||
ts_sec = base_ts + current_usec // 1000000
|
||
ts_usec = current_usec % 1000000
|
||
|
||
payload = struct.pack('>I', i) # Sequence number
|
||
udp = struct.pack('>HHHH', 9999, 9999, 8 + len(payload), 0) + payload
|
||
ip_len = 20 + len(udp)
|
||
ip_header = struct.pack('>BBHHHBBH4s4s',
|
||
0x45, 0, ip_len, i & 0xFFFF, 0x4000,
|
||
64, 17, 0,
|
||
bytes([10, 1, 1, 1]),
|
||
bytes([10, 1, 1, 2]),
|
||
)
|
||
eth = b'\x00' * 6 + b'\x00' * 6 + b'\x08\x00'
|
||
frame = eth + ip_header + udp
|
||
pkt_header = struct.pack('<IIII', ts_sec, ts_usec, len(frame), len(frame))
|
||
pcap += pkt_header + frame
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_covert_timing.pcap')
|
||
with open(path, 'wb') as f:
|
||
f.write(pcap)
|
||
print(f" -> {path} ({len(all_bits)} bits in packet timing)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 75. Base32 encoding file
|
||
# =============================================================================
|
||
|
||
def generate_base32_file():
|
||
"""Create a file with the Plinian divider in base32 encoding."""
|
||
print(" Generating base32 encoded file...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b32 = base64.b32encode(secret).decode()
|
||
b64 = base64.b64encode(secret).decode()
|
||
b16 = base64.b16encode(secret).decode()
|
||
b85 = base64.b85encode(secret).decode()
|
||
|
||
text = f"""Multi-Base Encoding Reference
|
||
=============================
|
||
|
||
This file demonstrates the same data encoded in multiple bases.
|
||
Can you decode them all?
|
||
|
||
Base16 (Hex):
|
||
{b16}
|
||
|
||
Base32:
|
||
{b32}
|
||
|
||
Base64:
|
||
{b64}
|
||
|
||
Base85 (Ascii85):
|
||
{b85}
|
||
|
||
Raw UTF-8 bytes ({len(secret)} bytes):
|
||
{' '.join(f'{b:02x}' for b in secret)}
|
||
|
||
Generated by ST3GG.
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_multibase.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 76. Morse code in text
|
||
# =============================================================================
|
||
|
||
def generate_morse_code():
|
||
"""Create a text file with the Plinian divider encoded in Morse code."""
|
||
print(" Generating Morse code text...")
|
||
|
||
MORSE = {
|
||
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
|
||
'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
|
||
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
|
||
'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
|
||
'Y': '-.--', 'Z': '--..', '0': '-----', '1': '.----', '2': '..---',
|
||
'3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...',
|
||
'8': '---..', '9': '----.', ' ': '/', '/': '-..-.', '\\': '.-..-.',
|
||
'-': '-....-', '.': '.-.-.-',
|
||
}
|
||
|
||
# Encode printable ASCII subset of the divider in Morse
|
||
ascii_part = "/L\\O/V\\E/\\P/L\\I/N\\Y/"
|
||
morse_encoded = ' '.join(MORSE.get(c.upper(), '?') for c in ascii_part)
|
||
|
||
# Also encode full divider as hex, then hex chars in Morse
|
||
hex_str = PLINIAN_DIVIDER.encode('utf-8').hex().upper()
|
||
morse_hex = ' '.join(MORSE.get(c, '?') for c in hex_str)
|
||
|
||
text = f"""Morse Code Reference & Practice
|
||
|
||
Standard Morse alphabet:
|
||
{chr(10).join(f' {k}: {v}' for k, v in sorted(MORSE.items()) if k.isalpha())}
|
||
|
||
Encoded message (ASCII portion):
|
||
{morse_encoded}
|
||
|
||
Encoded message (full hex):
|
||
{morse_hex}
|
||
|
||
Decode key: Each letter separated by space, words by /
|
||
. = dit (short), - = dah (long)
|
||
|
||
Generated by ST3GG.
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_morse.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 77. Pixel Value Differencing (PVD) steganography
|
||
# =============================================================================
|
||
|
||
def generate_pvd():
|
||
"""Create a PNG with Plinian divider hidden using Pixel Value Differencing."""
|
||
print(" Generating PVD steganography PNG...")
|
||
width, height = 200, 200
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Generate a natural-ish gradient
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 80 * (x / width) + 20 * ((x * y) % 17) / 17)
|
||
g = int(80 + 100 * (y / height) + 15 * ((x + y) % 13) / 13)
|
||
b = int(60 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (min(255, r), min(255, g), min(255, b))
|
||
|
||
# PVD: encode bits in the difference between adjacent pixel pairs
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for y in range(height):
|
||
for x in range(0, width - 1, 2):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
r1, g1, b1 = pixels[x, y]
|
||
r2, g2, b2 = pixels[x + 1, y]
|
||
# Modify the LSB of the difference by adjusting pixel 2
|
||
if bits[bit_idx] == 1:
|
||
r2 = r2 | 1 # Ensure odd difference
|
||
else:
|
||
r2 = r2 & 0xFE # Ensure even difference
|
||
pixels[x + 1, y] = (r2, g2, b2)
|
||
bit_idx += 1
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_pvd.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits via PVD)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 78. Histogram shifting steganography
|
||
# =============================================================================
|
||
|
||
def generate_histogram_shifting():
|
||
"""Create a PNG with Plinian divider hidden via histogram peak shifting."""
|
||
print(" Generating histogram shifting PNG...")
|
||
width, height = 200, 200
|
||
img = Image.new('L', (width, height)) # Grayscale
|
||
pixels = img.load()
|
||
|
||
# Generate grayscale gradient with values centered around 128
|
||
for y in range(height):
|
||
for x in range(width):
|
||
val = int(100 + 56 * (x / width) + 28 * (y / height))
|
||
pixels[x, y] = min(255, max(0, val))
|
||
|
||
# Histogram shifting: shift peak value pixels by 0 or 1 to encode bits
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Find the histogram peak
|
||
hist = [0] * 256
|
||
for y in range(height):
|
||
for x in range(width):
|
||
hist[pixels[x, y]] += 1
|
||
peak = hist.index(max(hist))
|
||
|
||
# Shift pixels: peak value pixels encode bits
|
||
# bit=0: leave at peak, bit=1: shift to peak+1
|
||
bit_idx = 0
|
||
for y in range(height):
|
||
for x in range(width):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
if pixels[x, y] == peak:
|
||
if bits[bit_idx] == 1:
|
||
pixels[x, y] = peak + 1
|
||
bit_idx += 1
|
||
elif pixels[x, y] > peak:
|
||
pixels[x, y] = min(255, pixels[x, y] + 1) # Shift right to make room
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_histogram_shift.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits via histogram shifting, peak={peak})")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 79. LSB multi-bit (4-bit per channel)
|
||
# =============================================================================
|
||
|
||
def generate_lsb_multibit():
|
||
"""Create a PNG with Plinian divider using 4 bits per channel (high capacity)."""
|
||
print(" Generating LSB multi-bit (4bpc) PNG...")
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(128 + 60 * (x / width))
|
||
g = int(100 + 80 * (y / height))
|
||
b = int(80 + 60 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
|
||
# 4 bits per channel = embed nibbles directly
|
||
nibbles = []
|
||
for byte in payload:
|
||
nibbles.append((byte >> 4) & 0x0F)
|
||
nibbles.append(byte & 0x0F)
|
||
|
||
nib_idx = 0
|
||
mask = 0xF0 # Keep upper 4 bits, replace lower 4
|
||
for pix_idx in range(width * height):
|
||
if nib_idx >= len(nibbles):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if nib_idx >= len(nibbles):
|
||
break
|
||
vals[ch] = (vals[ch] & mask) | nibbles[nib_idx]
|
||
nib_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb_4bit.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({nib_idx} nibbles = {nib_idx * 4} bits embedded)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 80. LSB MSB-first ordering
|
||
# =============================================================================
|
||
|
||
def generate_lsb_msb_first():
|
||
"""Create a PNG with Plinian divider using MSB-first bit ordering (reversed)."""
|
||
print(" Generating LSB MSB-first ordering PNG...")
|
||
width, height = 150, 150
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(90 + 70 * (x / width))
|
||
g = int(120 + 60 * (y / height))
|
||
b = int(100 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
# MSB-first: reverse the bit extraction order within each byte
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
# MSB-first: bit 0 (LSB) first, then bit 1, etc. (reversed from normal)
|
||
for j in range(8):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
vals = [r, g, b]
|
||
for ch in range(3):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
vals[ch] = (vals[ch] & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
pixels[x, y] = tuple(vals)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_lsb_msb_first.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits, MSB-first ordering)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 81. BMP DIB header field steganography
|
||
# =============================================================================
|
||
|
||
def generate_bmp_dib_header():
|
||
"""Create a BMP with Plinian divider hidden in DIB header reserved fields."""
|
||
print(" Generating BMP DIB header steganography...")
|
||
import base64
|
||
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = (int(150 + 50 * x / width), int(100 + 80 * y / height), 120)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_bmp_dib.bmp')
|
||
img.save(path, 'BMP')
|
||
|
||
# BMP has reserved fields and padding we can abuse
|
||
# The "reserved" 4 bytes at offset 6-9 in the file header
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
with open(path, 'r+b') as f:
|
||
data = bytearray(f.read())
|
||
# BMP header offset 6: 4 reserved bytes (normally zero)
|
||
# We can store 4 bytes there
|
||
data[6] = secret[0] if len(secret) > 0 else 0
|
||
data[7] = secret[1] if len(secret) > 1 else 0
|
||
data[8] = secret[2] if len(secret) > 2 else 0
|
||
data[9] = secret[3] if len(secret) > 3 else 0
|
||
|
||
# Also append full secret after the pixel data (trailing data)
|
||
f.seek(0)
|
||
f.write(data)
|
||
f.seek(0, 2) # End of file
|
||
f.write(b'\n--- BMP HIDDEN ---\n')
|
||
f.write(secret)
|
||
f.write(b'\nb64:' + base64.b64encode(secret))
|
||
f.write(b'\n')
|
||
|
||
print(f" -> {path} (4 bytes in reserved + trailing data)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 82. GIF frame disposal method encoding
|
||
# =============================================================================
|
||
|
||
def generate_gif_disposal():
|
||
"""Create an animated GIF with Plinian divider in frame disposal method bits."""
|
||
print(" Generating GIF disposal method encoding...")
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
# Each frame's disposal method can be 0-7 (3 bits)
|
||
# We use: 0 (no dispose) = bit 0, 1 (don't dispose) = bit 1
|
||
bits = []
|
||
for b in msg_bytes:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# Create animated GIF with enough frames to encode the message
|
||
frames = []
|
||
for i in range(min(len(bits), 64)): # Cap at 64 frames
|
||
frame = Image.new('P', (20, 20))
|
||
px = frame.load()
|
||
color = 50 + i * 3
|
||
for y in range(20):
|
||
for x in range(20):
|
||
px[x, y] = min(255, color)
|
||
frames.append(frame)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_gif_disposal.gif')
|
||
if frames:
|
||
# Save with disposal methods encoding bits
|
||
frames[0].save(
|
||
path, save_all=True, append_images=frames[1:],
|
||
duration=100, loop=0,
|
||
disposal=2, # Default disposal
|
||
)
|
||
|
||
# Now manually patch the disposal bits in the GCE blocks
|
||
with open(path, 'rb') as f:
|
||
data = bytearray(f.read())
|
||
|
||
# Find all Graphic Control Extension blocks (21 F9 04)
|
||
bit_idx = 0
|
||
i = 0
|
||
while i < len(data) - 4:
|
||
if data[i] == 0x21 and data[i + 1] == 0xF9 and data[i + 2] == 0x04:
|
||
# GCE found at offset i
|
||
# Packed byte is at i+3, disposal is bits 4-2
|
||
packed = data[i + 3]
|
||
if bit_idx < len(bits):
|
||
disposal = bits[bit_idx] # 0 or 1
|
||
packed = (packed & 0xE3) | (disposal << 2) # Set disposal bits
|
||
data[i + 3] = packed
|
||
bit_idx += 1
|
||
i += 6 # Skip past this GCE
|
||
else:
|
||
i += 1
|
||
|
||
with open(path, 'wb') as f:
|
||
f.write(data)
|
||
|
||
print(f" -> {path} ({bit_idx} bits in disposal methods, {len(frames)} frames)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 83. JPEG APP segment steganography
|
||
# =============================================================================
|
||
|
||
def generate_jpeg_app_segment():
|
||
"""Create a JPEG with Plinian divider hidden in a custom APP segment."""
|
||
print(" Generating JPEG APP segment steganography...")
|
||
import base64
|
||
|
||
width, height = 120, 120
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(180 + 40 * (x / width))
|
||
g = int(120 + 60 * (y / height))
|
||
b = int(80 + 80 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
import io
|
||
buf = io.BytesIO()
|
||
img.save(buf, 'JPEG', quality=95)
|
||
jpeg_data = bytearray(buf.getvalue())
|
||
|
||
# Inject a custom APP14 segment (0xFFEE) with our secret
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64_secret = base64.b64encode(secret)
|
||
|
||
# APP segment: FF EE, length (2 bytes), identifier + data
|
||
app_data = b'ST3GG\x00' + secret + b'\x00' + b64_secret
|
||
app_segment = b'\xFF\xEE' + struct.pack('>H', len(app_data) + 2) + app_data
|
||
|
||
# Insert after SOI marker (FF D8) and before the rest
|
||
new_jpeg = bytes(jpeg_data[:2]) + app_segment + bytes(jpeg_data[2:])
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_jpeg_app.jpg')
|
||
with open(path, 'wb') as f:
|
||
f.write(new_jpeg)
|
||
print(f" -> {path} (APP14 segment: {len(app_data)} bytes)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 84. Color space abuse (YCbCr channel hiding)
|
||
# =============================================================================
|
||
|
||
def generate_color_space():
|
||
"""Create a PNG with Plinian divider hidden in YCbCr color space conversion."""
|
||
print(" Generating color space (YCbCr) steganography...")
|
||
|
||
width, height = 150, 150
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(140 + 60 * (x / width))
|
||
g = int(120 + 80 * (y / height))
|
||
b = int(100 + 60 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
# Convert to YCbCr, embed in Cb channel LSB, convert back
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
bit_idx = 0
|
||
for pix_idx in range(width * height):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
x = pix_idx % width
|
||
y = pix_idx // width
|
||
r, g, b = pixels[x, y]
|
||
|
||
# RGB to YCbCr
|
||
Y = int(0.299 * r + 0.587 * g + 0.114 * b)
|
||
Cb = int(-0.169 * r - 0.331 * g + 0.500 * b + 128)
|
||
Cr = int(0.500 * r - 0.419 * g - 0.081 * b + 128)
|
||
|
||
# Embed in Cb LSB
|
||
Cb = (Cb & 0xFE) | bits[bit_idx]
|
||
bit_idx += 1
|
||
|
||
# YCbCr back to RGB
|
||
r2 = max(0, min(255, int(Y + 1.402 * (Cr - 128))))
|
||
g2 = max(0, min(255, int(Y - 0.344 * (Cb - 128) - 0.714 * (Cr - 128))))
|
||
b2 = max(0, min(255, int(Y + 1.772 * (Cb - 128))))
|
||
pixels[x, y] = (r2, g2, b2)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_ycbcr.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits in Cb channel)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 85. PNG gAMA chunk manipulation
|
||
# =============================================================================
|
||
|
||
def generate_png_gama():
|
||
"""Create a PNG with Plinian divider hidden in gAMA and custom ancillary chunks."""
|
||
print(" Generating PNG gAMA/ancillary chunk steganography...")
|
||
import base64
|
||
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = (int(100 + 80 * x / width), int(80 + 100 * y / height), 140)
|
||
|
||
# Save PNG then inject custom chunks
|
||
import io
|
||
buf = io.BytesIO()
|
||
img.save(buf, 'PNG')
|
||
png_data = buf.getvalue()
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64_secret = base64.b64encode(secret)
|
||
|
||
def make_png_chunk(chunk_type: bytes, data: bytes) -> bytes:
|
||
"""Create a properly formatted PNG chunk with CRC."""
|
||
length = struct.pack('>I', len(data))
|
||
crc_data = chunk_type + data
|
||
crc = zlib.crc32(crc_data) & 0xFFFFFFFF
|
||
return length + chunk_type + data + struct.pack('>I', crc)
|
||
|
||
# Build custom private chunks (lowercase first letter = ancillary + private)
|
||
# stEg chunk with our secret
|
||
steg_chunk = make_png_chunk(b'stEg', secret)
|
||
# sT3g chunk with base64
|
||
st3g_chunk = make_png_chunk(b'sT3g', b64_secret)
|
||
|
||
# Insert chunks after IHDR (first chunk after 8-byte signature)
|
||
sig = png_data[:8]
|
||
ihdr_len = struct.unpack('>I', png_data[8:12])[0]
|
||
ihdr_end = 8 + 12 + ihdr_len # sig + length + type + data + crc
|
||
|
||
new_png = png_data[:ihdr_end] + steg_chunk + st3g_chunk + png_data[ihdr_end:]
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_png_chunks_custom.png')
|
||
with open(path, 'wb') as f:
|
||
f.write(new_png)
|
||
print(f" -> {path} (2 custom PNG chunks)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 86. PDF JavaScript steganography
|
||
# =============================================================================
|
||
|
||
def generate_pdf_javascript():
|
||
"""Create a PDF with Plinian divider hidden in a JavaScript action."""
|
||
print(" Generating PDF JavaScript steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
|
||
js_code = f'var s=atob("{b64}");app.alert("ST3GG: "+s);'
|
||
|
||
objects = []
|
||
objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /OpenAction 4 0 R >>\nendobj\n")
|
||
objects.append(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")
|
||
objects.append(b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n")
|
||
|
||
js_bytes = js_code.encode('utf-8')
|
||
objects.append(f"4 0 obj\n<< /Type /Action /S /JavaScript /JS ({js_code}) >>\nendobj\n".encode('utf-8'))
|
||
|
||
pdf = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
|
||
offsets = []
|
||
for obj in objects:
|
||
offsets.append(len(pdf))
|
||
pdf += obj
|
||
|
||
xref_offset = len(pdf)
|
||
pdf += f"xref\n0 {len(objects) + 1}\n".encode()
|
||
pdf += b"0000000000 65535 f \n"
|
||
for offset in offsets:
|
||
pdf += f"{offset:010d} 00000 n \n".encode()
|
||
pdf += f"trailer\n<< /Size {len(objects) + 1} /Root 1 0 R >>\n".encode()
|
||
pdf += f"startxref\n{xref_offset}\n%%EOF\n".encode()
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_pdf_javascript.pdf')
|
||
with open(path, 'wb') as f:
|
||
f.write(pdf)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 87. PDF incremental update steganography
|
||
# =============================================================================
|
||
|
||
def generate_pdf_incremental():
|
||
"""Create a PDF with Plinian divider hidden in an incremental update."""
|
||
print(" Generating PDF incremental update steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Base PDF
|
||
base = b"%PDF-1.4\n"
|
||
base += b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"
|
||
base += b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n"
|
||
base += b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n"
|
||
xref1 = len(base)
|
||
base += b"xref\n0 4\n0000000000 65535 f \n0000000009 00000 n \n0000000062 00000 n \n0000000115 00000 n \n"
|
||
base += b"trailer\n<< /Size 4 /Root 1 0 R >>\n"
|
||
base += f"startxref\n{xref1}\n%%EOF\n".encode()
|
||
|
||
# Incremental update: add a hidden annotation with our secret
|
||
incr = f'4 0 obj\n<< /Type /Annot /Subtype /Text /Contents ({PLINIAN_DIVIDER}) /Rect [0 0 0 0] /F 2 >>\nendobj\n'.encode('utf-8')
|
||
incr_offset = len(base)
|
||
update = incr
|
||
xref2 = len(base) + len(update)
|
||
update += f"xref\n4 1\n{incr_offset:010d} 00000 n \ntrailer\n<< /Size 5 /Root 1 0 R /Prev {xref1} >>\nstartxref\n{xref2}\n%%EOF\n".encode()
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_pdf_incremental.pdf')
|
||
with open(path, 'wb') as f:
|
||
f.write(base + update)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 88. PDF form field steganography
|
||
# =============================================================================
|
||
|
||
def generate_pdf_form_fields():
|
||
"""Create a PDF with Plinian divider hidden in form field default values."""
|
||
print(" Generating PDF form field steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
|
||
objects = []
|
||
objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [5 0 R 6 0 R] >> >>\nendobj\n")
|
||
objects.append(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")
|
||
objects.append(b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Annots [5 0 R 6 0 R] >>\nendobj\n")
|
||
objects.append(b"4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n")
|
||
# Hidden form field with secret as default value
|
||
objects.append(f"5 0 obj\n<< /Type /Annot /Subtype /Widget /FT /Tx /T (steg_field) /V ({PLINIAN_DIVIDER}) /Rect [0 0 0 0] /F 6 >>\nendobj\n".encode('utf-8'))
|
||
objects.append(f"6 0 obj\n<< /Type /Annot /Subtype /Widget /FT /Tx /T (steg_b64) /V ({b64}) /Rect [0 0 0 0] /F 6 >>\nendobj\n".encode('utf-8'))
|
||
|
||
pdf = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"
|
||
offsets = []
|
||
for obj in objects:
|
||
offsets.append(len(pdf))
|
||
pdf += obj
|
||
xref_offset = len(pdf)
|
||
pdf += f"xref\n0 {len(objects) + 1}\n".encode()
|
||
pdf += b"0000000000 65535 f \n"
|
||
for offset in offsets:
|
||
pdf += f"{offset:010d} 00000 n \n".encode()
|
||
pdf += f"trailer\n<< /Size {len(objects) + 1} /Root 1 0 R >>\n".encode()
|
||
pdf += f"startxref\n{xref_offset}\n%%EOF\n".encode()
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_pdf_forms.pdf')
|
||
with open(path, 'wb') as f:
|
||
f.write(pdf)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 89. HTML event handler steganography
|
||
# =============================================================================
|
||
|
||
def generate_html_events():
|
||
"""Create an HTML file with Plinian divider hidden in event handler attributes."""
|
||
print(" Generating HTML event handler steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
hex_enc = secret.hex()
|
||
|
||
html = f'''<!DOCTYPE html>
|
||
<html>
|
||
<head><title>Event Handler Steganography</title></head>
|
||
<body onload="void('{b64}')"
|
||
onresize="void('{hex_enc}')"
|
||
onscroll="void(0)"
|
||
data-init="{PLINIAN_DIVIDER}">
|
||
<h1>ST3GG Event Handler Example</h1>
|
||
<p onclick="void('{b64}')" onmouseover="void(0)">
|
||
This page contains hidden data in event handler attributes.
|
||
</p>
|
||
<div onmouseenter="void('{hex_enc}')" style="display:none">
|
||
{PLINIAN_DIVIDER}
|
||
</div>
|
||
<input type="hidden" name="steg" value="{PLINIAN_DIVIDER}">
|
||
<form action="javascript:void(0)">
|
||
<input type="hidden" name="payload" value="{b64}">
|
||
<input type="hidden" name="payload_hex" value="{hex_enc}">
|
||
</form>
|
||
</body>
|
||
</html>'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_html_events.html')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(html)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 90. XML entity declaration steganography
|
||
# =============================================================================
|
||
|
||
def generate_xml_entities():
|
||
"""Create an XML file with Plinian divider hidden in entity declarations."""
|
||
print(" Generating XML entity steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
|
||
# Encode each byte as an XML entity
|
||
entity_defs = '\n'.join(
|
||
f' <!ENTITY steg{i:02x} "{b:03d}">'
|
||
for i, b in enumerate(secret)
|
||
)
|
||
|
||
xml = f'''<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE data [
|
||
<!ENTITY steg_payload "{PLINIAN_DIVIDER}">
|
||
<!ENTITY steg_b64 "{b64}">
|
||
{entity_defs}
|
||
]>
|
||
<data>
|
||
<visible>This XML contains hidden data in entity declarations.</visible>
|
||
<payload>&steg_payload;</payload>
|
||
<encoded>&steg_b64;</encoded>
|
||
</data>'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_xml_entities.xml')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(xml)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 91. Nested ZIP steganography
|
||
# =============================================================================
|
||
|
||
def generate_nested_zip():
|
||
"""Create a nested ZIP (ZIP inside ZIP) with Plinian divider at inner level."""
|
||
print(" Generating nested ZIP steganography...")
|
||
import zipfile, io
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Inner ZIP
|
||
inner_buf = io.BytesIO()
|
||
with zipfile.ZipFile(inner_buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||
zf.writestr('secret.txt', PLINIAN_DIVIDER)
|
||
zf.writestr('flag.txt', 'ST3GG{n3st3d_z1p_m4tr10shk4}')
|
||
zf.comment = secret
|
||
inner_zip = inner_buf.getvalue()
|
||
|
||
# Outer ZIP
|
||
path = os.path.join(OUTPUT_DIR, 'example_nested.zip')
|
||
with zipfile.ZipFile(path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||
zf.writestr('data/readme.txt', 'Just a normal archive. Nothing to see here.\n')
|
||
zf.writestr('data/inner.zip', inner_zip)
|
||
zf.writestr('data/notes.txt', 'Check inner.zip for more data.\n')
|
||
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 92. Emoji skin tone selector steganography
|
||
# =============================================================================
|
||
|
||
def generate_emoji_skin_tone():
|
||
"""Create text with Plinian divider hidden in emoji skin tone modifier choices."""
|
||
print(" Generating emoji skin tone selector text...")
|
||
|
||
# Skin tone modifiers: U+1F3FB to U+1F3FF (5 tones = ~2.3 bits each)
|
||
# We use 4 tones for clean 2-bit encoding:
|
||
# Light (1F3FB)=00, MedLight (1F3FC)=01, MedDark (1F3FE)=10, Dark (1F3FF)=11
|
||
TONES = ['\U0001F3FB', '\U0001F3FC', '\U0001F3FE', '\U0001F3FF']
|
||
# Base emojis that support skin tones
|
||
BASES = ['\U0001F44D', '\U0001F44B', '\U0001F64C', '\U0001F44F', # thumbs up, wave, raised hands, clap
|
||
'\U0001F91D', '\U0001F4AA', '\U0001F91E', '\U0001F596', # handshake, flex, crossed fingers, vulcan
|
||
'\U0001F44C', '\U0001F918', '\U0001F919', '\U0001F91F', # ok, horns, call me, love you
|
||
'\U0001F448', '\U0001F449', '\U0001F446', '\U0001F447'] # point left/right/up/down
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
# Convert to 2-bit pairs
|
||
pairs = []
|
||
for b in msg_bytes:
|
||
pairs.append((b >> 6) & 0x03)
|
||
pairs.append((b >> 4) & 0x03)
|
||
pairs.append((b >> 2) & 0x03)
|
||
pairs.append(b & 0x03)
|
||
|
||
lines = ["Emoji Skin Tone Diversity Display\n"]
|
||
pair_idx = 0
|
||
for i, base in enumerate(BASES):
|
||
row = f" {i+1:2d}. "
|
||
for _ in range(len(msg_bytes) // len(BASES) + 2):
|
||
if pair_idx < len(pairs):
|
||
tone = TONES[pairs[pair_idx]]
|
||
row += base + tone + ' '
|
||
pair_idx += 1
|
||
else:
|
||
row += base + TONES[0] + ' '
|
||
lines.append(row.rstrip())
|
||
|
||
lines.append(f"\nTotal emoji: {pair_idx}")
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_emoji_skin_tone.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(lines))
|
||
print(f" -> {path} ({pair_idx} 2-bit pairs)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 93. Punycode / IDN homograph steganography
|
||
# =============================================================================
|
||
|
||
def generate_punycode():
|
||
"""Create a text file with Plinian divider hidden in Punycode-encoded domains."""
|
||
print(" Generating Punycode/IDN steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
|
||
# Encode each byte as a Punycode domain with homograph characters
|
||
domains = []
|
||
for i, b in enumerate(secret):
|
||
# Create a domain that encodes the byte value
|
||
# Use mix of ASCII and non-ASCII to make it look like IDN
|
||
label = f"s{b:02x}"
|
||
# Add a non-ASCII char to force Punycode
|
||
idn_label = label + chr(0x0430 + (b % 20)) # Cyrillic letters
|
||
try:
|
||
puny = idn_label.encode('idna').decode('ascii')
|
||
except (UnicodeError, UnicodeDecodeError):
|
||
puny = f"xn--{label}"
|
||
domains.append(f"{puny}.example.com")
|
||
|
||
text = f"""IDN / Punycode Domain Reference
|
||
================================
|
||
|
||
The following domains demonstrate Internationalized Domain Names
|
||
(IDN) with Punycode encoding. Each domain encodes data in its
|
||
label structure.
|
||
|
||
Domains:
|
||
{chr(10).join(f' {d}' for d in domains)}
|
||
|
||
Base64 encoded payload:
|
||
{b64}
|
||
|
||
Raw hex:
|
||
{secret.hex()}
|
||
|
||
Total domains: {len(domains)}
|
||
Generated by ST3GG.
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_punycode.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path} ({len(domains)} encoded domains)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 94. JPEG restart marker steganography
|
||
# =============================================================================
|
||
|
||
def generate_jpeg_restart_markers():
|
||
"""Create a JPEG with Plinian divider hidden in restart marker intervals."""
|
||
print(" Generating JPEG restart marker steganography...")
|
||
|
||
width, height = 100, 100
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = (int(160 + 60 * x/width), int(100 + 80 * y/height), 120)
|
||
|
||
import io
|
||
buf = io.BytesIO()
|
||
img.save(buf, 'JPEG', quality=90)
|
||
jpeg_data = bytearray(buf.getvalue())
|
||
|
||
# Inject restart markers (FFD0-FFD7) carrying data
|
||
# The marker index (0-7) encodes 3 bits
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
import base64
|
||
b64 = base64.b64encode(secret)
|
||
|
||
# Build restart marker payload and inject after SOS
|
||
rst_data = bytearray()
|
||
for i, byte_val in enumerate(secret[:8]): # 8 restart markers available
|
||
marker_idx = byte_val % 8
|
||
rst_data.extend([0xFF, 0xD0 + marker_idx])
|
||
|
||
# Find SOS marker (FFDA) and inject before image data
|
||
sos_pos = jpeg_data.find(b'\xFF\xDA')
|
||
if sos_pos > 0:
|
||
# Find end of SOS header (after the scan header)
|
||
sos_len = struct.unpack('>H', jpeg_data[sos_pos+2:sos_pos+4])[0]
|
||
inject_pos = sos_pos + 2 + sos_len
|
||
new_jpeg = bytes(jpeg_data[:inject_pos]) + bytes(rst_data) + bytes(jpeg_data[inject_pos:])
|
||
else:
|
||
new_jpeg = bytes(jpeg_data)
|
||
|
||
# Also add a COM marker with the full secret
|
||
com_data = b'ST3GG: ' + secret
|
||
com_marker = b'\xFF\xFE' + struct.pack('>H', len(com_data) + 2) + com_data
|
||
new_jpeg = new_jpeg[:2] + com_marker + new_jpeg[2:]
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_jpeg_restart.jpg')
|
||
with open(path, 'wb') as f:
|
||
f.write(new_jpeg)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 95. Matched pairs LSB steganography
|
||
# =============================================================================
|
||
|
||
def generate_matched_pairs_lsb():
|
||
"""Create a PNG with Plinian divider using matched-pairs LSB embedding."""
|
||
print(" Generating matched pairs LSB PNG...")
|
||
width, height = 200, 200
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
import random
|
||
random.seed(777)
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 80 * (x / width) + random.randint(-3, 3))
|
||
g = int(80 + 100 * (y / height) + random.randint(-3, 3))
|
||
b = int(60 + 80 * ((x+y) / (width+height)) + random.randint(-3, 3))
|
||
pixels[x, y] = (max(0,min(255,r)), max(0,min(255,g)), max(0,min(255,b)))
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Matched pairs: use pixel pairs where the bit is encoded in
|
||
# whether pixel[2k] > pixel[2k+1] (1) or not (0) in the R channel
|
||
bit_idx = 0
|
||
for y in range(height):
|
||
for x in range(0, width - 1, 2):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
r1, g1, b1 = pixels[x, y]
|
||
r2, g2, b2 = pixels[x + 1, y]
|
||
if bits[bit_idx] == 1:
|
||
# Ensure r1 > r2
|
||
if r1 <= r2:
|
||
r1, r2 = max(r1, r2), min(r1, r2)
|
||
if r1 == r2:
|
||
r1 = min(255, r1 + 1)
|
||
else:
|
||
# Ensure r1 <= r2
|
||
if r1 > r2:
|
||
r1, r2 = min(r1, r2), max(r1, r2)
|
||
pixels[x, y] = (r1, g1, b1)
|
||
pixels[x + 1, y] = (r2, g2, b2)
|
||
bit_idx += 1
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_matched_pairs.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits via matched pairs)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 96. PNG scanline filter abuse
|
||
# =============================================================================
|
||
|
||
def generate_png_scanline_filter():
|
||
"""Create a PNG with Plinian divider encoded in scanline filter byte choices."""
|
||
print(" Generating PNG scanline filter abuse...")
|
||
|
||
width, height = 100, 200 # Need enough scanlines for the message
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = (int(120 + 60*x/width), int(100 + 80*y/height), 130)
|
||
|
||
# Save normally first
|
||
import io
|
||
buf = io.BytesIO()
|
||
img.save(buf, 'PNG')
|
||
png_data = buf.getvalue()
|
||
|
||
# Parse PNG, decompress IDAT, modify filter bytes, recompress
|
||
# Each scanline starts with a filter byte (0-4)
|
||
# We encode: filter 0 (None) = bit 0, filter 1 (Sub) = bit 1
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Find and decompress IDAT chunks
|
||
pos = 8 # Skip PNG signature
|
||
idat_data = b''
|
||
chunks_before = bytearray()
|
||
chunks_after = bytearray()
|
||
found_idat = False
|
||
past_idat = False
|
||
|
||
while pos < len(png_data):
|
||
chunk_len = struct.unpack('>I', png_data[pos:pos+4])[0]
|
||
chunk_type = png_data[pos+4:pos+8]
|
||
chunk_data = png_data[pos+8:pos+8+chunk_len]
|
||
chunk_crc = png_data[pos+8+chunk_len:pos+12+chunk_len]
|
||
|
||
if chunk_type == b'IDAT':
|
||
idat_data += chunk_data
|
||
found_idat = True
|
||
elif not found_idat:
|
||
chunks_before.extend(png_data[pos:pos+12+chunk_len])
|
||
else:
|
||
past_idat = True
|
||
chunks_after.extend(png_data[pos:pos+12+chunk_len])
|
||
|
||
pos += 12 + chunk_len
|
||
|
||
if idat_data:
|
||
raw = zlib.decompress(idat_data)
|
||
scanline_len = width * 3 + 1 # 3 bytes per pixel + 1 filter byte
|
||
|
||
# Modify filter bytes to encode our message
|
||
raw_array = bytearray(raw)
|
||
for i, bit in enumerate(bits):
|
||
if i >= height:
|
||
break
|
||
offset = i * scanline_len
|
||
# Set filter byte: 0=None (bit 0), 1=Sub (bit 1)
|
||
raw_array[offset] = 1 if bit == 1 else 0
|
||
|
||
# Recompress
|
||
compressed = zlib.compress(bytes(raw_array))
|
||
new_idat = b'IDAT' + compressed
|
||
idat_crc = zlib.crc32(new_idat) & 0xFFFFFFFF
|
||
idat_chunk = struct.pack('>I', len(compressed)) + new_idat + struct.pack('>I', idat_crc)
|
||
|
||
new_png = png_data[:8] + bytes(chunks_before) + idat_chunk + bytes(chunks_after)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_scanline_filter.png')
|
||
with open(path, 'wb') as f:
|
||
f.write(new_png)
|
||
print(f" -> {path} ({min(len(bits), height)} bits in filter bytes)")
|
||
else:
|
||
path = os.path.join(OUTPUT_DIR, 'example_scanline_filter.png')
|
||
img.save(path)
|
||
print(f" -> {path} (fallback: no filter modification)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 97. QR code error correction steganography
|
||
# =============================================================================
|
||
|
||
def generate_qr_steg():
|
||
"""Create a text-based QR representation with Plinian divider in the data."""
|
||
print(" Generating QR code steganography...")
|
||
import base64
|
||
|
||
# Since we can't easily generate actual QR images without qrcode lib,
|
||
# we create a text file that describes a QR payload with hidden data
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64 = base64.b64encode(secret).decode()
|
||
|
||
# Build QR data segments as they would be encoded
|
||
# Mode indicator (0100 = byte mode) + char count (8 bits) + data
|
||
mode = '0100'
|
||
count = format(len(secret), '08b')
|
||
data_bits = ''.join(format(b, '08b') for b in secret)
|
||
|
||
# Build a visual text-QR using block characters
|
||
block = '\u2588' # Full block
|
||
light = ' '
|
||
import random
|
||
random.seed(42)
|
||
qr_size = 25
|
||
# Create a pseudo-QR pattern
|
||
qr_rows = []
|
||
for y in range(qr_size):
|
||
row = ''
|
||
for x in range(qr_size):
|
||
# Finder patterns in corners
|
||
if (x < 7 and y < 7) or (x >= qr_size-7 and y < 7) or (x < 7 and y >= qr_size-7):
|
||
# Finder pattern
|
||
dx = x if x < 7 else (x - (qr_size-7)) if x >= qr_size-7 else x
|
||
dy = y if y < 7 else (y - (qr_size-7))
|
||
if dx in (0,6) or dy in (0,6) or (2<=dx<=4 and 2<=dy<=4):
|
||
row += block
|
||
else:
|
||
row += light
|
||
else:
|
||
# Data area - encode our secret bits
|
||
bit_pos = (y * qr_size + x) - 49 # Offset past finders
|
||
if 0 <= bit_pos < len(data_bits):
|
||
row += block if data_bits[bit_pos] == '1' else light
|
||
else:
|
||
row += block if random.random() > 0.5 else light
|
||
qr_rows.append(row)
|
||
|
||
qr_visual = '\n'.join(qr_rows)
|
||
|
||
text = f"""QR Code Steganography Example
|
||
|
||
Visual QR pattern (text representation):
|
||
{qr_visual}
|
||
|
||
QR Data Analysis:
|
||
Mode: Byte (0100)
|
||
Character count: {len(secret)}
|
||
Data bits: {len(data_bits)}
|
||
|
||
Encoded payload (base64):
|
||
{b64}
|
||
|
||
Raw data bits (first 64):
|
||
{data_bits[:64]}...
|
||
|
||
The QR error correction capacity allows modifying up to 30% of
|
||
data codewords while maintaining readability, creating space for
|
||
steganographic embedding in the error correction blocks.
|
||
|
||
Generated by ST3GG.
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_qr_steg.txt')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 98. Echo hiding steganography
|
||
# =============================================================================
|
||
|
||
def generate_echo_hiding():
|
||
"""Create a WAV with Plinian divider hidden in echo delay patterns."""
|
||
print(" Generating echo hiding WAV...")
|
||
import numpy as np
|
||
import math
|
||
|
||
sample_rate = 22050
|
||
duration = 3
|
||
num_samples = sample_rate * duration
|
||
|
||
# Generate a rich tone
|
||
t = np.arange(num_samples) / sample_rate
|
||
signal = (0.4 * np.sin(2 * np.pi * 440 * t) +
|
||
0.2 * np.sin(2 * np.pi * 880 * t) +
|
||
0.1 * np.sin(2 * np.pi * 1320 * t))
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Echo hiding: bit=0 -> short echo (150 samples), bit=1 -> long echo (300 samples)
|
||
SHORT_DELAY = 150
|
||
LONG_DELAY = 300
|
||
ECHO_AMP = 0.3
|
||
segment_len = num_samples // len(bits) if bits else num_samples
|
||
|
||
output = signal.copy()
|
||
for i, bit in enumerate(bits):
|
||
start = i * segment_len
|
||
end = min(start + segment_len, num_samples)
|
||
delay = LONG_DELAY if bit == 1 else SHORT_DELAY
|
||
for j in range(start + delay, end):
|
||
output[j] += ECHO_AMP * signal[j - delay]
|
||
|
||
# Normalize and convert to 16-bit
|
||
output = output / np.max(np.abs(output)) * 0.8
|
||
samples_16 = (output * 16000).astype(np.int16)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_echo_hiding.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2)
|
||
wav.setframerate(sample_rate)
|
||
wav.writeframes(samples_16.tobytes())
|
||
print(f" -> {path} ({len(bits)} bits in echo delays)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 99. Phase coding steganography
|
||
# =============================================================================
|
||
|
||
def generate_phase_coding():
|
||
"""Create a WAV with Plinian divider hidden in phase of frequency components."""
|
||
print(" Generating phase coding WAV...")
|
||
import numpy as np
|
||
|
||
sample_rate = 22050
|
||
segment_size = 512 # FFT segment size
|
||
num_segments = 200 # Total segments
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Generate base signal
|
||
total_samples = segment_size * num_segments
|
||
t = np.arange(total_samples) / sample_rate
|
||
signal = 0.5 * np.sin(2 * np.pi * 440 * t) + 0.2 * np.random.randn(total_samples) * 0.05
|
||
|
||
# Phase coding: modify phase of first segment's DFT
|
||
# bit=0 -> phase=0, bit=1 -> phase=pi
|
||
output = signal.copy()
|
||
for i, bit in enumerate(bits):
|
||
if i >= num_segments:
|
||
break
|
||
start = i * segment_size
|
||
segment = output[start:start + segment_size]
|
||
spectrum = np.fft.rfft(segment)
|
||
magnitudes = np.abs(spectrum)
|
||
phases = np.angle(spectrum)
|
||
|
||
# Modify phase of a specific frequency bin
|
||
target_bin = 10 + (i % 20) # Spread across bins
|
||
phases[target_bin] = np.pi if bit == 1 else 0
|
||
|
||
# Reconstruct
|
||
spectrum = magnitudes * np.exp(1j * phases)
|
||
output[start:start + segment_size] = np.fft.irfft(spectrum, n=segment_size)
|
||
|
||
# Normalize and convert
|
||
output = output / np.max(np.abs(output)) * 0.8
|
||
samples_16 = (output * 16000).astype(np.int16)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_phase_coding.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2)
|
||
wav.setframerate(sample_rate)
|
||
wav.writeframes(samples_16.tobytes())
|
||
print(f" -> {path} ({min(len(bits), num_segments)} bits in phase)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 100. Spread spectrum (DSSS) steganography
|
||
# =============================================================================
|
||
|
||
def generate_spread_spectrum():
|
||
"""Create a WAV with Plinian divider hidden via Direct Sequence Spread Spectrum."""
|
||
print(" Generating spread spectrum (DSSS) WAV...")
|
||
import numpy as np
|
||
|
||
sample_rate = 22050
|
||
chip_rate = 100 # Chips per bit
|
||
duration = 4
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Generate carrier signal
|
||
total_samples = sample_rate * duration
|
||
t = np.arange(total_samples) / sample_rate
|
||
carrier = 0.5 * np.sin(2 * np.pi * 440 * t)
|
||
|
||
# Generate PN (pseudo-noise) sequence
|
||
np.random.seed(42) # Fixed seed = shared secret
|
||
samples_per_bit = sample_rate // chip_rate
|
||
|
||
# Spread each bit across samples_per_bit samples using PN sequence
|
||
spread = np.zeros(total_samples)
|
||
for i, bit in enumerate(bits):
|
||
start = i * samples_per_bit
|
||
if start + samples_per_bit > total_samples:
|
||
break
|
||
pn = np.random.choice([-1, 1], size=samples_per_bit)
|
||
data_val = 1 if bit == 1 else -1
|
||
spread[start:start + samples_per_bit] = data_val * pn * 0.02 # Low amplitude
|
||
|
||
output = carrier + spread
|
||
output = output / np.max(np.abs(output)) * 0.8
|
||
samples_16 = (output * 16000).astype(np.int16)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_spread_spectrum.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2)
|
||
wav.setframerate(sample_rate)
|
||
wav.writeframes(samples_16.tobytes())
|
||
print(f" -> {path} ({len(bits)} bits via DSSS)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 101. Quantization noise steganography
|
||
# =============================================================================
|
||
|
||
def generate_quantization_noise():
|
||
"""Create a WAV with Plinian divider hidden in quantization noise patterns."""
|
||
print(" Generating quantization noise WAV...")
|
||
import numpy as np
|
||
|
||
sample_rate = 22050
|
||
duration = 2
|
||
num_samples = sample_rate * duration
|
||
|
||
# Generate 16-bit audio
|
||
t = np.arange(num_samples) / sample_rate
|
||
signal = 0.5 * np.sin(2 * np.pi * 523.25 * t) + 0.2 * np.sin(2 * np.pi * 659.25 * t)
|
||
samples = (signal * 16000).astype(np.int16)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for byte in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((byte >> j) & 1)
|
||
|
||
# Quantization noise: add controlled noise to the 2nd LSB
|
||
# bit=0: 2nd LSB = 0, bit=1: 2nd LSB = 1
|
||
for i, bit in enumerate(bits):
|
||
if i >= len(samples):
|
||
break
|
||
val = int(samples[i])
|
||
# Handle signed 16-bit: convert to unsigned, modify, convert back
|
||
u = val & 0xFFFF
|
||
u = (u & 0xFFFD) | (bit << 1)
|
||
samples[i] = np.int16(u if u < 32768 else u - 65536)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_quantization_noise.wav')
|
||
with wave.open(path, 'w') as wav:
|
||
wav.setnchannels(1)
|
||
wav.setsampwidth(2)
|
||
wav.setframerate(sample_rate)
|
||
wav.writeframes(samples.tobytes())
|
||
print(f" -> {path} ({len(bits)} bits in quantization noise)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 102. BPCS (Bit-Plane Complexity Segmentation) steganography
|
||
# =============================================================================
|
||
|
||
def generate_bpcs():
|
||
"""Create a PNG with Plinian divider using BPCS embedding in complex bit planes."""
|
||
print(" Generating BPCS steganography PNG...")
|
||
import numpy as np
|
||
|
||
width, height = 200, 200
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
|
||
# Generate a natural-looking image with varying complexity
|
||
np.random.seed(123)
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(100 + 80 * (x/width) + np.random.randint(-10, 10))
|
||
g = int(80 + 100 * (y/height) + np.random.randint(-10, 10))
|
||
b = int(60 + 80 * ((x+y)/(width+height)) + np.random.randint(-10, 10))
|
||
pixels[x, y] = (max(0,min(255,r)), max(0,min(255,g)), max(0,min(255,b)))
|
||
|
||
# Extract image as numpy array
|
||
img_array = np.array(img, dtype=np.uint8)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
|
||
# BPCS: replace "complex" 8x8 blocks in bit plane 0 with message data
|
||
# Complexity threshold: blocks with >40% transitions are "complex enough"
|
||
BLOCK_SIZE = 8
|
||
THRESHOLD = 0.4
|
||
|
||
def block_complexity(block):
|
||
"""Calculate complexity as ratio of bit transitions."""
|
||
transitions = 0
|
||
total = 0
|
||
for r in range(block.shape[0]):
|
||
for c in range(block.shape[1] - 1):
|
||
if block[r, c] != block[r, c + 1]:
|
||
transitions += 1
|
||
total += 1
|
||
for r in range(block.shape[0] - 1):
|
||
for c in range(block.shape[1]):
|
||
if block[r, c] != block[r + 1, c]:
|
||
transitions += 1
|
||
total += 1
|
||
return transitions / total if total > 0 else 0
|
||
|
||
# Convert payload to bits
|
||
payload_bits = []
|
||
for b in payload:
|
||
for j in range(7, -1, -1):
|
||
payload_bits.append((b >> j) & 1)
|
||
|
||
# Embed in bit plane 0 of red channel
|
||
bit_idx = 0
|
||
channel = img_array[:, :, 0] # Red channel
|
||
bit_plane = (channel >> 0) & 1 # Bit plane 0
|
||
|
||
for by in range(0, height - BLOCK_SIZE + 1, BLOCK_SIZE):
|
||
for bx in range(0, width - BLOCK_SIZE + 1, BLOCK_SIZE):
|
||
if bit_idx + BLOCK_SIZE * BLOCK_SIZE > len(payload_bits):
|
||
break
|
||
block = bit_plane[by:by+BLOCK_SIZE, bx:bx+BLOCK_SIZE]
|
||
if block_complexity(block) >= THRESHOLD:
|
||
# Replace this complex block with message data
|
||
for r in range(BLOCK_SIZE):
|
||
for c in range(BLOCK_SIZE):
|
||
if bit_idx < len(payload_bits):
|
||
bit_plane[by+r, bx+c] = payload_bits[bit_idx]
|
||
bit_idx += 1
|
||
if bit_idx + BLOCK_SIZE * BLOCK_SIZE > len(payload_bits):
|
||
break
|
||
|
||
# Reconstruct channel
|
||
channel = (channel & 0xFE) | bit_plane
|
||
img_array[:, :, 0] = channel
|
||
result = Image.fromarray(img_array)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_bpcs.png')
|
||
result.save(path)
|
||
print(f" -> {path} ({bit_idx} bits via BPCS)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 103. JPEG DCT coefficient steganography
|
||
# =============================================================================
|
||
|
||
def generate_jpeg_dct():
|
||
"""Create a JPEG with Plinian divider hidden in DCT coefficients (manual)."""
|
||
print(" Generating JPEG DCT coefficient steganography...")
|
||
import numpy as np
|
||
|
||
width, height = 128, 128 # Multiple of 8 for DCT blocks
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(140 + 60 * np.sin(x * 0.1) + 30 * np.cos(y * 0.08))
|
||
g = int(120 + 50 * np.cos(x * 0.08 + y * 0.05))
|
||
b = int(100 + 40 * np.sin((x + y) * 0.06))
|
||
pixels[x, y] = (max(0,min(255,r)), max(0,min(255,g)), max(0,min(255,b)))
|
||
|
||
# Convert to grayscale numpy array for DCT processing
|
||
gray = np.array(img.convert('L'), dtype=np.float64)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for b in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# Manual 2D DCT using numpy (type-II DCT via FFT)
|
||
def dct2(block):
|
||
return np.real(np.fft.fft2(block))
|
||
|
||
def idct2(block):
|
||
return np.real(np.fft.ifft2(block))
|
||
|
||
# Embed bits in mid-frequency DCT coefficients of 8x8 blocks
|
||
bit_idx = 0
|
||
BLOCK = 8
|
||
for by in range(0, height, BLOCK):
|
||
for bx in range(0, width, BLOCK):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
block = gray[by:by+BLOCK, bx:bx+BLOCK].copy()
|
||
coeffs = dct2(block)
|
||
# Modify coefficient at position (3,4) — mid-frequency
|
||
if bit_idx < len(bits):
|
||
val = coeffs[3, 4]
|
||
if bits[bit_idx] == 1:
|
||
coeffs[3, 4] = abs(val) + 10 if val >= 0 else -(abs(val) + 10)
|
||
else:
|
||
coeffs[3, 4] = val * 0.5 # Reduce magnitude
|
||
bit_idx += 1
|
||
gray[by:by+BLOCK, bx:bx+BLOCK] = np.clip(idct2(coeffs), 0, 255)
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
# Save as grayscale image (the DCT modification is in the pixel data)
|
||
result = Image.fromarray(gray.astype(np.uint8), 'L')
|
||
path = os.path.join(OUTPUT_DIR, 'example_dct_manual.png')
|
||
result.save(path)
|
||
print(f" -> {path} ({bit_idx} bits in DCT coefficients)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 104. DFT (Discrete Fourier Transform) embedding
|
||
# =============================================================================
|
||
|
||
def generate_dft_embedding():
|
||
"""Create a PNG with Plinian divider hidden in DFT magnitude spectrum."""
|
||
print(" Generating DFT embedding PNG...")
|
||
import numpy as np
|
||
|
||
width, height = 128, 128
|
||
img = Image.new('L', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = int(128 + 60 * np.sin(x * 0.1) + 40 * np.cos(y * 0.12))
|
||
|
||
gray = np.array(img, dtype=np.float64)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for b in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# 2D DFT
|
||
spectrum = np.fft.fft2(gray)
|
||
magnitude = np.abs(spectrum)
|
||
phase = np.angle(spectrum)
|
||
|
||
# Embed bits in magnitude of mid-frequency components
|
||
bit_idx = 0
|
||
for freq in range(10, 60):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
r, c = freq, freq
|
||
if bits[bit_idx] == 1:
|
||
magnitude[r, c] *= 1.5
|
||
magnitude[height - r, width - c] *= 1.5 # Conjugate symmetry
|
||
else:
|
||
magnitude[r, c] *= 0.7
|
||
magnitude[height - r, width - c] *= 0.7
|
||
bit_idx += 1
|
||
|
||
# Reconstruct
|
||
spectrum = magnitude * np.exp(1j * phase)
|
||
result = np.real(np.fft.ifft2(spectrum))
|
||
result = np.clip(result, 0, 255).astype(np.uint8)
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_dft.png')
|
||
Image.fromarray(result, 'L').save(path)
|
||
print(f" -> {path} ({bit_idx} bits in DFT magnitude)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 105. DWT (Discrete Wavelet Transform) - Haar wavelet
|
||
# =============================================================================
|
||
|
||
def generate_dwt_haar():
|
||
"""Create a PNG with Plinian divider hidden in Haar wavelet coefficients."""
|
||
print(" Generating DWT (Haar wavelet) PNG...")
|
||
import numpy as np
|
||
|
||
width, height = 128, 128
|
||
img = Image.new('L', (width, height))
|
||
pixels = img.load()
|
||
np.random.seed(555)
|
||
for y in range(height):
|
||
for x in range(width):
|
||
pixels[x, y] = int(100 + 80 * (x/width) + 40 * (y/height) + np.random.randint(-5, 5))
|
||
|
||
gray = np.array(img, dtype=np.float64)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for b in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b >> j) & 1)
|
||
|
||
# Simple Haar wavelet transform (1 level)
|
||
h = height // 2
|
||
w = width // 2
|
||
LL = np.zeros((h, w))
|
||
LH = np.zeros((h, w))
|
||
HL = np.zeros((h, w))
|
||
HH = np.zeros((h, w))
|
||
|
||
for y in range(h):
|
||
for x in range(w):
|
||
a = gray[2*y, 2*x]
|
||
b_val = gray[2*y, 2*x+1]
|
||
c = gray[2*y+1, 2*x]
|
||
d = gray[2*y+1, 2*x+1]
|
||
LL[y, x] = (a + b_val + c + d) / 4
|
||
LH[y, x] = (a + b_val - c - d) / 4
|
||
HL[y, x] = (a - b_val + c - d) / 4
|
||
HH[y, x] = (a - b_val - c + d) / 4
|
||
|
||
# Embed bits in HH (high-frequency detail) coefficients
|
||
bit_idx = 0
|
||
for y in range(h):
|
||
for x in range(w):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
if bits[bit_idx] == 1:
|
||
HH[y, x] = abs(HH[y, x]) + 5
|
||
else:
|
||
HH[y, x] = abs(HH[y, x]) * 0.3
|
||
bit_idx += 1
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
# Inverse Haar
|
||
result = np.zeros((height, width))
|
||
for y in range(h):
|
||
for x in range(w):
|
||
ll = LL[y, x]; lh = LH[y, x]; hl = HL[y, x]; hh = HH[y, x]
|
||
result[2*y, 2*x] = ll + lh + hl + hh
|
||
result[2*y, 2*x+1] = ll + lh - hl - hh
|
||
result[2*y+1, 2*x] = ll - lh + hl - hh
|
||
result[2*y+1, 2*x+1] = ll - lh - hl + hh
|
||
|
||
result = np.clip(result, 0, 255).astype(np.uint8)
|
||
path = os.path.join(OUTPUT_DIR, 'example_dwt_haar.png')
|
||
Image.fromarray(result, 'L').save(path)
|
||
print(f" -> {path} ({bit_idx} bits in HH wavelet band)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 106. Image subsampling (4:2:0 vs 4:4:4 chroma)
|
||
# =============================================================================
|
||
|
||
def generate_subsampling():
|
||
"""Create a PNG with Plinian divider encoded in chroma subsampling pattern."""
|
||
print(" Generating chroma subsampling steganography...")
|
||
import numpy as np
|
||
|
||
width, height = 200, 200
|
||
img = Image.new('RGB', (width, height))
|
||
pixels = img.load()
|
||
for y in range(height):
|
||
for x in range(width):
|
||
r = int(140 + 60 * (x / width))
|
||
g = int(120 + 80 * (y / height))
|
||
b = int(100 + 60 * ((x + y) / (width + height)))
|
||
pixels[x, y] = (r, g, b)
|
||
|
||
msg_bytes = PLINIAN_DIVIDER.encode('utf-8')
|
||
length_bytes = struct.pack('>I', len(msg_bytes))
|
||
payload = length_bytes + msg_bytes
|
||
bits = []
|
||
for b_val in payload:
|
||
for j in range(7, -1, -1):
|
||
bits.append((b_val >> j) & 1)
|
||
|
||
# Subsampling steg: for each 2x2 pixel block, if bit=1, average the
|
||
# chroma (B channel) across the block (simulating 4:2:0 subsampling).
|
||
# If bit=0, leave chroma at full resolution (4:4:4).
|
||
bit_idx = 0
|
||
for by in range(0, height - 1, 2):
|
||
for bx in range(0, width - 1, 2):
|
||
if bit_idx >= len(bits):
|
||
break
|
||
if bits[bit_idx] == 1:
|
||
# Average B channel across 2x2 block (4:2:0 simulation)
|
||
b_avg = 0
|
||
for dy in range(2):
|
||
for dx in range(2):
|
||
r, g, b = pixels[bx + dx, by + dy]
|
||
b_avg += b
|
||
b_avg //= 4
|
||
for dy in range(2):
|
||
for dx in range(2):
|
||
r, g, _ = pixels[bx + dx, by + dy]
|
||
pixels[bx + dx, by + dy] = (r, g, b_avg)
|
||
bit_idx += 1
|
||
if bit_idx >= len(bits):
|
||
break
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_subsampling.png')
|
||
img.save(path)
|
||
print(f" -> {path} ({bit_idx} bits in chroma subsampling)")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 107. Self-extracting archive steganography
|
||
# =============================================================================
|
||
|
||
def generate_self_extracting():
|
||
"""Create a self-extracting shell script with Plinian divider as embedded payload."""
|
||
print(" Generating self-extracting archive...")
|
||
import base64, tarfile, io
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64_secret = base64.b64encode(secret).decode()
|
||
|
||
# Create a tar.gz payload with the secret
|
||
tar_buf = io.BytesIO()
|
||
with tarfile.open(fileobj=tar_buf, mode='w:gz') as tf:
|
||
info = tarfile.TarInfo(name='secret.txt')
|
||
info.size = len(secret)
|
||
tf.addfile(info, io.BytesIO(secret))
|
||
tar_b64 = base64.b64encode(tar_buf.getvalue()).decode()
|
||
|
||
script = f'''#!/bin/sh
|
||
# Self-extracting archive - ST3GG steganography example
|
||
# This script contains hidden data as an embedded base64 payload
|
||
echo "Extracting..."
|
||
PAYLOAD="{tar_b64}"
|
||
echo "$PAYLOAD" | base64 -d | tar xzf - 2>/dev/null
|
||
if [ -f secret.txt ]; then
|
||
echo "Secret extracted to secret.txt"
|
||
cat secret.txt
|
||
else
|
||
echo "Extraction failed"
|
||
fi
|
||
# Hidden direct payload (not used by extraction):
|
||
# {b64_secret}
|
||
# End of self-extracting archive
|
||
'''
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_self_extracting.sh')
|
||
with open(path, 'w') as f:
|
||
f.write(script)
|
||
os.chmod(path, 0o755)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 108. Extended attributes (xattr) steganography
|
||
# =============================================================================
|
||
|
||
def generate_xattr_steg():
|
||
"""Create a file with Plinian divider hidden in filesystem extended attributes."""
|
||
print(" Generating xattr steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
|
||
# Create the carrier file
|
||
path = os.path.join(OUTPUT_DIR, 'example_xattr.txt')
|
||
with open(path, 'w') as f:
|
||
f.write("This file contains hidden data in its filesystem extended attributes.\n")
|
||
f.write("Use 'getfattr -d' or 'xattr -l' to view them.\n")
|
||
f.write("The actual file content is innocuous.\n")
|
||
|
||
# Set extended attributes with the secret
|
||
try:
|
||
os.setxattr(path, b'user.st3gg.payload', secret)
|
||
os.setxattr(path, b'user.st3gg.b64', base64.b64encode(secret))
|
||
os.setxattr(path, b'user.st3gg.hex', secret.hex().encode())
|
||
os.setxattr(path, b'user.st3gg.flag', b'ST3GG{x4ttr_m4st3r}')
|
||
print(f" -> {path} (4 xattr attributes set)")
|
||
except OSError as e:
|
||
# xattr may not be supported on all filesystems
|
||
print(f" -> {path} (xattr not supported: {e})")
|
||
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# 109. TLS certificate field steganography
|
||
# =============================================================================
|
||
|
||
def generate_tls_cert_steg():
|
||
"""Create a self-signed cert with Plinian divider in certificate fields."""
|
||
print(" Generating TLS certificate steganography...")
|
||
import base64
|
||
|
||
secret = PLINIAN_DIVIDER.encode('utf-8')
|
||
b64_secret = base64.b64encode(secret).decode()
|
||
|
||
# We'll create a PEM-formatted "certificate" with the secret in fields
|
||
# Not a real x509 cert (would need cryptography lib), but structurally valid-looking
|
||
text = f"""-----BEGIN CERTIFICATE-----
|
||
ST3GG Steganography Certificate Example
|
||
This is not a real X.509 certificate but demonstrates hiding data in
|
||
certificate-like structures.
|
||
|
||
Subject: CN={b64_secret}
|
||
Issuer: CN=ST3GG CA, O=STEGOSAURUS WRECKS, L={secret.hex()}
|
||
Serial: {int.from_bytes(secret[:8], 'big')}
|
||
Not Before: Jan 01 00:00:00 2024 GMT
|
||
Not After: Dec 31 23:59:59 2099 GMT
|
||
|
||
Subject Alternative Names:
|
||
DNS: {b64_secret[:30]}.example.com
|
||
DNS: {secret.hex()[:30]}.st3gg.local
|
||
|
||
Extensions:
|
||
X509v3 Subject Key Identifier: {secret.hex()[:40]}
|
||
X509v3 Authority Key Identifier: {secret.hex()[40:]}
|
||
1.2.3.4.5.6.7.8.9: {PLINIAN_DIVIDER}
|
||
|
||
Payload (base64): {b64_secret}
|
||
-----END CERTIFICATE-----
|
||
"""
|
||
|
||
path = os.path.join(OUTPUT_DIR, 'example_tls_cert.pem')
|
||
with open(path, 'w', encoding='utf-8') as f:
|
||
f.write(text)
|
||
print(f" -> {path}")
|
||
return path
|
||
|
||
|
||
# =============================================================================
|
||
# Main
|
||
# =============================================================================
|
||
|
||
def main():
|
||
print("=" * 60)
|
||
print("ST3GG Example File Generator")
|
||
print("=" * 60)
|
||
print(f"Output directory: {OUTPUT_DIR}")
|
||
print(f"Hidden message: {SECRET_MSG}")
|
||
print()
|
||
|
||
files = []
|
||
|
||
# Original examples
|
||
files.append(generate_lsb_png())
|
||
files.append(generate_text_chunk_png())
|
||
files.append(generate_trailing_data_png())
|
||
files.append(generate_zero_width_text())
|
||
files.append(generate_whitespace_text())
|
||
files.append(generate_invisible_ink_text())
|
||
files.append(generate_audio_lsb_wav())
|
||
files.append(generate_exif_png())
|
||
|
||
# Chunk 1: Image formats
|
||
files.append(generate_lsb_bmp())
|
||
files.append(generate_gif_comment())
|
||
files.append(generate_gif_lsb())
|
||
files.append(generate_tiff_metadata())
|
||
files.append(generate_tiff_lsb())
|
||
files.append(generate_ppm_lsb())
|
||
files.append(generate_pgm_lsb())
|
||
files.append(generate_svg_hidden())
|
||
files.append(generate_ico_lsb())
|
||
files.append(generate_webp_metadata())
|
||
files.append(generate_webp_lsb())
|
||
|
||
# Chunk 2: Document & structured data formats
|
||
files.append(generate_html_hidden())
|
||
files.append(generate_xml_hidden())
|
||
files.append(generate_json_hidden())
|
||
files.append(generate_csv_hidden())
|
||
files.append(generate_yaml_hidden())
|
||
files.append(generate_pdf_hidden())
|
||
files.append(generate_rtf_hidden())
|
||
files.append(generate_markdown_hidden())
|
||
|
||
# Chunk 3: Audio, binary & archive formats
|
||
files.append(generate_aiff_lsb())
|
||
files.append(generate_au_lsb())
|
||
files.append(generate_zip_hidden())
|
||
files.append(generate_tar_hidden())
|
||
files.append(generate_gzip_hidden())
|
||
files.append(generate_sqlite_hidden())
|
||
files.append(generate_hexdump_hidden())
|
||
files.append(generate_midi_hidden())
|
||
files.append(generate_pcap_hidden())
|
||
|
||
# Chunk 4: Code & config formats
|
||
files.append(generate_python_hidden())
|
||
files.append(generate_js_hidden())
|
||
files.append(generate_c_hidden())
|
||
files.append(generate_css_hidden())
|
||
files.append(generate_ini_hidden())
|
||
files.append(generate_shell_hidden())
|
||
files.append(generate_sql_hidden())
|
||
files.append(generate_latex_hidden())
|
||
files.append(generate_toml_hidden())
|
||
|
||
# Chunk 5: Unicode & text tricks
|
||
files.append(generate_homoglyph())
|
||
files.append(generate_variation_selector())
|
||
files.append(generate_combining_diacritics())
|
||
files.append(generate_confusable_whitespace())
|
||
files.append(generate_emoji_substitution())
|
||
|
||
# Chunk 6: Network protocol steganography
|
||
files.append(generate_dns_tunnel_pcap())
|
||
files.append(generate_icmp_steg_pcap())
|
||
files.append(generate_tcp_covert_pcap())
|
||
files.append(generate_http_header_pcap())
|
||
|
||
# Chunk 7: Image format tricks
|
||
files.append(generate_png_zip_polyglot())
|
||
files.append(generate_png_filter_encoding())
|
||
files.append(generate_alpha_lsb())
|
||
|
||
# Chunk 8: Misc techniques
|
||
files.append(generate_json_key_ordering())
|
||
files.append(generate_capitalization_encoding())
|
||
files.append(generate_silence_interval_wav())
|
||
|
||
# Chunk 9: More Unicode & text tricks
|
||
files.append(generate_directional_override())
|
||
files.append(generate_hangul_filler())
|
||
files.append(generate_braille_pattern())
|
||
files.append(generate_math_alphanumeric())
|
||
files.append(generate_unicode_normalization())
|
||
files.append(generate_sentence_length())
|
||
files.append(generate_word_choice())
|
||
files.append(generate_misspelling())
|
||
|
||
# Chunk 10: Network & encoding tricks
|
||
files.append(generate_ip_ttl_covert())
|
||
files.append(generate_ip_id_covert())
|
||
files.append(generate_tcp_window_covert())
|
||
files.append(generate_tcp_urgent_covert())
|
||
files.append(generate_dns_txt_record())
|
||
files.append(generate_covert_timing())
|
||
files.append(generate_base32_file())
|
||
files.append(generate_morse_code())
|
||
|
||
# Chunk 11: Image techniques
|
||
files.append(generate_pvd())
|
||
files.append(generate_histogram_shifting())
|
||
files.append(generate_lsb_multibit())
|
||
files.append(generate_lsb_msb_first())
|
||
files.append(generate_bmp_dib_header())
|
||
files.append(generate_gif_disposal())
|
||
files.append(generate_jpeg_app_segment())
|
||
files.append(generate_color_space())
|
||
files.append(generate_png_gama())
|
||
|
||
# Chunk 12: Document, archive & misc
|
||
files.append(generate_pdf_javascript())
|
||
files.append(generate_pdf_incremental())
|
||
files.append(generate_pdf_form_fields())
|
||
files.append(generate_html_events())
|
||
files.append(generate_xml_entities())
|
||
files.append(generate_nested_zip())
|
||
files.append(generate_emoji_skin_tone())
|
||
files.append(generate_punycode())
|
||
|
||
# Chunk 13: Final techniques
|
||
files.append(generate_jpeg_restart_markers())
|
||
files.append(generate_matched_pairs_lsb())
|
||
files.append(generate_png_scanline_filter())
|
||
files.append(generate_qr_steg())
|
||
|
||
# Chunk 14: Audio DSP techniques
|
||
files.append(generate_echo_hiding())
|
||
files.append(generate_phase_coding())
|
||
files.append(generate_spread_spectrum())
|
||
files.append(generate_quantization_noise())
|
||
|
||
# Chunk 15: Image DSP techniques
|
||
files.append(generate_bpcs())
|
||
files.append(generate_jpeg_dct())
|
||
files.append(generate_dft_embedding())
|
||
files.append(generate_dwt_haar())
|
||
files.append(generate_subsampling())
|
||
|
||
# Chunk 16: Misc final techniques
|
||
files.append(generate_self_extracting())
|
||
files.append(generate_xattr_steg())
|
||
files.append(generate_tls_cert_steg())
|
||
|
||
print()
|
||
print(f"Generated {len(files)} example files!")
|
||
print()
|
||
for f in files:
|
||
size = os.path.getsize(f)
|
||
print(f" {os.path.basename(f):40s} {size:>8,} bytes")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|