Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Привилегии в Windows играют очень важную роль, так как администратор имеет возможность предоставить специальные права пользователям для решения их задач. В этой статье мы познакомимся с инструментом Privileger, который позволяет отыскивать в системе учетные записи с определенными привилегиями и менять привилегии у заданного аккаунта.
О работе с привилегиями я писал в прошлой статье, но мы рассмотрели лишь обрывки кода, которые, как оказалось, сложно было превратить в полноценный проект. Поэтому, когда на очередном пентесте мне вновь потребовалось кодить все с нуля, я понял, что без готового инструмента не обойтись. И сделал Privileger, который, к моему удивлению, стал достаточно популярным.
Релиз инструмента
В сегодняшней статье мы разберем принцип работы этого инструмента, а также пробежимся по всем пяти режимам, которые позволяют:
Итак, все, как и в любом другом проекте на языке С, начинается с функции 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;}
Если требуется использовать первый режим работы, то есть добавить привилегии аккаунту, пользователь должен предоставить следующие входные данные:
Программное имя привилегии — это, собственно, само имя привилегии. Есть еще так называемое дружественное имя — это ее описание. Например, программное имя 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