CreateProcessAsUser from service with proper PEB and ACL - windows

I have read tons of SO questions on this matter, but I didn't find a real definitive guide for doing this the right way.
My goal is to enumerate [disconnected and active] user console sessions and start a process in each one of them. Every user session process requires at least these rights in its DACL :
Token access rights :
TOKEN_QUERY (for GetTokenInformation())
TOKEN_QUERY_SOURCE (for GetTokenInformation())
Process access rights :
PROCESS_QUERY_INFORMATION (for OpenProcessToken())
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ (for GetModuleFileNameEx())
PROCESS_VM_OPERATION (used with GetTokenInformation() to get other processes' username later with LookupAccountSid())
But as you can read here (at the bottom) : "Windows Vista introduces protected processes to enhance support for Digital Rights Management. The system restricts access to protected processes and the threads of protected processes."
So I thought maybe only with PROCESS_QUERY_LIMITED_INFORMATION I can get some information about other processes. I tried QueryFullProcessImageName() for elevated processes starting from Vista (see Giori's answer) but it doesn't work anymore as it seems.
Solution : CreateProcessAs_LOCAL_SYSTEM using a duplicated token of the Windows service.
Problem : The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things. But if I use the service's token I inherit its PEB and I can't even translate the mapped drives to their UNC paths.
So I started looking for ways to "elevate" the process and bypassing the UAC prompt, I tried :
Enabling some privileges like SE_DEBUG_PRIVILEGE in the token using AdjustTokenPrivileges() (does not work if the token does not have those privileges, verification can be done first using LookUpPrivilegeValue())
using the token from winlogon.exe. (does not work)
Changing the DACL (source code) (didn't work)
The steps I'm following are :
Enumerate sessions using WTSEnumerateSessions()
Get the token (two choices) :
SYSTEM token : OpenProcessToken(GetCurrentProcess(),TokenAccessLevels.MaximumAllowed, out hProcessToken)
User token : WTSQueryUserToken(sessionId, out hUserToken)
Duplicate the token using DuplicateTokenEx()
LookUpPrivilegeValue() / AdjustTokenPrivileges() (useless ?)
CreateEnvironmentBlock()
CreateProccessAsUser(), flags : NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, Startup info's desktop : "WinSta0\Default"
Change process DACL (see link above, useless ?)
Dispose/Clean : destroy PEB created, close opened handles and free memory.
My question : how to grant the process created using CreateProccessAsUser() from a Windows Service running under LOCAL_SYSTEM account enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?

You're confused about a number of things.
Every user session process requires at least these rights in its DACL
The process DACL controls access to that process, it does not determine what access that process has. The security token for the process determines access rights.
Windows Vista introduces protected processes to enhance support for Digital Rights Management.
It seems clear that you haven't gotten far enough to worry about protected processes yet. Get it to work for ordinary processes first!
The spawned processes should have the respective logged on user's environment variables set to be able to locate network printers and mapped drives among other things.
Network printers and mapped drives have nothing to do with environment variables. I think what you're trying to do is to put the new process into the user's logon session, that's what controls network drive mappings and the like.
how to grant the process created using CreateProccessAsUser() [...] enough privileges/rights to get information on other processes (from other sessions; of other users and different integrity levels) without losing the user's environment variables ?
Don't. This would violate the integrity of the security model.
Instead, enumerate and query processes from the system service, and pass only whatever information is necessary to the user session processes, using shared memory (look up "file mapping object" in MSDN) or another suitable IPC mechanism.

I know that this has been asked a while ago. Since I happened to have been doing the same, below is the working pseudo-code.
First, how to run a process in a user session from a service:
//IMPORTANT: All error checks are omitted for brevity!
// Each of the lines of code below MUST be
// checked for possible errors!!!
//INFO: The following pseudo-code is intended to run
// from the Windows local service.
DWORD dwSessionID; //Session ID to run your user process in
//Get token for the session ID
HANDLE hToken;
WTSQueryUserToken(dwSessionID, &hToken);
//Duplicate this token
HANDLE hToken2;
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
PSID gpSidMIL_High = NULL;
if(you_want_to_change_integrity_level_for_user_process)
{
if(!Windows_XP)
{
//For example, create "high" mandaroty integrity level SID
::ConvertStringSidToSid(L"S-1-16-12288", &gpSidMIL_High);
TOKEN_MANDATORY_LABEL tml = {0};
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;
SetTokenInformation(hToken2, TokenIntegrityLevel, &tml,
sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1));
}
}
//Copy environment strings
LPVOID pEnvBlock = NULL;
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
//Initialize the STARTUPINFO structure.
// Specify that the process runs in the interactive desktop.
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = _T("winsta0\\default");
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
//Create non-const buffer
TCHAR pBuffCmdLine[MAX_PATH];
pBuffCmdLine[0] = 0;
//Copy process path & parameters to the non-constant buffer
StringCchCopy(pBuffCmdLine, MAX_PATH, L"\"C:\\Program Files (x86)\\Company\\Brand\\process.exe\" -parameter");
//Impersonate the user
ImpersonateLoggedOnUser(hToken2);
//Launch the process in the user session.
bResult = CreateProcessAsUser(
hToken2, // client's access token
L"C:\\Program Files (x86)\\Company\\Brand\\process.exe", // file to execute
pBuffCmdLine[0] != 0 ? pBuffCmdLine : NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, // creation flags
pEnvBlock, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
//Get last error
nOSError = GetLastError();
//Revert to self
RevertToSelf();
//At this point you may want to wait for the user process to start, etc.
//using its handle in `pi.hProcess`
...
//Otherwise, close handles
if(pi.hProcess)
CloseHandle(pi.hProcess);
if(pi.hThread)
CloseHandle(pi.hThread);
//Clean-up
if(pEnvBlock)
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hToken);
if(gpSidMIL_High)
::LocalFree(gpSidMIL_High);
If you need to run your process in all sessions with a logged in interactive user, you can run the method I gave above for the sessions that you can obtain from the following enumeration:
//Enumerate all sessions
WTS_SESSION_INFO* pWSI = NULL;
DWORD nCntWSI = 0;
if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWSI, &nCntWSI))
{
//Go through all sessions
for(DWORD i = 0; i < nCntWSI; i++)
{
//To select logged in interactive user session,
//try to get its name. If you get something, then
//this session has a user logged in to...
LPTSTR pUserName = NULL;
DWORD dwcbSzUserName = 0;
if(WTSQuerySessionInformation(WTS_CURRENT_SERVER_HANDLE,
pWSI[i].SessionId,
WTSUserName, &pUserName, &dwcbSzUserName) &&
pUserName &&
dwcbSzUserName >= sizeof(TCHAR) &&
pUserName[0] != 0)
{
//Use my method above to run your user process
// in this session.
DWORD dwSessionID = pWSI[i].SessionId;
}
//Free mem
if(pUserName)
WTSFreeMemory(pUserName);
}
//Free mem
WTSFreeMemory(pWSI);
}

