Privileger. Управляем привилегиями в Windows

Содержание статьи

  • Добавляем привилегии аккаунту
  • Запускаем процесс с привилегией
  • Удаляем привилегию из аккаунта
  • Ищем объекты с привилегией
  • Смотрим привилегии объекта
  • Выводы

При­виле­гии в Windows игра­ют очень важ­ную роль, так как адми­нис­тра­тор име­ет воз­можность пре­дос­тавить спе­циаль­ные пра­ва поль­зовате­лям для решения их задач. В этой статье мы поз­накомим­ся с инс­тру­мен­том Privileger, который поз­воля­ет отыс­кивать в сис­теме учет­ные записи с опре­делен­ными при­виле­гиями и менять при­виле­гии у задан­ного акка­унта.

О работе с при­виле­гиями я писал в прош­лой статье, но мы рас­смот­рели лишь обрывки кода, которые, как ока­залось, слож­но было прев­ратить в пол­ноцен­ный про­ект. Поэто­му, ког­да на оче­ред­ном пен­тесте мне вновь пот­ребова­лось кодить все с нуля, я понял, что без готово­го инс­тру­мен­та не обой­тись. И сде­лал Privileger, который, к моему удив­лению, стал дос­таточ­но популяр­ным.

Ре­лиз инс­тру­мен­та

В сегод­няшней статье мы раз­берем прин­цип работы это­го инс­тру­мен­та, а так­же про­бежим­ся по всем пяти режимам, которые поз­воля­ют:

  • до­бавить при­виле­гии локаль­ному акка­унту с помощью вызова лишь одной фун­кции. Рань­ше это мож­но было сде­лать толь­ко через GPO и, если мне не изме­няет память, еще тре­бова­лась перезаг­рузка хос­та, что не очень удоб­но;
  • за­пус­тить про­цесс, добавив в его токен кон­крет­ную при­виле­гию;
  • уда­лить при­виле­гию у акка­унта. Опять же рань­ше это выпол­нялось с помощью GPO — сей­час исполь­зует­ся вызов фун­кции;
  • об­наружить акка­унт с нуж­ной при­виле­гией на какой‑нибудь машине;
  • об­наружить при­виле­гии у акка­унта на какой‑нибудь машине.

 

Добавляем привилегии аккаунту

Итак, все, как и в любом дру­гом про­екте на язы­ке С, начина­ется с фун­кции main(), в которую при­лета­ют все нуж­ные парамет­ры. Пос­ле чего для обес­печения кор­рек­тно­го вывода кирил­личес­ких (и иных) сим­волов дер­гаем setlocale(), выводим прек­расный ASCII-бан­нер и прис­тупа­ем к валида­ции вход­ных дан­ных.

int wmain(int argc, wchar_t* argv[]) { setlocale(LC_ALL, ""); ShowAwesomeBanner(); DWORD dwRC = 0, dwV = 0; if (argc != 4) { ShowHelp(); return 0; } switch (*argv[1]) { case '1': if (ValidateAccInfo(argv[2], argv[3]) == 0) { dwRC = InitMode1(argv[2], argv[3]); } break; case '2': if (ValidatePathInfo(argv[2], argv[3]) == 0) { dwRC = InitMode2(argv[2], argv[3]); } break; case '3': if (ValidateAccInfo(argv[2], argv[3]) == 0) { dwRC = InitMode3(argv[2], argv[3]); } break; case '4': if (ValidatePriv(argv[3])) { dwRC = InitMode4(argv[2], argv[3]); } else { std::wcout << L"[-] ValidatePriv() Failed" << std::endl; } break; case '5': std::wcout << L"[!] I'm not able to validate username and PC name. Make sure you enter the correct data." << std::endl; Sleep(500); std::wcout << L"[!] Starting" << std::endl; if (InitMode5(argv[2], argv[3]) != 0) { std::wcout << L"[-] InitMode 5 Error" << std::endl; } break; default: std::wcout << L"[-] No such mode" << std::endl; return 0; } return dwRC;}

Ес­ли тре­бует­ся исполь­зовать пер­вый режим работы, то есть добавить при­виле­гии акка­унту, поль­зователь дол­жен пре­дос­тавить сле­дующие вход­ные дан­ные:

  • 1 — режим работы;
  • имя поль­зовате­ля — кому навеши­вать при­виле­гию;
  • прог­рам­мное имя при­виле­гии — какую при­виле­гию добав­лять.

