Avira Optimizer Local Privilege Escalation

Version: Avira Optimizer < 1.2.0.367
Operating System tested on: Windows 10 1803 (x64)
Vulnerability: Avira Optimizer Local Privilege Escalation through insecure named pipes

Vulnerability Overview
When users install the latest Avira antivirus, it comes shipped with a few different components along with it. One of these components is the Avira Optimizer. In short, “Avira.OptimizerHost.exe” runs as “NT AUTHORITY\SYSTEM” and takes commands issued over the “AviraOptimizerHost” named pipe (\\.\pipe\AviraOptimizerHost). The service does improper validation of the calling client along with invalid checks for started executables, which allows malicious code to issue process create calls to “Avira.OptimizerHost.exe” leading to local privilege escalation.

Identification and Exploitation
When assessing software for privilege escalation vulnerabilities, finding a starting point can often be overwhelming as there are many different primitives and vulnerability classes that exist. My approach often includes starting with the basics and working my way up in complexity. This process typically involves running a tool such as PowerUp, which will identify various trivial (yet common) misconfigurations.

If nothing interesting is returned, the next step is often looking for logical vulnerabilities. These vulnerabilities can be more difficult to automatically identify and require a little more manual poking. My workflow typically involves analyzing world-writable directories, writeable registry locations, exposed named pipes and RPC interfaces via NTObjectManager. When analyzing existing named pipes, it became apparent that some Avira process had created a named pipe with a NULL DACL. This effectively means that full access is granted to any user that requests it:

While interesting, it isn’t incredibly useful if the pipe isn’t being used by a privileged Avira process in some way. Checking the using process IDs of the pipe revealed that a SYSTEM Avira process is utilizing it:

The next step would be to figure out what “Avira.OptimizerHost.exe” is actually doing with the named pipe. This is a rabbit hole worth exploring since a privileged process is interacting with a resource that low privileged users have control over. Since “Avira.OptimizerHost.exe” has a handle to the pipe, it would make sense that the process is ingesting some sort of data being passed over it. In an effort to validate this, the next step was to pop open “Avira.OptimizerHost.exe” in IDA. After some poking, it became evident that the service was taking any client that connected to the “AviraOptimizerHost” named pipe and validating that it is a valid Avira file.

 In order to abuse this named pipe, we need to circumvent this check in order to successfully send data to the service via the named pipe. The service does the check by getting the connecting client via GetNamedPipeClientProcessID() and then pulls the full image path via QueryFullProcessImageNameW()

Once the path is obtained, the service pulls the calling client’s certificate and makes sure that it is signed by Avira and hasn’t been tampered with. The idea here was to make sure only valid Avira processes are able to issue commands to the service. In order to circumvent this, we can simply inject our code into a running Avira process (or probably just clone an existing certificate).

The next step is to figure out what we can issue to the service over the named pipe. In cases like this, I typically like to investigate any potential legitimate clients and see what they do during normal operation. Since this pipe is a part of Avira’s optimizer, I began to look through the installed Avira components. After some dead ends, Avira’s System Speedup application boiled to the top due to the fact that optimization and speedup are Synonymous. After looking in Avira’s “System Speedup” folder, I stumped upon the Avira System Speedup libraries. I then loaded all of the files in the System Speedup folder into DnSpy and began to search for named pipe references. This led me down to “Avira.SystemSpeedup.Core.Client.Services.dll”, specifically the “StartServiceHost()” method.

As suspected, this is code to connect to the “AviraOptimizerHost” named pipe. Underneath, this function goes on to call the “OptimizerHostCommandsClient.Connect()” in the Avira.Optimizer.Common.Tools.OptimizerHostClient class, which sounds really interesting. When looking at this function, it just calls WaitNamedPipe() to wait for the pipe to be ready. Once it is, CreateFile is used to get a handle to the named pipe.

Looking back at the “StartServiceHost” method, it instantiates an instance of the Avira.Optimizer.Common.Tools.OptimizerHostClient class, connects to the “AviraOptimizerHost” named pipe and then goes on to call an interesting method named “StartParentProcess()”. 

When looking at that instantiated class, there are many interesting methods. Such items include: StartProcess, StartParentProcess, StopProcess, AddTask and RemoveTask. These methods take various parameters and then go on to call “SendMessage” after converting the tasking to JSON:

The “SendMessage()” method takes the JSON of the command and sends it to the “AviraOptimizerHost” named pipe, where the SYSTEM process “Avira.OptimizerHost.exe” ingests it:

Looking at “Avira.OptimizerHost.exe”, we can see where the service reads in the JSON and parses out the arguments:

In this case, if we send the “StartProcess()” method to the named pipe, the service will pull out the “procid”, “exec” (executable path),“args” (arguments)/etc from the JSON blob sent over the named pipe. From there, it follows the same logic that was used to validate the named pipe in client, in which it takes the executable path from the “exec” parameter and checks the file’s certificate in order to ensure it belongs to Avira. The service relies on the subject and certificate serial number (both of which are attacker controlled), so it is possible to use a tool like SigPirate to clone the certificate off of a valid Avira executable and apply it to a custom payload.

In order to exploit this, we need to accomplish a few things:

  1. Prepare our payload. In this case, it is a .NET executable named Avira.SystemSpeedup.RealTime.Client.exe that starts cmd.exe
  2. Clone the certificate off of a valid Avira file and apply it to our payload
  3. Write code that injects into a valid Avira process, loads up Avira.Optimizer.Common.Tools.dll and instantiates an instance of the OptimizerHostClient class
  4. Use the exposed methods to connect to the “AviraOptimizerHost” named pipe and issue our commands to the service

For payload creation and certificate cloning, I will leave that as an exercise for the reader. In order to connect to the named pipe and send commands, we can reuse the existing Avira libraries by adding a reference to Avira.Optimizer.Common.Tools.dll and importing the Avira.Optimizer.Common.Tools.OptimizerHostClient namespace. Once done, we can just create an instance of the OptimizerHostCommandsClient class and call any of the interesting methods, such as “StartProcess”.

