
Реализация Beacon Object File для обхода UAC через десериализацию Event Viewer
Это реализация Beacon Object File для обхода UAC через десериализацию Event Viewer, обнаруженного @orange_8361, и PoC, собранного CsEnox.

Протестировано на x64 Win10/Win11
Данный обход UAC выполняет следующие действия, которые следует учитывать с точки зрения OPSEC:
-1. Записывает бинарный файл в %LOCALAPPDATA%\Microsoft\Event Viewer\RecentViews
-2. Вызывает ShellExecute() для запуска mmc.exe / Event Viewer
--A. Event Viewer откроется с GUI!
--B. Event Viewer вызовет cmd.exe
---a. Cmd.exe вызовет Taskkill для закрытия mmc.exe
---b. Cmd.exe выведет сообщение об ошибке в иногда открытое окно консоли, чтобы ввести пользователя в заблуждение, что всё в порядке
---c. Cmd.exe выполнит любые другие команды, переданные пользователем
-3. Бинарный файл удаляется из %LOCALAPPDATA%\Microsoft\Event Viewer\RecentViews
Следуя оригинальной демонстрации и PoC от CsEnox, 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; я изучал CreateProcess() и флаг CREATE_NO_WINDOW, однако CreateProcess() плохо работает с UAC. Посты на StackOverflow по этой теме направили меня к API ShellExecuteA(), который имеет флаг SW_HIDE (и другие), но это не дало результата. Похоже, что всплывание mmc.exe на экране неизбежно.
Ysoserial вызывает cmd.exe /c, и это используется для вызова taskkill для закрытия mmc.exe. && используется для объединения команд, чтобы можно было выполнить нужное действие с правами администратора. В ходе тестирования я обнаружил, что в некоторых случаях это действие удерживает окно cmd.exe, порождённое mmc.exe, открытым; это происходит при вызове полезных нагрузок типа Local Injector (normal), например, исполняемого файла по умолчанию Cobalt Strike. Для решения этой проблемы я перенаправил вывод команды taskkill (который ранее выводился в удерживаемое окно cmd.exe) в /dev/null, а на экран вывел сообщение об обработанной ошибке, чтобы попытаться скрыть подозрительное поведение от неопытного пользователя.
Полезные нагрузки, которые запускают процесс, внедряются в него и завершаются, не удерживают окно cmd.exe открытым, поэтому сообщение не нужно. Аналогично, команды, которые выполняются и завершаются (например, net user Administrator newpassword), не удерживают окно открытым.
Вы можете выполнять команды в кодировке PowerShell через этот BOF:



Команда taskkill и сообщение пользователю встроены в команду ysoserial. Если вы захотите изменить это поведение, вам нужно будет выполнить свою собственную команду ysoserial и следовать приведённой ниже демонстрации создания кода.
С помощью пользовательского инструмента бинарный файл был прочитан и выведен в шестнадцатеричном виде:

Копируя это в блокнот и выполняя поиск '&' в шестнадцатеричном виде, находим нужные байты (обратите внимание, есть несколько наборов, нам нужен последний):

Полезный груз разбивается на две части сразу после этих байт:

Длина каждой части вычисляется и запоминается для дальнейшего использования.
Здесь всё становится сложнее.
Честно говоря, я мало что знаю о десериализации или ysoserial, но я обнаружил, что в полезном грузе есть набор байт, представляющих длину «важной» части полезного груза, включая пользовательскую команду. Это видно на следующем скриншоте, где показан шестнадцатеричный дамп двух разных полезных грузов, созданных ysoserial, каждый из которых содержал свою команду для выполнения:

По причинам, которые я не могу объяснить, эти байты отражают длину полезного груза в base-128; 0x05 умножается на 128, что даёт 640; это добавляется к значению другого байта (0xb0 или 0xd4) минус 128; я обнаружил, что когда второй байт (0x05) переполняется (0x06), первый байт начинается с 0x80, а не с 0x00, как можно было ожидать.
Полезный груз, используемый в этом инструменте, выглядит так:

Преобразуя все значения в десятичные, математика выглядит так:
(6 x 128) + (180-128) = 820
Размер файла — 1042; в ходе тестирования я обнаружил, что (по крайней мере, с этим гаджетом от ysoserial) всегда есть разница в 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, и не все создают свои собственные полезные грузы подобным образом. Возможно, я внедрю это в свои личные раннеры.