一. 背景介绍

在 Windows 系统管理中,注销用户会话是一个常见的需求。Windows 提供了命令行工具 logoff 来实现这一功能,其基本语法如下:

LOGOFF [sessionname | sessionid] [/SERVER:servername] [/V] [/VM]

  sessionname         指定要注销的会话名称
  sessionid           指定要注销的会话 ID
  /SERVER:servername  指定远程终端服务器(默认为本地服务器)
  /V                  显示详细的操作信息
  /VM                 注销虚拟机中的会话(需要指定唯一会话 ID)

通过逆向分析 logoff 命令的实现原理,我们发现它主要依赖于 Windows Terminal Services API(也称为 WinSta API)中的两个关键函数:

  1. WinStationOpenServerW - 用于获取服务器句柄
  2. WinStationReset - 用于执行会话注销操作

ae674d3b68a84b3fafea45d7211f0c45

通过调用这些底层 API,我们可以实现与 logoff 命令相同的功能:

00efff20cb0f404b9c14e1565001a957

二.编写调用代码 ​

1.导出函数定义

Winsta API微软并没有公布文档,但还是能找到泄露的函数定义
https://processhacker.sourceforge.io/doc/winsta_8h.html

typedef HANDLE(WINAPI* WinStationOpenServerW_t)(_In_ PWSTR ServerName);
typedef BOOL(WINAPI* WinStationReset_t)(_In_opt_ HANDLE hServer, _In_ ULONG SessionId, _In_ BOOLEAN bWait);

2.加载DLL

HMODULE hModule = LoadLibrary(L"winsta.dll");

if (hModule == NULL)
{
	cout << "Error [-]: failed loading winsta.dll" << endl;
	return 1;
}

3.获取函数地址

WinStationOpenServerW_t WinStationOpenServerW = (WinStationOpenServerW_t)GetProcAddress(hModule, "WinStationOpenServerW");

if (WinStationOpenServerW == NULL)
{
	cout << "Error [-]: failed getting WinStationOpenServerW function address" << endl;
	return 1;
}

WinStationReset_t WinStationReset = (WinStationReset_t)GetProcAddress(hModule, "WinStationReset");
if (WinStationReset == NULL)
{
	cout << "Error [-]: failed getting WinStationReset function address" << endl;
    return 1;
}

4.获取计算机名称

WinStationOpenServerW函数需传入servername,这里使用GetComputerName函数获取名称

wchar_t serverName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(serverName) / sizeof(serverName[0]);
GetComputerNameW(serverName, &size);
if (serverName[0] == L'\0')
{
	cout << "Error [-]: failed getting server name" << endl;
	return 1;
}

5.调用函数

HANDLE hServer = WinStationOpenServerW(serverName);
if (hServer == NULL)
{
	cout << "Error [-]: failed opening server" << endl;
}

if (WinStationReset(hServer, sessionID, TRUE))
{
     cout << "Success [+]: session " << sessionID << " reset" << endl;
}

三.完整代码

#include<Windows.h>
#include<iostream>
using namespace std;

int main(int argc, char* argv[])
{
	if (argc < 2)
	{
		cout << "Usage: "<< argv[0] <<" <sessionID>" << endl;
		return 1;
	}

	auto sessionID = atoi(argv[1]);


	typedef HANDLE(WINAPI* WinStationOpenServerW_t)(_In_ PWSTR ServerName);
	typedef BOOL(WINAPI* WinStationReset_t)(_In_opt_ HANDLE hServer, _In_ ULONG SessionId, _In_ BOOLEAN bWait);

	HMODULE hModule = LoadLibrary(L"winsta.dll");

	if (hModule == NULL)
	{
		cout << "Error [-]: failed loading winsta.dll" << endl;
		return 1;
	}

	WinStationOpenServerW_t WinStationOpenServerW = (WinStationOpenServerW_t)GetProcAddress(hModule, "WinStationOpenServerW");
	if (WinStationOpenServerW == NULL)
	{
		cout << "Error [-]: failed getting WinStationOpenServerW function address" << endl;
		return 1;
	}

	wchar_t serverName[MAX_COMPUTERNAME_LENGTH + 1];
	DWORD size = sizeof(serverName) / sizeof(serverName[0]);
	GetComputerNameW(serverName, &size);
	if (serverName[0] == L'\0')
	{
		cout << "Error [-]: failed getting server name" << endl;
		return 1;
	}

	WinStationReset_t WinStationReset = (WinStationReset_t)GetProcAddress(hModule, "WinStationReset");
	if (WinStationReset == NULL)
	{
		cout << "Error [-]: failed getting WinStationReset function address" << endl;
		return 1;
	}

	HANDLE hServer = WinStationOpenServerW(serverName);
	if (hServer == NULL)
	{
		cout << "Error [-]: failed opening server" << endl;
		return 1;
	}

	if (WinStationReset(hServer, sessionID, TRUE))
	{
		cout << "Success [+]: session " << sessionID << " reset" << endl;
	}
	else
	{
		cout << "Error [-]: failed to reset session " << sessionID << endl;
	}

	return 0;
}