Related

Getting process information of every process

I am trying to create a program that any normal user can run on windows and generate a process list of all processes, including the executable location. I have used CreateToolhelp32Snapshot() to get all process names, pid, ppid. But having issues getting the image path. Everything I do results in pretty much Access Denied.
I have tried ZwQueryInformationProcess, GetProcessImageFileName, etc. and also using OpenProcess to get the handle to each process. I can get the handle by using PROCESS_QUERY_LIMITED_INFORMATION, but any other option doesn't work. I am lost and have been at this for a few days. Can anyone point me in the right direction?
This is the code that works for non-admin user on Windows. Use the szExeFile member of PROCESSENTRY32 to get the path:
HANDLE hProcessSnap = NULL;
HANDLE hProcess = NULL;
PROCESSENTRY32 pe32;
DWORD dwPriorityClass = 0;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
return;
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32))
{
CloseHandle(hProcessSnap); // clean the snapshot object
return;
}
// Now walk the snapshot of processes, and
// display information about each process in turn
do
{
// do something with the pe32 struct.
// pe32.szExeFile -> path of the file
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);

Windows process with Untrusted Integrity level

I couldn't find much information about Untrusted integrity level in Windows, and have some questions about it:
Is there a place where an untrusted integrity level process can create named objects? (mutexes, events, etc..)
Should untrusted integrity level process be able to open an existing named object, that was given a security descriptor in it's creation time with ACE with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP to MandatoryLevelUntrusted? When I try it, it fails with 0xc0000022(access denied), while with MandatoryLevelLow it works great.
How do usually untrusted integrity processes communicate with their broker process? (like how does a google chrome tab communicates with the google chrome broker?)
Is there a place where an untrusted integrity level process can create
named objects? (mutexes, events, etc..)
by default - no. code with untrusted token (thread or process) can create object only in directory with Untrusted Mandatory Level - no one standard folders have this kind of label. some have Low Mandatory Level but untrusted - no.
but you can easy create this folder yourself. with Untrusted Mandatory Level and NULL DACL - untrusted code can create objects in this folder.
NTSTATUS CreateUntrustedFolder(PHANDLE phObject, PCUNICODE_STRING ObjectName)
{
ULONG cb = MAX_SID_SIZE;
PSID UntrustedSid = (PSID)alloca(MAX_SID_SIZE);
if (CreateWellKnownSid(WinUntrustedLabelSid, 0, UntrustedSid, &cb))
{
PACL Sacl = (PACL)alloca(cb += sizeof(ACL) + sizeof(ACE_HEADER) + sizeof(ACCESS_MASK));
InitializeAcl(Sacl, cb, ACL_REVISION);
if (AddMandatoryAce(Sacl, ACL_REVISION, 0, 0, UntrustedSid))
{
SECURITY_DESCRIPTOR sd;
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
SetSecurityDescriptorSacl(&sd, TRUE, Sacl, FALSE);
OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, (PUNICODE_STRING)ObjectName, OBJ_CASE_INSENSITIVE|OBJ_OPENIF, &sd };
return ZwCreateDirectoryObject(phObject, DIRECTORY_ALL_ACCESS, &oa);
}
}
return STATUS_UNSUCCESSFUL;
}
about untrusted code creation - if start process at begin with token marked as untrusted integrity level - process fail to start. this when ntdll.dll try load kernel32.dll - it try open section \KnownDlls\kernel32.dll with SECTION_MAP_WRITE as well , but this object have Low Mandatory Level with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP - as result untrusted code fail open this section with write access.
as result you need initially create process say with Low Mandatory Level and then set untrusted level
ULONG SetProcessUntrusted(HANDLE hProcess)
{
TOKEN_MANDATORY_LABEL tml = { { (PSID)alloca(MAX_SID_SIZE), SE_GROUP_INTEGRITY } };
ULONG cb = MAX_SID_SIZE;
HANDLE hToken;
if (!CreateWellKnownSid(WinUntrustedLabelSid, 0, tml.Label.Sid, &cb) ||
!OpenProcessToken(hProcess, TOKEN_ADJUST_DEFAULT, &hToken))
{
return GetLastError();
}
ULONG dwError = NOERROR;
if (!SetTokenInformation(hToken, TokenIntegrityLevel, &tml, sizeof(tml)))
{
dwError = GetLastError();
}
CloseHandle(hToken);
return dwError;
}
Should untrusted integrity level process be able to open an existing
named object
this depend from object label(level and mask) , code intergrity level and required access. if code intergrity level >= object label level - we can open object (if dacl let do this). otherwise need look for object label mask and required access. for example object have Low Mandatory Level with SYSTEM_MANDATORY_LABEL_NO_WRITE_UP and code Untrusted Mandatory Level - this code can open object with read and execute access, but fail open it for write access

