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”.

Razer Synapse Service loading assemblies from C:\ProgramData

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):

DACL C:\ProgramData\Razer\Synapse3\Service\Bin

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\*”.

Razer Synapse Service searching for all DLL files

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.

Razer Synapse Service doing certificate comparison

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.

Razer Synapse Service checking for IPackage Interface

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.

Razer Synapse Service adding verified assemblies to a list

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

Razer Synapse Service calling RegisterPackages()

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.

Write a custom assembly that implements the IPackage interface from the SimpleInjector project Add malicious logic in the “RegisterServices()” method inside the IPackage interface Compile the assembly and use a tool such as SigPirate to clone the certificate chain from a valid Razer assembly Drop the final malicious assembly into “C:\ProgramData\Razer\Synapse3\Service\bin” 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:

PoC assembly with IPackage interface and a malicious RegisterServices() function

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 SigPirate to clone a Razer certificate

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

Verifying certificate clone

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.

Malicious assembly being loaded

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.

Added mitigation in the Razer Synapse service

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

WinVerifyTrust implementation

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:

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.

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.