起因用户态进程 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即是。