Difference between access token taken from process than from user/pass

I am trying to understand the difference between 2 supposly identical access tokens.
One way to get access token is using OpenProcessToken(). The function returns an access token that can be used to create other processes or to impersonate.
Another way (and there are more, but lets focus these two) is to use LogonUser() function that also returns an access token that can be used to create other processes or to impersonate.
The difference is that the access token from (1) is valid as long as the process exists, while the access token in (2) is not attached to any process.
So, (1) what exactly do I get from OpenProcessToken()? Not an access token?
(2) Can I "change" the access token from OpenProcessToken() to be "detached" from the process? (i.e. still valid after the process dies)
access token from (1) is valid as long as the process exists
This is not true. The token is not "attached" to any process. Of course this isn't documented, but you can parse the pdb symbols and observe that struct _TOKEN doesn't have any links to the _EPROCESS object.
As with any kernel object, the token has a reference count, and it will be destroyed when the reference count reaches zero - but until then, it remains valid. Every _EPROCESS has a reference to the token, but not the other way around. Once you've opened the process token, it will remain valid as long as you keep it open, even if the process that you obtained it from terminates.
This is easy to test: open a token from some process (with the necessary permissions) and then close/terminate the process. You will find that you can still use the token with CreateProcessAsUserW to start a new process.
So no, there is no difference between the two tokens; in both cases you have a handle to a kernel object.
Here is demonstration code showing that the token from OpenProcessToken remains valid even after the process exits. We create an instance of notepad, open its token, wait until the process exists, and then attempt (successfully!) to use the token to start a new instance of notepad.
void demo()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
// create new process in suspended state
// path to notepad hardcoded for for simplicity
if (CreateProcessW(L"c:\\windows\\notepad.exe", 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0,&si, &pi))
{
// open process token with TOKEN_QUERY only
HANDLE hToken;
NTSTATUS status = NtOpenProcessToken(pi.hProcess, TOKEN_QUERY, &hToken);
// resume process
ResumeThread(pi.hThread);
NtClose(pi.hThread);
// close notepad
WaitForInputIdle(pi.hProcess, INFINITE);
PostThreadMessage(pi.dwThreadId, WM_QUIT, 0, 0);
// wait notepad exit
ZwWaitForSingleObject(pi.hProcess, FALSE, 0);
NtClose(pi.hProcess);
// try use "invalid" token from died process
if (0 <= status)
{
// we need TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY access for CreateProcessAsUserW
// we can at begin (in NtOpenProcessToken open it with the necessary access, this is only for demo
if (0 <= ZwDuplicateObject(NtCurrentProcess(),
hToken, NtCurrentProcess(), &hToken,
TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY, 0, DUPLICATE_CLOSE_SOURCE))
{
if (CreateProcessAsUserW(hToken, L"c:\\windows\\notepad.exe", 0, 0, 0, 0, 0, 0, 0, &si, &pi))
{
NtClose(pi.hThread);
NtClose(pi.hProcess);
}
else
{
GetLastError();
}
NtClose(hToken);
}
}
}
}

