2016年6月

WTSQueryUserToken的一点事

起因用户态进程 hToken 为Null。
HANDLE hToken = WTSQueryUserToken(WTSGetActiveConsoleSessionId());

为啥获取的是Null呢。

WTSGetActiveConsoleSessionId function

Retrieves the session identifier of the console session. The console session is the session that is currently attached to the physical console. Note that it is not necessary that Remote Desktop Services be running for this function to succeed.

kernel32!WTSGetActiveConsoleSessionId的结构十分简单:

; Exported entry 1274. WTSGetActiveConsoleSessionId



; DWORD __stdcall WTSGetActiveConsoleSessionId()
public _WTSGetActiveConsoleSessionId@0
_WTSGetActiveConsoleSessionId@0 proc near
mov     eax, ds:7FFE02D8h
retn
_WTSGetActiveConsoleSessionId@0 endp

7ffe02d8h是什么?查询属于SharedUserData+0x2d8,也即

struct KUSER_SHARED_DATA
typedef struct _KUSER_SHARED_DATA
{
     ULONG TickCountLowDeprecated;
     ULONG TickCountMultiplier;
     KSYSTEM_TIME InterruptTime;
     KSYSTEM_TIME SystemTime;
     KSYSTEM_TIME TimeZoneBias;
     WORD ImageNumberLow;
     WORD ImageNumberHigh;
     WCHAR NtSystemRoot[260];
     ULONG MaxStackTraceDepth;
     ULONG CryptoExponent;
     ULONG TimeZoneId;
     ULONG LargePageMinimum;
     ULONG Reserved2[7];
     NT_PRODUCT_TYPE NtProductType;
     UCHAR ProductTypeIsValid;
     ULONG NtMajorVersion;
     ULONG NtMinorVersion;
     UCHAR ProcessorFeatures[64];
     ULONG Reserved1;
     ULONG Reserved3;
     ULONG TimeSlip;
     ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture;
     LARGE_INTEGER SystemExpirationDate;
     ULONG SuiteMask;
     UCHAR KdDebuggerEnabled;
     UCHAR NXSupportPolicy;
     ULONG ActiveConsoleId;

这个字段。

SharedUserData是Windows为各进程提供的共享数据结构,存有许多系统信息,如时间等等,看上面的定义,名字应该都是自解释的。

#define KI_USER_SHARED_DATA        0xffdf0000
#define SharedUserData  ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)

SharedUserData在内核中的地址是0xffdf0000,Windows通过共享映射把这个结构以只读方式映射到每个进程的0x7ffe0000的地址中,可以供各内存使用。因此,使用该API取出该字段的值是不成问题的。因此问题就出在外面的WTSQueryUserToken中。

根据MSDN的说法,只有LocalSystem且包含SE_TCB_NAME权限的进程可以调用它。

0:019> x *!*WTSQueryUserToken*
768ef736          kernel32!WTSQueryUserToken (<no parameter info>)
00a75514          XXX!_imp__WTSQueryUserToken = <no type information>
74cb1f81          WTSAPI32!WTSQueryUserToken (<no parameter info>)
761f52e6          iertutil!WTSQueryUserToken (<no parameter info>)
761ec526          iertutil!ext_ms_win_session_wtsapi32_l1_1_0_WTSQueryUserToken (<no parameter info>)
0:019> dd 00a75514
00a75514  74cb1f81 00000000 75110965 75118683
0:019> u 74cb1f81 
WTSAPI32!WTSQueryUserToken:
74cb1f81 8bff            mov     edi,edi
74cb1f83 55              push    ebp
74cb1f84 8bec            mov     ebp,esp
74cb1f86 83ec0c          sub     esp,0Ch
74cb1f89 56              push    esi
74cb1f8a 8b750c          mov     esi,dword ptr [ebp+0Ch]
74cb1f8d 85f6            test    esi,esi
74cb1f8f 0f845d0a0000    je      WTSAPI32!WTSQueryUserToken+0x10 (74cb29f2)

查看服务进程XXX的引用,可以发现实现者是wtsapi32!WTSQueryUserToken。

signed int __stdcall WTSQueryUserToken(int a1, int a2)
{
  int v2; // esi@1
  DWORD v4; // [sp+4h] [bp-Ch]@3
  DWORD v5; // [sp+8h] [bp-8h]@3
  int v6; // [sp+Ch] [bp-4h]@4

  v2 = a2;
  if ( a2 )
  {
    if ( IsProcessPrivileged() )
    {
      v4 = GetCurrentProcessId();
      v5 = GetCurrentThreadId();
      if ( (unsigned __int8)WinStationQueryInformationW(0, a1, 14, &v4, 12, &a2) )
      {
        *(_DWORD *)v2 = v6;
        return 1;
      }
    }
  }
  else
  {
    SetLastError(0x57u);
  }
  return 0;
}


signed int __cdecl IsProcessPrivileged()
{
  HANDLE v0; // eax@1
  HANDLE v1; // eax@3
  BOOL v2; // edi@4
  signed int result; // eax@8
  struct _PRIVILEGE_SET RequiredPrivileges; // [sp+8h] [bp-1Ch]@4
  BOOL pfResult; // [sp+1Ch] [bp-8h]@1
  HANDLE hObject; // [sp+20h] [bp-4h]@1

  pfResult = 0;
  hObject = 0;
  v0 = GetCurrentThread();
  if ( !OpenThreadToken(v0, 0x2000000u, 0, &hObject) )
  {
    if ( GetLastError() != 1008 || (v1 = GetCurrentProcess(), !OpenProcessToken(v1, 0x2000000u, &hObject)) )
      goto LABEL_14;
  }
  RequiredPrivileges.Privilege[0].Luid.LowPart = 7;
  RequiredPrivileges.PrivilegeCount = 1;
  RequiredPrivileges.Control = 1;
  RequiredPrivileges.Privilege[0].Luid.HighPart = 0;
  RequiredPrivileges.Privilege[0].Attributes = 2;
  v2 = PrivilegeCheck(hObject, &RequiredPrivileges, &pfResult);
  if ( v2 && !pfResult )
    SetLastError(0x522u);
  CloseHandle(hObject);
  if ( v2 && pfResult )
    result = 1;
  else
LABEL_14:
    result = 0;
  return result;
}

阅读相关代码,可以发现确实如此,用户态进程在直接调用这个函数时,必然会因为权限检查而失败。而在使用x过程中可以看到另一个比较好玩的东西,WTSQueryUserToken在kernel32.dll中也有实现,是这样的:

.text:7DE0F5DE ; __stdcall WTSQueryUserToken(x, x)
.text:7DE0F5DE _WTSQueryUserToken@8 proc near          ; DATA XREF: .text:7DD726E4o
.text:7DE0F5DE                                         ; .text:7DD726ECo ...
.text:7DE0F5DE                 push    65Ah            ; dwErrCode
.text:7DE0F5E3                 call    _SetLastError@4 ; SetLastError(x)
.text:7DE0F5E8                 xor     eax, eax
.text:7DE0F5EA                 retn    8
.text:7DE0F5EA _WTSQueryUserToken@8 endp

显然不能获取正确的UserToken。为什么微软要在kernel32中也实现一个同名的呢?很谜。直接调kernel32.dll的函数,它的LastError会告诉你不应该调这个DLL里的WTS函数,可能是因为kernel32中导出了WTSGetActiveConsoleSessionId,所以微软就认为大家可能也会认为WTSQueryUserToken在kernel32中导出?完全不明白。

#define ERROR_FUNCTION_NOT_CALLED    1626L
BOOL kernel32!WTSQueryUserToken(_In_  ULONG   SessionId,  _Out_ PHANDLE phToken)
{
    NOT_IMPLEMENTED();

    SetLastError(ERROR_FUNCTION_NOT_CALLED); //无法执行函数。 
    return FALSE;
}

那如何在应用态进程获取user token呢?easy,先找到explorer.exe,OpenProcess打开之获取到Token即是。