Админ-экспресс
Исследуем обход UAC на примере Elevation Moniker
Моникеры
Первым делом отметим, что моникеры — один из кирпичиков подсистемы COM (Component Object Model). С помощью COM разработчики могут создавать такие программные компоненты, которые способны взаимодействовать друг с другом в самых разных плоскостях. Например, с помощью COM можно из кода Visual Basic дергать сборку .NET и наоборот! А что ты думаешь по поводу связки программ на Go и C++?

Здесь есть сервер — им считается тот, кто предоставляет функции COM, — и клиент — тот, кто обращается к функциям сервера. Возможности сервера COM прописываются в классе COM. При обращении к серверу COM создается экземпляр класса COM, и клиент получает этот экземпляр. После чего может взаимодействовать с сервером.
Конечно, уже опытные смешарики скажут, что в действительности идет работа с интерфейсами: сервер зачастую отдает маршализированный интерфейс, через который будет происходить взаимодействие, а еще между клиентом и сервером висит прокси, стаб, апартаменты, RPC... Впрочем, такие подробности не нужны. Пока достаточно понимать, что есть сервер и клиент COM. Их взаимодействие происходит через экземпляр класса COM, в котором определены некие возможности.
Изучить базу про COM можно в статье Inside COM+: Base Services или в официальной документации.
Для создания экземпляра класса COM (процесс инстанцирования) требуется специальное значение CLSID (Class Identifier). Оно однозначно идентифицирует класс COM, к которому хочет обратиться клиент. Помимо того, само создание объекта COM реализовывало порождающий паттерн проектирования, называемый фабрикой.
Все с этим нормально жили и не тужили, однако в один момент захотели избавиться от обязательного использования CLSID и фабрики. Хотелось сделать всё так, чтобы объект находился как‑нибудь сам, автоматически, по минимальному общему входному набору данных.
И тогда на помощь пришли моникеры. Они позволяют идентифицировать конкретный объект COM (или даже полноценно его реализовать) по простой строке (moniker
). Эдакая строковая презентация объекта СОМ.
Процесс инстанцирования объекта COM из моникера называется активацией или же связыванием моникера (Binding). Моникер обладает достаточной информацией, чтобы найти и активировать нужный объект СОМ. Эту информацию моникер получает один раз, во время своего создания — в виде строки, формат которой он понимает. Позже менять эту информацию уже нельзя. Поэтому моникер, будучи однажды созданным, всегда находит и активирует один и тот же объект.
Моникерами считаются все объекты, которые реализуют интерфейс IMoniker
. Возврат клиенту объекта COM происходит в функции IMoniker::
.
HRESULT BindToObject( [in] IBindCtx *pbc, [in] IMoniker *pmkToLeft, [in] REFIID riidResult, [out] void **ppvResult);
-
pbc
— контекст связывания. Это специальный объект СОМ, реализующий интерфейсIBindCtx
. С помощью этого объекта указывается, каким образом должен осуществляться и как проходит в этот момент процесс связывания (активации объекта СОМ по моникеру); -
pmkToLeft
— целевой моникер, по которому создавать объект COM; -
riidResult
— внутри каждого класса COM может быть реализовано несколько интерфейсов. Здесь указывается IID (Interface Identifier) целевого интерфейса, который хотим получить; -
ppvResult
— переменная, которая получит инициализированный указатель на интерфейс, через который можно взаимодействовать с классом COM.
Подвиды моникеров
Моникеры делятся на два подвида:
- системные (они же встроенные, они же стандартные) — набор моникеров, предоставляемых Microsoft;
- пользовательские — созданные сторонними разработчиками.
Даже системных моникеров очень много. Мы остановимся на одном конкретном: Elevation Moniker. Этот моникер позволяет активировать объект COM в контексте повышенных привилегий. С помощью этого моникера хакер способен из процесса со средним уровнем целостности получить доступ к объекту COM, расположенному в процессе с высоким уровнем целостности, а затем, злоупотребляя возможностями этого объекта, выбраться в привилегированный процесс.
Помимо этого, существуют Session Moniker, с их помощью клиент может инстанцировать объект COM в чужой сессии. На этом был основан эксплоит COM Session Moniker EOP. Впрочем, о нем поговорим в следующей статье.
Для успешного байпаса UAC нужно соблюдение нескольких условий:
- наличие интересных возможностей в классе COM: выполнение команд, удаление файлов, добавление пользователей — в общем, все, что требует токена с высоким уровнем целостности;
- правильная регистрация класса COM в реестре Windows.
Начнем со второго пункта.
Регистрация Elevation Moniker
Класс COM (его поле RunAs) должен иметь значение The
(это значение дефолтное, применяется в том числе, если поле RunAs
пустое) либо Activate
. Если значение другое, то при попытке забиндиться к моникеру получим ошибку CO_E_RUNAS_VALUE_MUST_BE_AAA
.
Предлагаю рассматривать на примере класса COM, который соответствует всем требованиям. Вот его CLSID:
{3E5FC7F9-9A51-4367-9063-A120244FBEC7}
Значение RunAs
указывается для AppID, поэтому сначала вычленяем AppID для CLSID.

