这是对 @orange_8361 发现的、由 CsEnox 整理的 Event Viewer 反序列化 UAC 绕过的 Beacon Object File 实现。

已在 x64 Win10/Win11 上测试
该 UAC 绕过会执行以下操作,需考虑其对 OPSEC 的影响:
-1. 将二进制文件写入 %LOCALAPPDATA%\Microsoft\Event Viewer\RecentViews
-2. 调用 ShellExecute() 启动 mmc.exe / Event Viewer
--A. Event Viewer 会打开图形界面!
--B. Event Viewer 会调用 cmd.exe
---a. cmd.exe 会调用 Taskkill 以关闭 mmc.exe
---b. cmd.exe 会在(有时保持打开的)控制台窗口中打印一条错误信息,以欺骗用户认为一切正常
---c. cmd.exe 会执行用户提供的其他命令
-3. 二进制文件会从 %LOCALAPPDATA%\Microsoft\Event Viewer\RecentViews 中被删除
参照原始演示和 CsEnox 的 POC,使用 ysoserial 创建了初始载荷:

ysoserial.exe -o raw -f BinaryFormatter -g TextFormattingRunProperties -c "taskkill /f /im mmc.exe >nul && echo Windows has Recovered from an Unexpected Error. You may Close this Window. &&" > cmd.bin
此 UAC 绕过通过调用 mmc.exe 实现,mmc.exe 会打开窗口并需要关闭。遗憾的是,我尚未找到在不打开 GUI 的情况下启动 mmc.exe 的方法;我曾尝试过 CreateProcess() 和 CREATE_NO_WINDOW 标志,但 CreateProcess() 与 UAC 配合不佳。StackOverflow 上关于此问题的帖子指引我使用 ShellExecuteA() API,它确实提供 SW_HIDE 标志(以及其他标志),但效果不佳。mmc.exe 闪屏似乎无法避免。
Ysoserial 会调用 cmd.exe /c,因此我们利用它来调用 taskkill 以关闭 mmc.exe。使用 && 将命令串联起来,以便以管理员身份执行所需操作。测试中发现,某些情况下该操作会保持由 mmc.exe 生成的 cmd.exe 提示符保持打开状态;例如调用本地注入器(普通)载荷(如默认的 Cobalt Strike 可执行载荷)时就是如此。为解决此问题,我将 taskkill 命令(之前会打印到保持打开的 cmd.exe 中)的输出重定向到 /dev/null,并在屏幕上回显一条关于已处理错误的消息,以试图让不知情的用户不对这种可疑行为产生警觉。
派生进程、注入后立即退出的载荷不会保持 cmd.exe 窗口打开,因此无需显示消息。同样,执行后立即退出的命令(如 net user Administrator newpassword)也不会保持窗口打开。
您可以通过此 BOF 执行 PowerShell 编码命令:



taskkill 命令和用户消息已嵌入 ysoserial 命令中。如果您想改变此行为,则需要运行自己的 ysoserial 命令,并按照下方代码创建演示进行操作。
使用自定义工具,将二进制文件读取并以十六进制形式打印:

将其复制到记事本中,在十六进制中搜索 '&' 可定位到目标字节(注意存在多组,我们需要最后一组):

载荷在此字节之后被拆分为两部分:

计算每部分的长度并记录下来以备后用。
这里开始变得复杂。
老实说,我对反序列化或 ysoserial 知之甚少,但我发现载荷中存在一组字节,代表了载荷中“重要”部分的长度(包括用户自定义命令)。这可以从以下并排显示的两种不同载荷的十六进制转储中看出,两种载荷分别包含要执行的不同命令:

出于无法解释的原因,这些字节以 base-128 表示载荷长度:0x05 乘以 128 得到 640;再加上另一个字节(0xb0 或 0xd4)的值减去 128;我发现当第二个字节(0x05)翻转为 0x06 时,第一个字节从 0x80 重新开始,而不是预期的 0x00。
本工具中使用的载荷如下所示:

将所有值转换为十进制,计算如下:
(6 × 128) + (180 - 128) = 820
文件大小为 1042;测试中发现(至少对于此 ysoserial gadget),文件大小与这组字节所表示的长度之间始终相差 222 字节。这 222 字节似乎就是载荷的“头部”。
将两部分载荷复制到 main.c 中,原始长度 820 也被设置为一个变量。计算用户命令的长度,将该值加到原始 820 上(再加上一个额外字节,用于必须插入的空格),从而计算出需要在这组字节中表示的最终值。第二个字节通过 LenBytes 除以 128 得到,第一个字节通过 LenBytes 的余数加上 128 得到(以适应其从 0x80 开始的情况)。

设置好这些字节后,将它们复制到 Before 缓冲区中的适当位置,以便根据载荷内容将长度设置为正确值。最终缓冲区组装如下:

然后将最终载荷写入磁盘,并调用 mmc.exe 来执行它。之后清理载荷。
x86_64-w64-mingw32-gcc -o EventViewerUAC.x64.o -Os -c main.c -DBOF
Ysoserial 提供了 --rawcmd 选项,可以调用不同于 cmd.exe 的可执行文件,但无法向其传递任何参数。这为直接调用载荷而不调用 cmd.exe 从而避免控制台窗口保持打开提供了可能;但这样一来就没有东西来关闭 mmc.exe 了。可以编写载荷,使其判断父进程是否为 mmc.exe,如果是则杀死 mmc.exe 以清理此 UAC 绕过。这完全可行,但我没有费心去实现,因为现有的 UAC 绕过有很多,而且并非每个人都会创建这样的自定义载荷。我可能会将其集成到个人运行器中。