穿透 SESSION 0 隔离

2022/4/5 23:19:43

本文主要是介绍穿透 SESSION 0 隔离,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

注:这套程序在这里只是作为了解,博主本人并未测试,所有测试均来自文章最底层链接的原文章。

查看目标进程运行的SESSION位置

在实际开发过程中,可以通过Process Explorer 检查服务或程序处于哪个Session,会不会遇到Session 0 隔离问题。可以看到svchost.exe处于SESSION 0。QQ作为用户进程运行在SESSION 1。

 

 


 

测试程序

下面来做一个名叫AlertService 的服务,它的作用就是向用户发出一个提示对话框,我们看看这个服务在Windows 7 中会发生什么情况。

using System.ServiceProcess;
using System.Windows.Forms;

namespace AlertService
{
    public partial class Service1 : ServiceBase
    {
        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            MessageBox.Show("A message from AlertService.");
        }

        protected override void OnStop()
        {
        }
    }
}

 


 

穿透 SESSION 0 隔离

对于简单的交互,服务可以通过 WTSSendMessage 函数,在用户 Session 上显示消息窗口。
对于一些复杂的UI 交互,必须调用CreateProcessAsUser 或其他方法(WCF、.NET远程处理等)进行跨 Session 通信,在桌面用户上创建一个应用程序界面。

 


 

WTSSendMessage 函数

如果服务只是简单的向桌面用户 Session 发送消息窗口,则可以使用WTSSendMessage 函数实现。首先,加入如下代码:

    public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;

    public static void ShowMessageBox(string message, string title)
    {
        int resp = 0;

        // 调用WTSSendMessage函数
        WTSSendMessage(
            WTS_CURRENT_SERVER_HANDLE,
            WTSGetActiveConsoleSessionId(),
            title, title.Length,
            message, message.Length,
            0, 0, out resp, false);
    }


    // 获取kernel32.dll的导出函数WTSGetActiveConsoleSessionId
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern int WTSGetActiveConsoleSessionId();

    // 获取wtsapi32.dll的导出函数WTSSendMessage
    [DllImport("wtsapi32.dll", SetLastError = true)]
    public static extern bool WTSSendMessage(
        IntPtr hServer,
        int SessionId,
        String pTitle,
        int TitleLength,
        String pMessage,
        int MessageLength,
        int Style,
        int Timeout,
        out int pResponse,
        bool bWait);

 

  在ShowMessageBox 函数中调用了WTSSendMessage 来发送信息窗口,这样我们就可以在Service 的OnStart 函数中使用,加入下面代码:

protected override void OnStart(string[] args)
{
    ShowMessageBox("This a message from AlertService.","AlertService Message");
}

 

编译程序后在服务管理器中重新启动AlertService 服务,从下图中可以看到消息窗口是在当前用户桌面显示的,而不是Session 0 中。

 

 

 


 

CreateProcessAsUser 函数

如果想通过服务向桌面用户 Session 创建一个复杂 UI 程序界面,则需要使用 CreateProcessAsUser 函数为用户创建一个新进程用来运行相应的程序。需要注意这个CreateProcessAsUser需要system权限,仅admin是不行的,虽然网上有例子说可以手动提升admin权限,但我在本地安全策略里添加了相关权限依旧不行。

CreateProcessAsUser,打开程序之前需要一个令牌降权,有两种方法,一个是获取用户的令牌

dwSessionID = ::WTSGetActiveConsoleSessionId();//获取用户id
if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken)//hToken获取到令牌
{
    int i = GetLastError();//获取错误编码
}

 

另一种方法是通过获取其他程序的令牌,当然这种很有可能出现意外

LPWSTR lpa = ConvertCharToLPWSTR(_T("EXPLORER.EXE"))
GetTokenByName(hToken, lpa)
BOOL   GetTokenByName(HANDLE   &hToken, LPWSTR   lpName)
{
	if (!lpName)
	{
		return   FALSE;
	}
	HANDLE  hProcessSnap = NULL;
	BOOL  bRet = FALSE;
	PROCESSENTRY32   pe32 = { 0 };
	hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProcessSnap == INVALID_HANDLE_VALUE)
		return   (FALSE);

	pe32.dwSize = sizeof(PROCESSENTRY32);

	if (Process32First(hProcessSnap, &pe32))
	{
		do
		{
			CString exefile = pe32.szExeFile;
			CString paraname = lpName;
			LPCSTR cname = wtoc(lpName).c_str;
			if (!exefile.CompareNoCase(cname))
			{
				HANDLE   hProcess =
					OpenProcess(PROCESS_QUERY_INFORMATION,
						FALSE, pe32.th32ProcessID);
				bRet = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
				CloseHandle(hProcessSnap);
				return   (bRet);
			}
		} while (Process32Next(hProcessSnap, &pe32));
		bRet = TRUE;
	}
	else
		bRet = FALSE;

	CloseHandle(hProcessSnap);
	return   (bRet);
}

  

这里使用的是第一种方案。

