Skip to content

Back to the catalog

0xv1n.github.iowriteup

Remote Session Enumeration via Undocumented Windows APIs

A reverse-engineering walkthrough of qwinsta.exe showing it uses the undocumented WinStation API (winsta.dll) — WinStationOpenServerW, WinStationEnumerateW, WinStationQueryInformationW — rather than the documented Terminal Services (WTS) API used in harmj0y's prior PowerShell approach. The author demonstrates that remote session enumeration via these APIs succeeds against a target host even when Remote Desktop is disabled, and provides C++ code to load winsta.dll, resolve the undocumented function pointers via GetProcAddress, and parse the resulting byte-array structures to extract usernames and session state.

TL;DR Reverse-engineers qwinsta to show how the undocumented winsta.dll WinStation API enables remote Windows session/user enumeration even with RDP disabled.
Document

What it is

A walkthrough of reimplementing qwinsta.exe's remote session enumeration by calling the undocumented WinStation API in winsta.dll directly, rather than the documented Terminal Services / WTS API (wtsapi32.h). The author confirms via disassembly that qwinsta uses WinStation, and rebuilds the same capability in C++.

How it works

winsta.dll exports the functions but ships with no SDK header (no winsta.h/winsta32.h), so you cannot link against it normally. The technique: define your own function prototypes as typedefs, LoadLibrary("winsta.dll"), resolve each export with GetProcAddress, and call through the pointers. The five functions are invoked in a fixed order:

flowchart LR
    A[WinStationOpenServerW<br/>PWSTR hostname] --> B[WinStationEnumerateW<br/>list sessions]
    B --> C[WinStationQueryInformationW<br/>per-session detail]
    C --> D[WinStationFreeMemory]
    D --> E[WinStationCloseServer]

The structures needed by these calls (e.g. SESSIONIDW, which unions SessionId/LogonId with WinStationName and WINSTATIONSTATECLASS) are also undocumented; the author sources both prototypes and structs from the Process Hacker winsta.h (https://processhacker.sourceforge.io/doc/winsta_8h_source.html), porting each dependent struct one at a time.

Preconditions / Affected

Remote enumeration targets a Windows host via /server:{hostname} (or WinStationOpenServerW(serverName)). The author's key claim: RDP does not need to be enabled on the target for enumeration to succeed, despite the API's Remote Desktop framing.

What to do

For the initial open/close handle proof, the working snippet resolves and calls two exports:

typedef HANDLE(WINAPI* LPFN_WinStationOpenServerW)(PWSTR);
typedef BOOLEAN(WINAPI* LPFN_WinStationCloseServer)(HANDLE);

HINSTANCE hDLL = LoadLibrary(TEXT("winsta.dll"));
LPFN_WinStationOpenServerW pfnWinStationOpenServerW =
    (LPFN_WinStationOpenServerW)GetProcAddress(hDLL, "WinStationOpenServerW");
wchar_t serverName[] = L"TARGET_HOST";
HANDLE hServer = pfnWinStationOpenServerW(serverName);

Enumeration then uses:

typedef BOOLEAN(WINAPI* LPFN_WinStationEnumerateW)(HANDLE, PSESSIONIDW*, PULONG);

For defenders, the detection surface is the pairing of a winsta.dll load with WinStation* calls (rather than wtsapi32.dll) from non-standard processes, and remote WinStationOpenServerW traffic where RDP is disabled.

Caveats

The fetched text cuts off at the WinStationEnumerateW call, so the full enumerate/query loop and output parsing are not shown. All prototypes and structs are community-reverse-engineered (Process Hacker), not Microsoft-documented, so they carry no stability guarantee across Windows versions.

Organisations named here

  • Grzegorz Tworek
  • harmj0y
  • WinStationEnumerateW
  • WinStationOpenServerW
  • WinStationQueryInformationW

Technologies named here

  • qwinsta
  • Windows
  • winsta.dll
  • wtsapi32.h / Terminal Services API
  • PowerShell
  • Disassembler
  • winsta.h
  • wintern.h
  • RDP
  • Process Hacker

Techniques named here

  • Remote session enumeration via undocumented WinStation API
  • Byte array parsing of undocumented struct fields