In order to achieve LPE, all we need to do is inject this assembly into an Avira process and invoke our entrypoint. Again, this is an exercise left up to the reader…but there are various projects that make this process easy (https://github.com/ChadSki/SharpNeedle). 

After injecting into an Avira process and executing the above C# code, cmd.exe will be started as SYSTEM after the assembly connects to the “AviraOptimizerHost” named pipe and sends the “StartProcess()” method with the “exec” argument set to the payload with a cloned Avira certificate (in this case, a payload named Avira.SystemSpeedup.RealTime.Client.exe).

This vulnerability has been fixed in Avira Optimizer version 1.2.0.367. After glancing at the fix, Avira now utilizes WinVerifyTrust() and an apparent path whitelist to ensure started processes aren’t influenced.

Disclosure Timeline

I’d like to take a second to give Avira and their development team props. The team remains in constant contact and fixes issues at a rapid pace. In the case of this report, a fix was developed and distributed to the public around 30 days after the initial report. It is refreshing to work with a vendor that takes vulnerability reports seriously and follows the community’s set expectations of 90 day fixes.

As committed as SpecterOps is to transparency, we acknowledge the speed at which attackers adopt new offensive techniques once they are made public. This is why prior to publication of a new bug or offensive technique, we regularly inform the respective vendor of the issue, supply ample time to mitigate the issue, and notify select, trusted vendors in order to ensure that detections can be delivered to their customers as quickly as possible.

  • July 23rd, 2019: Vulnerability sent to the Avira security team
  • July 24th,  2019: Avira acknowledged the report, noted some compile issues with the PoC
  • July 26th, 2019: Avira reproduced the vulnerability with the PoC provided
  • August 6th, 2019: Avira noted the developers fixed the issue, asked if I would like to test the fix
  • August 6th, 2019: Replied to Avira with a bypass for the patch, provided updated PoC and details
  • August 16th, 2019: Avira replied noting the developers implemented a new fix and asked if I’d like to test it
  • August 16th, 2019: Tested the new fix. Let Avira know that it seemed decent enough
  • August 27th, 2019: Fix pushed live
  • August 29th, 2019: Details published

CVE-2019-13382: Local Privilege Escalation in SnagIt

Version: Snagit 2019.1.2 Build 3596
Operating System tested on: Windows 10 1803 (x64)
Vulnerability: SnagIt Relay Classic Recorder Local Privilege Escalation through insecure file move

This vulnerability was found in conjunction with Marcus Sailler, Rick Romo and Gary Muller of Capital Group’s Security Testing Team

Vulnerability Overview
Every 30-60 seconds, the TechSmith Uploader Service (UploaderService.exe) checks the folder “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations” for any presentation files in the “*.xml” format. If an invalid one is found, the service moves that file to “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations” as SYSTEM.

Since a low privileged user has full control over the QueuedPresentations and InvalidPresentations folders, it is possible to create an invalid presentation in the QueuedPresentations folder and then place a symbolic link for that file name in the InvalidPresentations folder that points to a privileged location.

When the service checks for presentations, it will move the file out of the QueuedPresentations folder and into the InvalidPresentations folder. When it does so, the service will hit the symbolic link and write the new file into a protected location with permissions that allow the low privileged user full control over the contents, resulting in Elevation of Privilege to NT AUTHORITY\SYSTEM.

Identification and Exploitation
When assessing software for privilege escalation vulnerabilities, finding a starting point can often be overwhelming as there are many different primitives and vulnerability classes that exist. My approach often includes starting with the basics and working my way up in complexity. This process typically involves running a tool such as PowerUp, which will identify various trivial (yet common) misconfigurations.

If nothing interesting is returned, the next step is often looking for logical vulnerabilities; specifically abusing symlink/mountpoint/hardlink primitives. In order to quickly identify potential vulnerabilities that could be exploited with the linking primitives, we need to identify locations on the OS where a privileged process (often SYSTEM) is interacting with a folder or file that a low privileged user has control over. This logic is true in most logical vulnerabilities in that interesting attack surface is linked to a privileged process utilizing a resource a low privileged user controls.

When hunting for such bugs, I often start with running Process Monitor with a filter on SYSTEM processes and commonly abused filesystem locations, such as C:\ProgramData, C:\Windows\Temp and C:\Users\<username>\AppData. Such a filter might look like so:

 

When applying the Process Monitor and watching the output for a few minutes, it became apparent that “UploaderService.exe” was querying the “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations” directory for any XML files:

Looking at the DACL on that folder, it also stood out that that “BUILTIN\Users” had write access:

This is particularly interesting in that a privileged SYSTEM process (UploaderService.exe) is looking for files in a directory that low privileged users have read/write access. With this information, the next step was to give “UploaderService.exe” an XML file to find and see what happens.

As expected, “UploaderService.exe” checks “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations” for any XML files and finds the one we created:

The next question was, what does “UploaderService.exe” do with our XML file? Does it read it in and ingest the contents? Does it place it someplace else?

Looking at the rest of the Process Monitor output answers that question for us. In this case, “UploaderService.exe” takes any XML files in “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations” and determines if the XML presentation file is valid. Since we simply echoed “1” into our XML file, the service executable determines that “1.xml” is an invalid presentation and moves it to “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations\1.xml”:

Looking at the “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations” directory, “BUILTIN\Users” also have read/write access:

At this point, we have identified that a SYSTEM process (UploaderService.exe) is checking a user-controlled directory for any XML files. When found, the privileged process takes the attacker supplied XML file and moves it from the QueuedPresentations folder to the InvalidPresentations folder while retaining the original file name.

Why is this interesting? This presents the opportunity to use Symbolic Links during the move file operation to accomplish a privileged file write. How you might ask? The workflow would be like so:

  • Create a Symbolic Link on “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations\1.xml” that points to “C:\Windows\System32\ualapi.dll”
    • It should be noted that “C:\Windows\System32\ualapi.dll” doesn’t exist. This is a DLL we are planting to get code-execution as SYSTEM
    • Since the process is privileged “SYSTEM”, it will have the correct permissions to write this file.
  • Write a dummy xml file to “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations\1.xml”
  • When “UploaderService.exe” checks “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations” for any XML files, it will find “1.xml” and move it to “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations\1.xml”. While doing so, it will hit our Symbolic Link and instead move the file to “C:\Windows\System32\ualapi.dll” (while retaining the original DACL)

In theory, this should work. Let’s test it out! For the Symbolic Link, I used “CreateSymlink.exe” from James Forshaw’s Symbolic Link Testing Tools repo. All we need to do is place a symbolic link on “C:\ProgramData\Techsmith\TechSmith Recorder\InvalidPresentations\1.xml” that points to “C:\Windows\System32\ualapi.dll” and then create “C:\ProgramData\Techsmith\TechSmith Recorder\QueuedPresentations\1.xml”:

With the symlink created and our dummy XML file created, we wait 60 seconds for “UploaderService.exe” to check the QueuedPresentations folder. When it does, it finds our “1.xml” file and tries to move it to “C:\ProgramData\TechSmith\TechSmith Recorder\InvalidPresentations\1.xml”. When it does so, it hits our symbolic link on “C:\ProgramData\TechSmith\TechSmith Recorder\InvalidPresentations\1.xml” and instead writes it to “C:\Windows\System32\ualapi.dll”:

We can then confirm the existence of “C:\Windows\System32\ualapi.dll”:

This is great and all, but shouldn’t the newly created “ualapi.dll” file simply inherit the permissions of the parent folder (C:\Windows\System32) and prevent a low privileged user from writing to it? That was my thought at first (before checking the DACL on the file), but “UploaderService.exe” uses MoveFileW(). According to the documentation, MoveFileW() retains the original DACL when moving the file on the same volume:

While not explicitly stated, it can be inferred that if the file is not moved across volumes, it is moved with the DACL intact. This means that when “UploaderService.exe” hits the symbolic link on “C:\ProgramData\TechSmith\TechSmith Recorder\InvalidPresentations\1.xml” and tries to move the original file to “C:\Windows\System32\ualapi.dll”, it keeps the original DACL for “1.xml”.  Since it was created by a low privileged user, it has a DACL that has the low privileged user as the Owner with “FullControl” rights:

At this point, we now have “C:\Windows\System32\ualapi.dll” that allows our low privileged user to write to it. This means we can simply copy over the newly created ualapi.dll file with a payload of our choosing. In this case, the payload starts cmd.exe when loaded.

We now have a payload sitting in C:\Windows\System32\ualapi.dll. This DLL gets loaded when the spooler service starts. For the PoC, all that is left is to reboot the host in order to get the spooler service to restart. Additionally, one could use the CollectorService to load the DLL without a reboot. Since this is a PoC, that is an exercise left up to the reader.

Once the host is rebooted, “spoolsv.exe” will load our payload from C:\Windows\System32\ualapi.dll as SYSTEM, resulting in privilege escalation:

A video of exploitation can be found here: https://www.youtube.com/watch?v=V90JRwlaHRY&feature=youtu.be

This vulnerability has been fixed in SnagIt versions 2019.1.3, 2018.2.4 and 13.1.7 with CVE-2019-13382. The fixed involved using _time64 when moving the file combined with a check for reparse points (FSCTL_GET_REPARSE_POINT). If a reparse point exists, it is removed.

 

Disclosure Timeline

As committed as SpecterOps is to transparency, we acknowledge the speed at which attackers adopt new offensive techniques once they are made public. This is why prior to publicization of a new bug or offensive technique, we regularly inform the respective vendor of the issue, supply ample time to mitigate the issue, and notify select, trusted vendors in order to ensure that detections can be delivered to their customers as quickly as possible.

  • June 19th, 2019: Vulnerability identified in conjunction with Capital Group’s Security Testing Team
  • June 20th, 2019: Joint disclosure with Capital Group began. Support case opened with a request for contact information for the security team at TechSmith
  • June 21st, 2019: Case assigned to a handler, new comment stated that the details can be uploaded to the current case and they will be forwarded to the security team
  • June 21st, 2019: Full write-up, PoC code and a demonstration video was uploaded to the open support case
  • June 25th, 2019: TechSmith confirmed the vulnerability and offered temporary remediation advice. TechSmith also requested notice before public disclosure.
  • June 25th, 2019: Informed TechSmith that public disclosure would be 90 days after the initial report with a note that an extension would be considered if needed.
  • July 2nd, 2019: TechSmith stated a fixed build is done and set to be deployed before the end of July with a note asking if we would verify the fix
  • July 2nd, 2019: Informed TechSmith that I would verify the fix
  • July 3rd, 2019: TechSmith provided a private fixed build
  • July 9th, 2019: Informed SnagIt that based on testing, the fix seemed sufficient 
  • July 23rd, 2019: Patch released, issue publicly disclosed

CVE-2019-13142: Razer Surround 1.1.63.0 EoP

Version: Razer Surround 1.1.63.0
Operating System tested on: Windows 10 1803 (x64)
Vulnerability: Razer Surround Elevation of Privilege through Insecure folder/file permissions

Purpose
I hope that this post serves as a motivator for folks who see vulnerability research as an intimidating area to get started in. While this bug can be considered simple, the primary purpose of this post is to outline the methodology behind how to get started and what to look for. Additionally, I’d like it to serve as a reminder to not discount the low hanging fruit, no matter how large the organization.

Brief Description:
Razer Surround installs a service named “RzSurroundVADStreamingService” that runs as SYSTEM. This service runs “RzSurroundVADStreamingService.exe” out of “C:\ProgramData\Razer\Synapse\Devices\Razer Surround\Driver”. The permissions on  RzSurroundVADStreamingService.exe and “C:\ProgramData\Razer\Synapse\Devices\Razer Surround\Driver” allow for overwriting the service binary with a malicious one, resulting in elevation of privilege to SYSTEM.

Identification and Exploitation
When doing vulnerability research, picking a target to go after can be challenging. Hunting for bugs in large software platforms can be intimidating as there is an assumption that all vulnerabilities are complex and take a special skill set to identify. I’d like to use this vulnerability as an example as to why the act of hunting for vulnerabilities isn’t as hard as it sounds.

You may ask, why Razer? How do you identify a piece of software to begin hunting for vulnerabilities in? The answer is simple: Investigate what interests you. In this case, I own various Razer products. It is hard to ignore the urge to look when you use a product and the software associated with it every day.

When looking for vulnerabilities, there is often a common workflow that I follow once the software of interest is installed. This stage involves analyzing the potential attack surface that the target software has exposed. I typically start with the basics and then resort to dynamic/static analysis if needed. The things I typically look for initially are:

  1. Installed services (both the service permissions and the service executable/path permission)
  2. Named pipes (and their ACLs)
  3. Log file permissions in folders like C:\ProgramData
  4. Network sockets
  5. DCOM servers and hosted interfaces

As far as tooling goes, I mostly stick to Process Monitor and James Forshaw’s NTObjectManager project.

In the instance of Razer Surround, I began by checking what privileged processes the software uses by looking at the process list. This revealed that “RzSurroundVADStreamingService.exe” was running as “NT AUTHORITY\SYSTEM”. The next step was to figure out how that process was being started. Given the name of the process has “service” in it, that is a good starting point. To verify, it was easy enough to do “Get-Service *Rz*” in Powershell, which returned all of the services with “Rz” in the name. This led me to the “RzSurroundVadStreamingService” system service with the ImagePath set to the executable of interest. After dumping the ImagePath, the location of the service executable stood out as it was running out of “C:\ProgramData\Razer\Synapse\Devices\Razer Surround\Driver\”

Why is this interesting? By default, “BUILTIN\Users” have “GenericWrite” access to C:\ProgramData:

A very common error that software developers make is not properly locking down the permissions of any created subfolders in C:\ProgramData. If an installer simply creates a folder in C:\ProgramData, that folder and any subfolders will have inherited permissions of C:\ProgramData, which include the “GenericWrite” access right for “BUILTIN\Users”.

Improper file and folder permissions were the culprit in this case as “Everyone” was eventually granted “FullControl” over any files in “C:\ProgramData\Razer\Synapse\Devices\Razer Surround\Driver”

As previously noted, this path is where the “RzSurroundVADStreamingService” ImagePath for the service executable was pointing to. Given a low privileged user has “FullControl” over the folder and included files, it is possible to just replace the service executable for the “RzSurroundVADStreamingService” system service:

Once the payload is copied, rebooting the host will cause the service to start the new executable as SYSTEM. In this instance, the new service executable will start cmd.exe as SYSTEM:

Razer fixed this vulnerability by moving “RzSurroundVADStreamingService.exe” and the associated dependencies to a secured location in “C:\Program Files (x86)\Razer”.

Disclosure Timeline
As committed as SpecterOps is to transparency, we acknowledge the speed at which attackers adopt new offensive techniques once they are made public. This is why prior to publicization of a new bug or offensive technique, we regularly inform the respective vendor of the issue, supply ample time to mitigate the issue, and notify select, trusted vendors in order to ensure that detections can be delivered to their customers as quickly as possible.

  • March 20th, 2019 — Initial report sent to Razer
  • March 21st, 2019  — Report acknowledgement received from Razer
  • April 30th, 2019 —  30 days after initial report
  • May 2nd, 2019  —  Razer provided a fixed build to test
  • May 2nd, 2019 —  Fix was verified
  • May 20th, 2019 —  60 days after initial report
  • June 6th, 2019 —  Reached out to Razer for a timeframe regarding a public fix
  • June 6th, 2019 —  Razer informed me a fix should be live, but verification from the development team was needed
  • June 7th, 2019 —  Informed Razer that a fix wasn’t available on the site or via Surround’s update mechanism
  • June 10th, 2019 —  Razer informed me that there had been some internal confusion and that a fix was going live the end of June
  • June 11th, 2019 —  Informed Razer I would hold off on disclosure until the fix is live
  • June 20th, 2019 —  90 days after initial report, extension granted
  • July 1st, 2019 —  Razer informed me that a note is out to the development team regarding when the fix would be pushed live
  • July 5th, 2019 —  Fix published

-Matt N.

Avira VPN (2.15.2.28160) Elevation of Privilege through Insecure Update location

Product Version: Avira Phantom VPN
Downloaded from: https://package.avira.com/package/oeavira/win/int/avira_en_vpn__ws.exe
Operating System tested on: Windows 10 1709 (x64)
Vulnerability: Avira VPN Elevation of Privilege

Brief Description: The Avira Phantom VPN Service performs a handful of checks when it checks for an update (this happens when the service starts, so on boot or via manual start/stopping). When updating, the VPN service downloads the update to “C:\Windows\Temp\” as AviraVPNInstaller.exe (which any user can write to). It then checks if the file is signed, and if the version is less than the installed product version. If these requirements are met, the service will run the update binary as SYSTEM.  It is possible to overwrite “AviraVPNInstaller.exe” with a signed, valid Avira executable (with a version greater than the installed version) that looks for various DLLs in the current directory. Due to the search order, it is possible to plant a DLL in “C:\Windows\Temp” and elevate to NT AUTHORITY\SYSTEM.

Vulnerability Explanation
When the Avira VPN service (Avira.VPNService.exe, version 2.15.2.28160) starts, it checks to see if there is an update available:

After some poking, it was determined that the VPN service updates from “C:\Windows\Temp”

As you may know already, any authenticated user can write (but not read) to C:\Windows\Temp. Once the update is there (as C:\Windows\Temp\AviraVPNInstaller.exe), the VPN service checks the “FileVersion” property of the executable in order to determine if the “update” is already installed on the system:

If the version of the file shows it hasn’t been installed, the service will check the file to make sure it is valid and signed by Avira:

If the file is indeed valid and signed by Avira, the VPN service will start the “update” package. Since all of this happens in a place a low privilege user can write to, it is possible to hijack the update package and perform DLL sideloading.

In order to hijack “AviraVPNInstaller.exe”, three of the following conditions have to be met:

  1. “AviraVPNInstaller.exe” has to be signed by Avira
  2. The signature on “AviraVPNInstaller.exe” has to be valid (any modification of that file would invalidate the signature
  3. The version number of “AviraVPNInstaller.exe” proves the update hasn’t been installed on the system.

This means we need to abuse an Avira signed file that has a version greater than what is deployed.

After some hunting, “ToastNotifier.exe” fit the bill as it satisfies all three requirements:

(If you are curious of where ToastNotifier.exe came from, it is from the Avira Antivirus product suite that was abused in a similar manner in a bug reported on the Antivirus platform).

To exploit this, all we need to do is copy “ToastNotifier.exe” to “C:\Windows\Temp\AviraVPNInstaller.exe”. Since the requirements are met, the VPN service will run it as SYSTEM when the service restarts. This can be accomplished via a reboot, or by running powershell as an Administrator and then doing “Get-Service AviraPhantomVPN | Stop-Service” followed by “Get-Service AviraPhantomVPN | Start-Service”. Once executed, “ToastNotifier.exe” (which is now C:\Windows\Temp\AviraVPNInstaller.exe) will try to load a handful of DLLs out of C:\Windows\temp:

 To elevate our privileges to SYSTEM, all we need to do is provide one of those DLLs. In this case, we will hijack “cryptnet.dll”. This will result in the VPN service starting our “AviraVPNInstaller.exe” binary as SYSTEM, which will then load “cryptnet.dll” as SYSTEM, which is our malicious code. This results in elevation from a low privileged user to SYSTEM:

 

Disclosure Timeline:

  1. Submitted to Avira on September 28, 2018
  2. October 1, 2018: Issue reproduced by Avira
  3. December 13, 2018: Issue fixed by Avira

 

 

 

 

 

 

 

 

 

Avira VPN 2.15.2.28160 Elevation of Privilege

Product Version: Avira Phantom VPN version 2.15.2.28160

Downloaded from: https://package.avira.com/package/oeavira/win/int/avira_en_vpn__ws.exe

Operating System tested on: Windows 10 1803 (x64)

Vulnerability: Avira VPN Elevation of Privilege

Brief Description: The Avira Phantom VPN service changes the DACL on “C:\ProgramData\Avira\VPN\VpnSharedSettings.backup” and “C:\ProgramData\Avira\VPN\VpnSharedSettings.config” when a configuration change is made in the VPN settings menu. By setting a hardlink on “C:\ProgramData\Avira\VPN\VpnSharedSettings.backup”, it is possible to overwrite the DACL on an arbitrary file, leading to elevation from a low privileged user to SYSTEM.

Vulnerability Explanation

When making a configuration change via the VPN GUI, the VPN service (Avira.VPNService.exe) calls the function “AdjustSecurity()” that resides in “Avira.VPN.Core.dll” to change the DACL to allow any authenticated user to write to “C:\ProgramData\Avira\VPN\VpnSharedSettings.backup” or “C:\ProgramData\Avira\VPN\VpnSharedSettings.config”. When a configuration change is made (which can be done as a low privileged user), the service makes sure the “Shared” VPN profile setting file (C:\ProgramData\Avira\VPN\VpnSharedSettings.backup and C:\ProgramData\Avira\VPN\VpnSharedSettings.config) exist by calling “EnsureFileExists()”:

If the configuration file doesn’t exist, the service will create it and continue. If it does, it simply continues to  the “StorageSecurity.AdjustSecurity()” function. In the case of the shared vs private configuration profiles, the “StorageType” being passed is either “AllUserAccess” or “Secure”. The “private” VPN profile is assigned the “Secure” storage type while the shared profile is assigned “AllUserAccess”.

When the “AdjustSecurity()” function is called (passing the storage type of the configuration file), it adjusts the DACL on the file itself appropriately. This is where the bug lies. For the “shared” profile (with the StorageType of “AlluserAccess”), the VPN service calls “AdjustSecurity()” and grants the everyone full control over the file:

This is dangerous because the “SetAccessControll()” call changes the DACL on a file in which a low privileged user can control. So, how do we exploit this?

First, an attacker needs to create a hardlink on “C:\ProgramData\Avira\VPN\VpnSharedSettings.backup” and point it at a file. In this case, we will point it at  “C:\Program Files (x86)\Avira\VPN\OpenVPN\phantomvpn.exe”. Doing so will overwrite the DACL on “C:\Program Files (x86)\Avira\VPN\OpenVPN\phantomvpn.exe”.

After doing so, open the VPN and click the “Settings” tab:

After doing so, uncheck the “Send Diagnostic Data” box. This will trigger a config change and kick the code-path off:

At this point, the DACL on “C:\Program Files (x86)\Avira\VPN\OpenVPN\phantomvpn.exe” has been overwritten to allow any user to write to it. The attacker now just needs to copy a malicious binary to replace it:

Once done, execution of the malicious binary can be accomplished by simply trying to connect to the VPN. This can be done by clicking “Secure my Connection” in the VPN GUI:

After clicking “Secure my connection”, you should see a few cmd.exe processes running as SYSTEM:

Disclosure Timeline:

  1. Reported to Avira on September 28, 2018
  2. October 1, 2018: Avira acknowledged the report
  3. October 4, 2018: Avira was able to reproduce
  4. December 13, 2018: Issue resolved

Razer Synapse 3 Elevation of Privilege

Product Version: Razer Synapse 3 (3.3.1128.112711) Windows Client
Downloaded from: https://www.razer.com/downloads
Operating System tested on: Windows 10 1803 (x64)
Vulnerability: Razer Synapse Windows Service EoP

Brief Description: The Razer Synapse software has a service (Razer Synapse Service) that runs as “NT AUTHORITY\SYSTEM” and loads multiple .NET assemblies from “C:\ProgramData\Razer\*”. The folder “C:\ProgramData\Razer\*” and recursive directories/files have weak permissions that grant any authenticated user FullControl over the contents. It is possible to circumvent signing checks and elevate to SYSTEM using assembly sideloading.

Vulnerability Explanation:
When the Razer Synapse service starts, it will load .NET assemblies out of various directories within “C:\ProgramData\Razer\*”, such as “C:\ProgramData\Razer\Synapse3\Service\bin”.

When looking at the DACL on the folder “C:\ProgramData\Razer\Synapse3\Service\bin”, you will notice that “Everyone” has “FullControl” rights over the folder (including any files within the folder):

In theory, an attacker could simply replace an existing .NET assembly with a malicious one, reboot the system and let the Razer Synapse Service load it when it starts. This approach came with some complications, such as a race condition to replace an assembly before the service loads it. Additionally, the service implements some checks that must be passed before the assembly can be loaded. For efficient exploitation, it is important to fully understand the conditions in which an assembly can be loaded successfully.

The first issue to tackle is getting a malicious assembly planted in such a way that the service will try to load it. Hijacking an existing assembly can be challenging as low privileged users do not have rights to stop or start the Razer Synapse service. This means that to trigger the assembly loading code path, the box needs to be rebooted. This makes winning the race condition for swapping out a legitimate assembly with a malicious one challenging. Looking at the service, this problem is solved pretty easily as it recursively enumerates all DLLs in “C:\ProgramData\Razer\*”.

This means that we can simply drop an assembly in one of the folders (C:\ProgramData\Razer\Synapse3\Service\bin, for example) and it will be treated the same as an existing, valid assembly.

After recursively enumerating all DLLs in “C:\ProgramData\Razer\*”, the service attempts to ensure those identified assemblies are signed by Razer. This is done by grabbing certificate information from “Razer.cer”, calling X509Certificate.CreateFromSignedFile() on each assembly and then comparing the certificate chain from Razer.cer with the assembly being loaded.

If the certificate chain on the assembly doesn’t match that of Razer.cer, the service will not load it. While the thought behind checking the trust of .NET assemblies before loading them is good, the implementation wasn’t robust, as X509Certificate.CreateFromSignedFile() only extracts the certificate chain and in no way attests the validity of the signature of the file being checked (https://twitter.com/tiraniddo/status/1072475737142239233). This means that it is possible to use a tool such as SigPirate to clone the certificate from a valid Razer assembly onto a malicious one, due to the fact that the signature of said assembly is never actually verified.

Once the assembly passes the certificate check, the service will then load it into the current app domain via  Assembly.LoadFile(). No malicious code will execute during the Assembly.LoadFile() call, however. After doing so, the service will check to make sure there is an IPackage interface implemented.

This interface is specific to the SimpleInjector project, which is well documented. The only requirement to pass this check is to implement the IPackage interface in our malicious assembly. Once the service validates the certificate chain of the assembly and verifies the presence of IPackage, it adds the assembly to a running list. Once this is done for all the assemblies found in “C:\ProgramData\Razer\*”, the list is then passed to SimpleInjector’s “RegisterPackages()” function.

RegisterPackages() will take the list of “verified” assemblies and call the “RegisterServices()” function within the IPackage interface of each assembly.

This is the point in which we, as an attacker, can execute malicious code. All that needs done is to add malicious logic in the “RegisterServices()” method within the IPackage interface of our malicious assembly.

At this point, we have found ways to abuse all of the requirements to get elevated code-execution.

  1. Write a custom assembly that implements the IPackage interface from the SimpleInjector project
  2. Add malicious logic in the “RegisterServices()” method inside the IPackage interface
  3. Compile the assembly and use a tool such as SigPirate to clone the certificate chain from a valid Razer assembly
  4. Drop the final malicious assembly into “C:\ProgramData\Razer\Synapse3\Service\bin”
  5. Restart the service or reboot the host

Exploitation:
After understanding the requirements to get arbitrary code-execution in an elevated context, we can now exploit it. First, we need to create our malicious assembly that implements the required IPackage interface. To do so, a reference to the “SimpleInjector” and “SimpleInjector.Packaging” assemblies need to be added from the SimpleInjector project. Once the reference is added, we just need to implement the interface and add malicious logic. A PoC assembly would look something like this:

Since the Razer service is 32-bit, we compile the assembly as x86. Once compiled, we need to pass the certificate chain check. Since the service is using X509Certificate.CreateFromSignedFile() without any signature validation, we can simply clone the certificate from a signed Razer assembly using SigPirate:

Using “Get-AuthenticodeSignature” in PowerShell, we can verify that the certificate was applied to our “lol.dll” assembly that was created from SigPirate:

At this point, we have a malicious assembly with a “backdoored” IPackage interface that has a cloned certificate chain from a valid Razer assembly. The last step is to drop “lol.dll” in “C:\ProgramData\Razer\Synapse3\Service\bin” and reboot the host. Once the host restarts, you will see that “Razer Synapse Service.exe” (running as SYSTEM) will have loaded “lol.dll” out of “C:\ProgramData\Razer\Synapse3\Service\bin”, causing the “RegisterServices()” method in the implemented IPackage interface to execute cmd.exe.

When the service loads “lol.dll”, it sees it as valid due to the cloned certificate, and EoP occurs due to the “malicious” logic in the IPackage implementation.

Razer fixed this by implementing a new namespace called “Security.WinTrust”, which contains functionality for integrity checking. The service will now call “WinTrust.VerifyEmbeddedSignature() right after pulling all the “*.dll” files from the Razer directory.

When looking at “WinTrust.VerifyEmbeddedSignature()”, the function utilizes “WinTrust.WinVerifyTrust()” to validate that the file being checked has a valid signature (through WinVerifyTrust()).

If the file has a valid signature AND the signer is by Razer, then the service will continue the original code path of checking for a valid IPackage interface before loading the assembly. By validating the integrity of the file, an attacker can no longer clone the certificate off of a signed Razer file as the signature of the newly cloned file will not be valid.

For additional reading on trust validation, I encourage you to read the whitepaper “Subverting Trust in Windows” by Matt Graeber.

Disclosure Timeline:

06/05/2018: Submitted vulnerability report to Razer’s HackerOne program
06/08/2018: Response posted on the H1 thread acknowledging the report
06/08/2018: H1 staff asked for specific version number of the Synapse 3 installer
06/08/2018: Synapse 3 installer version number provided to Razer
07/05/2018: Asked for an update
08/06/2018: Report marked as triaged
08/27/2018: Asked for an update, no response
09/14/2018: Asked for update, along with a direct email address to speed up communication. No response
12/14/2018: Asked for a security contact for Razer via Twitter
12/14/2018: H1 program manager reached out to investigate the H1 report
12/15/2018: Razer CEO Min-Liang Tan reached out directly asking for a direct email to pass to the security team
12/16/2018: The Information Security Manager and SVP of Software reached out directly via email. I was provided context that a fix would be pushed out to the public in a couple of weeks
12/19/2018: Pulled down the latest Synapse 3 build and investigated vulnerable code path. Submitted additional information to Razer’s H1 program, along with notice to Razer’s Manager of Information Security
12/25/2018: I was contacted by someone at Razer with a link to an internal build for remediation verification
12/27/2018: Per their request, provided feedback on the implemented mitigation via the H1 report
01/09/2019: Asked for a timeline update for the fixed build to be provided to the public (via H1)
01/10/2019: Informed that the build is now available to the public
01/10/2019: Report closed
01/10/2019: Requested permission for public disclosure
01/10/2019: Permission for public disclosure granted by Razer
01/21/2019: Report published

*Note: While the disclosure timeline was lengthy, I have to assume it was due to a disconnect between the folks at Razer managing the H1 program and the folks at Razer working on the fix. Once I was provided an internal contact, the timeline and experience improved drastically.

 

-Matt N.

CVE-2018–8414: A Case Study in Responsible Disclosure

The process of vulnerability disclosure can be riddled with frustrations, concerns about ethics, and communication failure. I have had tons of bugs go well. I have had tons of bugs go poorly.

I submit a lot of bugs, through both bounty programs (Bugcrowd/HackerOne) and direct reporting lines (Microsoft). I’m not here to discuss ethics. I’m not here to provide a solution to the great “vulnerability disclosure” debate. I am simply here to share one experience that really stood out to me, and I hope it causes some reflection on the reporting processes for all vendors going forward.

First, I’d like to give a little background on myself and my relationship with vulnerability research.

I’m not an experienced reverse engineer. I’m not a full-time developer. Do I know C/C++ well? No. I’m relatively new to the industry (3 years in). I give up my free time to do research and close my knowledge gaps. I don’t find crazy kernel memory leaks, rather, I find often overlooked user-mode logic bugs (DACL overwrite bugs, anyone?).

Most importantly, I do vulnerability research (VR) as a hobby in order to learn technical concepts I’m interested in that don’t necessarily apply directly to my day job. While limited, my experience in VR comes with the same pains that everyone else has.

When I report bugs, the process typically goes like this:

  1. Find bug->Disclose bug->Vendor’s eyes open widely at bug->Bug is fixed and CVE possibly issued (with relevant acknowledgement)->case closed
  2. Find bug->Disclose bug->Vendor fails to see the impact, issues a won’t fix->case closed

When looking at these two situations, there are various factors that can determine if your report lands on #1 or #2. Such factors can include:

  1. Internal vendor politics/reorg
  2. Case handler experience/work ethic/communication (!!!!)
  3. Report quality (did you explain the bug well, and outline the impact the bug has on a product?)

Factors that you can’t control can start to cause frustration when they occur repeatedly. This is where the vendor needs to be open to feedback regarding their processes, and where researchers need to be open to feedback regarding their reports.

So, let us look at a case study in a vulnerability report gone wrong (and then subsequently rectified):

On Feb 16, 2018 at 2:37 PM, I sent an email to secure@microsoft.com with a write-up and PoC for RCE in the .SettingContent-ms file format on Windows 10. Here is the original email:

This situation is a good example where researchers need to be open to feedback. Looking back on my original submission, I framed the bug mostly around Office 2016’s OLE block list and a bypass of the Attack Surface Reduction Rules in Windows Defender. I did, however, mention in the email that “The PoC zip contains the weaponized .settingcontent-ms file (which enables code-execution from the internet with no security warnings for the user)”. This is a very important line, but it was overshadowed by the rest of the email.

On Feb 16, 2018 at 4:34 PM, I received a canned response from Microsoft stating that a case number was assigned. My understanding is that this email is fairly automated when a case handler takes (or is assigned) your case:

Great. At this point, it is simply a waiting game while they triage the report. After a little bit of waiting, I received an email on March 2nd, 2018 at 12:27pm stating that they successfully reproduced the issue:

Awesome! This means that they were able to take my write-up with PoC and confirm its validity. At this very point, a lot of researchers see frustration. You take the time to find a bug, you take the time to report it, you get almost immediate responses from the vendor, and once they reproduce it, things go quiet. This is understandable since they are likely working on doing root cause analysis on the issue. This is the critical point in which it will be determined if the bug is worth fixing or not.

I will admit, I generally adhere to the 90 day policy that Google Project Zero uses. I do not work for GPZ, and I don’t get paid to find bugs (or manage multiple reports). I tend to be lenient if the communication is there. If a vendor doesn’t communicate with me, I drop a blog post the day after the 90 days window closes.

Vendors, PLEASE COMMUNICATE TO YOUR RESEARCHERS!

In this case, I did as many researchers would do once more than a month goes by without any word…I asked for an update:

At this point, it has almost been a month and a half since I have heard anything. After asking for an update, this email comes in:

Interesting…I all of the sudden have someone else handling my case? I can understand this as Microsoft is a huge organization with various people handling the massive load of reports they get each day. Maybe my case handler got swamped?

Let’s pause and evaluate things thus far: I reported a bug. This bug was assigned a case number. I was told they reproduced the issue, then I hear nothing for a month and a half. After reaching out, I find out the case was re-assigned. Why?

Vendors, this is what causes frustration. Researchers feel like they are being dragged along and kept in the dark. Communication is key if you don’t want 0days to end up on Twitter. In reality, a lot of us sacrifice personal time to find bugs in your products. If people feel like they don’t matter or are placed on the backburner, they are less likely to report bugs to you and more likely to sell them or drop them online.

Ok, so my case was re-assigned on April 25th, 2018 at 12:42 pm. I say “Thanks!!” a few days later and let the case sit while they work the bug.

Then, a little over a month goes by with no word. At this point, it has been over 90 days since I submitted the original report. In response, I sent another follow up on June 1st, 2018 at 1:29pm:

After a few days, I get a response on June 4th, 2018 at 10:29am:

Okay. So, let’s take this from the top. On Feb 16, 2018, I reported a bug. After the typical process of opening a case and verifying the issue, I randomly get re-assigned a case handler after not hearing back for a while. Then, after waiting some time, I still don’t hear anything. So, I follow up and get a “won’t fix” response a full 111 days after the initial report.

I’ll be the first to admit that I don’t mind blogging about something once a case is closed. After all, if the vendor doesn’t care to fix it, then the world should know about it, in my opinion.

Given that response, I went ahead and blogged about it on July 11, 2018. After I dropped the post, I was contacted pretty quickly by another researcher on Twitter letting me know that my blog post resulted in 0days in Chrome and Firefox due to Mark-of-the-Web (MOTW) implications on the .SettingContent-ms file format. Given this new information, I sent a fresh email to MSRC on June 14, 2018 at 9:44am:

At this point, I saw two exploits impacting Google Chrome and Mozilla FireFox that utilized the .SettingContent-ms file format. After resending details, I got an email on June 14, 2018 at 11:05am, in which MSRC informed me the case would be updated:

On June 26, 2018 at 12:17pm, I sent another email to MSRC letting them know that Mozilla issued CVE-2018-12368 due to the bug:

That same day, MSRC informed me that the additional details would be passed along to the team:

This is where things really took a turn. I received another email on July 3, 2018 at 9:52pm stating that my case had been reassigned once again, and that they are re-evaluating the case based on various other MSRC cases, the Firefox CVE, and the pending fixes to Google Chrome:

This is where sympathy can come into play. We are all just people doing jobs. While the process I went through sucked, I’m not bitter or angry about it. So, my response went like this:

After some time, I became aware that some crimeware groups were utilizing the technique in some really bad ways (https://www.proofpoint.com/us/threat-insight/post/ta505-abusing-settingcontent-ms-within-pdf-files-distribute-flawedammyy-rat). After seeing it being used in the wild, I let MSRC know:

MSRC quickly let me know that they are going to ship a fix as quickly as possible…which is a complete 180 compared to the original report assessment:

Additionally, there was mention of “another email on a different MSRC case thread”. That definitely piqued my interest. A few days later, I got a strange email with a different case number than the one originally assigned:

At this point, my jaw was on the floor. After sending some additional information to a closed MSRC case, the bug went from a “won’t fix” to “we are going to ship a fix as quickly as possible, and award you a bounty, too”. After some minor logistic exchanges with the Microsoft Bounty team, I saw that CVE-2018-8414 landed a spot on cve.mitre.org. This was incredibly interesting given less than a month ago, the issue was sitting as a “won’t fix”. So, I asked MSRC about it:

This is when I quickly found out that CVE-2018-8414 was being issued for the .SettingContent-ms RCE bug:

This is where the process gets cool. Previously, I disclosed a bug. That bug was given a “won’t fix” status. So, I blogged about it (https://posts.specterops.io/the-tale-of-settingcontent-ms-files-f1ea253e4d39). I then found out it had been used to exploit 2 browsers, and it was being used in the wild. Instead of letting things sit, I was proactive with MSRC and let them know about all of this. Once the August patch Tuesday came around, I received this email:

Yay!!! So Microsoft took a “Won’t Fix” bug and reassessed it based on new information I had provided once the technique was public. After a few more days and some logistical emails with Microsoft, I received this:

I have to give it to Microsoft for making things right. This bug report went from “won’t fix” to a CVE, public acknowledgement and a $15,000 bounty pretty quickly.

As someone who likes to critique myself, I can’t help but acknowledge that the original report was mostly focused on Office 2016 OLE and Windows Defender ASR, neither of which are serviceable bugs (though, RCE was mentioned). How could I have done better, and what did I learn?

If you have a bug, demonstrate the most damage it can do. I can’t place all the fault on myself, though. While I may have communicated the *context* of the bug incorrectly, MSRC’s triage and product teams should have caught the implications in the original report, especially since I mentioned “which enables code-execution from the internet with no security warnings for the user”.

This brings me to my next point. We are all human beings. I made a mistake in not clearly communicating the impact/context of my bug. MSRC made a mistake in the assessment of the bug. It happens.

Here are some key points I learned during this process:

  1. Vendors are people. Try to do right by them, and hopefully they try to do right by you. MSRC gave me a CVE, an acknowledgement and a $15,000 bounty for a bug which ended up being actively exploited before being fixed
  2. Vendors: PLEASE COMMUNICATE TO YOUR RESEARCHERS. This is the largest issue I have with vulnerability disclosure. This doesn’t just apply to Microsoft, this applies to every vendor. If you leave a researcher in the dark, without any sort of proactive response (or an actual response), your bugs will end up in the last place you want them.
  3. If you think your bug was misdiagnosed, see it through by following up and stating your case. Can any additional information be provided that might be useful? If you get a bug that is issued a “won’t fix”, and then you see it being exploited left and right, let the vendor know. This information could change the game for both you and their customers.

Vulnerability disclosure is, and will continue to be, a hard problem. Why? Because there are vendors out there that will not do right by their researchers. I am sitting on 0days in products due to a hostile relationship with “VendorX” (not Microsoft, to be clear). I also send literally anything I think might remotely resemble a bug to other vendors, because they do right by me.

At the end of the day, treat people the way you would like to be treated. This applied to both the vendors and the researchers. We are all in this to make things better. Stop adding roadblocks.

Timeline:

Feb 16, 2018 at 2:37 PM EDT: Report submitted to secure@microsoft.com
Feb 16, 2018 at 4:34 PM EDT: MSRC acknowledged the report and opened a case
March 2, 2018 at 12:27 PM EDT: MSRC responded noting they could reproduce the issue
April 24, 2018 at 4:06 PM EDT: Requested an update on the case
April 25, 2018 at 12:42 PM EDT: Case was reassigned to another case handler.
June 1, 2018 at 1:29 PM EDT: Asked new case handler for a case update
June 4, 2018 at 10:29 AM EDT: Informed the issue was below the bar for servicing; case closed.
July 11, 2018: Issue is publicly disclosed via a blog post
June 14, 2018 at 9:44 AM EDT: Sent MSRC a follow up after hearing of 2 browser bugs using the bug
June 14, 2018 at 11:05 AM EDT: Case was updated with new information
June 26, 2018 at 12:17 PM EDT: informed MSRC of mozilla CVE (CVE-2018-12368)
June 26, 2018 at 1:15 PM EDT: MSRC passed the mozilla CVE to the product team
July 3, 2018 at 9:52 PM EDT: Case was reassigned to another case handler
Jul 23, 2018 at 4:49 PM EDT: Let MSRC know .settingcontent-ms was being abused in the wild.
Jul 27, 2018 at 7:47 PM EDT: MSRC informed me they are shipping a fix ASAP
Jul 27, 2018 at 7:55 PM EDT: MSRC informed me of bounty qualification
Aug 6, 2018 at 3:39 PM EDT: Asked MSRC if CVE-2018-8414 was related to the case
Aug 6, 2018 at 4:23 PM EDT: MSRC confirmed CVE-2018-8414 was assigned to the case
Aug 14, 2018: Patch pushed out to the public
Sept 28, 2018 at 4:36 PM EDT: $15,000 bounty awarded

Before publishing this blog post, I asked MSRC to review it and offer any comments they may have. They asked that I include on official response statement from them, which you can find below:

Cheers,
Matt N.

CVE-2018-8212: Device Guard/CLM bypass using MSFT_ScriptResource

Device Guard and the enlightened scripting environments that come with it are a lethal combination for disrupting attacker activity. Device Guard will prevent unapproved code from executing while placing scripting languages such as PowerShell and the Windows Scripting Host in a locked down state. In order to operate in such an environment, researching bypasses can be immensely useful. Additionally, there are evasion advantages that can come with executing unsigned code via signed/approved scripts or programs.

When hunting for Constrained Language Mode (CLM) bypasses in the context of Device Guard, investigating Microsoft-signed PowerShell modules for calls that allow arbitrary, unsigned code to be executed is always a fruitful endeavor as most Microsoft PowerShell modules will be signed (i.e. implicitly approved per policy). To combat abusing signed PowerShell modules to circumvent CLM, Microsoft added a check to make sure a module can only execute exported functions if the module is loaded in CLM (CVE-2017-8715). This means that, while a script may be signed and allowed per policy, that script can only execute functions that are explicitly exported via Export-ModuleMember. This addition significantly reduces the attack surface for signed PowerShell modules as non-exported functions will be subject to CLM, the same as unsigned code.

While this addition reduces the attack surface, it doesn’t remove it entirely. While analyzing Microsoft-signed PowerShell module files for functions that allowed unsigned code-execution, “MSFT_ScriptResource.psm1” from the Desired State Configuration (DSC) module cropped up. This module is signed by Microsoft, and has a function called “Get-TargetResource” that takes a “GetScript” parameter:

Looking at this function, the code passed via -GetScript is added to a new scriptblock via [ScriptBlock]::Create(). After doing so, it passes the psboundparameters to the function “ScriptExecutionHelper”.

If we take a look at “ScriptExecutionHelper”, all it does is take the psboundparameters (which includes our newly created ScriptBlock) and execute it via the call operator (&):

Since all of this is happening within a Microsoft signed module, the module is permitted to run in FullLanguage mode (i.e. without any restrictions imposed upon it). To abuse this, all we need to do is pass our malicious PowerShell code to Get-TargetResource via the -GetScript parameter. But, isn’t the Export-ModuleMember mitigation from CVE-2017-8715 supposed to prevent function abuse? Looking at the exported functions in “MSFT_ScriptResource.psm1”, the abusable function “Get-TargetResource” is actually exported for us:

Excellent! To test this out, we can add some arbitrary C# code (that simply takes the Square Root of 4) to a PowerShell variable called $code:

After doing so, we just need to import the “MSFT_ScriptResource” PowerShell module and call “Get-TargetResource” with “Add-Type -TypeDefinition $code” as the -GetScript parameter. When this executes, the Microsoft signed PowerShell module will be loaded in FullLanguage mode (since it is signed and permitted via the Device Guard policy), and the code passed to the Get-TargetResource function will thus be executed in FullLanguage mode as well:

As you can see above, we are running in ConstrainedLanguage mode and getting the square root of 4 fails as those method calls are blocked. We then add our “malicious” code to the $code variable. All this code does is take the SquareRoot of 4, like we previously tried to do. Once that is done, the “MSFT_ScriptResource” module is imported and our “malicious” code is passed to “Get-TargetResource” via the -GetScript parameter. When that executes, the Add-Type call is executed and our “malicious” code is executed, thus circumventing CLM on Device Guard. It should be noted that enabling ScriptBlock logging will still capture the CLM bypass attempt.

This bug was fixed via CVE-2018-8212. If you are interested, Microsoft recently added bypasses like this to the WDAC Application Security bounty program: https://www.microsoft.com/en-us/msrc/windows-security-servicing-criteria

 

Cheers,

Matt N.

The Tale of SettingContent-ms Files

As an attacker, initial access can prove to be quite the challenge against a hardened target. When selecting a payload for initial access, an attacker has to select a file format that allows arbitrary code execution or shell command execution with minimal user interaction. These file formats can be sparse, which is why attackers have relied on file types like .HTAs, Office macros, .VBS, .JS, etc. There are obviously a finite number of built-in file extensions on Windows, and as defenses improve, the number of effective payloads continues to shrink.

Additionally, an attacker has to get that file to the end user in a way that will result in execution. Again, options for this can be limited as directly linking to payloads or attaching them to emails tend to get blocked by antivirus or browser protections. This is why attackers have resorted to Object Linking and Embedding (OLE), ZIP files, etc. To combat the file delivery vector, Office 2016 introduced blocking all of the “dangerous” file formats from being embedded via OLE by default. This reduces the effectiveness of one of the most relied upon payload delivery methods. When trying to activate a blocked file extension, Office will throw an error and prevent execution:

In addition to OLE blocking, Microsoft also introduced Attack Surface Reduction (ASR) rules  into Windows 10 (which requires Windows Defender AV as a dependency). The purpose of these rules are to reduce the functionality that an attacker can abuse or exploit to obtain code-execution on the system. One of the most touted and effective ASR rules is “Block Office applications from creating child processes”. This rule will block any attempt to spawn a process as a child of an Office application:

When you combine OLE blocking and ASR together, the options to execute code on an endpoint from the internet become a little more limited. Most useful file types can’t be delivered via OLE per the new block in Office 2016, and ASR’s child process creation rule prevents any instances of a child process spawning under an Office application.

How could we circumvent these controls? I first decided to tackle the file format problem. I spent a lot of hours going through the registry looking for new file formats that might allow execution. Most of these formats can be found in the root of the HKCR:\ registry hive. The process involved pulling all of the registered file formats out and then looking at them to see if the format itself allowed for anything interesting.

After countless hours reading file specifications, I stumbled across the “.SettingContent-ms” file type. This format was introduced in Windows 10 and allows a user to create “shortcuts” to various Windows 10 setting pages. These files are simply XML and contain paths to various Windows 10 settings binaries. An example .SettingContent-ms file will look like this:

All this file does is open the Control Panel for the user. The interesting aspect of this file is the <DeepLink> element in the schema. This element takes any binary with parameters and executes it. What happens if we simply substitute “control.exe” to something like “cmd.exe /c calc.exe”?

Then if we double-click the file:

What is interesting is that when double-clicking the file, there is no “open” prompt. Windows goes straight to executing the command.

Great! So we have a file format that allows shell command execution via a file open. This solves the “what file format to use” problem of initial access. Now, how can we deliver it? My next thought was to see what happens if this file comes straight from the internet via a link.

Yikes!! When this file comes straight from the internet, it executes as soon as the user clicks “open”. Looking at the streams of the file, you will notice that it does indeed grab Mark-Of-The-Web:

When looking up the ZoneIds online, “3” equals “URLZONE_INTERNET”. For one reason or another, the file still executes without any notification or warning to the user.

So, we now have a file type that allows arbitrary shell command execution and displays zero warnings or dialogues to the user. When trying to get initial access, going across a target’s perimeter with an unusual file type can be risky. Ideally, this file would be placed in a container of a more common file type, such as an Office document.

As mentioned before, Office 2016 blocks a preset list of “known bad” file types when embedded with Object Linking and Embedding. The SettingContent-ms file format, however, is not included in that list:

At this point, we can evade the Office 2016 OLE file extension block by embedding a malicious .SettingContent-ms file via OLE:

When a document comes from the internet with a .SettingContent-ms file embedded in it, the only thing the user sees is the “Open Package Contents” prompt. Clicking “Open” will result in execution. If an environment doesn’t have any Attack Surface Reduction rules enabled, this is all an attacker needs to execute code on the endpoint. I was curious, so I poked at how this holds up with ASR’s child process creation rules enabled. It is also worth noting that at of the time of this post, ASR rules do not appear to work on Office if Office was installed from the Windows Store.

Enabling these rules is pretty simple, and can be done with one PowerShell command: “Set-MpPreference -AttackSurfaceReductionRules_Ids <rule ID> -AttackSurfaceReductionRules_Actions Enabled”

The <rule ID> parameter is the GUID of the rule you want to enabled. You can find the GUID for each ASR rule documented here. For this test, I want to enable the Child Process Creation rule, which is GUID D4F940AB-401B-4EFC-AADC-AD5F3C50688A.

After enabling the rule, the attack no longer works:

Since the rule is designed to block child processes from being spawned from an Office application, our payload executed, but the rule blocked the command. This got me thinking into how ASR achieves that without breaking some functionality. I first started to just test random binaries in random paths to see if ASR was blocking based on the image path. This was quite time consuming and didn’t get me very far.

I ended up taking a step back and thinking about what parts of Office have to work. After running ProcMon and looking at Process Explorer for a little bit while clicking around in Word, I noticed that there were still child processes being spawned by Word.

This makes sense, as Office needs to use features that rely on other programs. I thought that maybe the ASR rule blocks child processes based on image path, but images in the Office path are allowed to be spawned when features are activated.

To test this theory, I changed my .SettingContent-ms file to the path of “Excel.exe”:

The next step is to embed this new file into a Word document and see if ASR blocks “Excel.exe” from being spawned.

Interestingly enough, ASR allows Excel to start. So the child process creation ASR rule appears to be making decisions based on whitelisted paths.

This sent me down a long, long path of trying to find a binary I could use that existed in the path “C:\Program Files\Microsoft Office”. After going through a few of them and passing “C:\Windows\System32\cmd.exe” to them as a parameter on the command line, one of them kicked:

Perfect! We are able to abuse “AppVLP” to execute shell commands. Normally, this binary is used for Application Virtualization, but we can use it as an abuse binary to circumvent the ASR file path rule. To test this full chain, I updated my .SettingContent-ms file to look like this:

Now, the file just needs embedded into an Office document and executed:

As you can see, with Office 2016’s OLE block rule and ASR’s Child Process Creation rule enabled, .SettingContent-ms files combined with “AppVLP.exe” in the Office folder allow us to circumvent these controls and execute arbitrary commands.

While Office documents are often marked with MOTW and are opened in the Protected View Sandbox, there are file formats that allow OLE and aren’t triggered by the Protected View sandbox. You can find more on that here.

Wrap Up:

After looking into ASR and the new file formats in Windows 10, I realized that it is important to try and audit new binaries and file types that get added in each release of Windows. In this case, the .SettingContent-ms extension can allow an attacker to run arbitrary commands on the latest version of Windows while evading ASR and Office 2016 OLE blocks. Additionally, the file type appears to give execution (even from the internet) immediately after it’s opened, despite having the MOTW applied.

Defenses:

Great, so what can you do about it? Ultimately, a .SettingContent-ms file should not be executing anywhere outside of the “C:\Windows\ImmersiveControlPanel” path. Additionally, since the file format only allows for executing shell commands, anything being run through that file is subject to command line logging.

It is also a good idea to always monitor child process creations from Office applications. There are a few applications that should be spawning under the Office applications, so monitoring for outliers can be useful. One tool that can accomplish this is Sysmon.

Another option is to neuter the file format by killing its handler. I have NOT tested this extensively and can offer no guarantee that something within Windows won’t break by doing this. For those who want to play around with the implications of killing the handler to the .SettingContent-ms file format, you can set  the “DelegateExecute” key in “HKLM:\SettingContent\Shell\Open\Command” to be empty. Again, this might break functionality on the OS, so proceed with caution.

You can find a PoC SettingContent-ms file here: https://gist.github.com/enigma0x3/b948b81717fd6b72e0a4baca033e07f8

Reporting timeline:

2/16/2018: Report sent MSRC

2/16/2018: MSRC acknowledged the report, case number assigned

3/2/2018: MSRC confirmed that they could reproduce the issue

4/24/2018: Requested status update

4/25/2018: MSRC informed me of a case handler change. An update was requested from the engineering team and would be relayed to me ASAP

6/1/2018: Requested another update from MSRC

6/4/2018: MSRC responded with a note that the severity of the issue is below the bar for servicing and that the case will be closed.

06/11/2018: Report published

 

Cheers!
Matt N.

Reviving DDE: Using OneNote and Excel for Code Execution

TL;DR: You can achieve DDE execution with Excel SpreadSheets embedded within OneNote. This bypasses the original Excel mitigation ruleset (Microsoft has released a patch to properly mitigate this) as well as the Protected View sandbox 🙂

Dynamic Data Exchange (DDE) has been a hot topic as of late. For those unfamiliar with DDE, it is designed to transfer data between two applications. In 2014, Contextis put out a nice blog post on using DDE in Microsoft Excel for code execution by utilizing the “=DDE()” formula.

Then, on October 9th 2017, SensePost released a really great blog post on abusing the DDEAUTO field code in Microsoft Word to get code execution. Shortly after, various malware families adopted the technique and it was quickly seen in the wild.

After seeing a spike in malicious use, Will Dormann (@wdormann) of US-CERT published some registry changes that would widely mitigate most DDE threats. These changes disabled DDE and prevented links from automatically updating automatically for Word and Excel. Will added a OneNote block after sharing the details outlined below with him privately. Unfortunately the only fix was to completely kill embedded files, which is less than ideal. You can find these registry changes here: https://gist.github.com/wdormann/732bb88d9b5dd5a66c9f1e1498f31a1b

This guidance was really helpful to those dealing with actors using DDE techniques more and more. Then, on November 8th 2017, Microsoft published an official post that outlines mitigating the DDE threat for those who don’t use the protocol in their environment, which was released under Advisory ADV170021 with additional documentation here. These mitigations were largely just for Word and it involved preventing any execution entirely as opposed to stopping automatic link updating. In addition to this post, Microsoft also stated that Protected View will prevent automatic DDE execution and that users should open untrusted documents with caution.

After seeing these new DDE mitigation recommendations, I became curious how these were handled when executed from within a different Office application, such as Publisher or OneNote. At the time, Will Dormann’s gist was the only source for mitigation options in other Office apps (such as Excel) as Microsoft only released official guidance for Word.

So, why OneNote? Well, it allows a user to embed Excel spreadsheets into a note document and then save it. This provides the end user the ability to reference or use Excel features directly within OneNote. As you may know, you can abuse DDE in Excel to get code execution! Ideally, Will’s Excel registry changes to stop DDE attacks would apply to any Excel sheets embedded in OneNote. Unfortunately, this wasn’t the case.

When implementing Will’s Excel registry change (specifically “DDEAllowed” set to DWORD 0), you will see something like this when opening a spreadsheet that contains a DDE formula:

So, the Excel DDE block is working as expected. Now, let’s look at OneNote. In order to utilize the Excel functionality in OneNote, you can go to “SpreadSheet” under the “Files” tab and either import an existing Excel Spreadsheet or create a new one.

So, OneNote allows us to import an existing spreadsheet. What happens if we import a DDE-laced spreadsheet? First, we need to create it. Ryan Hanson (@ryhanson) put out a tweet showing that you can manipulate the warning box during DDE execution and change the binary name. This can be helpful as you can change it to something like “MSEXCEL.exe” instead of displaying “cmd.exe” or “powershell.exe”.

           Source: https://twitter.com/ryHanson/status/918598525792935936

 

After adding that formula to an Excel spreadsheet and saving it, we can now test it to ensure it displays properly. To do so, I have removed the Excel DDE mitigation registry changes.

Great, so it works. Next, let’s test it with Will’s Excel registry changes applied:

Awesome, so these changes do indeed block the Excel DDE POC that we have just created. Now that we have our DDE spreadsheet ready and tested, we can import it into OneNote by going to “Insert->SpreadSheet->Existing Excel SpreadSheet”

OneNote will ask you to browse to the file you want to import, which will be the previously created DDE laced spreadsheet. Next, it will ask you if you want to attach the file or insert the spreadsheet. We will do “Insert SpreadSheet”

OneNote will then import the spreadsheet and during that process, it will attempt to execute your DDE command. To prevent that, simply click “No”

Finally, save the OneNote file. At this point, that OneNote file has a DDE laced Excel SpreadSheet directly embedded in it. Now, let’s see what happens when the Excel SpreadSheet is accessed from within the OneNote file with the Excel DDE mitigation registry changes in place:

Clicking “Yes” results in the command being executed:

So, despite blocking DDE in Excel via “DDEAllowed”, the functionality is still there when accessed through OneNote. After chatting with Will Dormann, the only working mitigation is to set “DisableEmbeddedFiles” to 1. This obviously kills all file embedding functionality as a side-effect, which isn’t great for usability.

As mentioned above, one of Microsoft’s statements notes that Protected View will prevent the DDE vectors when originating from an untrusted source (such as the internet). This is the case for most Office applications as any content originating from an untrusted source is opened in a sandbox first. OneNote, however, is not enrolled in Protected View and will not trigger it when pulled from the internet. If a user has OneNote installed, an attacker can embed a weaponized Excel spreadsheet into a OneNote file and send it to a victim via a weblink or an email attachment. When the user receives the OneNote file and opens the embedded spreadsheet, it will not open in Protected View and they will simply be presented with the DDE prompt (which you can tamper with as demonstrated above):

*It should be noted that the Protected View aspect was reported to MSRC on April 20th, 2017 and it was deemed not a security issue.

So, what can you do? Well, at the time, the only mitigation was to completely kill embedding in OneNote. This was reported to Microsoft on October 10th of 2017 and on January 9th, 2018 they pushed out an update to all Office versions going back to 2007. The Excel update was added to the already existing Advisory ADV170021, in which that advisory now details how to implement mitigations for both Excel and Word (since it was previously only Word that was available). Additional documentation can be found here. This update created a value you can add under Microsoft Excel’s security options in the registry. By setting “DisableDDEServerLaunch” to DWORD 1, DDE will effectively be neutered for Excel. This is important because OneNote itself wasn’t entirely interesting. It was the embedded Excel functionality that made this attack work. By adding mitigation options for Excel, users can protect themselves from this attack.

Additionally, you can employ Attack Surface Reduction (ASR) rules in Windows 10 1709 to prevent not only DDE attacks, but other attacks where an Office program is spawning a child process. You can read more on ASR here.

-Matt N.