问题描述
我编写了在本地系统帐户下运行的C#服务.当用户登录终端服务器时,我用它来生成一个进程.该服务实现OnSessionChange
方法,并接收具有相应SessionID的SessionChangeDescription
消息.
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.
我使用此SessionID通过WTSQueryUserToken
从用户获取访问令牌.我将此令牌转换为主要令牌,并将其传递给CreateEnvironmentBlock
以检索指向用户环境变量的指针.经过进一步的准备后,我调用CreateProcessAsUser
函数,最终以最近登录到其winsta0\default
桌面的用户的身份生成我的进程.
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.
当我使用ProcessExplorer
调查流程时,发现流程上下文中没有CLIENTNAME环境变量.但是应用程序需要此变量.
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.
是否可能存在一些计时问题?还是以其他任何方式将CLIENTNAME var应用于流程?
Is it possible, that there is some timing issue? Or does the CLIENTNAME var gets applied to a process in any other way?
这是我调用CreateEnvironmentBlock
函数的方式:
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.
推荐答案
环境变量不仅取决于用户 SID ,而且还取决于 SessionId ,因为每个变量都是会议.
the environment variables depend not only from user SID but from SessionId too, because some variable was per session.
我们可以在HKEY_USERS\<SID>\Volatile Environment
用户环境变量下的注册表中查看.存在SessionId
子键.在子键下-每个会话变量
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
因此CreateEnvironmentBlock
必须做的下一步-从令牌中获取用户 SID ,打开HKEY_USERS\<SID>\Volatile Environment
密钥,然后查询其值.
so CreateEnvironmentBlock
must do next - get user SID from token, open HKEY_USERS\<SID>\Volatile Environment
key, and query it values.
然后必须通过GetTokenInformation(hToken, TokenSessionId, )
从令牌查询SessionId
并查询Volatile Environment\SessionId
子键.
then must query SessionId
from token via GetTokenInformation(hToken, TokenSessionId, )
and query Volatile Environment\SessionId
sub key.
但是错误地系统使用了当前进程 PEB 中的SessionId
而不是从获取它的令牌中使用.下一个代码在系统dll中:
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,这是PEB中SessionId的偏移量
mov rax,gs:[60h]// rax -> PEB
2c0h this is offset of SessionId in PEB
当您从服务执行应用程序时-PEB
中的SessionId
为0,结果是CLIENTNAME
和SESSIONNAME
未添加到环境块中.
when you exec application from service - the SessionId
in PEB
was 0, as result CLIENTNAME
and SESSIONNAME
not added to environment block.
这是常见的系统错误.为了进行测试,您可以运行两个cmd.exe
-一个未提升的(从 explorer.exe 执行),另一个以管理员身份运行(从 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
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:
简单但不正确:
_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
怎么办?
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 ?
另一种方法,相对较大的代码,但是正确-自己遍历SessionId
子键并扩展环境块.
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;
}
,并将此代码用作:
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
的定义
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
);
这篇关于使用CreateProcessAsUser和CreateEnvironmentBlock创建进程后未设置客户端名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!