2017-08-29 17 views
2

我已经编写了在本地系统帐户下运行的C#服务。我用它在用户登录到终端服务器时产生一个进程。该服务执行OnSessionChange方法并接收具有相应SessionID的SessionChangeDescription消息。使用CreateProcessAsUser和CreateEnvironmentBlock创建进程后未设置客户端名称

我使用此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

你叫'CreateEnvironmentBlock' - 所以有什么问题的样子 - 是'newEnvironment'中存在'CLIENTNAME'字符串?和'我把这个令牌转换成一个主令牌' - 'WTSQueryUserToken'获得主访问令牌 - 所以没有必要转换 – RbMm

+0

@RbMm我会写一些代码,通过'newEnvironment'数组。如果我使用'DuplicateTokenEx'将主令牌转换为主令牌,会造成一些伤害吗? – wolf633

+0

不,如果你打电话给'DuplicateTokenEx'm但是为了你需要复制它,这将不会有什么坏处?你可以像从WTSQueryUserToken中返回一样使用它。你需要看'PWSTR sz =(PWSTR)lpEnvironment; \t \t \t而(* SZ) \t \t \t { \t \t \t \t DbgPrint( “%S \ n” 个,SZ); \t \t \t \t sz + = wcslen(sz)+ 1; \t \t \t}'是'CLIENTNAME'出现。如果它没有出现在块中 - 它将不会出现在子进程中。如果它存在 - 可能是你不使用'CreateProcessAsUser'中的'CREATE_UNICODE_ENVIRONMENT'标志 – RbMm

回答

2

的环境变量不仅取决于从用户SID的SessionID太多,因为有些变量是每个会话。

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

所以CreateEnvironmentBlock必须做下一个 - 获取用户SID从令牌,开放HKEY_USERS\<SID>\Volatile Environment键,查询它的值。

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

但由于错误系统使用SessionId从当前进程PEB而不是从它获取它的标记。下一个代码是在系统DLL:

WCHAR buf[MAX_PATH]; 
StringCchPrintfW(buf, RTL_NUMBER_OF(buf), 
    L"%s\\%d", L"Volatile Environment", RtlGetCurrentPeb()->SessionId); 

enter image description here

MOV RAX,GS:[60H] // RAX - > PEB
2c0h这在PEB偏移的SessionID的

当您从服务中执行应用程序 - 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 - 没有这个字符串。这是因为从的svchost.exe -k netsvcs具有SessionId == 0

用于修复被称为CreateEnvironmentBlock这可以是2路:

容易,但不正确:

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

想法这里 - 临时替代SessionIdPEBSessionId从令牌。这将是工作。 bu在这里坏 - 如果另一个并行线程将使用SessionIdPEB

另一种方式,相对较大的代码,但正确 - 你自己通过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

经过一些实验,看起来CreateEnvironmentBlock只设置CLIENTNAME如果进程在与令牌关联的同一个远程桌面会话中运行。假冒没有任何区别。这可以说是Windows中的一个错误。

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

+0

是的,你几乎正确 - 'CreateEnvironmentBlock'使用调用者进程'SessionId'来检查环境变量,但不使用来自令牌的'SessionId'。但有些环境依赖于'SessionId' - 查看'HKEY_USERS \ S - * \ volatile volatile \ SessionId' - 通常是'CLIENTNAME'和'SESSIONNAME'。我如何运行一些进程正常 - 如果存在字符串'SESSIONNAME =控制台'在它env。但是如果以admin的身份运行它 - 没有这个字符串,因为在这种情况下,进程是'svchost.exe'的exec,它运行在会话0 – RbMm

+0

@RbMm中,当我在不匹配的会话中运行我的测试代码时与令牌相关的会话),它根本没有设置“CLIENTNAME”。我认为它可能会检索与运行代码的会话相关联的客户端名称,但它没有。我想这是因为目标用户帐户在'Volatile Environment'内没有相应的会话条目。 –

+0

我用'SESSIONNAME'字符串测试,注意它存在于所有没有升级的进程中。但如果我运行进程作为adim - 没有这个变种。可以很容易地在自己的代码中测试这个 - 运行测试程序,查询这个字符串是否升高而不升高。这是因为'CreateEnvironmentBlock'使用来自* PEB *的'SessionId'而不是来自给定的令牌。关于'CLIENTNAME',它是如何在远程桌面设置的 - 需要检查。但在所有情况下,它从注册节点 – RbMm

相关问题