我编写了一个在本地系统帐户下运行的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;
}
如果您需要更多信息或代码示例,请随时提问。
答案 0 :(得分:2)
环境变量不仅取决于用户 SID ,还取决于 SessionId ,因为某些变量是每个会话。
我们可以在HKEY_USERS\<SID>\Volatile Environment
用户环境变量下的注册表中查看。此处存在SessionId
个子键。在子键下 - 每个会话变量
所以CreateEnvironmentBlock
必须接下来做 - 从令牌中获取用户 SID ,打开HKEY_USERS\<SID>\Volatile Environment
键,并查询其值。
然后必须通过SessionId
查询来自令牌的GetTokenInformation(hToken, TokenSessionId, )
并查询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 - &gt; PEB
2c0h这是PEB中SessionId的偏移量
当您从服务执行应用程序时 - SessionId
中的PEB
为0,结果CLIENTNAME
且SESSIONNAME
未添加到环境块。
这是常见的系统错误。对于测试,你可以运行两个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 - 没有此字符串。这是因为来自 svchost.exe -k netsvcs 的CreateEnvironmentBlock
来自SessionId
== 0
for fix可以是双向的:
简单但不正确:
_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
);
答案 1 :(得分:1)
经过一些实验,如果进程在与令牌关联的同一远程桌面会话中运行,CreateEnvironmentBlock
似乎只设置CLIENTNAME
。假冒没有任何区别。这可以说是Windows中的一个错误。
要解决此问题,您可以自行将CLIENTNAME
添加到环境块,或者您可以在用户的会话中启动一个流程来代表您调用CreateEnvironmentBlock
功能。