C#으로 원격 제어 응용 프로그램을 작성하여 기술 지원 도구 세트를 작성합니다. Winlogon 데스크톱에 SendInput을 사용할 수 없다는 점을 제외하고는 모두 잘 작동합니다. 필자는 Default에서 Winlogon으로의 변경을 성공적으로 감지하고 있으며이를 전환하여 스크린 샷을 캡처 할 수 있습니다. 그것은 단지 SendInput 함수를 받아들이지 않습니다. 나는 TeamViewer가 그것을하기 때문에 가능하다는 것을 알고 있으며, 매니페스트에 uiAccess = true가 없다. 그들은 내가하는 것과 같은 과정을 사용하고있는 것처럼 보입니다.C#/pinvoke를 사용하여 WinSta0 Winlogon 데스크톱으로 SendInput하는 방법
다음은 제가 한 마디로하고있는 것입니다 : 서비스를 설치하십시오. 서비스가 연결 요청을 수신합니다. 서비스는 사용자 프로세스에서 CreateProcessAsUser와 winlogon.exe에서 중복 된 액세스 토큰으로 새로운 프로세스를 시작합니다. 뷰어가 새 프로세스에 연결됩니다.
누가 새로운 프로세스가 SendInput에 대한 액세스 권한을 잃어 버렸는지 식별 할 수 있습니까? 다음은 서비스에서 새 프로세스를 시작하는 데 사용하는 코드입니다. 다음은 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);