#!/usr/bin/env python3 """ env_setup.py - Make an analysis environment appear like a real user workstation. Creates realistic user artifacts to defeat sandbox/VM detection that checks for signs of an analysis environment (empty desktop, no documents, default hostnames, etc.). Supports both Linux and Windows environments. Usage: python3 env_setup.py --profile corporate python3 env_setup.py --profile home --skip-hostname --skip-mac python3 env_setup.py --undo # Remove artifacts created by this script python3 env_setup.py --list # Show what would be created """ from __future__ import annotations import argparse import datetime import json import logging import os import platform import random import string import subprocess import sys from pathlib import Path from typing import Optional logger = logging.getLogger(__name__) # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- SCRIPT_MARKER = ".env_setup_manifest.json" CORPORATE_HOSTNAMES = [ "DESKTOP-{rand}", "WS-{rand}", "PC-{rand}", "LAPTOP-{rand}", "{user}-PC", "{user}-LAPTOP", ] HOME_HOSTNAMES = [ "DESKTOP-{rand}", "{user}-Desktop", "MyPC-{rand}", "Home-PC", "{user}s-Computer", ] REALISTIC_USERNAMES = [ "jsmith", "mwilson", "agarcia", "ljohnson", "klee", "rthompson", "cmartinez", "dbrown", "pnguyen", "swright", ] VM_MAC_PREFIXES = [ "00:0C:29", "00:50:56", "08:00:27", # VMware, VirtualBox "00:1C:42", "00:03:FF", # Parallels, HyperV ] REAL_MAC_PREFIXES = [ "D8:BB:C1", "AC:BC:32", "F0:18:98", # Dell "3C:7C:3F", "98:E7:43", "B4:6B:FC", # HP "54:AB:3A", "60:67:20", "CC:96:E5", # Lenovo "A4:83:E7", "DC:A6:32", # Intel ] COMMON_RESOLUTIONS = [ (1920, 1080), (1366, 768), (2560, 1440), (1440, 900), (1680, 1050), (3840, 2160), ] DOCUMENT_NAMES = [ "Q4_Budget_Review.xlsx", "Meeting_Notes_2024.docx", "Project_Timeline.xlsx", "Employee_Handbook.pdf", "Quarterly_Report.docx", "Expense_Report_March.xlsx", "Team_Objectives_2024.docx", "Vendor_Contracts.pdf", "Training_Materials.pptx", "Client_Presentation.pptx", "Performance_Review.docx", "Invoice_4521.pdf", "Network_Diagram.png", "Office_Layout.pdf", "IT_Policy_Update.docx", "Holiday_Calendar.pdf", ] HOME_DOCUMENT_NAMES = [ "vacation_photos.zip", "recipe_collection.docx", "tax_return_2023.pdf", "family_budget.xlsx", "shopping_list.txt", "resume_2024.docx", "movie_list.txt", "workout_plan.xlsx", "book_recommendations.txt", "travel_itinerary.pdf", ] BROWSER_HISTORY_URLS = [ "https://www.google.com/search?q=weather+today", "https://mail.google.com/", "https://www.linkedin.com/feed/", "https://stackoverflow.com/questions", "https://www.amazon.com/", "https://en.wikipedia.org/wiki/Main_Page", "https://www.youtube.com/", "https://www.reddit.com/r/all", "https://news.ycombinator.com/", "https://outlook.office.com/mail/", "https://teams.microsoft.com/", "https://github.com/", ] PROCESS_NAMES_CORPORATE = [ "outlook.exe", "teams.exe", "chrome.exe", "explorer.exe", "excel.exe", "word.exe", "onedrive.exe", "slack.exe", "notepad.exe", "powershell.exe", "svchost.exe", ] PROCESS_NAMES_HOME = [ "chrome.exe", "discord.exe", "spotify.exe", "explorer.exe", "steam.exe", "firefox.exe", "vlc.exe", "notepad.exe", ] # --------------------------------------------------------------------------- # Profile definitions # --------------------------------------------------------------------------- PROFILES = { "corporate": { "hostnames": CORPORATE_HOSTNAMES, "documents": DOCUMENT_NAMES, "processes": PROCESS_NAMES_CORPORATE, "browser_urls": BROWSER_HISTORY_URLS, "create_office_docs": True, }, "home": { "hostnames": HOME_HOSTNAMES, "documents": HOME_DOCUMENT_NAMES, "processes": PROCESS_NAMES_HOME, "browser_urls": BROWSER_HISTORY_URLS[:6], "create_office_docs": False, }, "developer": { "hostnames": ["{user}-dev", "devbox-{rand}", "DESKTOP-{rand}"], "documents": [ "project_spec.md", "api_docs.pdf", "architecture.drawio", "deployment_notes.txt", "TODO.md", "docker-compose.yml", ], "processes": PROCESS_NAMES_CORPORATE + ["code.exe", "node.exe", "python.exe"], "browser_urls": BROWSER_HISTORY_URLS, "create_office_docs": False, }, "minimal": { "hostnames": CORPORATE_HOSTNAMES, "documents": DOCUMENT_NAMES[:3], "processes": PROCESS_NAMES_CORPORATE[:4], "browser_urls": BROWSER_HISTORY_URLS[:3], "create_office_docs": False, }, } # --------------------------------------------------------------------------- # Helper functions # --------------------------------------------------------------------------- def rand_string(length: int = 7) -> str: return "".join(random.choices(string.ascii_uppercase + string.digits, k=length)) def is_windows() -> bool: return platform.system() == "Windows" def is_linux() -> bool: return platform.system() == "Linux" def run_cmd(cmd: list, check: bool = False) -> Optional[subprocess.CompletedProcess]: """Run a subprocess command, suppressing errors unless check=True.""" try: return subprocess.run(cmd, capture_output=True, text=True, check=check) except (subprocess.CalledProcessError, FileNotFoundError) as e: logger.debug(f"Command failed: {cmd}: {e}") return None def get_home_dir() -> Path: return Path.home() def get_desktop() -> Path: home = get_home_dir() if is_windows(): desktop = home / "Desktop" else: desktop = home / "Desktop" desktop.mkdir(parents=True, exist_ok=True) return desktop def get_documents() -> Path: home = get_home_dir() if is_windows(): docs = home / "Documents" else: docs = home / "Documents" docs.mkdir(parents=True, exist_ok=True) return docs def get_downloads() -> Path: home = get_home_dir() dl = home / "Downloads" dl.mkdir(parents=True, exist_ok=True) return dl # --------------------------------------------------------------------------- # Artifact creation functions # --------------------------------------------------------------------------- def create_documents(profile: dict, manifest: dict) -> None: """Create realistic document files in Documents and Desktop.""" docs_dir = get_documents() desktop = get_desktop() created = [] for name in profile["documents"]: # Place some on desktop, some in documents target_dir = random.choice([docs_dir, desktop]) filepath = target_dir / name if filepath.exists(): logger.debug(f"File already exists: {filepath}") continue # Create file with plausible content try: if name.endswith((".txt", ".md")): filepath.write_text(f"# {name}\n\nContent placeholder\nLast updated: {datetime.date.today()}\n") elif name.endswith(".yml"): filepath.write_text("version: '3'\nservices:\n app:\n image: myapp:latest\n") else: # Binary-ish files: write a small header + random data content = name.encode() + b"\x00" * 64 + os.urandom(random.randint(1024, 8192)) filepath.write_bytes(content) # Set realistic modification time (within last 90 days) days_ago = random.randint(1, 90) mtime = (datetime.datetime.now() - datetime.timedelta(days=days_ago)).timestamp() os.utime(filepath, (mtime, mtime)) created.append(str(filepath)) logger.info(f"Created: {filepath}") except OSError as e: logger.warning(f"Failed to create {filepath}: {e}") manifest["documents"] = created def create_download_artifacts(manifest: dict) -> None: """Create files in Downloads folder.""" dl = get_downloads() created = [] download_files = [ "Setup_v2.1.exe", "meeting_recording.mp4", "photo_2024.jpg", "report_final.pdf", "archive.zip", ] for name in download_files: filepath = dl / name if not filepath.exists(): try: filepath.write_bytes(os.urandom(random.randint(512, 4096))) days_ago = random.randint(1, 30) mtime = (datetime.datetime.now() - datetime.timedelta(days=days_ago)).timestamp() os.utime(filepath, (mtime, mtime)) created.append(str(filepath)) except OSError: pass manifest["downloads"] = created def create_browser_history(profile: dict, manifest: dict) -> None: """Create fake browser history artifacts.""" home = get_home_dir() if is_windows(): # Chrome history (SQLite) location chrome_dir = home / "AppData" / "Local" / "Google" / "Chrome" / "User Data" / "Default" else: chrome_dir = home / ".config" / "google-chrome" / "Default" # Create a simple browsing history indicator file # (Full SQLite history creation would require sqlite3 module and is complex) history_dir = home / ".browsing_artifacts" history_dir.mkdir(exist_ok=True) history_file = history_dir / "recent_urls.json" history_data = [] for url in profile["browser_urls"]: days_ago = random.randint(0, 14) ts = (datetime.datetime.now() - datetime.timedelta(days=days_ago, hours=random.randint(0, 23))).isoformat() history_data.append({"url": url, "visited": ts, "visit_count": random.randint(1, 20)}) try: history_file.write_text(json.dumps(history_data, indent=2)) manifest["browser_history"] = str(history_file) logger.info(f"Created browser history: {history_file}") except OSError as e: logger.warning(f"Failed to create browser history: {e}") def set_hostname(profile: dict, manifest: dict, skip: bool = False) -> None: """Set a realistic hostname.""" if skip: logger.info("Skipping hostname change (--skip-hostname)") return current = platform.node() user = os.getenv("USER", os.getenv("USERNAME", "user")) template = random.choice(profile["hostnames"]) new_hostname = template.format(rand=rand_string(), user=user) manifest["original_hostname"] = current manifest["new_hostname"] = new_hostname if is_linux(): logger.info(f"Setting hostname: {current} -> {new_hostname}") result = run_cmd(["sudo", "hostnamectl", "set-hostname", new_hostname]) if result is None: # Fallback try: Path("/etc/hostname").write_text(new_hostname + "\n") except PermissionError: logger.warning("Cannot set hostname (need root)") elif is_windows(): logger.info(f"Setting hostname: {current} -> {new_hostname}") run_cmd(["powershell", "-Command", f"Rename-Computer -NewName '{new_hostname}' -Force"], check=False) else: logger.warning(f"Hostname change not supported on {platform.system()}") def set_mac_address(manifest: dict, skip: bool = False) -> None: """Change MAC address to a non-VM vendor prefix.""" if skip: logger.info("Skipping MAC address change (--skip-mac)") return if not is_linux(): logger.info("MAC address change only supported on Linux (use Technitium MAC Changer on Windows)") return # Find primary interface result = run_cmd(["ip", "route", "show", "default"]) if not result or not result.stdout: logger.warning("Could not detect primary interface") return try: iface = result.stdout.split()[4] except (IndexError, AttributeError): logger.warning("Could not parse primary interface") return # Get current MAC result = run_cmd(["ip", "link", "show", iface]) if result and result.stdout: for line in result.stdout.splitlines(): if "link/ether" in line: current_mac = line.strip().split()[1] manifest["original_mac"] = current_mac break # Generate new MAC with real vendor prefix prefix = random.choice(REAL_MAC_PREFIXES) suffix = ":".join(f"{random.randint(0, 255):02x}" for _ in range(3)) new_mac = f"{prefix}:{suffix}" manifest["new_mac"] = new_mac manifest["interface"] = iface logger.info(f"Setting MAC on {iface}: {manifest.get('original_mac', 'unknown')} -> {new_mac}") run_cmd(["sudo", "ip", "link", "set", iface, "down"]) run_cmd(["sudo", "ip", "link", "set", iface, "address", new_mac]) run_cmd(["sudo", "ip", "link", "set", iface, "up"]) def set_resolution(manifest: dict, skip: bool = False) -> None: """Set screen resolution to a common value.""" if skip: return width, height = random.choice(COMMON_RESOLUTIONS) manifest["resolution"] = f"{width}x{height}" if is_linux(): result = run_cmd(["xrandr", "--output", "default", "--mode", f"{width}x{height}"]) if result is None: # Try Virtual-1 (common in VMs) run_cmd(["xrandr", "--output", "Virtual-1", "--mode", f"{width}x{height}"]) elif is_windows(): # Windows resolution change requires more complex API calls logger.info(f"Set resolution to {width}x{height} via Display Settings") def create_recent_activity(manifest: dict) -> None: """Create recent file access timestamps and temp files.""" home = get_home_dir() created = [] # Recent files/tmp artifacts if is_windows(): recent_dir = home / "AppData" / "Roaming" / "Microsoft" / "Windows" / "Recent" else: recent_dir = home / ".local" / "share" / "recently-used.xbel" # Create a simple recently-used file recent_dir = home / ".local" / "share" recent_dir.mkdir(parents=True, exist_ok=True) # Create temp files that indicate usage tmp_dirs = [Path("/tmp") if is_linux() else Path(os.environ.get("TEMP", "C:\\Temp"))] for tmp in tmp_dirs: if tmp.exists(): for i in range(random.randint(2, 5)): tf = tmp / f"~tmp{rand_string(5)}.tmp" try: tf.write_bytes(os.urandom(random.randint(64, 512))) created.append(str(tf)) except OSError: pass manifest["temp_files"] = created def create_windows_registry_artifacts(manifest: dict) -> None: """Create Windows registry artifacts that indicate a real system.""" if not is_windows(): return try: import winreg except ImportError: logger.debug("winreg not available (not Windows)") return reg_artifacts = [] # Add recent programs to MUI cache try: key_path = r"SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_WRITE) programs = [ ("C:\\Program Files\\Microsoft Office\\root\\Office16\\WINWORD.EXE", "Microsoft Word"), ("C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "Google Chrome"), ("C:\\Program Files\\Microsoft Office\\root\\Office16\\EXCEL.EXE", "Microsoft Excel"), ] for path, name in programs: try: winreg.SetValueEx(key, f"{path}.FriendlyAppName", 0, winreg.REG_SZ, name) reg_artifacts.append(f"MuiCache: {name}") except OSError: pass winreg.CloseKey(key) except OSError: pass manifest["registry_artifacts"] = reg_artifacts # --------------------------------------------------------------------------- # Undo function # --------------------------------------------------------------------------- def undo_setup() -> None: """Remove artifacts created by a previous run.""" home = get_home_dir() manifest_path = home / SCRIPT_MARKER if not manifest_path.exists(): logger.error("No manifest found. Nothing to undo.") sys.exit(1) manifest = json.loads(manifest_path.read_text()) logger.info("Undoing environment setup...") # Remove created files for key in ("documents", "downloads", "temp_files"): for filepath in manifest.get(key, []): try: Path(filepath).unlink(missing_ok=True) logger.info(f"Removed: {filepath}") except OSError as e: logger.warning(f"Could not remove {filepath}: {e}") # Remove browser history bh = manifest.get("browser_history") if bh: try: Path(bh).unlink(missing_ok=True) logger.info(f"Removed: {bh}") except OSError: pass # Restore hostname orig_hostname = manifest.get("original_hostname") if orig_hostname: if is_linux(): run_cmd(["sudo", "hostnamectl", "set-hostname", orig_hostname]) logger.info(f"Hostname restored to: {orig_hostname}") # Restore MAC orig_mac = manifest.get("original_mac") iface = manifest.get("interface") if orig_mac and iface and is_linux(): run_cmd(["sudo", "ip", "link", "set", iface, "down"]) run_cmd(["sudo", "ip", "link", "set", iface, "address", orig_mac]) run_cmd(["sudo", "ip", "link", "set", iface, "up"]) logger.info(f"MAC address restored on {iface}: {orig_mac}") manifest_path.unlink() logger.info("Undo complete.") # --------------------------------------------------------------------------- # List function # --------------------------------------------------------------------------- def list_actions(profile_name: str) -> None: """Show what would be created without making changes.""" profile = PROFILES[profile_name] print(f"Profile: {profile_name}") print(f" Documents to create: {len(profile['documents'])}") for d in profile["documents"]: print(f" - {d}") print(f" Browser history URLs: {len(profile['browser_urls'])}") print(f" Hostname templates: {profile['hostnames']}") print(f" Download artifacts: 5 files") print(f" Temp file artifacts: 2-5 files") if is_windows(): print(f" Registry artifacts: MUI cache entries") print(f" MAC address: will be changed to real vendor prefix") print(f" Resolution: random from {[f'{w}x{h}' for w, h in COMMON_RESOLUTIONS]}") # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- def parse_arguments() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Make analysis environment appear like a real user workstation.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Profiles: corporate - Office/enterprise environment with business documents home - Home user with personal files developer - Developer workstation with code-related files minimal - Minimal artifacts for quick setup Examples: %(prog)s --profile corporate %(prog)s --profile home --skip-hostname --skip-mac %(prog)s --undo %(prog)s --list --profile corporate """, ) parser.add_argument("--input", "--profile", "-p", choices=PROFILES.keys(), default="corporate", help="Environment profile (default: corporate)") parser.add_argument("--skip-hostname", action="store_true", help="Do not change hostname") parser.add_argument("--skip-mac", action="store_true", help="Do not change MAC address") parser.add_argument("--skip-resolution", action="store_true", help="Do not change screen resolution") parser.add_argument("--undo", action="store_true", help="Remove all artifacts from a previous run") parser.add_argument("--list", action="store_true", help="List actions without performing them") parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output") parser.add_argument("--format", default="json", choices=["json", "text", "csv"], help="Output format (default: json)") return parser.parse_args() def main() -> None: args = parse_arguments() logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(levelname)s: %(message)s", ) if args.undo: undo_setup() return if args.list: list_actions(args.profile) return profile = PROFILES[args.profile] manifest = {"profile": args.profile, "timestamp": datetime.datetime.now().isoformat()} logger.info(f"Setting up {args.profile} environment on {platform.system()}") # Apply all environment modifications create_documents(profile, manifest) create_download_artifacts(manifest) create_browser_history(profile, manifest) set_hostname(profile, manifest, skip=args.skip_hostname) set_mac_address(manifest, skip=args.skip_mac) set_resolution(manifest, skip=args.skip_resolution) create_recent_activity(manifest) create_windows_registry_artifacts(manifest) # Save manifest for undo manifest_path = get_home_dir() / SCRIPT_MARKER manifest_path.write_text(json.dumps(manifest, indent=2)) logger.info(f"Manifest saved to {manifest_path}") print() print("Environment setup complete.") print(f" Profile: {args.profile}") print(f" Documents created: {len(manifest.get('documents', []))}") print(f" Downloads created: {len(manifest.get('downloads', []))}") print(f" Hostname: {manifest.get('new_hostname', 'unchanged')}") print(f" MAC: {manifest.get('new_mac', 'unchanged')}") print(f" Resolution: {manifest.get('resolution', 'unchanged')}") print() print("To undo: python3 env_setup.py --undo") if __name__ == "__main__": main()