This post zeroes in on the subset that truly shows up in the wild. Our telemetry reveals that roughly fifteen techniques account for about 80 % of all persistence techniques used in the wild, and many are staples of well‑documented threat groups. By homing in on those, we avoid getting lost in exotic fringe cases and deliver guidance that directly improves day‑to‑day defenses. All examples target Windows environments so the recommendations remain practical.
Rather than skimming every persistence possibility, we will dissect the most common method, persistence via registry run keys, and their many variants, because each one offers attackers a wealth of footholds, and defenders an equally rich detection surface. Along the way, we’ll demonstrate hunting with Nextron’s THOR paired with Timesketch, giving you a workflow you can drop straight into your current standards.
By the end, you will understand the key persistence mechanisms that matter most, possess a solid framework for detecting them at scale, and know how to spot the tell‑tale traces manually during threat‑hunting or incident‑response engagements.
Most Popular Persistence Methods
As mentioned earlier, we aim to focus on the most used persistence techniques, rather than getting lost in rarely observed or exotic methods. While we could rely on personal experience, we’ve chosen to base this selection on actual data.
To achieve this, we load the MITRE ATT&CK Enterprise dataset and map each persistence technique to the threat groups that use it. This enables us to analyze which methods are most prevalent objectively.
First, we need to set up our working environment and install the required MITRE ATT&CK Python library:
python –m venv .venv venv\Scripts\Activate.ps1 git clone https://github.com/mitre/cti pip install mitreattack-python
The relevant dataset will be in cti/enterprise-attack/enterprise-attack.json. Once the environment is ready, we can write a quick proof-of-concept script to extract all persistence techniques and count how many threat groups use each one.
from mitreattack.stix20 import MitreAttackData
def main():
mitre_attack_data = MitreAttackData("./cti/enterprise-attack/enterprise-attack.json")
# Get all groups related to techniques
groups_using_techniques = mitre_attack_data.get_all_groups_using_all_techniques()
for stix_id, groups in groups_using_techniques.items():
stix_data = mitre_attack_data.get_object_by_stix_id(stix_id)
# Skip non-persistence techniques
is_persistence_technique = "persistence" in [phase.get("phase_name", "") for phase in stix_data.get("kill_chain_phases", "")]
if not is_persistence_technique:
continue
# Get ATT&CK ID
external_references = stix_data.get("external_references", "")
if external_references:
attack_source = external_references[0]
if attack_source.get("external_id") and attack_source.get("source_name") == "mitre-attack":
external_id = attack_source["external_id"]
else:
continue
else:
continue
technique_name = stix_data.get("name", "")
group_count = len(groups)
print(f"{external_id} - {technique_name} used by {group_count} groups")
if __name__ == "__main__":
main()
The script returns results like this:
PS C:\nextron > python .\mitre.py T1136.001 - Local Account used by 14 groups T1133 - External Remote Services used by 27 groups T1574.001 - DLL used by 32 groups T1543.003 - Windows Service used by 26 groups T1547.001 - Registry Run Keys / Startup Folder used by 54 groups T1078 - Valid Accounts used by 43 groups T1112 - Modify Registry used by 29 groups T1137.001 - Office Template Macros used by 1 groups T1505.003 - Web Shell used by 32 groups T1556.009 - Conditional Access Policies used by 1 groups T1078.001 - Default Accounts used by 4 groups T1053.005 - Scheduled Task used by 53 groups T1574.012 - COR_PROFILER used by 1 groups T1098.004 - SSH Authorized Keys used by 3 groups T1136 - Create Account used by 4 groups T1574.006 - Dynamic Linker Hijacking used by 3 groups T1053.002 - At used by 3 groups T1078.003 - Local Accounts used by 13 groups T1546.013 - PowerShell Profile used by 1 groups T1546.010 - AppInit DLLs used by 1 groups T1197 - BITS Jobs used by 5 groups T1546.003 - Windows Management Instrumentation Event Subscription used by 10 groups […]
This provides a quick overview of the persistence techniques frequently used by known threat actors.
To make the results easier to understand, we upgraded our script to also generate a bar chart and a pie chart that visualize the distribution of these techniques. We focus on the top 15 techniques and categorize the remaining ones under an “Other” category to maintain clarity.
from mitreattack.stix20 import MitreAttackData
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
def main():
mitre_attack_data = MitreAttackData("./cti/enterprise-attack/enterprise-attack.json")
# Get all groups related to techniques
groups_using_techniques = mitre_attack_data.get_all_groups_using_all_techniques()
results = []
for stix_id, groups in groups_using_techniques.items():
stix_data = mitre_attack_data.get_object_by_stix_id(stix_id)
# Skip non-persistence techniques
is_persistence_technique = "persistence" in [phase.get("phase_name", "") for phase in stix_data.get("kill_chain_phases", "")]
if not is_persistence_technique:
continue
# Get ATT&CK ID
external_references = stix_data.get("external_references", "")
if external_references:
attack_source = external_references[0]
if attack_source.get("external_id") and attack_source.get("source_name") == "mitre-attack":
external_id = attack_source["external_id"]
else:
continue
else:
continue
technique_name = stix_data.get("name", "")
group_count = len(groups)
results.append({
'technique': technique_name,
'attack_id': external_id,
'group_count': group_count
})
results = sorted(results, key=lambda x: x['group_count'], reverse=True)
top_n = 15
top_results = results[:top_n]
other_results = results[top_n:]
technique_labels = [f"{item['attack_id']} {item['technique']}" for item in top_results]
group_counts = [item['group_count'] for item in top_results]
if other_results:
other_count = sum(item['group_count'] for item in other_results)
technique_labels.append("Other")
group_counts.append(other_count)
# Gradient colors
colors = plt.cm.magma(np.linspace(0.2, 0.8, len(technique_labels)))
# Set dark mode
background_color = '#232136'
plt.rcParams['figure.facecolor'] = background_color
plt.rcParams['axes.facecolor'] = background_color
# Horizontal bar chart
fig, ax = plt.subplots(figsize=(14, 0.6 * len(technique_labels) + 2))
bars = ax.barh(technique_labels, group_counts, color=colors, edgecolor='white')
ax.set_xlabel('Number of Groups Using Technique', fontsize=14, color='white')
ax.set_title('Top Persistence Techniques Used by Threat Actors (MITRE ATT&CK)', fontsize=16, color='white', pad=20)
ax.invert_yaxis()
ax.tick_params(colors='white', labelsize=12)
# Add labels
for bar in bars:
ax.text(bar.get_width() + 0.5, bar.get_y() + bar.get_height() / 2,
f'{int(bar.get_width())}', va='center', fontsize=11, color='white')
plt.grid(axis='x', linestyle='--', alpha=0.3)
plt.tight_layout()
plt.show()
# Pie chart
fig, ax = plt.subplots(figsize=(12, 12))
wedges, texts, autotexts = ax.pie(
group_counts,
colors=plt.cm.plasma(np.linspace(0.2, 0.8, len(technique_labels))),
autopct='%1.1f%%',
startangle=140,
pctdistance=0.85,
wedgeprops={'linewidth': 1, 'edgecolor': 'white'}
)
# Style text
for text in texts:
text.set_color('white')
text.set_fontsize(10)
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontsize(10)
# Add leader lines and labels
for i, wedge in enumerate(wedges):
ang = (wedge.theta2 + wedge.theta1) / 2
x = np.cos(np.deg2rad(ang))
y = np.sin(np.deg2rad(ang))
label_x = 1.35 * x
label_y = 1.35 * y
ax.text(label_x, label_y, technique_labels[i], ha='center', va='center', fontsize=9, color='white', wrap=True)
line_x = 0.92 * x
line_y = 0.92 * y
ax.plot([line_x, label_x], [line_y, label_y], color='white', linewidth=0.8)
ax.set_title('Persistence Technique Usage Distribution (MITRE ATT&CK)', fontsize=16, color='white', pad=20)
plt.axis('equal')
plt.tight_layout()
plt.show()
if __name__ == "__main__":
main()
The bar chart illustrates the most popular persistence techniques by displaying the number of threat groups employing each one.
The pie chart visualizes the relative distribution of these techniques, with clear leader lines connecting each slice to its label for easy readability.
Based on our analysis of the MITRE ATT&CK Enterprise dataset, we found that a small number of persistence techniques dominate the landscape. When we mapped each technique to the threat groups that use it, something stood out: the 15 most common persistence techniques account for almost 80% of all group associations MITRE has recorded so far.
To put that into perspective, Registry Run Keys / Startup Folder (T1547.001) is linked to 54 threat actor groups, Scheduled Task / Job (T1053.005) appears in campaigns attributed to 53 groups and Valid Accounts (T1078) can be attributes to 43 groups – And these three techniques on their own already cover almost one third of all tracked persistence method occurrences. These techniques are not just theoretically common; they repeatedly show up in real-world intrusions.
Certainly, ATT&CK coverage is not exhaustive. Some techniques used in the wild may never be publicly reported, and some operations will always remain undocumented. Still, these numbers highlight a fundamental yet critical priority. Suppose your detection capabilities can reliably catch high-frequency techniques, such as Run key modifications or rogue scheduled tasks. In that case, you are already covering a significant share of known attacker behavior and forcing threat actors to invest time in finding alternative attack paths.
With this in mind, we’ll now introduce the most widely used persistence technique: Registry Run Keys / Startup Folders.
T1547.001 – Registry Run Keys / Startup Folder
The Registry Run Keys / Startup Folder technique, part of the broader MITRE ATT&CK technique “Boot or Logon Autostart Execution”, is one of the most widely used and reliable persistence methods on Windows systems. By adding specific entries to the Windows Registry or placing executables in startup folders, threat actors can ensure that their payloads will automatically run when the system boots or when a user logs in.
This technique is especially popular because it does not require privilege escalation when the attacker targets user-level keys. It is compatible across all modern Windows versions and is often overlooked in environments with limited registry monitoring or incomplete autorun coverage. Establishing persistence through this method is also straightforward; it can be done manually without specialized tools, though a variety of tools exist that make the process even easier for attackers.
It is worth noting, however, that there is some ambiguity about what exactly qualifies as a “Run Key” within this context. MITRE’s documentation lists a broad set of registry keys and locations that can be abused to execute code at startup or logon.
However, not all of them are “run keys” in the narrow sense. For example, the HKLM\System\CurrentControlSet\Control\Session Manager\BootExecute value triggers filesystem checks on boot but is not typically classified as a run key. Similarly, other keys referenced by security experts, including Mark Russinovich, extend beyond the run key definition. Microsoft’s documentation currently defines just four primary run keys that directly cause programs to execute when a user logs in. At the same time, MITRE includes additional keys that run programs during system boot or logon; not all of these fall under the strict definition of run keys. Beyond that, dozens of other registry locations support event‑based persistence (the screensaver hook is a classic example), broadening the landscape even further.
For clarity and focus, this chapter examines only the registry locations that trigger execution at logon or boot. Event‑driven keys and similar outliers are left aside, even though the detection logic is largely the same. We will refer to the selected paths collectively as autostart registry locations; some are bona‑fide run keys, others are not, but every one of them delivers the same outcome, a guaranteed foothold the next time Windows starts or a user signs including so called ASEP (Auto-Start Extensibility Points) registry locations:
Per-user ASEPs
HKCU\Software\Microsoft\Windows\CurrentVersion\RunHKCU\Software\Microsoft\Windows\CurrentVersion\RunOnceHKCU\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunHKCU\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunOnceHKCU\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunonceExHKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows\LoadHKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows\RunHKCU\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\ShellHKCU\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
Service startup during boot
HKCU\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnceHKLM\Software\Microsoft\Windows\CurrentVersion\RunServicesOnceHKCU\Software\Microsoft\Windows\CurrentVersion\RunServicesOnceHKLM\Software\Microsoft\Windows\CurrentVersion\RunServicesHKCU\Software\Microsoft\Windows\CurrentVersion\RunServices
Systemwide ASEPs
HKLM\Software\Microsoft\Windows\CurrentVersion\RunHKLM\Software\Microsoft\Windows\CurrentVersion\RunOnceHKLM\Software\Microsoft\Windows\CurrentVersion\RunOnceExHKLM\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunHKLM\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunOnceHKLM\Software\Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\Software\Microsoft\Windows\CurrentVersion\RunonceExHKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunHKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnceHKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnceExGroup Policy ControlledHKLM\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\RunHKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run
Boot execute
HKLM\System\CurrentControlSet\Control\ServiceControlManagerExtensionHKLM\System\CurrentControlSet\Control\Session Manager\BootExecuteHKLM\System\CurrentControlSet\Control\Session Manager\ExecuteHKLM\System\CurrentControlSet\Control\Session Manager\S0InitialCommandHKLM\System\CurrentControlSet\Control\Session Manager\SetupExecute
Startup folder location
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell FoldersHKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell FoldersHKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell FoldersHKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
In addition to registry-based persistence, executables and shortcuts placed in the Windows startup folders are automatically executed when a user logs on. These folders include:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup (Current User)C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup (All Users)
ASEP Registry Run Key Usage Detection
Every persistence technique leaves traces, and the classic Run key method is no exception. Although an infection chain begins elsewhere, malspam, Living-off-the-land execution, credential theft, good defenders still want to spot the moment a payload becomes autostart-capable. Below is a tiered approach that combines registry auditing, process telemetry, and native Windows channels, making it difficult for an attacker to remain invisible.
Registry Modification
Windows records no registry edits by default, so defenders must enable either native object auditing or Sysmon to track these changes. Object auditing is a native Windows capability: Enable Audit Registry under secpol.msc > Advanced Audit Policy > Object Access.
Auditing alone is not enough; the key also needs a SACL (System Access Control List). For demonstration purposes, a broad SACL on HKLM\Software is acceptable; however, in production, it should be scoped narrowly, as each additional subkey multiplies the log volume. Right-click the key, choose Permissions > Advanced > Auditing, add the principal (for example, Everyone), and tick at least Set Value and Create Subkey.
With auditing active, write a test value and perform a logoff/logon to force evaluation:
C:\Users\azureuser> reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Run /v TestRunKey /d "calc.exe" /f C:\Users\azureuser> # Reauthenticate to trigger the execution C:\Users\azureuser> shutdown /l
The logon triggers Security 4663, followed by 4657, which records the value name, old and new data, calling process, and user SID.
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" />
<EventID>4657</EventID>
<Version>0</Version>
<Level>0</Level>
<Task>12801</Task>
<Opcode>0</Opcode>
<Keywords>0x8020000000000000</Keywords>
<TimeCreated SystemTime="2025-06-27T12:34:08.9493097Z" />
<EventRecordID>2029333</EventRecordID>
<Correlation />
<Execution ProcessID="4" ThreadID="16740" />
<Channel>Security</Channel>
<Computer>redacted</Computer>
<Security />
</System>
<EventData>
<Data Name="SubjectUserSid">S-1-5-21-...-500</Data>
<Data Name="SubjectUserName">azureuser</Data>
<Data Name="SubjectDomainName">redacted</Data>
<Data Name="SubjectLogonId">0x9ab8ad1</Data>
<Data Name="ObjectName">\REGISTRY\USER\S-1-5-21-...-500\Software\Microsoft\Windows\CurrentVersion\Run</Data>
<Data Name="ObjectValueName">TestRunKey</Data>
<Data Name="HandleId">0xb8</Data>
<Data Name="OperationType">%%1904</Data>
<Data Name="OldValueType">-</Data>
<Data Name="OldValue">-</Data>
<Data Name="NewValueType">%%1873</Data>
<Data Name="NewValue">calc.exe</Data>
<Data Name="ProcessId">0x278c</Data>
<Data Name="ProcessName">C:\Windows\System32\reg.exe</Data>
</EventData>
</Event>
Sysmon achieves the same result with less GPO work. When its configuration enables registry logging, each write generates an Event ID 13 Registry value set, including the process ID, registry value, and registry key.
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Sysmon" Guid="{5770385f-c22a-43e0-bf4c-06f5698ffbd9}" />
<EventID>13</EventID>
<Version>2</Version>
<Level>4</Level>
<Task>13</Task>
<Opcode>0</Opcode>
<Keywords>0x8000000000000000</Keywords>
<TimeCreated SystemTime="2025-06-27T12:29:32.6a601717Z" />
<EventRecordID>1163</EventRecordID>
<Correlation />
<Execution ProcessID="2900" ThreadID="16148" />
<Channel>Microsoft-Windows-Sysmon/Operational</Channel>
<Computer>redacted</Computer>
<Security UserID="S-1-5-18" />
</System>
<EventData>
<Data Name="RuleName">T1060,RunKey</Data>
<Data Name="EventType">SetValue</Data>
<Data Name="UtcTime">2025-06-27 12:29:32.658</Data>
<Data Name="ProcessGuid">{51e2ac6f-8eac-685e-202b-000000001300}</Data>
<Data Name="ProcessId">19976</Data>
<Data Name="Image">C:\Windows\system32\reg.exe</Data>
<Data Name="TargetObject">HKU\S-1-5-21-...-500\Software\Microsoft\Windows\CurrentVersion\Run\TestRunKey</Data>
<Data Name="Details">calc.exe</Data>
<Data Name="User">redacted\azureuser</Data>
</EventData>
</Event>
Process Execution
Run key payloads leave traces in the usual execution artefacts such as Prefetch entries or SRUM ((System Resource Usage Monitor) records. Yet those sources rarely reveal how the binary started; they blur together auto‑starts (ASEPs) and all other program executions. A more precise strategy is two‑fold: first, monitor for registry‑editing utilities like reg.exe (or any process that writes to the relevant hives); second, watch for the moment the Run‑key value is invoked and the payload spawns. Together, these signatures capture the full lifecycle—both the registry change that implants persistence and the subsequent execution of the malicious code.
For example, when the Microsoft-Windows-Shell-Core/Operational channel is enabled, Windows logs Event 9707 whenever payload is launched from an ASEP Run key (system-wide or per-user) and Event 9708 when that launch completes successfully.
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Shell-Core" Guid="{30336ed4-e327-447c-9de0-51b652c86108}" />
<EventID>9707</EventID>
<Version>0</Version>
<Level>4</Level>
<Task>9707</Task>
<Opcode>1</Opcode>
<Keywords>0x2000000004010000</Keywords>
<TimeCreated SystemTime="2025-06-27T09:08:14.5701197Z" />
<EventRecordID>7866</EventRecordID>
<Correlation />
<Execution ProcessID="10548" ThreadID="18824" />
<Channel>Microsoft-Windows-Shell-Core/Operational</Channel>
<Computer>redacted</Computer>
<Security UserID="S-1-5-21-...-500" />
</System>
<EventData>
<Data Name="Command">calc.exe</Data>
</EventData>
</Event>
But that is only part of the picture. Instead of waiting for the persistence payload to execute, we can catch the registry‑modifying process itself. When process‑creation auditing is enabled, Windows Security Event ID 4688 logs every launch of reg.exe along with its full command line. By alerting on invocations whose arguments point to ASEP run‑key locations or Startup‑folder paths, we detect the tool that implants the beacon—rather than the beacon’s C2 activity after it is already in place.
The reg.exe call that writes to the system-wide Run key is recorded as Security event ID 4688 (process creation) followed by ID 4689 (process termination); with Sysmon enabled, the same action appears as Event ID 1.
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" />
<EventID>4688</EventID>
<Version>2</Version>
<Level>0</Level>
<Task>13312</Task>
<Opcode>0</Opcode>
<Keywords>0x8020000000000000</Keywords>
<TimeCreated SystemTime="2025-06-27T12:34:08.9426932Z" />
<EventRecordID>2029331</EventRecordID>
<Correlation />
<Execution ProcessID="4" ThreadID="16740" />
<Channel>Security</Channel>
<Computer>redacted</Computer>
<Security />
</System>
<EventData>
<Data Name="SubjectUserSid">S-1-5-21-...-500</Data>
<Data Name="SubjectUserName">azureuser</Data>
<Data Name="SubjectDomainName">redacted</Data>
<Data Name="SubjectLogonId">0x9ab8ad1</Data>
<Data Name="NewProcessId">0x278c</Data>
<Data Name="NewProcessName">C:\Windows\System32\reg.exe</Data>
<Data Name="TokenElevationType">%%1936</Data>
<Data Name="ProcessId">0x4d00</Data>
<Data Name="CommandLine">"C:\Windows\system32\reg.exe" add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v TestRunKey /d calc.exe /f</Data>
<Data Name="TargetUserSid">S-1-0-0</Data>
<Data Name="TargetUserName">-</Data>
<Data Name="TargetDomainName">-</Data>
<Data Name="TargetLogonId">0x0</Data>
<Data Name="ParentProcessName">C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Data>
<Data Name="MandatoryLabel">S-1-16-12288</Data>
</EventData>
</Event>
Exotic Registry Modification Techniques Detection
Up to this point, we can already trace changes to Run key locations, capture payload launches from those keys and watch the processes that perform the edits. If your visibility ends at process-creation telemetry, however, an attacker can slip past by choosing tools other than reg.exe or regedit.exe. The following sections walk through three such alternatives: PowerShell, regini.exe, and VBScript, showing the commands that perform the write and the log sources you must enable to see them.
PowerShell
PowerShell offers a fully featured registry provider. A single line such as New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name Evil -Value "C:\evil.exe" -PropertyType String –Force creates the value Evil = C:\evil.exe without using regedit.exe. The same applies to its WMI/CIM counterpart Invoke-WmiMethod -Namespace root\default -Class StdRegProv -Name SetStringValue -ArgumentList @(2147483650, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", "C:\\evil.exe", "Evil").
It is important to note that, unless these commands are not executed within an existing PowerShell process, no Security Event 4688 or Sysmon Event 1 is generated. Instead, PowerShell Script-Block Logging captures both methods when Script-Block Logging (or, at the very least, Module Logging) is enabled. With Script-Block Logging active, the Microsoft-Windows-PowerShell/Operational channel records Event 4104, which contains the complete command line:
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-PowerShell" Guid="{a0c1853b-5c40-4b15-8766-3cf1c58f985a}" />
<EventID>4104</EventID>
<Version>1</Version>
<Level>5</Level>
<Task>2</Task>
<Opcode>15</Opcode>
<Keywords>0x0</Keywords>
<TimeCreated SystemTime="2025-06-27T13:54:47.4318017Z" />
<EventRecordID>13660</EventRecordID>
<Correlation ActivityID="{c3cf0db0-d2d5-0007-178e-c837bee5db01}" />
<Execution ProcessID="19712" ThreadID="16332" />
<Channel>Microsoft-Windows-PowerShell/Operational</Channel>
<Computer>redacted</Computer>
<Security UserID="S-1-5-21-...-500" />
</System>
<EventData>
<Data Name="MessageNumber">1</Data>
<Data Name="MessageTotal">1</Data>
<Data Name="ScriptBlockText">New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name Evil -Value "C:\evil.exe" -PropertyType String -Force</Data>
<Data Name="ScriptBlockId">bcf07a2b-7b09-426e-b767-82e854cb8708</Data>
<Data Name="Path" />
</EventData>
</Event>
regini.exe
A second Living-off-the-Land option is regini.exe. Because this application consumes an ASCII “registry script”. You first have to construct a small text block and then feed it to the binary.
C:\Users\azureuser> (echo \Registry\Machine\Software\Microsoft\Windows\CurrentVersion\Run & echo Evil = REG_SZ C:\evil.exe) > addRun.ini
C:\Users\azureuser> type addRun.ini
\Registry\Machine\Software\Microsoft\Windows\CurrentVersion\Run
Evil = REG_SZ C:\evil.exe
C:\Users\azureuser> regini addRun.ini
The spaces before the value name Evil are essential; regini expects the value line to be indented by four characters by default.
VBScript
You can also drop a tiny VBS file and execute it with wscript.exe:
C:\Users\azureuser> echo Set s=CreateObject("WScript.Shell"): s.RegWrite "HKLM\Software\Microsoft\Windows\CurrentVersion\Run\Evil", "C:\evil.exe", "REG_SZ" > addRun.vbs & wscript.exe addRun.vbs
Alternatively, cscript.exe or mshta.exe (which can embed VBScript), or direct COM use of WScript.Shell, for example, achieve the same results. Using mshta.exe, you don’t even need to write the VBScript to disk:
C:\Users\azureuser> mshta vbscript:Execute("Set s=CreateObject(""WScript.Shell""):s.RegWrite ""HKLM\Software\Microsoft\Windows\CurrentVersion\Run\Evil"", ""C:\evil.exe"", ""REG_SZ"": window.close")
The same applies to rundll32.exe:
C:\Users\azureuser> rundll32 vbscript:"\..\mshtml,RunHTMLApplication "+CreateObject("Wscript.Shell").RegWrite("HKLM\Software\Microsoft\Windows\CurrentVersion\Run\Evil","C:\evil.exe","REG_SZ")(window.close)
AppLocker can be configured to block VBS execution, for example, with the following FilePathRule:
<?xml version="1.0" encoding="utf-8"?>
<AppLockerPolicy Version="1">
<RuleCollection Type="Script" EnforcementMode="Enabled">
<FilePathRule Id="0c4a61d6-3a1e-4242-a643-146ef829e816"
Name="Block all VBS scripts"
Description="Prevent execution of any .vbs files"
UserOrGroupSid="S-1-1-0"
Action="Deny">
<Conditions>
<FilePathCondition Path="*.vbs" />
</Conditions>
</FilePathRule>
</RuleCollection>
</AppLockerPolicy>
This ensures that every file ending with .vbs cannot be executed. Alternatively, the rule can be set to Audit mode. Activate it with:
C:\Users\azureuser> Set-AppLockerPolicy -XmlPolicy BlockVBS.xml –Merge C:\Users\azureuser> gpupdate /force C:\Users\azureuser> Restart-Service AppIDSvc
Validate that the rule is active:
C:\Users\azureuser> Get-AppLockerPolicy -Effective -Xml
The output should contain the <FilePathRule> fragment shown above. Execution of a VBS file then triggers a Microsoft-Windows-AppLocker/MSI and Script Event 8007 (in Enforcement mode) or 8004 (in Audit mode):
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-AppLocker" Guid="{cbda4dbf-8d5d-4f69-9578-be14aa540d22}" />
<EventID>8007</EventID>
<Version>0</Version>
<Level>2</Level>
<Task>0</Task>
<Opcode>0</Opcode>
<Keywords>0x4000000000000000</Keywords>
<TimeCreated SystemTime="2025-06-28T18:04:22.1259421Z" />
<EventRecordID>1</EventRecordID>
<Correlation />
<Execution ProcessID="3232" ThreadID="4764" />
<Channel>Microsoft-Windows-AppLocker/MSI and Script</Channel>
<Computer>DESKTOP-299P60T</Computer>
<Security UserID="S-1-5-21-…-1001" />
</System>
<UserData>
<RuleAndFileData
xmlns="http://schemas.microsoft.com/schemas/event/Microsoft.Windows/1.0.0.0">
<PolicyNameLength>6</PolicyNameLength>
<PolicyName>SCRIPT</PolicyName>
<RuleId>{0c4a61d6-3a1e-4242-a643-146ef829e816}</RuleId>
<RuleNameLength>21</RuleNameLength>
<RuleName>Block all VBS scripts</RuleName>
<RuleSddlLength>52</RuleSddlLength>
<RuleSddl>D:(XD;;FX;;;S-1-1-0;(APPID://PATH Contains "*.VBS"))</RuleSddl>
<TargetUser>S-1-5-21-…-1001</TargetUser>
<TargetProcessId>3232</TargetProcessId>
<FilePathLength>18</FilePathLength>
<FilePath>%OSDRIVE%\EVIL.VBS</FilePath>
<FileHashLength>0</FileHashLength>
<FileHash />
<FqbnLength>1</FqbnLength>
<Fqbn>-</Fqbn>
<TargetLogonId>0x35fb2</TargetLogonId>
<FullFilePathLength>11</FullFilePathLength>
<FullFilePath>C:\evil.vbs</FullFilePath>
</RuleAndFileData>
</UserData>
</Event>
Interim Summary
We have explored multiple methods for modifying persistence-related registry keys using built-in Windows tools and the telemetry generated by each technique. The key takeaway is defense in depth: rely on both process-level signals (4688/Sysmon 1, AppLocker 8007, AMSI 4104) and registry-level auditing (Security 4657, Sysmon 13). If one log source is suppressed, the other still exposes the activity.
Focusing solely on “known” Run key locations is risky; new autostart paths appear, and attackers often repurpose non-standard keys. A healthier approach is to baseline normal registry and process behaviour, and then alert on deviations, especially when signed but rarely used binaries (e.g., mshta.exe, cscript.exe, regini.exe) interact with persistence branches of the hive.
Finally, remember that live registry inspection is valuable during incident response, but it should never replace proper logging. Transaction files, USN records, and memory-resident handles can help corroborate timeline gaps; however, they are slower and more error-prone than having the correct events in the first place. Layered detections, thorough baselining, and resilient log forwarding remain the best insurance against stealthy registry abuse.
File Write Detection
Now, let’s look at file-write detections. The basics include NTFS artefacts you already know—$MFT, $UsnJrnl, $J, $I30, and $LogFile. Additionally, we can use Windows event logs to detect file-write activity in specific locations. Remember, this post focuses on T1547.001 – Registry Run Keys/Startup Folder, which involves not only registry changes but also the Startup folders located at %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup (for the Current User) and C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup (for All Users). Attackers can also add new startup folders by editing keys such as HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders.
As with registry object auditing, we can enable directory auditing. First, enable Audit File System in secpol.msc when in a workgroup, or via a GPO in a domain.
Next, create a SACL on the directory to monitor—in this case, C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup. Open the folder’s Security > Advanced > Auditing tab, choose a principal (e.g., Everyone or Users), and select at least write activities.
When any principal writes to this directory, Windows normally generates three Security 4663 events:
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" />
<EventID>4663</EventID>
<Version>1</Version>
<Level>0</Level>
<Task>12800</Task>
<Opcode>0</Opcode>
<Keywords>0x8020000000000000</Keywords>
<TimeCreated SystemTime="2025-06-28T20:22:25.0406075Z" />
<EventRecordID>2037495</EventRecordID>
<Correlation />
<Execution ProcessID="4" ThreadID="4144" />
<Channel>Security</Channel>
<Computer>redacted</Computer>
<Security />
</System>
<EventData>
<Data Name="SubjectUserSid">S-1-5-21-...-500</Data>
<Data Name="SubjectUserName">azureuser</Data>
<Data Name="SubjectDomainName">redacted</Data>
<Data Name="SubjectLogonId">0x9ab8ad1</Data>
<Data Name="ObjectServer">Security</Data>
<Data Name="ObjectType">File</Data>
<Data Name="ObjectName">C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\calc.exe</Data>
<Data Name="HandleId">0x50d0</Data>
<Data Name="AccessList">%%4417</Data>
<Data Name="AccessMask">0x2</Data>
<Data Name="ProcessId">0x2934</Data>
<Data Name="ProcessName">C:\Windows\explorer.exe</Data>
<Data Name="ResourceAttributes">S:AI</Data>
</EventData>
</Event>
Look for access types WriteData (or AddFile), AppendData (or AddSubdirectory or CreatePipeInstance), and WriteAttributes. A corresponding Sysmon Event 11 also records the file write:
<Event
xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
<System>
<Provider Name="Microsoft-Windows-Sysmon" Guid="{5770385f-c22a-43e0-bf4c-06f5698ffbd9}" />
<EventID>11</EventID>
<Version>2</Version>
<Level>4</Level>
<Task>11</Task>
<Opcode>0</Opcode>
<Keywords>0x8000000000000000</Keywords>
<TimeCreated SystemTime="2025-06-28T20:20:58.5343488Z" />
<EventRecordID>4113</EventRecordID>
<Correlation />
<Execution ProcessID="2900" ThreadID="16148" />
<Channel>Microsoft-Windows-Sysmon/Operational</Channel>
<Computer>redacted</Computer>
<Security UserID="S-1-5-18" />
</System>
<EventData>
<Data Name="RuleName">T1023</Data>
<Data Name="UtcTime">2025-06-28 20:20:58.532</Data>
<Data Name="ProcessGuid">{51e2ac6f-5f7a-685e-6229-000000001300}</Data>
<Data Name="ProcessId">10548</Data>
<Data Name="Image">C:\Windows\Explorer.EXE</Data>
<Data Name="TargetFilename">C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\calc.exe</Data>
<Data Name="CreationUtcTime">2025-06-28 20:20:33.239</Data>
<Data Name="User">redacted\azureuser</Data>
</EventData>
</Event>
For completeness, inspect the $MFT and $UsnJrnl:
C:\Users\azureuser> MFTECmd.exe -f ".\$Extend\$UsnJrnl%3A$J" -m $MFT --csv . --csvf usnjrnl.csv C:\Users\azureuser> MFTECmd.exe -f $MFT --csv . --csvf mft.csv C:\Users\azureuser> LogFileParser64.exe /LogFileFile:.\$LogFile /SkipSqlite3:1 /OutputPath:.
Sorting the Master File Table by the Created0x10 timestamp makes the new calc.exe in the Startup folder easy to spot.
If the file were later deleted, the $MFT entry would disappear and a RenameOldName record would appear in the journal.
Remember that the NTFS $LogFile preserves the most complete history of every change. Additional NTFS artefacts—combined with file-system auditing—will often expose any rogue .reg or .ini file an attacker drops to alter registry data. Because such scripts are typically tiny, they usually reside entirely within their MFT record, making them easy to carve directly from the MFT itself.
THOR Timelining using Timesketch
The newest THOR helper, thor2timesketch, went public only a couple of months ago and closes the gap between THOR reports and a Timesketch workflow. Instead of paging through the classic HTML summary or shipping raw Syslog to another host, you can now feed THOR findings straight into your forensic timeline.
The setup is trivial. Run THOR ≥ 10.7 with JSON v2 output enabled and point the output at a file you’ll later import:
C:\Tools\THOR\thor64.exe --lab -p .\triage\ --jsonv2 --jsonfile results.json
In this example we scan a Velociraptor SANS‑triage directory to mimic the “grab‑and‑scan” pattern of a routine host‑forensics engagement. Before launching THOR we wiped the event logs, removed existing Run‑key values, replayed each persistence variant discussed in this post, and dropped several binaries into the Startup folders.
THOR completes its sweep within minutes and leaves results.json, a self‑contained collection of every THOR finding, in the working directory. Ordinarily you might parse that file further, convert it to HTML, or forward the data elsewhere. Here, however, we rely on the Timesketch conversion utility to produce a timeline‑ready artifact:
thor2ts results.json -o results_ts.jsonl
Timesketch demands a very specific JSONL structure, so the intermediate conversion step is mandatory; piping the raw THOR output directly will fail. Run the helper, and any other Python script, in a dedicated virtual environment to avoid dependency clashes.
Spin up Timesketch, import that JSON with thor2timesketch, and the timeline instantly flags every persistence trick we used, Run keys, Startup-folder droppers, the mshta.exe and VBScript, alongside the usual THOR signature hits. No extra parsing, no manual tagging, just a clean, searchable view that chains our artefacts together and gives investigators a head start the moment an incident begins.
Wrapping Up
In this walkthrough, we explored the full persistence chain behind T1547.001—beginning with registry Run key edits, extending to files planted in Startup folders, and ending with the forensic artefacts and log records each step leaves behind. On the registry side, we showed how attackers could write Run key values with native binaries such as reg.exe, regini.exe, mshta.exe, cscript.exe, and even inline VBScript delivered through rundll32. Each technique produces its own telemetry: Security 4688 or Sysmon 1 for the launching process, Script-Block Event 4104 for PowerShell, Security 4657 or Sysmon 13 for the key change, and AppLocker Events 8007 or 8004 when script hosts are denied or audited. For file-based autostarts we enabled Audit File System and attached a SACL to the Startup directory, confirming that every payload write generates the expected trio of Security 4663 events while Sysmon 11 records the same operation with full path, process, and hash.
Throughout the post, we emphasised defence in depth. Relying on a single signal—only process starts, only registry auditing, only file writes—creates blind spots a determined adversary can exploit. Combining multiple, independent controls not only raises the chance of detection but also yields higher-quality timelines for responders. THOR detects the shown patterns out of the box, and a Timesketch notebook ties registry, process, and NTFS evidence together for rapid pivoting.
We also looked forward: memory-resident handles reveal keys that remain open long after values are changed, hive timestamps and transaction logs expose stealthier edits, and ETW consumers or direct API hooks can supplement Windows logging for high-sensitivity environments. Finally, because sophisticated actors increasingly tamper with the logging pipeline itself, we noted the need to monitor for ETW patching and to forward Security and Sysmon streams in real-time so local log clearing cannot erase the record.
Taken together, these layers give SOC analysts and incident responders a robust, repeatable playbook for spotting—and proving—Run key and Startup-folder persistence, even when attackers mix LOLBins, script hosts, and file-less techniques in an attempt to hide.

















Florian Roth
Marc Hirtz
Franziska Ploss