I need to understand the use of lpEnvironment in CreateProcessAsUser.
I have these two lines of code in my solution which in invoked by a windows service running as Local System.
CreateProcessAsUser(hTokenCpy, null, applicationName, ref saProcess, ref saThread, false, CREATE_UNICODE_ENVIRONMENT, hEnv, null, ref si, out procInfo)
which is called after calling this
CreateEnvironmentBlock(out hEnv, hTokenCpy, false) || hEnv == IntPtr.Zero)
However if the call to CreateProcessAsUser fails.
Another attempt is made by passing hEnv/IpEnvironment = 0
CreateProcessAsUser(hTokenCpy, null, applicationName, ref saProcess, ref saThread, false, 0, IntPtr.Zero, null, ref si, out procInfo
However when the process is created under a specific user name the created process fails to interact with the desktop. Even though the logon user and the user under which the process is running are same.
Is this because the process is getting created under the memory block of the Windows service instead of the logged on User ?
Related
I want to spawn a batch file from my FMX app (on Win32) with elevated privileges. From Remy's answer at the bottom of this thread on ShellExecute I found how to launch the batch file. Now, i can't figure out how to launch it with elevated privilege. Below is my code:
String Prog = "c:\\Users\\rwp\\Desktop\\test.bat";
int nErrorCode = (int) ShellExecute(NULL, L"runas", Prog.c_str(), NULL, NULL, SW_SHOWNORMAL);
if (nErrorCode <= 32) {
ShowMessage("an error occured");
}
I added "runas" for the second argument after reading this to no avail. Running the batch file manually (right-click and run as admin) works. Here is content of the batch file fyi (just kicks of a system imaging):
c:\Windows\system32\wbAdmin.exe start backup -backupTarget:D: -include:C: -allCritical -quiet
How can i ShellExecute this batch file as admin?
UPDATE 1: I'm attempting to use CreateProcess per Remy suggestion. Here is my code (based on this example):
//Code is inside a __fastcall button click
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.lpReserved = NULL;
siStartInfo.lpReserved2 = NULL;
siStartInfo.cbReserved2 = 0;
siStartInfo.lpDesktop = NULL;
siStartInfo.dwFlags = 0;
// String strCmdLine = "C:\\Users\\rwpatter\\Desktop\\test.bat";
String strCmdLine = "C:\\Windows\\System32\\wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
// Create the child process.
int rtrn = CreateProcess(
NULL,
strCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
// Wait for the processs to finish
DWORD rc = WaitForSingleObject(
piProcInfo.hProcess, // process handle
INFINITE);
ShowMessage(IntToStr(rtrn));
If I run it as shown (right-click on exe and run as admin) it returns 0 which means it failed. If I run it by putting the wbAdmin command line in the test.bat file (see commented line right above String strCmdLine in the code) then CreateProcess returns a 1 (success) but wbAdmin is still not running. It flashed a DOS window and i captured it as shown in the picture below. It shows oriental characters in the title bar and says not recognized as internal or external command. But, if i run that test.bat directly (elevated) it runs wbAdmin no problem.
Any ideas on what is wrong? Besides me obviously being ignorant. (p.s. i'll get to testing Golvind's answer on the ShellExecute after this...)
Running the batch file manually (right-click and run as admin) works.
Because you are running the 64-bit version of cmd when you start it manually.
It shows oriental characters in the title bar and says not recognized
as internal or external command.
Because your application is 32-bit. A 32-bit application does not see the same System32 folder as 64-bit applications. You can access the 64-bit System32 folder in 32-bit applications with the virtual sysnative folder.
#include <shellapi.h>
...
String strCmdLine = "wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
int rtrn = CreateProcess(
NULL,
strCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
if (!rtrn)
{
String newCmdLine = "c:\\windows\\sysnative\\wbAdmin.exe start backup -backupTarget:T: -include:C: -allCritical -quiet";
rtrn = CreateProcess(
NULL,
newCmdLine.c_str(),
NULL, // process security attributes
NULL, // primary thread security attributes
0, // handles are inherited
0, // creation flags
0, // use parent's environment
0, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo); // receives PROCESS_INFORMATION
}
Or compile your application to 64-bit.
You need to launch CMD.exe as Administrator with "runas", and specify the batch file as a "run-me-then-exit" (i.e. /c) argument to command prompt, as so:
WCHAR wszCmdPath[MAX_PATH];
GetEnvironmentVariableW(L"ComSpec", wszCmdPath, MAX_PATH);
ShellExecuteW(NULL, L"runas", wszCmdPath, L"/c \"C:\\Path\\BatchFile.bat\"", L"", SW_SHOW);
Both functions called here can fail, and robust code would test for success before proceeding.
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);
}
}
}
}
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);
}
I want to create a process from my program and inject a dll into the process.
But I keep having these "Access Denied" or "A required privilege is not held by the client." errors.
This is part of my code:
HANDLE hCurrentProcess = GetCurrentProcess();
HANDLE hCurrentToken;
BOOL tokenResult = OpenProcessToken(hCurrentProcess,
TOKEN_ALL_ACCESS,
&hCurrentToken);
BOOL result = CreateProcessWithTokenW(hCurrentToken, // hToken
0, // dwLogonFlags
processToInjectInto, // lpApplicationName
NULL, // lpCommandLine
CREATE_DEFAULT_ERROR_MODE, // dwCreationFlags
NULL, // lpEnvironment
NULL, // lpCurrentDirectory
&startupInfo, // lpStartupInfo
&processInformation); // lpProcessInformation
if(!result) {
cout << GetLastError();
return 1;
}
Can someone tell my what is wrong there? In this case i'm having 1314 error code (A required privilege is not held by the client).
BTW - I'm using windows 7 x64.
Update:
The problem was that I was trying to inject from a 32bit to 64bit process.
Did you check MSDN?
The process that calls CreateProcessWithTokenW must have the
SE_IMPERSONATE_NAME privilege. If this function fails with
ERROR_PRIVILEGE_NOT_HELD (1314), use the CreateProcessAsUser or
CreateProcessWithLogonW function instead. Typically, the process that
calls CreateProcessAsUser must have the SE_INCREASE_QUOTA_NAME
privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if
the token is not assignable. CreateProcessWithLogonW requires no
special privileges, but the specified user account must be allowed to
log on interactively. Generally, it is best to use
CreateProcessWithLogonW to create a process with alternate credentials
Using the token fromOpenProcessToken(GetCurrentProcess()) is also pointless since you could just call the normal CreateProcess function...
I have a call to CreateProcessWithTokenW that is failing with access denied. Any ideas how to debug this?
The call to CreateProcessWithTokenW is here: https://github.com/fschwiet/PShochu/blob/master/PShochu/PInvoke/NetWrappers/ProcessUtil.cs
For now I'm using a access token for the current process, eventually I'll use a token from another user. For now then I'm using https://github.com/fschwiet/PShochu/blob/master/PShochu/PInvoke/NetWrappers/AccessToken.cs to get the access token.
If you want to debug, pull down the sourcecode and run build_and_test.ps1. The error stack is:
1) Test Error : PShochu.Tests.can_run_remote_interactive_tasks, given a psake script which writes the current process id to output, when that script is invoked interactively, then the script succeeds
System.ComponentModel.Win32Exception : Access is denied
at PShochu.PInvoke.NetWrappers.ProcessUtil.CreateProcessWithToken(IntPtr userPrincipalToken, String applicationName,
String applicationCommand, Boolean dontCreateWindow, Boolean createWithProfile, StreamReader& consoleOutput, StreamReader& errorOutput) in c:\src\PShochu\PShochu\PInvoke\NetWrappers\ProcessUtil.cs:line 52
at PShochu.ProcessHandling.RunNoninteractiveConsoleProcessForStreams2(String command, String commandArguments, String& newLine) in c:\src\PShochu\PShochu\ProcessHandling.cs:line 36
at PShochu.ProcessHandling.RunNoninteractiveConsoleProcess(String command, String commandArguments) in c:\src\PShochu\PShochu\ProcessHandling.cs:line 20
at PShochu.Tests.can_run_remote_interactive_tasks.<>c__DisplayClass16.<>c__DisplayClass18.<Specify>b__2() in c:\src\PShochu\PShochu.Tests\can_run_remote_interactive_tasks.cs:line 27
at NJasmine.Core.Execution.DescribeState.<>c__DisplayClass7`1.<visitBeforeEach>b__3() in c:\src\NJasmine\NJasmine\Core\Execution\DescribeState.cs:line 62
Later update: I saw in some docs that additional privileges are needed (http://msdn.microsoft.com/en-us/library/aa374905%28v=vs.85%29.aspx). I am having trouble getting tests to verify I have these individual securities (they are set in secpol.msc pre-reboot)
SE_ASSIGNPRIMARYTOKEN_NAME "Replace a process level token"
SE_TCB_NAME "Act as part of the operatin system"
SE_INCREASE_QUOTA_NAME "Adjust memory quotas for a process"
These tests keep telling me I don't have the permissions I've set in the UI, https://github.com/fschwiet/PShochu/blob/master/PShochu.Tests/verify_privileges.cs
Through trial and error I figured out that the token you pass to CreateProcessWithTokenW() needs the following access flags (at least on Windows 7 SP1 64-bit):
TOKEN_ASSIGN_PRIMARY
TOKEN_DUPLICATE
TOKEN_QUERY
TOKEN_ADJUST_DEFAULT
TOKEN_ADJUST_SESSIONID
The last two in bold are very helpfully not mentioned at all in the documentation for CreateProcessWithTokenW().
EDIT: The following code works fine for me (when running elevated):
HANDLE hToken = NULL;
if(OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, &hToken))
{
HANDLE hDuplicate = NULL;
if(DuplicateTokenEx(hToken, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID, NULL, SecurityImpersonation, TokenPrimary, &hDuplicate))
{
TCHAR szCommandLine[MAX_PATH];
_tcscpy_s(szCommandLine, MAX_PATH, _T("C:\\Windows\\system32\\notepad.exe"));
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(STARTUPINFO));
StartupInfo.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION ProcessInformation;
ZeroMemory(&ProcessInformation, sizeof(PROCESS_INFORMATION));
if(CreateProcessWithTokenW(hDuplicate, LOGON_WITH_PROFILE, NULL, szCommandLine, 0, NULL, NULL, &StartupInfo, &ProcessInformation))
{
WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
CloseHandle(ProcessInformation.hThread);
ProcessInformation.hThread = NULL;
CloseHandle(ProcessInformation.hProcess);
ProcessInformation.hProcess = NULL;
}
CloseHandle(hDuplicate);
hToken = hDuplicate;
}
CloseHandle(hToken);
hToken = NULL;
}