【问题标题】:Clientname is not set after a process is created with CreateProcessAsUser and CreateEnvironmentBlock使用 CreateProcessAsUser 和 CreateEnvironmentBlock 创建进程后未设置 Clientname
【发布时间】:2018-02-06 23:09:36
【问题描述】:

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

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

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

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

有没有可能,有一些时间问题?或者 CLIENTNAME 变量是否以任何其他方式应用于进程?

这是我如何调用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;
    }

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

【问题讨论】:

  • 你打电话给CreateEnvironmentBlock - 那么在什么问题看来 - CLIENTNAME 字符串存在于newEnvironment 中吗?和I convert this token into a primary token - WTSQueryUserToken 获得主访问令牌 - 所以不需要转换
  • @RbMm 我将编写一些通过newEnvironment 数组的代码。如果我使用DuplicateTokenEx 将主令牌转换为主令牌,是否会造成一些伤害?
  • 不,如果你打电话给DuplicateTokenExm 不会有什么坏处,但是你需要复制它吗?您可以按照从WTSQueryUserToken 返回的方式使用它。你需要看看PWSTR sz = (PWSTR)lpEnvironment; while (*sz) { DbgPrint("%S\n", sz); sz += wcslen(sz) + 1; }CLIENTNAME 在场。如果它不存在于块中-它将不存在并且不在子进程中。如果它存在 - 可能你没有在 CreateProcessAsUser 中使用 CREATE_UNICODE_ENVIRONMENT 标志
  • 您可能需要先致电LoadUserProfile(),然后再致电CreateEnvironmentBlock()
  • @RbMm 我确实在 CreateProcessAsUser 的 creationFlags 中使用了 CREATE_UNICODE_ENVIRONMENT 标志(值为 0x00000400)。

标签: c# winapi windows-services environment-variables


【解决方案1】:

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

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

【讨论】:

  • 是的,你几乎是正确的 - CreateEnvironmentBlock 使用调用者进程 SessionId 来检查环境变量,而不是来自令牌的 SessionId。但是一些环境变量依赖于SessionId - 看看HKEY_USERS\S-*\Volatile Environment\SessionId - 这里通常是CLIENTNAMESESSIONNAME。我如何发现是否正常运行某些进程 - 环境中存在字符串SESSIONNAME=Console。但是如果以管理员身份运行它 - 没有这个字符串,因为在这种情况下进程是从svchost.exe 执行的,它在会话 0 中运行
  • @RbMm,当我在不匹配的会话(会话零和与令牌关联的会话)中运行我的测试代码时,它根本没有设置 CLIENTNAME。我认为它可能会检索与代码正在运行的会话关联的客户端名称,但事实并非如此。我想这是因为目标用户帐户在Volatile Environment 中没有相应的会话条目。
  • 我用SESSIONNAME 字符串测试,注意它存在于所有未提升的进程中。但如果我以 adim 身份运行进程 - 没有这个 var。可以在自己的代码中轻松测试 - 运行测试程序,该程序查询提升而不提升的字符串。这是因为 CreateEnvironmentBlock 使用来自 PEBSessionId 而不是来自给定令牌。关于CLIENTNAME,它是如何在远程桌面中设置的 - 需要检查。但在所有情况下,它都是从注册表节点读取的
  • 是的,和CLIENTNAME绝对一样的情况,只需要通过rdp进入windows即可。最简单的检查方法 - 运行 2 cmd.exe(以管理员身份而不是提升权限)并运行 set cmd。当以管理员 cmd.exe 运行时,您可以在未提升的 cmd.exe - CLIENTNAME=DESKTOP-2N5ODLBSESSIONNAME=RDP-Tcp#3 中查看 - 没有此字符串。
  • @wolf633 it appears that CreateEnvironmentBlock only sets CLIENTNAME if the process is running in the same Remote Desktop session that is associated with the token - 这完全是 100%。我在自己的回答中简单地解释了为什么会这样 - 因为 CreateEnvironmentBlock 使用 SessionId 来自调用进程 PEB 而不是来自令牌。并且只有当它相同(进程会话 id == 令牌会话 id)时,才会分配正确的 CLIENTNAME。你可以先试试短代码。尽管理论上它不是 100% 正确,但实际上出现某些副作用的可能性非常低
【解决方案2】:

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

我们可以在HKEY_USERS\<SID>\Volatile Environment用户环境变量下的注册表中查看。和 SessionId 这里存在子键。在子键下 - 每个会话变量

所以CreateEnvironmentBlock 必须下一步 - 从令牌中获取用户SID,打开HKEY_USERS\<SID>\Volatile Environment 密钥,并查询它的值。

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

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

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

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

为了解决这个问题,可以有两种方式:

简单,但不正确:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-30
    • 1970-01-01
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多