Part 2 Sideloading - By default, everything is possible

This is part two of our two-part blog post, describing our investigation into the process that attackers use when sideloading malicious DLLs into .NET executables. 

Now that we know how we can bypass strong-name signature verification, and we want to side-load a DLL into a Microsoft-signed .NET executable, what would be the easiest way to do this ? The general process one might follow is as follows:

  1. Pick a Microsoft-signed executable and load it into DNSpy.

  2. Set a breakpoint in the first statement of the Main method.

  3. Run the executable in DNSpy until it hits the breakpoint.

  4. Step through with the debugger until you hit a method in a DLL dependency. Take a note of the method’s name. At this point, you can stop debugging and just edit the method in DNSpy to add your malicious code. However, it’s possible (probable?) that DNSpy cannot properly re-compile the method, depending on whether advanced language features have been used in the DLL’s implementation. If that’s the case, the sure fire method is to edit the IL code directly as per the following steps.

Click the “Play” button to see steps 1 to 4 in action:

The next steps are as follows:

  1. Disassemble the dependent DLL using ildasm.exe and save as a new file.

  2. Search through the CIL output for the method name you encountered in step 4.

  3. Add your malicious code as CIL (you don’t have to hand-code this – just create another class in say C# with your malicious code, compile it and then disassemble to get the required CIL instructions for copy/pasting).

  4. Re-assemble using ilasm.exe into a new modified dependent DLL.

At this point you have a modified dependent DLL, but the re-assembly process does not re-add the original strong-name signature. To rectify this:

  1. Load the modified DLL into DNSpy

  2. Modify the COR header strongNameSignedAttribute.

  3. Save the assembly and confirm the public key token is correct with ‘sn.exe -T’

Your modified DLL will now be correctly loaded by the signed executable in a strong-name signature bypass scenario. Let’s go through this process with a random executable from the Visual Studio distribution: T4VSHostProcess.exe. This Microsoft-signed .NET executable does….. actually I have no idea what it does!  But fortunately, for the process of loading “malware” it doesn’t matter. As shown in the video above, you can see:

  • The assembly Microsoft.VisualStudio.TextTemplating.VSHost.dll contains the class, Microsoft.VisualStudio.TextTemplating.VSHost.TransformationRunFactory

  • The first method in that class encountered in a dependency when the executable is run is RegisterIpcChannel(string portName)
 

We can now disassemble the DLL using:

 ildasm.exe Microsoft.VisualStudio.TextTemplating.VSHost.dll /out=myfile.il

Then we search for method signature RegisterIpcChannel(string portName) and add our ‘malicious code’… which in this case just ‘System.Console.WriteLine(“HelloWorld”)’… not too evil!

We then re-assemble to a DLL with this command:

    ilasm.exe /DLL /OUTPUT=Microsoft.VisualStudio.TextTemplating.VSHost.dll .\myfile.il

If you like, load it up into DNSpy and decompile to confirm your malicious code is in there:

At this stage, if you attempt to run the executable with the modified DLL in the same directory, you’ll get an error message “strong-name validation failed”, as shown here:

What happened ? If you use the sn.exe tool to check the strong-name properties of the modified DLL you see the problem:

While the DLL has the correct public-key token (‘sn -T’), the runtime thinks the DLL is delay-signed (and hence the strong-name signature bypass conditions are not applicable). 

To fix this, we just need to check the ‘StrongNameSigned’ attribute in the COR header of the DLL using DNSpy:

Once that’s done, save the assembly and rerun the T4VSHostProcess.exe:

As you can see, the Microsoft-signed executable T4VSHostProcess.exe successfully loads and executes our malicious DLL.

One important thing we haven’t mentioned up until now, is this: The strong-name bypass feature can be disabled (per application or globally):

  • by using the <bypassTrustedAppstrongNames enabled=”false” /> element in the executable’s configuration file [per-application],

    or,

  • by creating a DWORD registry entry named AllowStrongNameBypass with a value of 0 under the following registry keys to apply the setting globally:
    • `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework`
    • `HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\.NETFramework`
 

Note that if the global setting is enabled, the per-configuration file setting is ignored. This allows administrators to enforce strong-name signature verification on the machine which is probably a wise policy, given how easy it is to sideload .NET DLLs.

Hardening - nothing is possible….. but

Suppose that administrators have set the above registry key organisation-wide to enforce strong-name signature verification. After all, some compliance standards require disabling the strong-name verification bypass globally. What are the options for an attacker in such a situation? 

For an malware-based attack to be successful, the attacker in this scenario will need:

  • An Authenticode-signed (ideally by Microsoft) .NET executable,

    and,

  • A .NET DLL dependency without a strong-name signature (which can be modified by the attacker to load their malicious code).  Modifying the DLL dependency will break any Authenticode signature on the file (if there is one). However, validation of DLL Authenticode signatures as part of a security control appears to be quite rare. See for example Dynamic Code Security in WDAC, which is disabled by default.

Unfortunately (for the attacker), strong-name signatures are ‘viral’ which means that a strong-name signed .NET assembly can only reference (statically) another assembly if it is also strong-name signed. And unfortunately all the core .NET assemblies are strong-name signed which means all their dependencies must be strong-name signed and hence cannot be exploited in this scenario. 

However there are numerous other Authenticode-signed executables which are not strong-name signed, and which load dependencies which are also not strong-name signed.

The first example we found was git-credential-manager.exe which appears in the standard Visual Studio distribution. This executable is Authenticode-signed (by github.com), not strong-name signed and has a dependency gcmcore.dll which is also not strong-name signed. 

Rather than go through the disassemble/modify/reassemble process described above, we just note that the entry method in the git-credential-manager.exe file has a very simple implementation:

Furthermore, we only need to implement these three methods from the GitCredentialManager.UI.Dispatcher class in order to successfully sideload gcmcore.dll.

				
					public static Dispatcher MainThread { get; private set; }
public static void Initialize()
public void Run()
				
			

But what if we really, really want to use an Authenticode-signed (by Microsoft) .NET executable which fulfils this requirement? 

Well you’re in luck! By searching through the Microsoft downloads pages we did indeed find one, the High Performance Computing client package. This package contains a number of Authenticode-signed (by Microsoft) .NET executables including hpccred.exe, cluscfg.exe, clusrun.exe, job.exe, and more. And more good news: These executables do not have a strong-name signature, and they are dependent upon clitools.dll, which is also not strong-name signed. 

These executables all have a very simple implementation. For example, cluscfg.exe has an entrypoint which just calls CliTools.Program.doClusCfgCommand(string[]), in clitools.dll:

So the steps for sideloading the DLL are:

  • Create a (malicious) clitools.dll with a single method CliTools.Program.doClusCfgCommand(string[])

  • Place the DLL in the same directory as the Microsoft-signed cluscfg.exe to get your code executed. In real life, this is typically achieved through phishing  where the user is tricked into clicking on a link which HTML-smuggles a zip file containing benign PDFs, MS-signed binaries and hidden DLLs.

Hello-world malware!

Conclusions, and defence tips

As we have seen, sideloading .NET DLLs can be very easy when strong-name signatures are not validated, due to the simple algorithm used to search for dependencies. However, even when strong-name signature validation is enforced there are still plenty of Authenticode-signed .NET executables which are not strong-name signed which can be (ab)used for sideloading malicious code.

As a defence practitioner, what are your options for detecting sideloading of .NET DLLs? The obvious tell in all the examples given above is that the DLLs which were modified were originally (authenticode-)signed and modification breaks their signature. For example looking at the DLLs loaded by cluscfg.exe (from the last example) in Process Hacker, we see that clitools.dll is clearly unsigned:

In terms of practical detection and prevention, the best way forward is probably implementation of application whitelisting through a solution such as ‘Application Control for Business’ (formally WDAC). 

Using the example above, attempting to run cluscfg.exe with the modified clitools.dll (with broken Authenticode signature) on a system with App Control enforcing even the moderate ‘Allow Microsoft’ base policy, execution will be prevented:

So in closing: While implementing App Control may be a non-trivial task in an Enterprise environment, it certainly does make an otherwise quite-easy life more difficult for attackers.  Give us a yell if you want to know more!