The Windows Restart Manager: How It Works and How It Can Be Hijacked, Part 2
Published 11/14/2023
Originally published by CrowdStrike.
In the first part of this series, we provided a brief overview of the Windows Restart Manager. In this blog post, we examine how these mechanisms can be exploited by adversaries.
Opportunities for Ransomware
The Restart Manager preempts unwelcome reboots by shutting down applications that are blocking specific resources. This implies that, as long as a process is blocking a resource, it can be the target of a shutdown caused by the Restart Manager. This raises the questions: Could someone use the library to kill target processes? What malicious purposes can this library be used for? Conti ransomware sheds light on answers to these questions, as it uses the Restart Manager to increase the efficiency of its encryption process.
Real-world Example: Conti Ransomware
In early 2022, the source code of Conti ransomware was published on Twitter. The source code revealed critical functionalities of the ransomware. Among them was a function dedicated to killing processes that would prevent file encryption. The function, named KillFileOwner(), iterates over every file in the system to check if the resource is currently used by a process or a service, and then attempts to kill it using the Restart Manager.
As shown in Figure 1, it first checks that the Restart Manager library, RstrtMgr.dll, is already loaded in memory, as the malware chose to dynamically load the library of the Restart Manager. Then, KillFileOwner() creates a Restart Manager session using RmStartSession(). With the newly allocated session handle, it iterates through every file in the system and registers each of them through RmRegisterResources() to retrieve potential processes that would use it and ultimately prevent encryption by blocking access to the file.
Figure 1. First part of the KillFileOwner function from the leaked source code of Conti ransomware
Once the target file has been registered as a resource within the session, the next step is to retrieve the list of processes and services that the Restart Manager can identify as applications currently using the resource, shown in Figure 2. To do this, Conti first performs a call to the function RmGetList() to get the exact number of applications identified as using the file, allocates the corresponding amount of memory for structures, and then performs a second call to RmGetList() to retrieve the information for each affected application.
Figure 2. Second part of the KillFileOwner function from the leaked source code of Conti ransomware
At this point, the variable ProcessInfo contains the information about the affected applications using the target file. Now, Conti is able to use this information to decide whether or not the application should be killed to free the resource and eventually release the lock of the file. Figure 3 shows for each application identified by the Restart Manager, Conti checks that the affected application is not the Conti process itself or a process part of an allowlist established beforehand. This allowlist includes processes such as: “spoolsv.exe,” “explorer.exe,” “sihost.exe,” “fontdrvhost.exe,” “cmd.exe,” “dwm.exe,” “LogonUI.exe,” “SearchUI.exe,” “lsass.exe,” “csrss.exe,” “smss.exe,” “winlogon.exe,” “services.exe” and “conhost.exe.”
If one of the affected applications is in the safe-exclusion list, Conti skips that target file and ends the Restart Manager session. However, if one of the affected applications returned by RmGetList() is not in that list, Conti uses RmShutdown() to request the shutdown of the processes and services using the target file.
Figure 3. Third part of the KillFileOwner function from the leaked source code of Conti ransomware
This way, Conti may remove locks on potential files it can encrypt in order to maximize the damage it can do. Instead of using a method relying on TerminateProcess() and a list of processes to kill, the functions of the Restart Manager allow the Conti process to attempt to kill only the processes that lock the targeted files.
Opportunities for Evasion Purposes
In addition to supporting the encryption process, the Restart Manager can also be used for other malicious purposes, including:
- Retrieving processes running on the system (process discovery)
- Detecting the presence of analysis processes that represent a risk for the malware authors (defense evasion)
- Detecting and potentially preventing the execution of processes that threaten the expected execution of the malware (impairing defense)
Let’s detail these three categories, explain why they are interesting for malware authors and see how they can be implemented using the Restart Manager.
Recon Purposes: Process Discovery
As part of reconnaissance, malware might attempt to gather information about a target system to identify its weaknesses and vulnerabilities. A similar corpus of information can also be collected as a preliminary step in data exfiltration, such as in the execution of backdoors and infostealers. This technique generally consists of successive calls to retrieve information about OS characteristics, user profiles, network specifications and processes running. Retrieving information about this latter category is referred as Process Discovery.
The Restart Manager can be hijacked as part of these campaigns to perform process discovery, representing one alternative to the classic method of using the Windows API CreateToolhelp32Snapshot()/Process32First()/ Process32Next(), or the commonly used tool tasklist. Without knowing anything about a victim’s system, an attacker can iterate over every executable file of the system and register each of them in a Restart Manager session. The malicious program would eventually iterate through binary executables of processes running on the system that are currently used by processes themselves. Therefore, the list of affected applications for binary files currently being executed includes the running process itself, revealing its presence (Figure 4). In this way, going over every file of the system allows an attacker to carry out process discovery and gather more information about running processes and services.
Figure 4. Process discovery mechanism using the Restart Manager
Anti-analysis Purposes: Debuggers and Virtualization/Sandbox Evasion
To analyze suspicious pieces of code, malware analysts generally execute samples in isolated environments (virtual machines, sandboxes) alongside analysis tools such as debuggers or various monitoring tools. Therefore, adversaries might use various tricks to detect and avoid their malware being debugged, analyzed or executed in a virtual environment/sandboxes. These techniques are respectively referenced under the naming Debugger Evasion and Virtualization/Sandbox Evasion. Once an analysis process has been spotted running by malware, it can adjust its behavior to make analyzing it more difficult, concealing core functionality of the implant or even completely disengaging. For instance, the Okrum loader attempts to verify it is not being executed within a sandbox using two calls to GetTickCount(), separated by some sleeping time, along with two calls to GetCursosPos(), and then comparing the results. If the distinct tick counts that are returned are not equal, or if the position of the mouse cursor has suddenly changed within a one-second interval, the malware terminates itself.
The Restart Manager can be used to perform evasion to target debuggers, virtual environments, sandboxes and other various types of monitoring tools. Using the process discovery methods explained, an attacker can iterate over all of the files in the system, retrieve processes currently running and compare the user-friendly names of processes found against a list of known debuggers/virtualization/sandboxes’ ones. When the lists match, attackers can therefore adjust their behavior and methods accordingly.
Anti-analysis Purposes: Disable Tools
As we’ve explained, ransomware authors leverage existing functions that kill processes to help with the encryption process. In addition, they can shut down processes to directly target applications that a malicious process would rather not see running on the system, thereby evading detection by antivirus and monitoring tools, to name a couple. This is a method commonly used to avoid possible detection, referenced in the MITRE ATT&CK® framework as Impair Defense: Disable or Modify Tools.
As we explained in the previous use cases, one program iterating over files in the system would eventually find processes currently running. If, among the list of affected applications, one is identified as an analysis tool, the malicious process can attempt to terminate it through the Restart Manager session. Unlike TerminateProcess(), which is used in conjunction with a list of executable names to kill, the Restart Manager method uses the user-friendly name of the applications. Indeed, when RmGetList() returns information about the affected applications, it doesn’t return the name of the executable but returns the user-friendly name of the application, which corresponds by default to the binary description. As the description remains unchanged most of the time despite renaming the executable, using the Restart Manager to terminate processes instead of using TerminateProcess() would catch cases where monitoring tools are renamed to be more stealthy.
Depending on the target application, the malicious process might be able to successfully terminate its target — or not. Indeed, the shutdown request can be accepted or not depending on the specifications of both processes requesting the termination and the target (whether the calling process has the PROCESS_TERMINATE right, whether a reboot is required regardless of the target’s shutdown, etc.). If debuggers and analysis processes usually run with the same level of privileges as classic applications and can be a victim of this method, antiviruses can be more robust because they can use other methods to protect themselves.
Implementation
First, let’s use RmStartSession() to create a Restart Manager session from which we will be able to register resources. Then, we need to retrieve the starting point where we want the search to begin. To get the highest number of files, we need to start iterating over the file system from the highest level: the volume. Therefore, let’s search for all of the volumes available and for each, do a recursive analysis of their subdirectories. We use the function FindFirstVolumeW() that gives us a handle on the first volume, typically the main drive of the system. The actual volume name is composed by a GUI, following the format “\\?\Volume{GUID}\”, so we need to call the function GetVolumePathNamesForVolumeNameW() to get the associated drive letter. This function retrieves from the full volume name the drive letter or the mounted folder path associated with it, such as “C:\”.
SearchForFilesLocked() is the recursive function responsible for iterating through files and directories of the system, and expects as arguments a handle on a Restart Manager session and a full path under the format “C:\My\Path”. To comply with this format, we copy the two first characters of the variable VolumePath and perform a first call to SearchForFilesLocked() with this path.
Figure 5. Function main()
The function first initializes several variables required later in the function and retrieves the first file or subdirectory of the directory given through arguments using FindFirstFileW().
Figure 6. First part of SearchForFilesLocked()
If FindFirstFileW() returns a directory, we check this is not one of the directories that are not worth seeking through (itself “.”, its parent “..”, “Common Files”, but we could add more). If it is not, then we perform a recursive call to SearchForFilesLocked() on this directory to browse files and subdirectories of this directory.
Figure 7. Second part of SearchForFilesLocked()
If the call to FindFirstFileW() resulted in a file, we should take a closer look at potential processes using the resource. To do so, we dedicate the CheckAffectedApps() function to check the affected applications for a given file using the Restart Manager.
Figure 8. Third part of SearchForFilesLocked()
CheckAffectedApps() is the key function to perform process discovery, defense evasion and defense impairment. By registering a file through the Restart Manager, this function will be able to get processes using the resources, and by checking for every file of the system, we will be able to draw a more complete list of processes running on the system.
To do so, we first register the file in the Restart Manager session given in arguments of RmRegisterResources(). Once the resource is successfully registered in the session, we perform a first call to RmGetList() that returns through the argument nProcInfoNeeded value. This one gives the number of affected applications found. Getting this number allows us to allocate the appropriate amount of RM_PROCESS_INFO structures to receive the information. Then we perform a second call to RmGetList() to actually get the RM_PROCESS_INFO information.
Figure 9. First part of CheckAffectedApps()
Once the information about the affected application is returned and stored in the RMProcInfo variable, we can parse them and compare them with the list of our targets. Please note that the RM_PROCESS_INFO structure does not contain the direct name of the executable, but the “user-friendly name” — in other words, the “Description” field for processes and the long name for the service. However, if the process is a critical process, the strAppName of the RM_PROCESS_INFO structure is the name of the executable file.
We normalize both of the strings we target, and the actual strAppName is compared with strings in our list. If there is a match, it means one of the affected applications for the specified file is one of our targets, and we can attempt to terminate it using RmShutdown() with the parameter lActionFlags set to RmForceShutdown (0x1).
Since the process detected might not be the only target, we create another Restart Manager session to be able to process other files and affected applications later on, using RmEndSession() and RmStartSession().
Figure 10. Second part of CheckAffectedApps()
Executing this on a system with a Windbg process opened, we can see the program iterates over the directories of the system until it finds one file blocked by the debugger itself. Then, our POC attempts to end the Restart Manager session, which results in the termination of Windbg as expected.
Figure 11. Result of the POC execution targeting Windbg
Whether conducting process discovery, defense evasion or defense impairment, it is very important to stay ahead of malicious authors and be aware of new methods, such as the ones using the Restart Manager. As little-known methods like this allow malware authors to avoid basic detection, the more we understand all of the possible methods, the better we can detect them and protect the systems.
Applications’ Immunity
As one malicious author could leverage the RmShutdown() function to terminate targeted applications, let’s observe what makes an application immune against this technique. Depending on whether the application is associated with a service or not, the underlying question can be either “What prevents a process from sending a message or a notification to another process?” or “What prevents a process from using ControlService()/TerminateProcess() on another process?” The User Interface Privilege Isolation (UIPI) defines boundaries that answer the first question, while the second one finds an answer in the implementation of protected processes. Let’s see what these two notions are and how they can help a process to be protected against termination techniques.
User Interface Privilege Isolation (UIPI)
User Interface Privilege Isolation was introduced in Windows Vista to prevent code injection into privileged applications using the Windows Messaging System. It relies on the processes’ integrity level, defined within the PROCESS_INFO structure, that can be one of the following:
- Low integrity
- Medium integrity
- High integrity
- System integrity
Based on the processes’ integrity level, the UIPI prevents lower-privilege applications from performing a window handle validation, sending a message, using hooks or performing injection into a higher-privilege process. Therefore, as long as the malicious application runs with a lower-integrity level than a targeted affected application, it will be unable to terminate it as the Restart Manager won’t be able to send the WM_QUERYENDSESSION/WM_ENDSESSION/WM_CLOSE messages.
Protected Processes and Protected Processes Light
Protected Processes and Protected Processes Light are a specific type of processes, defined by an attribute set within the EPROCESS structure named _PS_PROTECTION:
_PS_PROTECTION +0x000 Level : UChar +0x000 Type : Pos 0, 3 Bits +0x000 Audit : Pos 3, 1 Bit +0x000 Signer : Pos 4, 4 Bits
Within this structure, the value of the protection level basically depends on:
- The type of the application’s protection, indicating whether it is a protected process (0x2), a protected process light (0x1) or if there is no specific protection (0x0)
- The signer, which represents the binary’s signature origin, that can be one of the following:
_PS_PROTECTED_SIGNER PsProtectedSignerNone = 0n0 PsProtectedSignerAuthenticode = 0n1 PsProtectedSignerCodeGen = 0n2 PsProtectedSignerAntimalware = 0n3 PsProtectedSignerLsa = 0n4 PsProtectedSignerWindows = 0n5 PsProtectedSignerWinTcb = 0n6 PsProtectedSignerMax = 0n7
Depending on its specifications, each process of the system gets a protection level (Figure 12) and will benefit from the associated protections accordingly.
Figure 12. Representation of the different processes and their level of protection
Among those, Protected Processes Light was introduced to allow user mode processes from third parties, such as antivirus or EDR processes, to “defend against attacks on the user-mode service“ and protect processes that can be trusted against processes running with administrator rights. The Protected Processes benefit from protections that harden them against attacks such as memory tampering and process injection. The protected process attribute also implies that accesses requested to the process won’t be granted to non-protected process to limit attempts to terminate it, debug it, copy its descriptors, access to its memory, impersonate its token and so on.
Technically speaking, in the case of antivirus and EDRs, the binary is associated with an ELAM (Early Launch Anti-Malware) driver, which contains a resource section with information to register the anti-malware service and launch it as a protected service. Once launched as a Protected Process Light, no other process with a lower security level can perform a thread injection, write in its memory or terminate the process, as it can’t get a handle with the appropriate accesses on the protected process. Therefore, security solutions protect against this type of malicious process that could attempt to terminate the process.
Conclusion
The Windows Restart Manager is a powerful component introduced by Microsoft as an alternative for installers and updaters, intended to avoid unnecessary OS reboots by enabling software to ensure the availability of processes, services and files.
But this functionality may be hijacked to serve malicious purposes. Adversaries can utilize it as part of ransomware prior to encryption to make sure no other application of the system can prevent file encryption. Alternatively, adversaries could leverage the Restart Manager for anti-analysis and evasion purposes. Indeed, one malicious application could easily iterate through a list of common analysis tools and attempt to kill them to go undetected.
Related Articles:
A Vulnerability Management Crisis: The Issues with CVE
Published: 11/21/2024
Establishing an Always-Ready State with Continuous Controls Monitoring
Published: 11/21/2024
AI-Powered Cybersecurity: Safeguarding the Media Industry
Published: 11/20/2024