Прог­рам­мное имя при­виле­гии — это, собс­твен­но, само имя при­виле­гии. Есть еще так называ­емое дру­жес­твен­ное имя — это ее опи­сание. Нап­ример, прог­рам­мное имя SeDebugPrivilege, а дру­жес­твен­ное — Отладка программ.

Итак, обра­щаем­ся к Privileger.

.Privilegerx64.exe 1 Michael SeDebugPrivilege

Ус­пешное добав­ление при­виле­гии

Я пре­дус­мотрел про­вер­ку на валид­ность име­ни поль­зовате­ля, а так­же име­ни при­виле­гии, что­бы пре­дот­вра­тить оче­пят­ки. Про­вер­ка реали­зует­ся фун­кци­ей ValidateAccInfo(), которая при­нима­ет имя поль­зовате­ля, а так­же прог­рам­мное имя при­виле­гии.

DWORD ValidateAccInfo(wchar_t* cAccName, wchar_t* cPrivName) { // validating username DWORD sid_size = 0; PSID UserSid; LPTSTR wSidStr = NULL; DWORD domain_size = 0; SID_NAME_USE sid_use; DWORD wow = LookupAccountName(NULL, cAccName, NULL, &sid_size, NULL, &domain_size, &sid_use); DWORD dw = GetLastError(); if ((wow == 0) && ( (dw == 122) || (dw == 0))) { std::wcout << L"[+] User " << cAccName << L" found" << std::endl; // validating Privilege name if (!ValidatePriv(cPrivName)) { std::wcout << L"[-] ValidateAccInfo() failed" << std::endl; return 1; } else { std::wcout << L"[+] ValidateAccInfo() success" << std::endl; return 0; } } else { std::wcout << L"[-] Username may be incorrect. LookupAccountName() Err: " << dw << std::endl; return 1; } return 1;}

Ука­зание невер­ного име­ни при­виле­гии

Не­вер­ное имя поль­зовате­ля

Про­вер­ку име­ни поль­зовате­ля я сде­лал с помощью фун­кции LookupAccountName(). Сама по себе она слу­жит для получе­ния SID (security identifier) поль­зовате­ля по его име­ни, но нам нич­то не меша­ет исполь­зовать ее прос­то для про­вер­ки име­ни поль­зовате­ля, ведь если компь­ютер не обна­ружит поль­зовате­ля с таким име­нем, то и вызов фун­кции при­ведет к ошиб­ке.

Про­вер­ку прог­рам­мно­го име­ни при­виле­гии я так­же вынес в отдель­ную фун­кцию.

BOOL ValidatePriv(wchar_t* cPrivName) { LUID luid; if (!LookupPrivilegeValue(NULL, cPrivName, &luid)) { std::wcout << L"[-] Privilege " << cPrivName << L" may be incorrect" << std::endl; return FALSE; } else { std::wcout << L"[+] Privilege " << cPrivName << L" Found n[+] Validation Success" << std::endl; return TRUE; }}

Здесь алго­ритм схож: дер­гаем LookupPrivilegeValue(), если при­виле­гия есть — все ок, если нет — ошиб­ка.

Убе­див­шись, что пре­дос­тавлен­ные дан­ные вер­ны, инс­тру­мент вызыва­ет InitMode1(), которо­му так­же переда­ет имя поль­зовате­ля и имя при­виле­гии. Внут­ри этой фун­кции мы получа­ем хендл на LSA текуще­го компь­юте­ра (так как работа­ем с при­виле­гиями локаль­ного акка­унта) вызовом фун­кции GetPolicy().

DWORD InitMode1(wchar_t* cAccName, wchar_t* cPrivName) { std::wcout << L"[+] Initializing mode 1 n [+] Target Account: " << cAccName << "n [+] Privilege: " << cPrivName << std::endl; LSA_HANDLE hPolicy; if (GetPolicy(&hPolicy) != 0) { std::wcout << L" [-] GetPolicy() Error: " << std::endl; return 1; } AddUserPrivilege(hPolicy, cAccName, cPrivName, TRUE); return 0;}DWORD GetPolicy(PLSA_HANDLE LsaHandle){ wchar_t cCompName[MAX_COMPUTERNAME_LENGTH + 1] = { 0 }; DWORD size = sizeof(cCompName); if (GetComputerNameW(cCompName, &size)) { std::wcout << L" [+] ComputerName: " << cCompName << std::endl; } else { std::wcout << L" [-] GetComputerNameW Error: " << GetLastError() << std::endl; } LSA_OBJECT_ATTRIBUTES lsaOA = { 0 }; LSA_UNICODE_STRING lsastrComputer = { 0 }; lsaOA.Length = sizeof(lsaOA); lsastrComputer.Length = (USHORT)(lstrlen(cCompName) * sizeof(WCHAR)); lsastrComputer.MaximumLength = lsastrComputer.Length + sizeof(WCHAR); lsastrComputer.Buffer = (PWSTR)&cCompName; NTSTATUS ntStatus = LsaOpenPolicy(&lsastrComputer, &lsaOA, POLICY_ALL_ACCESS, LsaHandle); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr != ERROR_SUCCESS) { std::wcout << L" [-] LsaOpenPolicy() Error: " << lErr << std::endl; return 1; } else { std::wcout << L" [+] LsaOpenPolicy() Success" << std::endl; return 0; } return 1;}