Вот как это выглядит в коде:
DWORD GetAppIdFromClsid(IN std::wstring clsid, OUT std::wstring& appId){ HKEY hClsidKey; std::wstring clsidSubKey = L"CLSID\" + clsid; if (RegOpenKeyEx(HKEY_CLASSES_ROOT, clsidSubKey.c_str(), 0, KEY_READ, &hClsidKey) != ERROR_SUCCESS) { return ERROR_OPEN_FAILED; } TCHAR valueBuffer[256]; DWORD valueBufferSize = sizeof(valueBuffer); if (RegQueryValueEx(hClsidKey, L"AppID", NULL, NULL, (LPBYTE)valueBuffer, &valueBufferSize) == ERROR_SUCCESS) { appId = valueBuffer; } RegCloseKey(hClsidKey); return ERROR_SUCCESS;}
После чего проверяем ключ AppID
.

Следующим шагом нужно убедиться в наличии ключа LocalizedString
, в котором будет путь к целевой DLL с функциями COM. Если такой записи нет, активация возвращает ошибку CO_E_MISSING_DISPLAYNAME
.

В подпапочке Elevation
лежит ключ IconReference
, указывающий на значок приложения. Здесь -101
— идентификатор ресурса, а Enabled
— разрешить ли использовать этот COM-класс с Elevation Moniker.

Если какая‑то запись отсутствует, то активация возвращает ошибку CO_E_ELEVATION_DISABLED
. Обрати внимание, что эти записи должны существовать в ветке HKEY_LOCAL_MACHINE
, а не в ветке HKEY_CURRENT_USER
или HKEY_USERS
. Это не позволяет обычным пользователям регистрировать COM-классы, которые можно использовать с Elevation Moniker.
Для автоматизации поиска классов COM, подходящих для использования с Elevation Moniker, я создал программу WinCOMFuzzer.

После чего получим список CLSID, которые можно отдать в OleViewDotNet и проверить последнее значение: Auto
. Если оно установлено в True
, то при повышении уровня целостности через Elevation Moniker не появится окошко UAC.

Если эти требования соблюдены, то можно себе поаплодировать — мы нашли класс COM, подходящий под Elevation Moniker.
Использование Elevation Moniker
Elevation Moniker — это стандартный моникер COM. Он отправляет запрос активации на сервер COM с указанным уровнем повышения. CLSID, который нужно активировать, надо прописать в строке моникера.
Elevation Moniker поддерживает следующие уровни повышения:
-
Administrator
— это поле может использоваться только встроенной учетной записью локального администратора (RID 500). Гарантированно повысится до High IL; -
Highest
— это поле используется всеми остальными учетками, в том числе членами групп локальных администраторов. Здесь никаких гарантий нет. Windows попытается выдать максимально возможный уровень целостности для данного аккаунта. Обычный аккаунт получит Medium IL, учетка из группы ЛА — High IL.
Синтаксис моникера следующий:
Elevation:Administrator!new:{guid}
Elevation:Highest!new:{guid}
Здесь guid
— CLSID класса COM, установленный согласно требованиям, описанным выше.
Отмечу, что здесь используется специальное слово new
, которое позволяет при привязке к моникеру получить экземпляр его класса. Внутри вызывается IClassFactory
с последующим вызовом IClassFactory::
.
Помимо этого, есть возможность раздобыть указатель на фабрику, чтобы получить инстанс вручную. В таком случае используется ключевое слово clsid
. Ты получишь объект класса, который реализует IClassFactory
. Затем вызывающая сторона дергает CreateInstance(
, чтобы получить экземпляр объекта. Выглядит это так:
Elevation:Administrator!clsid:{guid}
В следующем примере кода показано, как использовать Elevation Moniker. Не забудь до этого в потоке вызвать CoInitialize(
!
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, __out void ** ppv){ BIND_OPTS3 bo; WCHAR wszCLSID[50]; WCHAR wszMonikerName[300]; StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0])); HRESULT hr = StringCchPrintf(wszMonikerName, sizeof(wszMonikerName)/sizeof(wszMonikerName[0]), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED(hr)) return hr; memset(&bo, 0, sizeof(bo)); bo.cbStruct = sizeof(bo); bo.hwnd = hwnd; bo.dwClassContext = CLSCTX_LOCAL_SERVER; return CoGetObject(wszMonikerName, &bo, riid, ppv);}
В качестве hwnd
можно передавать NULL
, COM в таком случае автоматически вызовет GetActiveWindow(
, чтобы найти хендл окна, связанного с текущим потоком.
Примеры COM-объектов
ICMLuaUtil
Это один из самых популярных эксплоитов для обхода UAC с помощью Elevation Moniker, PoC ты найдешь на GitHub. В коде можно явно наблюдать CLSID, который мы исследовали ранее, а также Elevation Moniker, еще не инициализированный.

В свою очередь, этот класс COM в интерфейсе ICMLuaUtil
реализует метод ShellExec(). Из его названия понятно, что он связан с исполнением команд. Запуск эксплоита с последующим инстанцированием класса COM через Elevation Moniker и вызов ShellExec(
позволяет исполнить команды в привилегированном контексте.

Кстати, этот метод использовал шифровальщик LockBit для обхода UAC.
IFileOperation
Предлагаю обратиться к утечкам WikiLeaks. Из них нам стало известно о COM-объекте, который позволяет выполнять файловые операции, не вызывая окошка UAC. В частности, PoC предоставляет возможность удаления произвольных файлов.
HRESULT CoCreateInstanceAsAdmin(HWND hwnd, REFCLSID rclsid, REFIID riid, void **ppv){ BIND_OPTS3 bo; WCHAR wszCLSID[50]; WCHAR wszMon[300]; StringFromGUID2(rclsid, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0])); HRESULT hr = StringCchPrintfW(wszMon, sizeof(wszMon)/sizeof(wszMon[0]), L"Elevation:Administrator!new:%s", wszCLSID); if (FAILED(hr)) return hr; memset(&bo, 0, sizeof(bo)); bo.cbStruct = sizeof(bo); bo.hwnd = hwnd; bo.dwClassContext = CLSCTX_LOCAL_SERVER; return CoGetObject(wszMon, &bo, riid, ppv);}void ElevatedDelete(){ MessageBox(NULL, "DELETING", "TESTING", MB_OK); // This is only availabe on Vista and higher HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); IFileOperation *pfo; hr = CoCreateInstanceAsAdmin(NULL, CLSID_FileOperation, IID_PPV_ARGS(&pfo)); pfo->SetOperationFlags(FOF_NO_UI); IShellItem *item = NULL; hr = SHCreateItemFromParsingName(L"C:\\WINDOWS\\TEST.DLL", NULL, IID_PPV_ARGS(&item)); pfo->DeleteItem(item, NULL); pfo->PerformOperations(); item->Release(); pfo->Release(); CoUninitialize();}
Однако интерфейс предоставляет методы и для копирования файлов, и для создания.
Конечно, это не такой простой пример, ведь мы не можем напрямую исполнять команды, у нас есть лишь возможности, связанные с файловыми операциями. Впрочем, их можно совместить с SymLinks Abuse, чтобы добиться привилегированного шелла.
Выводы
Иногда сами разработчики закладывают в свои программы функции, похожие на полноценный бэкдор. Нужно искать лазейки и обходы, и ты их, возможно, найдешь. Если хочешь попрактиковаться в обходе UAC с помощью Elevation Moniker, то обрати внимание на интерфейс IColoDataProxy
, на него еще нет поков!