Why do we need to add SE_SHUTDOWN_NAME privilege before using ExitWindowsEx

Before we can use the ExitWindowsEx function for shutting down the computer, we must add the SE_SHUTDOWN_NAME privilege to the process like this:
HANDLE hToken = NULL;
LUID luid;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
LookupPrivilegeValue(L"", SE_SHUTDOWN_NAME, &luid);
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bRet = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, 0);
ExitWindowsEx(EWX_REBOOT, 0);
This works perfectly fine.
I know that this is by design, but I don't understand what's the motivation of Microsoft for deciding that a privilege must be enabled before the ExitWindowsEx function can be used. It's obviously not designed to prevent a process from rebooting the computer, because all it needs to do for rebooting is adding the SE_SHUTDOWN_NAME to the process and call ExitWindowsEx.
The Windows developpment term could have put the code above directly into the ExitWindowsEx function.
You don't need to add it (you can't add a privilege to your current token), you need to enable it.
Most privileges are disabled by default, probably to avoid an accidental use (just like the read-only attribute on files). There's no need to run around with the privilege enabled at all times, and having the ExitWindowsEx function enable it by itself would break consistency with other privilege-dependent functions.

FindFirstPrinterChangeNotification with network printer fails with ERROR_INVALID_HANDLE (called from service)

I want to monitor print jobs on remote (shared) printers from service. I have impersonation access token for user who connects printer (e.g. \PC-PRINTER\Canon Printer).
After impersonation as the user I call OpenPrinter to printer and then FindFirstPrinterChangeNotification. OpenPrinter succeeded but FindFirstPrinterChangeNotification failed with ERROR_INVALID_HANDLE error.
If I try it as normal user (owner of printer - not a service), all succeeded and notification are functional. Also if I try it on Windows XP it succeeded, it fails only on Windows 7 and probably Windows Vista.
I suppose that I have to set some security attributes for impersonation token or pass some other attributes to OpenPrinter. I have tried many things, but I didn't find solution.
My code example:
PRINTER_NOTIFY_OPTIONS_TYPE optionsType;
unsigned short jobFields[] = {JOB_NOTIFY_FIELD_PRINTER_NAME};
optionsType.Type = JOB_NOTIFY_TYPE;
optionsType.Count = 1;
optionsType.pFields = &jobFields[0];
PRINTER_NOTIFY_OPTIONS options;
options.Version = 2;
options.Count = 1;
options.pTypes = &optionsType;
options.Flags = 0;
DWORD dwFilter = PRINTER_CHANGE_DELETE_JOB | PRINTER_CHANGE_SET_JOB ;
HANDLE hPrinterHandle = NULL;
ImpersonateLoggedOnUser(hUserToken); //I retrieve token from printer owner's process (I try every process for this user)
OpenPrinter(L"\\\\PC-PRINTER\\Canon Printer", &hPrinterHandle, NULL); \\it succeeded and I try many other parameters as well
HANDLE hNotification = FindFirstPrinterChangeNotification(hPrinterHandle, dwFilter, 0, (LPVOID) &options);
if (hNotification == INVALID_HANDLE_VALUE)
{//it comes here, GetLastError() == ERROR_INVALID_HANDLE (Windows 7, process runs as service)
//it succeded on Windows XP or when process runs in printer owner context
}
RevertToSelf();
Thanks for any solution!

Resources