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

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.