如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面
Posted
技术标签:
【中文标题】如何使用 C#/pinvoke 将输入发送到 WinSta0\\Winlogon 桌面【英文标题】:How to SendInput to WinSta0\Winlogon desktop using C#/pinvoke如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面 【发布时间】:2017-01-29 19:11:47 【问题描述】:我正在用 C# 为一套技术支持工具编写一个远程控制应用程序。一切正常,除了我无法使用 SendInput 到 Winlogon 桌面。我成功检测到从 Default 到 Winlogon 的更改,并且能够切换到它并捕获屏幕截图。它只是不接受 SendInput 功能。我知道这是可能的,因为 TeamViewer 做到了,而且他们的清单中没有 uiAccess=true 。他们似乎正在使用与我相同的过程。
简而言之,这就是我正在做的事情:安装服务。服务侦听连接请求。服务在用户会话中使用 CreateProcessAsUser 和来自 winlogon.exe 的重复访问令牌启动新进程。查看器连接到新进程。
任何人都可以确定缺少什么以使新进程可以访问 SendInput 到 winlogon 吗?这是我用来从服务启动新进程的代码。接下来是我用来检测对 Winlogon 桌面的更改并切换到它的代码。
public static bool OpenProcessAsSystem(string applicationName, out PROCESS_INFORMATION procInfo)
try
uint winlogonPid = 0;
IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
procInfo = new PROCESS_INFORMATION();
// Obtain session ID for active session.
uint dwSessionId = Kernel32.WTSGetActiveConsoleSessionId();
// Check for RDP session. If active, use that session ID instead.
var rdpSessionID = GetRDPSession();
if (rdpSessionID > 0)
dwSessionId = rdpSessionID;
// Obtain the process ID of the winlogon process that is running within the currently active session.
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
if ((uint)p.SessionId == dwSessionId)
winlogonPid = (uint)p.Id;
// Obtain a handle to the winlogon process.
hProcess = Kernel32.OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// Obtain a handle to the access token of the winlogon process.
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
Kernel32.CloseHandle(hProcess);
return false;
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// Copy the access token of the winlogon process; the newly created token will be a primary token.
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
return false;
// By default, CreateProcessAsUser creates a process on a non-interactive window station, meaning
// the window station has a desktop that is invisible and the process is incapable of receiving
// user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user
// interaction with the new process.
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
// flags that specify the priority and creation method of the process
uint dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current user's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
// invalidate the handles
Kernel32.CloseHandle(hProcess);
Kernel32.CloseHandle(hPToken);
Kernel32.CloseHandle(hUserTokenDup);
return result;
catch
procInfo = new PROCESS_INFORMATION() ;
return false;
public static uint GetRDPSession()
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSAPI32.WTSEnumerateSessions(WTSAPI32.WTS_CURRENT_SERVER_HANDLE, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTSAPI32.WTS_SESSION_INFO));
var sessList = new List<WTSAPI32.WTS_SESSION_INFO>();
Int64 current = (int)ppSessionInfo;
if (retval != 0)
for (int i = 0; i < count; i++)
WTSAPI32.WTS_SESSION_INFO sessInf = (WTSAPI32.WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTSAPI32.WTS_SESSION_INFO));
current += dataSize;
sessList.Add(sessInf);
uint retVal = 0;
var rdpSession = sessList.Find(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0);
if (sessList.Exists(ses => ses.pWinStationName.ToLower().Contains("rdp") && ses.State == 0))
retVal = (uint)rdpSession.SessionID;
return retVal;
这是我用来捕获屏幕、检测桌面变化并切换到它的内容。
var hWnd = User32.GetDesktopWindow();
var hDC = User32.GetWindowDC(hWnd);
var graphDC = graphic.GetHdc();
var copyResult = GDI32.BitBlt(graphDC, 0, 0, totalWidth, totalHeight, hDC, 0, 0, GDI32.TernaryRasterOperations.SRCCOPY | GDI32.TernaryRasterOperations.CAPTUREBLT);
// Change to input desktop if copy fails.
if (!copyResult)
var inputDesktop = User32.OpenInputDesktop();
if (User32.SetThreadDesktop(inputDesktop) == false)
graphic.Clear(System.Drawing.Color.White);
var font = new Font(FontFamily.GenericSansSerif, 30, System.Drawing.FontStyle.Bold);
graphic.DrawString("Waiting for screen capture...", font, Brushes.Black, new PointF((totalWidth / 2), totalHeight / 2), new StringFormat() Alignment = StringAlignment.Center );
var error = Marshal.GetLastWin32Error();
writeToErrorLog(new Exception("Failed to open input desktop. Error: " + error.ToString()));
var dw = User32.GetDesktopWindow();
User32.SetActiveWindow(dw);
User32.SetForegroundWindow(dw);
User32.CloseDesktop(inputDesktop);
graphic.ReleaseHdc(graphDC);
User32.ReleaseDC(hWnd, hDC);
【问题讨论】:
【参考方案1】:我让 SendInput 在登录桌面上工作(事实证明,在 UAC 安全桌面上)。 SetThreadDesktop 不得为您提供与您最初在目标桌面中启动该进程时相同的权限。
因此,当我检测到桌面更改时,我没有调用 SetThreadDesktop,而是使用 CreateProcessAsUser 在新桌面中启动了另一个进程。然后我示意查看器切换并关闭当前进程。
编辑(多年后):我最终错了。您只需要确保当前线程在当前桌面中没有任何打开的窗口或挂钩。由于这只是为调用线程(而不是进程)设置桌面,因此其他线程也需要调用它。
【讨论】:
嗨 Jared,我是 windows 编程和 dot.net 的新手。如果你能告诉我上面的代码应该放在哪里编译,那就太好了?如果您提供此工作脚本,那就太好了。我正在寻找一些可以自动解锁 Windows 10 机器的实用程序。提前致谢。 上面的代码允许从运行会话 0 的服务向登录屏幕发送输入。不过,它不会绕过密码或 PIN。您仍然需要手动输入密码才能解锁。这是完全实现它的项目:github.com/jay-rad/adit 嗨 Jared,感谢您的解释和 repo 链接。很快就会试一试。干杯。以上是关于如何使用 C#/pinvoke 将输入发送到 WinSta0\Winlogon 桌面的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 pinvoke 将二进制数据缓冲区从 C 传递到 C#
pinvoke:不确定如何使用 dllimport 和导入的库
在 c# 和 c++ 之间将 double 类型的二维多维数组作为输入和输出的 pinvoke 编组