使用CreateProcessAsUser和CreateEnvironmentBlock创建进程后,不会设置Clientname

我编写了一个在本地系统帐户下运行的C#服务。 当用户登录到Terminalserver时,我用它来生成进程。 该服务实现OnSessionChange方法,并使用相应的SessionID接收SessionChangeDescription消息。

我使用此SessionID通过WTSQueryUserToken从用户获取访问令牌。 我将此标记转换为主标记并将其传递给CreateEnvironmentBlock以检索指向用户环境变量的指针。 经过一些进一步的准备工作后,我调用CreateProcessAsUser函数最终将我的进程作为最近登录的用户在winsta0\default桌面上生成。

当我使用ProcessExplorer调查该过程时,我发现进程上下文中没有CLIENTNAME环境变量。 然而,应用程序需要这个变量。

我不知道我做错了什么。 或许我错过了一些东西。 应该加载用户配置文件,因为我在用户登录时做出反应。

是否可能存在一些时间问题? 或者CLIENTNAME var是否以任何其他方式应用于进程?

以下是我调用CreateEnvironmentBlock函数的方法:

  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; } 

如果您需要更多信息或代码示例,请随时提出。

环境变量不仅取决于用户SID,还取决于SessionId ,因为每个会话都有一些变量。

我们可以在HKEY_USERS\\Volatile Environment用户环境变量下的注册表中查看。 和SessionId子键存在。 在子键下 – 每个会话变量 在此处输入图像描述

所以CreateEnvironmentBlock必须做下一步 – 从令牌获取用户SID ,打开HKEY_USERS\\Volatile Environment键,并查询它的值。

然后必须通过GetTokenInformation(hToken, TokenSessionId, ) 从令牌中查询SessionId并查询Volatile Environment\SessionId子键。

但是错误的系统使用来自当前进程PEB的 SessionId而不是获取它的令牌。 下一个代码在系统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这是PEB中SessionId的偏移量

当您从服务执行应用程序时 – PEB中的SessionId为0,因此CLIENTNAMESESSIONNAME未添加到环境块中。

这是常见的系统错误。 对于测试,你可以运行两个cmd.exe – 一个没有提升(exec来自explorer.exe ),一个以admin身份运行(exec来自svchost.exe -k netsvcs ),然后在两个set命令中运行 – 它显示环境字符串。 你可以注意到,在没有提升的cmd.exe存在字符串SESSIONNAME=Console (或者如果你从rdp运行它,则SESSIONNAME=RDP-Tcp#N ),如果你在rdp中,则CLIENTNAME=DESKTOP-xxx 。 但在提升(以管理员身份运行) cmd.exe中 – 没有此字符串。 这是因为CreateEnvironmentBlocksvchost.exe -k netsvcs调用,其中SessionId == 0

修复这可以是2种方式:

容易,但不正确:

  _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; 

想法在这里 – 临时将PEB中的SessionId替换为来自令牌的SessionId 。 这将是有效的。 在这里不好 – 如果并发中的另一个线程将在PEB中使用SessionId怎么办?

另一种方式,相对大的代码,但正确 – 你自己通过SessionId子键和扩展环境块。

 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; } 

并使用此代码:

  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); } 

我使用的RtlFindCharInUnicodeString的定义,为了舒适

 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 ); 

经过一些实验后,如果进程在与令牌关联的同一远程桌面会话中运行,则CreateEnvironmentBlock似乎仅设置CLIENTNAME 。 假冒没有任何区别。 这可以说是Windows中的一个错误。

要解决此问题,您可以自己将CLIENTNAME添加到环境块,或者您可以在用户会话中启动进程以代表您调用CreateEnvironmentBlock函数。