I'd like to start a GUI application like notepad.exe or calc.exe with a restricted token that only has the permissions of some groups (with the CreateRestrictedToken()
API)
However, I get this error message from csrss.exe: The application was unable to start correctly (0xc0000022)
Here is the code I used:
#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include <conio.h>
#include <sstream>
#include <sddl.h>
#include <vector>
#ifndef UNICODE
typedef std::string STRING;
#else
typedef std::wstring STRING;
#endif
// Global Variables:
HINSTANCE hInst;
STRING szTitle = _T("TITLE");
STRING szWindowClass = _T("WINDOW_CLASS");
STRING szWindowInfo;
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
std::wcout << L"Command line: " << lpCmdLine << std::endl;
MyRegisterClass(hInstance);
// Pass the parsed credentials to InitInstance
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
MSG msg;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = szWindowClass.c_str();
wcex.hIconSm = NULL;
return RegisterClassEx(&wcex);
}
BOOL createWindow(HWND& hWnd, HINSTANCE hInstance, int nCmdShow) {
hInst = hInstance;
hWnd = CreateWindow(szWindowClass.c_str(), szTitle.c_str(), WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
szWindowInfo = _T("Class: ") + szWindowClass + _T(", Handle: 0x") + std::to_wstring((unsigned)hWnd);
STRING szCompleteTitle = szTitle + _T(" - ") + szWindowInfo;
::SetWindowText(hWnd, szCompleteTitle.c_str());
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
BOOL launchNotepad(HWND hWnd)
{
HANDLE hToken = NULL;
HANDLE hRestrictedToken = NULL;
std::vector<PSID> groupSids;
std::vector<SID_AND_ATTRIBUTES> sidAndAttributes;
std::vector<STRING> groupNames;
if (true)
groupNames = { _T("Users") };
//groupNames = { _T("testgroup") , _T("Users") };
SID_NAME_USE sidType;
// Open the current process token
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, &hToken))
{
std::wstringstream errorMsg;
errorMsg << L"OpenProcessToken failed. Error: " << GetLastError();
MessageBox(NULL, errorMsg.str().c_str(), _T("Error"), MB_OK | MB_ICONERROR);
return false;
}
// Iterate over group names to lookup SIDs
for (const auto& groupName : groupNames)
{
PSID pGroupSid = NULL;
STRING domainName = _T(".");
DWORD domainNameSize = static_cast<DWORD>(domainName.size());
DWORD dwSize = 0;
// Lookup the SID for the group name
if (!LookupAccountName(NULL, groupName.c_str(), NULL, &dwSize, &domainName[0], &domainNameSize, &sidType) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
pGroupSid = (PSID)malloc(dwSize);
if (!LookupAccountName(NULL, groupName.c_str(), pGroupSid, &dwSize, &domainName[0], &domainNameSize, &sidType))
{
std::wstringstream errorMsg;
errorMsg << L"LookupAccountName failed for group " << groupName << L". Error: " << GetLastError();
MessageBox(NULL, errorMsg.str().c_str(), _T("Error"), MB_OK | MB_ICONERROR);
free(pGroupSid);
continue;
}
}
else
{
std::wstringstream errorMsg;
errorMsg << L"LookupAccountName failed for group " << groupName << L". Error: " << GetLastError();
MessageBox(NULL, errorMsg.str().c_str(), _T("Error"), MB_OK | MB_ICONERROR);
continue;
}
groupSids.push_back(pGroupSid);
SID_AND_ATTRIBUTES sidAttr = { pGroupSid, 0 };
sidAndAttributes.push_back(sidAttr);
}
if (!CreateRestrictedToken(
hToken,
0, // DISABLE_MAX_PRIVILEGE,
0, NULL, // No privileges to disable
0, NULL, // No SIDs to delete
static_cast<DWORD>(sidAndAttributes.size()), sidAndAttributes.data(),
&hRestrictedToken))
{
std::wstringstream errorMsg;
errorMsg << L"CreateRestrictedToken failed. Error: " << GetLastError();
MessageBox(NULL, errorMsg.str().c_str(), _T("Error"), MB_OK | MB_ICONERROR);
for (auto sid : groupSids) free(sid); // Free allocated SIDs
return false;
}
_tprintf(_T("Restricted token created successfully.\n"));
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
STRING szCmdLine = _T("notepad.exe");
//STRING szCmdLine = _T("calc.exe");
if (!CreateProcessAsUser(
hRestrictedToken,
NULL,
&szCmdLine[0],
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi))
{
std::wstringstream errorMsg;
errorMsg << L"CreateProcessAsUser failed. Error: " << GetLastError();
MessageBox(NULL, errorMsg.str().c_str(), _T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
else
{
MessageBox(NULL, _T("Process created successfully with restricted token."), _T("Success"), MB_OK | MB_ICONINFORMATION);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
// Clean up
CloseHandle(hToken);
CloseHandle(hRestrictedToken);
for (auto sid : groupSids) free(sid); // Free allocated SIDs
//find window
HWND hNotepadWnd = NULL;
while (hNotepadWnd == NULL)
{
hNotepadWnd = FindWindow(_T("Notepad"), NULL);
Sleep(100);
}
MessageBox(
NULL, // Handle to the owner window (NULL for no owner)
_T("FindWindow success."), // Message to display
_T("Error"), // Title of the message box
MB_OK | MB_ICONINFORMATION // Buttons and icon type
);
//set parent
SetParent(hNotepadWnd, hWnd);
return TRUE;
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
if (!createWindow(hWnd, hInstance, nCmdShow))
{
MessageBox(NULL, _T("Failed to create window"), _T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
if (!launchNotepad(hWnd))
{
MessageBox(NULL, _T("Failed to launch Notepad"), _T("Error"), MB_OK | MB_ICONERROR);
return FALSE;
}
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
return DefWindowProc(hWnd, message, wParam, lParam);
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
RECT rcWin;
GetWindowRect(hWnd, &rcWin);
DrawText(hdc, szWindowInfo.c_str(), -1, &rcWin, DT_LEFT | DT_TOP);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
This can be compiled as follows:
If I disable the restriction to groupNames = { _T("Users") };
, Notepad starts without problem, but then the permissions are not restricted to a subset of the current users permissions as intended.
What do I need here to make this work?
Edit: Removed two calls to CloseHandle(hToken); when the group was not found. Only triggers when using other group names / SIDs in testing, should not be executed in the minimal example.
However, I get this error message from csrss.exe: The application was unable to start correctly (0xc0000022)
really you got this error message from ZwRaiseHardError api. this api send message to process hard error port. from other side csrss.exe handle this port and show MessageBoxW. process by self can not call MessageBoxW, because for this need load and init, user32.dll, gdi32.dll, etc. when for call ZwRaiseHardError need only ntdll.dll and nothing more
concrete STATUS_ACCESS_DENIED ( 0xc0000022 ) was from NtOpenDirectoryObject
for \KnownDlls
because security descriptor for \KnownDlls not grant any access to S-1-5-32-545 'BUILTIN\Users', but grant to S-1-5-12 'NT AUTHORITY\RESTRICTED' and S-1-1-0 '\Everyone', so we need add one of this SID.
then application usually need open HKCU key. for do this need have how minimum 'NT AUTHORITY\RESTRICTED' (for '\Everyone' nothing granted).
if application have UI, so interract with desctop, it need have 'NT AUTHORITY\LogonSessionId_0_*' LogonSession sid. we can got it from current process token by query TokenLogonSid
for access C:\WINDOWS\WinSxS\*
folders, process need how minimum S-1-5-32-545 'BUILTIN\Users' Sid. otherwise we will get an error (in case notepad)
SXS: Unable to open assembly directory under storage root "C:\WINDOWS\WinSxS\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.26100.1591_none_3e0fac18e32dc903"; Status = 0xc0000022
SXS: Attempt to probe assembly storage root C:\WINDOWS\WinSxS\ for assembly directory amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.26100.1591_none_3e0fac18e32dc903 failed with status = 0xc0000022
SXS: RtlGetAssemblyStorageRoot() unable to resolve storage map entry. Status = 0xc0000022
so restricted UI process need have how minimum 3 SIDs:
S-1-5-12 'NT AUTHORITY\RESTRICTED' - for access \KnownDlls and HKCU
S-1-5-32-545 'BUILTIN\Users' for access C:\WINDOWS\WinSxS\*
folders
'NT AUTHORITY\LogonSessionId_0_*' for access Desktop
demo code ( how i use alloca incompatible with /RTCs option. so it must be off)
void StartRestricted(PCWSTR lpApplicationName, PWSTR lpCommandLine)
{
HANDLE hToken, hRestrictedToken = 0;
NTSTATUS status;
if (0 <= (status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, &hToken)))
{
ULONG cb = 0, rcb = sizeof(TOKEN_GROUPS) + SECURITY_SID_SIZE(3),
cbSids = SECURITY_SID_SIZE(1) + SECURITY_SID_SIZE(2), cbSid;
union {
PBYTE pb;
PTOKEN_GROUPS ptg;
PVOID buf;
};
PVOID stack = alloca(cbSids);
do
{
cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
status = NtQueryInformationToken(hToken, TokenLogonSid, buf, cb, &rcb);
} while (STATUS_BUFFER_TOO_SMALL == status);
if (0 <= status)
{
if (1 == ptg->GroupCount)
{
static const WELL_KNOWN_SID_TYPE types[] = { WinRestrictedCodeSid, WinBuiltinUsersSid };
PSID_AND_ATTRIBUTES SidsToRestrict = ptg->Groups;
ptg->Groups->Attributes = 0;
pb += rcb;
alloca(_countof(types)*sizeof(SID_AND_ATTRIBUTES));
ULONG n = _countof(types);
do
{
(--SidsToRestrict)->Attributes = 0;
if (status = GetLastHr(CreateWellKnownSid(types[--n], 0, SidsToRestrict->Sid = pb, &(cbSid = cbSids))))
{
break;
}
pb += cbSid, cbSids -= cbSid;
} while (n);
if (S_OK == status)
{
status = GetLastHr(CreateRestrictedToken(hToken, LUA_TOKEN, 0, 0, 0, 0,
_countof(types) + 1, SidsToRestrict, &hRestrictedToken));
}
}
else
{
status = STATUS_INTERNAL_ERROR;
}
}
NtClose(hToken);
if (S_OK == status)
{
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi;
if (S_OK == (status = GetLastHr(CreateProcessAsUserW(
hRestrictedToken, lpApplicationName, lpCommandLine, 0, 0, 0, 0, 0, 0, &si, &pi))))
{
NtClose(pi.hThread);
NtClose(pi.hProcess);
}
NtClose(hRestrictedToken);
}
}
}
inline HRESULT GetLastHr(ULONG dwError = GetLastError())
{
return dwError ? HRESULT_FROM_WIN32(dwError) : S_OK;
}
(i use NtQueryInformationToken, NtOpenProcessToken, because it more easy to use, it direct return error code and not need call GetLastError(), but of course easy possible replace it to kernel32 api)