本文介绍了使用CreateProcessAsUser和CreateEnvironmentBlock创建进程后未设置客户端名称的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了在本地系统帐户下运行的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 CreateProcessAsUserfunction 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,结果是CLIENTNAMESESSIONNAME未添加到环境块中.

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创建进程后未设置客户端名称的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 23:57