public static void CreateProcess(string app, string path)
{
    bool result;
    IntPtr hToken = WindowsIdentity.GetCurrent().Token;
    IntPtr hDupedToken = IntPtr.Zero;

    PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
    SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
    sa.Length = Marshal.SizeOf(sa);

    STARTUPINFO si = new STARTUPINFO();
    si.cb = Marshal.SizeOf(si);

    int dwSessionID = WTSGetActiveConsoleSessionId();            // 获得当前Session ID
    result = WTSQueryUserToken(dwSessionID, out hToken);        // 获得当前Session的用户令牌

    if (!result)
    {
        ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
    }

    // 复制令牌
    result = DuplicateTokenEx(
        hToken,
        GENERIC_ALL_ACCESS,
        ref sa,
        (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
        (int)TOKEN_TYPE.TokenPrimary,
        ref hDupedToken
    );

    if (!result)
    {
        ShowMessageBox("DuplicateTokenEx failed", "AlertService Message");
    }

    IntPtr lpEnvironment = IntPtr.Zero;
    result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);        // 创建用户Session环境

    if (!result)
    {
        ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
    }

    // 在复制的用户Session下执行应用程序,创建进程
    result = CreateProcessAsUser(
        hDupedToken,
        app,
        String.Empty,
        ref sa, ref sa,
        false, 0, IntPtr.Zero,
        path, ref si, ref pi);

    if (!result)
    {
        int error = Marshal.GetLastWin32Error();
        string message = String.Format("CreateProcessAsUser Error: {0}", error);
        ShowMessageBox(message, "AlertService Message");
    }

    if (pi.hProcess != IntPtr.Zero)
        CloseHandle(pi.hProcess);
    if (pi.hThread != IntPtr.Zero)
        CloseHandle(pi.hThread);
    if (hDupedToken != IntPtr.Zero)
        CloseHandle(hDupedToken);
}

[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
    public Int32 cb;
    public string lpReserved;
    public string lpDesktop;
    public string lpTitle;
    public Int32 dwX;
    public Int32 dwY;
    public Int32 dwXSize;
    public Int32 dwXCountChars;
    public Int32 dwYCountChars;
    public Int32 dwFillAttribute;
    public Int32 dwFlags;
    public Int16 wShowWindow;
    public Int16 cbReserved2;
    public IntPtr lpReserved2;
    public IntPtr hStdInput;
    public IntPtr hStdOutput;
    public IntPtr hStdError;
}

[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
    public IntPtr hProcess;
    public IntPtr hThread;
    public Int32 dwProcessID;
    public Int32 dwThreadID;
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public Int32 Length;
    public IntPtr lpSecurityDescriptor;
    public bool bInheritHandle;
}

public enum SECURITY_IMPERSONATION_LEVEL
{
    SecurityAnonymous,
    SecurityIdentification,
    SecurityImpersonation,
    SecurityDelegation
}

public enum TOKEN_TYPE
{
    TokenPrimary = 1,
    TokenImpersonation
}

public const int GENERIC_ALL_ACCESS = 0x10000000;

[DllImport("kernel32.dll", SetLastError = true,
    CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool CloseHandle(IntPtr handle);

[DllImport("advapi32.dll", SetLastError = true,
    CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern bool CreateProcessAsUser(
    IntPtr hToken,
    string lpApplicationName,
    string lpCommandLine,
    ref SECURITY_ATTRIBUTES lpProcessAttributes,
    ref SECURITY_ATTRIBUTES lpThreadAttributes,
    bool bInheritHandle,
    Int32 dwCreationFlags,
    IntPtr lpEnvrionment,
    string lpCurrentDirectory,
    ref STARTUPINFO lpStartupInfo,
    ref PROCESS_INFORMATION lpProcessInformation);

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateTokenEx(
    IntPtr hExistingToken,
    Int32 dwDesiredAccess,
    ref SECURITY_ATTRIBUTES lpThreadAttributes,
    Int32 ImpersonationLevel,
    Int32 dwTokenType,
    ref IntPtr phNewToken);

[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(
    Int32 sessionId,
    out IntPtr Token);

[DllImport("userenv.dll", SetLastError = true)]
static extern bool CreateEnvironmentBlock(
    out IntPtr lpEnvironment,
    IntPtr hToken,
    bool bInherit);

 

在CreateProcess 函数中同时也涉及到DuplicateTokenEx、WTSQueryUserToken、CreateEnvironmentBlock 函数的使用,有兴趣的朋友可通过MSDN 进行学习。完成CreateProcess 函数创建后,就可以真正的通过它来调用应用程序了,回到Service1.cs 修改一下OnStart 我们来打开一个CMD 窗口。如下代码:

protected override void OnStart(string[] args)
{
    Interop.CreateProcess("cmd.exe", @"C:\Windows\System32\");
}

 

     重新编译程序,启动AlertService 服务便可看到下图界面。至此,我们已经可以通过一些简单的方法对Session 0 隔离问题进行解决。大家也可以通过WCF 等技术完成一些更复杂的跨Session 通信方式,实现在Windows 7 及Vista 系统中服务与桌面用户的交互操作。

 

 

 

参考文章链接:
https://www.cnblogs.com/gnielee/archive/2010/04/07/session0-isolation-part1.html/
https://www.cnblogs.com/moshuixiong/p/12169281.html/

 



这篇关于穿透 SESSION 0 隔离的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程