Clientname is not set after a process is created with CreateProcessAsUser and CreateEnvironmentBlock

主宰稳场 提交于 2019-12-22 18:15:50

问题


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 CreateProcessAsUserfunction 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!