问题
I have written a C# service running under the local system account. I use it to spawn a process when a user logs in on a Terminalserver. The service implements the OnSessionChange
method and receives SessionChangeDescription
messages with the corresponding SessionID.
I use this SessionID to get an access token from the user with WTSQueryUserToken
. I convert this token into a primary token and pass it to CreateEnvironmentBlock
to retrieve a pointer to the users environment variables. After some further preparations I call the CreateProcessAsUser
function to finally spawn my process as the recently logged on user on his winsta0\default
desktop.
When I investigate the process with ProcessExplorer
I see that there is no CLIENTNAME environment variable in the process context. Yet the applications needs this variable.
I wonder what I've done wrong. Or maybe I am missing something. The user profile should be loaded, since I react when the user has logged in.
Is it possible, that there is some timing issue? Or does the CLIENTNAME var gets applied to a process in any other way?
Here is how I call the CreateEnvironmentBlock
function:
private static IntPtr GetEnvironmentFromToken(IntPtr token)
{
// Get a pointer to the environment variables from the specified user access token
IntPtr newEnvironment = IntPtr.Zero;
if (!WinApi.CreateEnvironmentBlock(ref newEnvironment, token, false))
{
newEnvironment = IntPtr.Zero;
}
return newEnvironment;
}
If you need any more information or code samples, feel free to ask.
回答1:
the environment variables depend not only from user SID but from SessionId too, because some variable was per session.
we can view in registry under HKEY_USERS\<SID>\Volatile Environment
user environment variables. and SessionId
sub keys here exist. under sub key - per session variable
so CreateEnvironmentBlock
must do next - get user SID from token, open HKEY_USERS\<SID>\Volatile Environment
key, and query it values.
then must query SessionId
from token via GetTokenInformation(hToken, TokenSessionId, )
and query Volatile Environment\SessionId
sub key.
but by mistake system use SessionId
from current process PEB instead from get it token. the next code is in system dll:
WCHAR buf[MAX_PATH];
StringCchPrintfW(buf, RTL_NUMBER_OF(buf),
L"%s\\%d", L"Volatile Environment", RtlGetCurrentPeb()->SessionId);
mov rax,gs:[60h]// rax -> PEB
2c0h this is offset of SessionId in PEB
when you exec application from service - the SessionId
in PEB
was 0, as result CLIENTNAME
and SESSIONNAME
not added to environment block.
this is common system bug. for test you can run two cmd.exe
- one not elevated (exec from explorer.exe) and one run as admin ( exec from svchost.exe -k netsvcs) and then run in both set
command - it show environment strings. you can note that in not elevated cmd.exe
exist string SESSIONNAME=Console
(or SESSIONNAME=RDP-Tcp#N
if you run it from rdp) and , if you in rdp, CLIENTNAME=DESKTOP-xxx
. but in elevated (run as admin) cmd.exe - no this strings. this is because CreateEnvironmentBlock
called from svchost.exe -k netsvcs which have SessionId
== 0
for fix this can be 2 way:
easy, but not correct:
_PEB* peb = RtlGetCurrentPeb();
DWORD _SessionId = peb->SessionId, SessionId, rcb;
if (GetTokenInformation(hToken, TokenSessionId, &SessionId, sizeof(SessionId), &rcb))
{
peb->SessionId = SessionId;
}
PVOID Environment;
BOOL fOk = CreateEnvironmentBlock(&Environment, hToken, FALSE);
peb->SessionId = _SessionId;
idea here - temporary replace SessionId
in PEB to SessionId
from token. this will be work. bu bad here - what if another thread in concurrent will use SessionId
in PEB ?
another way, relative big code, but correct - yourself walk through SessionId
sub key and extend environment block.
void AddSessionEnv(HANDLE hToken, PVOID Environment, PVOID* pNewEnvironment)
{
SIZE_T cb = 1, len;
PWSTR sz = (PWSTR)Environment;
while (*sz)
{
len = wcslen(sz) + 1;
sz += len;
cb += len;
}
DWORD SessionId, rcb;
if (GetTokenInformation(hToken, TokenSessionId, &SessionId, sizeof(SessionId), &rcb))
{
PROFILEINFO pi = { sizeof(pi), PI_NOUI, L"*" };
if (LoadUserProfileW(hToken, &pi))
{
WCHAR SubKey[48];
swprintf(SubKey, L"Volatile Environment\\%d", SessionId);
HKEY hKey;
if (ERROR_SUCCESS == RegOpenKeyExW((HKEY)pi.hProfile, SubKey, 0, KEY_READ, &hKey))
{
cb *= sizeof(WCHAR);
ULONG cbNeed = 0x200, cbAllocated;
PVOID NewEnvironment;
do
{
if (NewEnvironment = LocalAlloc(0, cb + (cbAllocated = cbNeed)))
{
cbNeed = AddSessionEnv(hKey, (PWSTR)NewEnvironment, cbAllocated);
if (cbNeed && cbAllocated >= cbNeed)
{
memcpy((PBYTE)NewEnvironment + cbNeed, Environment, cb);
*pNewEnvironment = NewEnvironment;
break;
}
LocalFree(NewEnvironment);
}
} while (cbNeed);
RegCloseKey(hKey);
}
UnloadUserProfile(hToken, pi.hProfile);
}
}
}
static volatile UCHAR guz;
ULONG AddSessionEnv(HANDLE hKey, PWSTR sz, ULONG Length)
{
LONG status;
PVOID stack = alloca(guz);
ULONG TotalLength = 0, DataLength, Index = 0, cb = 0, rcb = sizeof(KEY_VALUE_FULL_INFORMATION) + 256;
union {
PVOID buf;
PKEY_VALUE_FULL_INFORMATION pkvfi;
};
do
{
do
{
if (cb < rcb) cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
if (0 <= (status = ZwEnumerateValueKey(hKey, Index, KeyValueFullInformation, buf, cb, &rcb)) &&
pkvfi->Type == REG_SZ &&
(DataLength = pkvfi->DataLength) &&
!(DataLength & (sizeof(WCHAR) - 1)))
{
static const UNICODE_STRING CharSet = { 2 * sizeof(WCHAR), 2 * sizeof(WCHAR), L"="};
USHORT NonInclusivePrefixLength;
UNICODE_STRING Name = { (USHORT)pkvfi->NameLength, Name.Length, pkvfi->Name };
// not add strings which containing 0 or `=` symbol or emply
if (Name.Length && RtlFindCharInUnicodeString(0, &Name, &CharSet, &NonInclusivePrefixLength) == STATUS_NOT_FOUND)
{
UNICODE_STRING Value = {
(USHORT)DataLength,
Value.Length,
(PWSTR)RtlOffsetToPointer(pkvfi, pkvfi->DataOffset)
};
PWSTR szEnd = (PWSTR)RtlOffsetToPointer(Value.Buffer, DataLength - sizeof(WCHAR));
if (!*szEnd) Value.Length -= sizeof(WCHAR);
// not add empty strings or containing 0 or `=` symbol
if (Value.Length && RtlFindCharInUnicodeString(0, &Value, &CharSet, &NonInclusivePrefixLength) == STATUS_NOT_FOUND)
{
ULONG cbNeed = Name.Length + 2 * sizeof(WCHAR) + Value.Length;
if (Length >= cbNeed)
{
sz += 1 + swprintf(sz, L"%wZ=%wZ", &Name, &Value), Length -= cbNeed;
}
else
{
Length = 0;
}
TotalLength += cbNeed;
}
}
}
} while (status == STATUS_BUFFER_OVERFLOW || status == STATUS_BUFFER_TOO_SMALL );
Index++;
} while (status != STATUS_NO_MORE_ENTRIES);
return TotalLength;
}
and use this code as:
PVOID Environment, NewEnvironment = 0;
if (CreateEnvironmentBlock(&Environment, hToken, FALSE))
{
AddSessionEnv(hToken, Environment, &NewEnvironment);
CreateProcessAsUserW(hToken, *, CREATE_UNICODE_ENVIRONMENT,
NewEnvironment ? NewEnvironment : Environment, *);
if (NewEnvironment)
{
LocalFree(NewEnvironment);
}
DestroyEnvironmentBlock(Environment);
}
the definition of RtlFindCharInUnicodeString
which i use, for for comfort
enum {
RTL_FIND_CHAR_IN_UNICODE_STRING_START_AT_END = 1,
RTL_FIND_CHAR_IN_UNICODE_STRING_COMPLEMENT_CHAR_SET = 2,
RTL_FIND_CHAR_IN_UNICODE_STRING_CASE_INSENSITIVE = 4
};
NTSYSAPI
NTSTATUS
NTAPI
RtlFindCharInUnicodeString(
ULONG Flags,
PCUNICODE_STRING StringToSearch,
PCUNICODE_STRING CharSet,
USHORT *NonInclusivePrefixLength
);
回答2:
After some experimentation, it appears that CreateEnvironmentBlock
only sets CLIENTNAME
if the process is running in the same Remote Desktop session that is associated with the token. Impersonation did not make any difference. This is arguably a bug in Windows.
To work around this, you could add CLIENTNAME
to the environment block yourself, or you could launch a process in the user's session to call CreateEnvironmentBlock
function on your behalf.
来源:https://stackoverflow.com/questions/45940033/clientname-is-not-set-after-a-process-is-created-with-createprocessasuser-and-cr