По­луча­ют хендл на полити­ку через фун­кцию LsaOpenPolicy. Единс­твен­ная осо­бен­ность работы с LSA сос­тоит в том, что у нее свои коды оши­бок, которые прос­то через GetLastError() не пой­мать. Нуж­но получать зна­чение NTSTATUS, которое воз­вра­щает каж­дая фун­кция, работа­ющая с LSA, а затем переда­вать это зна­чение в LsaNtStatusToWinError() для пре­обра­зова­ния в понят­ный челове­чес­кому гла­зу код ошиб­ки.

По коду ошиб­ки мож­но понять, что сло­малось, — заг­лядывай в офи­циаль­ную докумен­тацию.

Ес­ли сис­тема выдала нам кор­рек­тный хендл и мы не получи­ли ERROR_ACCESS_DENIED, перехо­дим к навеши­ванию при­виле­гии поль­зовате­лю, к фун­кции AddUserPrivilege().

DWORD AddUserPrivilege(LSA_HANDLE hPolicy, LPWSTR wUsername, LPWSTR wPrivName, BOOL bEnable) { PSID UserSid; DWORD sid_size = 0; LPTSTR wSidStr = NULL; DWORD domain_size = 0; SID_NAME_USE sid_use; if (!LookupAccountName(NULL, wUsername, NULL, &sid_size, NULL, &domain_size, &sid_use)) { UserSid = (PSID)VirtualAlloc(NULL, sid_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); LPTSTR domain = NULL; domain = (LPTSTR)VirtualAlloc(NULL, domain_size * sizeof(WCHAR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); LookupAccountName(NULL, wUsername, UserSid, &sid_size, domain, &domain_size, &sid_use); VirtualFree(domain, 0, MEM_RELEASE); ConvertSidToStringSid(UserSid, &wSidStr); std::wcout << L" [+] User SID: " << wSidStr << std::endl; LSA_UNICODE_STRING lsastrPrivs[1] = { 0 }; lsastrPrivs[0].Buffer = (PWSTR)wPrivName; lsastrPrivs[0].Length = lstrlen(lsastrPrivs[0].Buffer) * sizeof(WCHAR); lsastrPrivs[0].MaximumLength = lsastrPrivs[0].Length + sizeof(WCHAR); if (bEnable) { NTSTATUS ntStatus = LsaAddAccountRights(hPolicy, UserSid, lsastrPrivs, 1); ULONG lErr = LsaNtStatusToWinError(ntStatus); if (lErr == ERROR_SUCCESS) { std::wcout << L" [+] Adding " << wPrivName << L" Success" << std::endl; std::wcout << L" [+] Enumerating Current Privs" << std::endl; PrintTrusteePrivs(hPolicy, UserSid); VirtualFree(UserSid, 0, MEM_RELEASE); return 0; } else { wprintf(L" [-] Error LsaAddAccountRights() %d", lErr); return 1; } } else { ULONG lErr = LsaRemoveAccountRights(hPolicy, UserSid, FALSE, lsastrPrivs, 1); if (lErr == ERROR_SUCCESS) { std::wcout << L" [-] Removing " << wPrivName << L" Success" << std::endl; std::wcout << L" [+] Enumerating Current Privs" << std::endl; PrintTrusteePrivs(hPolicy, UserSid); VirtualFree(UserSid, 0, MEM_RELEASE); return 0; } else { wprintf(L" [-] Error LsaRemoveAccountRights() %d", lErr); return 1; } } } else { std::wcout << L" [-] LookupAccountName() Error: " << GetLastError() << std::endl; return 1; } return 1;}

Источник: xakep.ru

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *