tgjarwl的博客

道可道,非常道;名可名,非常名

记录生活,记录更好的自己。


获取当前所有登录的用户

Life

我一直坚信,在计算机的世界里没有秘密,只要用心寻找,很多东西就躺在那里,等着我们去慢慢的发掘。

调研

因为项目需求,需要获取当前登录的所有的用户,于是顺手就google了下。

https://blog.csdn.net/kingfox/article/details/116353723

这篇文章总结了几种获取用户名的方式,

1. GetUserName
2. GetEnvironmentVariable
3. Windows Terminal Session API
4. 通过获取当前Shell的创建者来间接获取当前登录的用户名
5. quser 工具获取

第一种有一个缺点就是只能获取到当前进程的用户名。第二种的缺点是获取的结果不是很理想。第三种最低支持的系统是vista,这让从xp开始的系统没法搞了。第四种确实一种可行的方案,每一个登陆的用户都会创建explorer.exe,如果同时两个用户登录该机器,则会出现两个不同的用户名的explorer.exe,但是实现的代码着实让人觉得复杂。

最后的重点就是微软提供的这个工具了

C:\Users\twin7>quser
 用户名                会话名             ID  状态    空闲时间   登录时间
>twin7                 console             1  运行中          .  2022/2/15 9:33
 administrator                             2  断开            .  2022/2/15 10:25

这不就是我心目中的那个它么!!!

原理

那就探究quser到底使用了哪个api,拖入ida后,发现了关键的函数过程


    ...

    if ( !(unsigned __int8)WinStationEnumerateW(hServerName, (int)&v12, (int)&v13) )
    {
      v10 = GetLastError();
      ErrorPrintf(0x66u, v10);
      PutStdErr(v10, 0);
      return 1;
    }
    v14 = 0;
    if ( v13 > 0 )
    {
      v9 = 0;
      do
      {
        DisplayUserInfo(0, hServerName, (int *)(v9 + v12), &user_string);
        ++v14;
        v9 += 76;
      }
      while ( v14 < v13 );
    }
    WinStationFreeMemory(v12);

    ...
    

DisplayUserInfo的关键代码如下:

char __userpurge DisplayUserInfo@<al>(char a1@<bl>, int a2, int *a3, wchar_t *user_string)
{
  
  ...

  result = WinStationQueryInformationW(a2, v4, 8, (int)&v13, 1216, (int)&v12);
  if ( !result )
    return result;
  v10 = a1;
  if ( !Str )
    return result;
  v6 = __wcslwr(&Str);
  TruncateString((int)v6, 0x14u);
  v7 = __wcslwr(&WideCharStr);
  TruncateString((int)v7, 0xFu);
  if ( !MatchedOne )
  {
    Message(0x70u, a1);
    MatchedOne = 1;
  }
  if ( a2 || v15 != CurrentLogonId )
    ANSI2OEM_Wprintf(L" ", v10);
  else
    ANSI2OEM_Wprintf(L">", v10);
  WideCharToMultiByte(1u, 0, &Str, -1, &MultiByteStr, 1024, 0, 0);
  WideCharToMultiByte(1u, 0, &WideCharStr, -1, &v21, 1024, 0, 0);
  v8 = &byte_100141B;
  if ( v13 != 4 )
    v8 = &v21;
  _fprintf(&__iob[1], "%-20s  %-15s  ", &MultiByteStr, v8);
  v9 = (const WCHAR *)StrConnectState(v13, 1);
  if ( v9 )
  {
    WideCharToMultiByte(1u, 0, v9, -1, &v20, 1024, 0, 0);
    _fprintf(&__iob[1], "%4u  %-6s  ", v15, &v20);
  }
  else
  {
    _fprintf(&__iob[1], "%4u  %-6s  ", v15, &byte_100141B);
  }
  DisplayLastInputTime(&v19, &v16);
  DisplayTime(&v17);
  result = ANSI2OEM_Wprintf(L"\n", v11);
  return result;
}

而在这个过程中,发现了两个关键的函数 WinStationEnumerateW 和 WinStationQueryInformationW 从这两个函数的名字,就会发现,这很微软的作风。通过搜索发现,这两个函数来自于 winsta.dll,通过进一步分析,发现最终涉及到一本协议。[MS-TSTS]: Terminal Services Terminal Server Runtime Interface Protocol。

关键细节,可以看最后的源码。

完整代码

最后整理后的代码如下,可以达到quser相同的效果:



#define MAX_THINWIRECACHE   4
#define DOMAIN_LENGTH   17
#define USERNAME_LENGTH   20
#define WINSTATIONNAME_LENGTH   32
typedef WCHAR WINSTATIONNAME[WINSTATIONNAME_LENGTH + 1];

typedef  enum _WINSTATIONSTATECLASS
{
    State_Active = 0,
    State_Connected = 1,
    State_ConnectQuery = 2,
    State_Shadow = 3,
    State_Disconnected = 4,
    State_Idle = 5,
    State_Listen = 6,
    State_Reset = 7,
    State_Down = 8,
    State_Init = 9
} WINSTATIONSTATECLASS;

typedef  enum _WINSTATIONINFOCLASS
{
    WinStationCreateData,
    WinStationConfiguration,
    WinStationPdParams,
    WinStationWd,
    WinStationPd,
    WinStationPrinter,
    WinStationClient,
    WinStationModules,
    WinStationInformation,
    WinStationTrace,
    WinStationBeep,
    WinStationEncryptionOff,
    WinStationEncryptionPerm,
    WinStationNtSecurity,
    WinStationUserToken,
    WinStationUnused1,
    WinStationVideoData,
    WinStationInitialProgram,
    WinStationCd,
    WinStationSystemTrace,
    WinStationVirtualData,
    WinStationClientData,
    WinStationSecureDesktopEnter,
    WinStationSecureDesktopExit,
    WinStationLoadBalanceSessionTarget,
    WinStationLoadIndicator,
    WinStationShadowInfo,
    WinStationDigProductId,
    WinStationLockedState,
    WinStationRemoteAddress,
    WinStationIdleTime,
    WinStationLastReconnectType,
    WinStationDisallowAutoReconnect,
    WinStationUnused2,
    WinStationUnused3,
    WinStationUnused4,
    WinStationUnused5,
    WinStationReconnectedFromId,
    WinStationEffectsPolicy,
    WinStationType,
    WinStationInformationEx
} WINSTATIONINFOCLASS;

typedef struct _SESSIONIDW {
    union {
        ULONG SessionId;
        ULONG LogonId;
    } _SessionId_LogonId_union;
    WINSTATIONNAME WinStationName;
    WINSTATIONSTATECLASS State;
} SESSIONIDW,
*PSESSIONIDW;

typedef struct _TSHARE_COUNTERS {
    ULONG Reserved;
} TSHARE_COUNTERS,
*PTSHARE_COUNTERS;

typedef struct _PROTOCOLCOUNTERS {
    ULONG WdBytes;
    ULONG WdFrames;
    ULONG WaitForOutBuf;
    ULONG Frames;
    ULONG Bytes;
    ULONG CompressedBytes;
    ULONG CompressFlushes;
    ULONG Errors;
    ULONG Timeouts;
    ULONG AsyncFramingError;
    ULONG AsyncOverrunError;
    ULONG AsyncOverflowError;
    ULONG AsyncParityError;
    ULONG TdErrors;
    USHORT ProtocolType;
    USHORT Length;
    union {
        TSHARE_COUNTERS TShareCounters;
        ULONG Reserved[100];
    } Specific;
} PROTOCOLCOUNTERS,
*PPROTOCOLCOUNTERS;

typedef struct _THINWIRECACHE {
    ULONG CacheReads;
    ULONG CacheHits;
} THINWIRECACHE,
*PTHINWIRECACHE;

typedef struct _RESERVED_CACHE {
    THINWIRECACHE ThinWireCache[MAX_THINWIRECACHE];
} RESERVED_CACHE,
*PRESERVED_CACHE;

typedef struct _TSHARE_CACHE {
    ULONG Reserved;
} TSHARE_CACHE,
*PTSHARE_CACHE;

typedef struct CACHE_STATISTICS {
    USHORT ProtocolType;
    USHORT Length;
    union {
        RESERVED_CACHE ReservedCacheStats;
        TSHARE_CACHE TShareCacheStats;
        ULONG Reserved[20];
    } Specific;
} CACHE_STATISTICS,
*PCACHE_STATISTICS;

typedef struct _PROTOCOLSTATUS {
    PROTOCOLCOUNTERS Output;
    PROTOCOLCOUNTERS Input;
    CACHE_STATISTICS Cache;
    ULONG AsyncSignal;
    ULONG AsyncSignalMask;
} PROTOCOLSTATUS,
*PPROTOCOLSTATUS;

typedef struct _WINSTATIONINFORMATIONW {
    WINSTATIONSTATECLASS ConnectState;
    WINSTATIONNAME WinStationName;
    ULONG LogonId;
    LARGE_INTEGER ConnectTime;
    LARGE_INTEGER DisconnectTime;
    LARGE_INTEGER LastInputTime;
    LARGE_INTEGER LogonTime;
    PROTOCOLSTATUS Status;
    WCHAR Domain[DOMAIN_LENGTH + 1];
    WCHAR UserName[USERNAME_LENGTH + 1];
    LARGE_INTEGER CurrentTime;
} WINSTATIONINFORMATIONW,
*PWINSTATIONINFORMATIONW;

typedef BOOL(WINAPI *pfn_WinStationEnumerateW)(_In_opt_ HANDLE hServer, _Out_ PSESSIONIDW *SessionIds, _Out_ PULONG Count);

typedef BOOL(WINAPI *pfn_WinStationQueryInformationW)(_In_opt_ HANDLE hServer, _In_ ULONG SessionId,
    _In_ WINSTATIONINFOCLASS WinStationInformationClass,
    _Out_writes_bytes_(WinStationInformationLength) PVOID pWinStationInformation, _In_ ULONG WinStationInformationLength,
    _Out_ PULONG pReturnLength);
typedef BOOL(WINAPI *pfn_WinStationFreeMemory)(_In_ PVOID Buffer);


int main(int argc, const char * argv[])
{
    BOOL bRet = FALSE;
    pfn_WinStationEnumerateW            pWinStationEnumerateW = NULL;
    pfn_WinStationFreeMemory            pWinStationFreeMemory = NULL;
    pfn_WinStationQueryInformationW     pWinStationQueryInformationW = NULL;

    HMODULE hDll = LoadLibrary(L"winsta.dll");
    if (hDll)
    {
        pWinStationEnumerateW = (pfn_WinStationEnumerateW)GetProcAddress(hDll, "WinStationEnumerateW");
        pWinStationFreeMemory = (pfn_WinStationFreeMemory)GetProcAddress(hDll, "WinStationFreeMemory");
        pWinStationQueryInformationW = (pfn_WinStationQueryInformationW)GetProcAddress(hDll, "WinStationQueryInformationW");
    }

    PSESSIONIDW pTerm = NULL;
    ULONG       uTermCount = 0;

    /*
    DWORD dwSessionID = -1;
    BOOL bRet = ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionID);
    if (bRet == FALSE)
    {
        return 0;
    }
    */

    do
    {
        DWORD dwSize = sizeof(SESSIONIDW);
        bRet = pWinStationEnumerateW(NULL, &pTerm, &uTermCount);
        if (bRet == FALSE)
        {
            break;
        }


        for (ULONG i = 0; i < uTermCount; ++i)
        {
            WINSTATIONINFORMATIONW pStationInfo;
            ULONG uInfoLen = sizeof(WINSTATIONINFORMATIONW);
            ULONG uRetLen = 0;
            if (FALSE == pWinStationQueryInformationW(NULL, pTerm->_SessionId_LogonId_union.SessionId, WinStationInformation, &pStationInfo, uInfoLen, &uRetLen))
            {
                break;
            }
            if (pStationInfo.UserName[0])
            {
                wprintf(L"domain : %s, user : %s \n", pStationInfo.Domain, pStationInfo.UserName);
            }

            pTerm++;
        }
        
    } while (FALSE);
    
    if (pTerm)
    {
        pWinStationFreeMemory(pTerm);
    }


    system("pause");

    return 0;
}

测试结果

最终的测试结果如下,且支持xp。

domain : twin7-PC, user : twin7
domain : twin7-PC, user : Administrator
请按任意键继续. . .