9remote 2.1.20 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/tray.ps1 +8 -0
- package/dist/bin/desktop-bridge.cs +174 -0
- package/dist/bin/desktop-elevate.cs +107 -0
- package/dist/cli.cjs +61 -61
- package/dist/ptyDaemon.cjs +6 -7
- package/dist/server.cjs +83 -81
- package/dist/ui/assets/{index-UFRGjGeL.js → index-vnJprY0p.js} +3932 -3813
- package/dist/ui/index.html +1 -1
- package/package.json +2 -2
package/dist/assets/tray.ps1
CHANGED
|
@@ -70,6 +70,14 @@ $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
|
70
70
|
$script:notifyIcon.ContextMenuStrip = $script:menu
|
|
71
71
|
$script:items = @()
|
|
72
72
|
|
|
73
|
+
# Left-click also opens the menu (right-click is the Windows default)
|
|
74
|
+
$script:notifyIcon.Add_MouseClick({
|
|
75
|
+
param($sender, $e)
|
|
76
|
+
if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
|
|
77
|
+
$script:menu.Show([System.Windows.Forms.Cursor]::Position, [System.Windows.Forms.ToolStripDropDownDirection]::BelowRight)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
73
81
|
function Write-Event($obj) {
|
|
74
82
|
$json = $obj | ConvertTo-Json -Compress
|
|
75
83
|
[Console]::Out.WriteLine($json)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// Desktop bridge daemon (runs as Local System). Named pipe server "9remote-desktop".
|
|
2
|
+
// Protocol (line-based ASCII):
|
|
3
|
+
// STATE -> "DESKTOP <name>" (name == "Winlogon" when on the login screen)
|
|
4
|
+
// TYPE <text> -> clears the field, types text via SendInput + VkKeyScanW, Enter; replies "OK"
|
|
5
|
+
// csc -nologo -target:winexe -out:desktop-bridge.exe desktop-bridge.cs
|
|
6
|
+
using System;
|
|
7
|
+
using System.Diagnostics;
|
|
8
|
+
using System.IO;
|
|
9
|
+
using System.IO.Pipes;
|
|
10
|
+
using System.Runtime.InteropServices;
|
|
11
|
+
using System.Security.AccessControl;
|
|
12
|
+
using System.Security.Principal;
|
|
13
|
+
using System.Threading;
|
|
14
|
+
|
|
15
|
+
class DesktopBridge {
|
|
16
|
+
[DllImport("user32.dll", SetLastError = true)] static extern uint SendInput(uint n, INPUT[] p, int cb);
|
|
17
|
+
[DllImport("user32.dll", SetLastError = true)] static extern IntPtr OpenInputDesktop(uint f, bool inh, uint da);
|
|
18
|
+
[DllImport("user32.dll", SetLastError = true)] static extern bool SetThreadDesktop(IntPtr h);
|
|
19
|
+
[DllImport("user32.dll", SetLastError = true)] static extern bool CloseDesktop(IntPtr h);
|
|
20
|
+
[DllImport("user32.dll", SetLastError = true)] static extern bool GetUserObjectInformationW(IntPtr o, int i, IntPtr p, uint len, out uint need);
|
|
21
|
+
[DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern short VkKeyScanW(char c);
|
|
22
|
+
|
|
23
|
+
// GENERIC_ALL required — GENERIC_READ silently drops keystrokes into Winlogon desktop.
|
|
24
|
+
const uint GENERIC_ALL = 0x10000000;
|
|
25
|
+
const uint INPUT_KEYBOARD = 1;
|
|
26
|
+
const uint KEYEVENTF_KEYUP = 0x0002;
|
|
27
|
+
const int UOI_NAME = 2;
|
|
28
|
+
const ushort VK_BACK = 0x08;
|
|
29
|
+
const ushort VK_RETURN = 0x0D;
|
|
30
|
+
const ushort VK_SHIFT = 0x10;
|
|
31
|
+
const string PIPE = "9remote-desktop";
|
|
32
|
+
|
|
33
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
34
|
+
struct MOUSEINPUT { public int dx, dy; public uint mouseData, dwFlags, time; public IntPtr dwExtraInfo; }
|
|
35
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
36
|
+
struct KEYBDINPUT { public ushort wVk, wScan; public uint dwFlags, time; public IntPtr dwExtraInfo; }
|
|
37
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
38
|
+
struct HARDWAREINPUT { public uint uMsg; public ushort wParamL, wParamH; }
|
|
39
|
+
[StructLayout(LayoutKind.Explicit)]
|
|
40
|
+
struct MKH {
|
|
41
|
+
[FieldOffset(0)] public MOUSEINPUT mi;
|
|
42
|
+
[FieldOffset(0)] public KEYBDINPUT ki;
|
|
43
|
+
[FieldOffset(0)] public HARDWAREINPUT hi;
|
|
44
|
+
}
|
|
45
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
46
|
+
struct INPUT { public uint type; public MKH u; }
|
|
47
|
+
|
|
48
|
+
static string NameOf(IntPtr h) {
|
|
49
|
+
uint need;
|
|
50
|
+
GetUserObjectInformationW(h, UOI_NAME, IntPtr.Zero, 0, out need);
|
|
51
|
+
if (need == 0) return "unknown";
|
|
52
|
+
IntPtr buf = Marshal.AllocHGlobal((int)need);
|
|
53
|
+
try {
|
|
54
|
+
GetUserObjectInformationW(h, UOI_NAME, buf, need, out need);
|
|
55
|
+
return Marshal.PtrToStringUni(buf);
|
|
56
|
+
} finally { Marshal.FreeHGlobal(buf); }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static string ActiveDesktop() {
|
|
60
|
+
IntPtr h = OpenInputDesktop(0, false, GENERIC_ALL);
|
|
61
|
+
if (h == IntPtr.Zero) return "none";
|
|
62
|
+
string n = NameOf(h);
|
|
63
|
+
CloseDesktop(h);
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static void AttachActive() {
|
|
68
|
+
IntPtr h = OpenInputDesktop(0, false, GENERIC_ALL);
|
|
69
|
+
if (h != IntPtr.Zero) { SetThreadDesktop(h); CloseDesktop(h); }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Log to worker.log beside the exe — tailed by the agent so SendInput failures
|
|
73
|
+
// (wrong session, wrong desktop, UIPI block) surface without a console.
|
|
74
|
+
// NOTE: csc v4.0.30319 is C# 5.0 — no string interpolation ($""), no expression-bodied members.
|
|
75
|
+
static string LOG = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "worker.log");
|
|
76
|
+
static void L(string m) {
|
|
77
|
+
try { File.AppendAllText(LOG, "[" + DateTime.Now.ToString("HH:mm:ss.fff") + "] " + m + "\n"); } catch {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Atomic down+up in ONE SendInput call — matches the .docs/login pattern that
|
|
81
|
+
// was verified working. The Winlogon credential UI can drop keystrokes when
|
|
82
|
+
// key-down and key-up are sent in separate SendInput calls.
|
|
83
|
+
static void SendVK(ushort vk) {
|
|
84
|
+
INPUT[] inp = new INPUT[2];
|
|
85
|
+
inp[0].type = INPUT_KEYBOARD; inp[0].u.ki.wVk = vk;
|
|
86
|
+
inp[1].type = INPUT_KEYBOARD; inp[1].u.ki.wVk = vk; inp[1].u.ki.dwFlags = KEYEVENTF_KEYUP;
|
|
87
|
+
uint r = SendInput((uint)inp.Length, inp, Marshal.SizeOf(typeof(INPUT)));
|
|
88
|
+
if (r == 0) L("SendInput FAIL vk=0x" + vk.ToString("X2") + " winerr=" + Marshal.GetLastWin32Error());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Single event (down or up) for modifier-hold sequences (shifted chars).
|
|
92
|
+
static void SendKey(ushort vk, bool up) {
|
|
93
|
+
INPUT inp = new INPUT { type = INPUT_KEYBOARD };
|
|
94
|
+
inp.u.ki.wVk = vk;
|
|
95
|
+
if (up) inp.u.ki.dwFlags = KEYEVENTF_KEYUP;
|
|
96
|
+
INPUT[] arr = new INPUT[] { inp };
|
|
97
|
+
uint r = SendInput(1, arr, Marshal.SizeOf(typeof(INPUT)));
|
|
98
|
+
if (r == 0) L("SendKey FAIL vk=0x" + vk.ToString("X2") + " up=" + up + " winerr=" + Marshal.GetLastWin32Error());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// VkKeyScanW high byte: bit0 = Shift, bit1 = Ctrl, bit2 = Alt. Only Shift handled here.
|
|
102
|
+
static void TypeText(string text) {
|
|
103
|
+
int sess = Process.GetCurrentProcess().SessionId;
|
|
104
|
+
string beforeDesk = ActiveDesktop();
|
|
105
|
+
L("TYPE session=" + sess + " desktop=" + beforeDesk + " len=" + text.Length);
|
|
106
|
+
AttachActive();
|
|
107
|
+
string afterDesk = ActiveDesktop();
|
|
108
|
+
if (afterDesk != beforeDesk) L(" attach flipped desktop " + beforeDesk + " -> " + afterDesk);
|
|
109
|
+
for (int i = 0; i < 30; i++) { SendVK(VK_BACK); Thread.Sleep(20); }
|
|
110
|
+
Thread.Sleep(80);
|
|
111
|
+
foreach (char c in text) {
|
|
112
|
+
short k = VkKeyScanW(c);
|
|
113
|
+
ushort vk = (ushort)(k & 0xFF);
|
|
114
|
+
bool shift = (k & 0x0100) != 0;
|
|
115
|
+
L(" char='" + c + "' vk=0x" + vk.ToString("X2") + " shift=" + shift);
|
|
116
|
+
if (shift) {
|
|
117
|
+
SendKey(VK_SHIFT, false);
|
|
118
|
+
SendKey(vk, false); Thread.Sleep(25); SendKey(vk, true);
|
|
119
|
+
SendKey(VK_SHIFT, true);
|
|
120
|
+
} else {
|
|
121
|
+
SendVK(vk); // atomic — same path as .docs/login for digits
|
|
122
|
+
}
|
|
123
|
+
Thread.Sleep(30);
|
|
124
|
+
}
|
|
125
|
+
Thread.Sleep(80);
|
|
126
|
+
SendVK(VK_RETURN);
|
|
127
|
+
L("TYPE done");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static void Main() {
|
|
131
|
+
try { L("start session=" + Process.GetCurrentProcess().SessionId + " pid=" + Process.GetCurrentProcess().Id + " user=" + WindowsIdentity.GetCurrent().Name); } catch {}
|
|
132
|
+
// ACL: SYSTEM + Administrators + Authenticated Users so the user-scope agent can connect.
|
|
133
|
+
var sec = new PipeSecurity();
|
|
134
|
+
sec.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));
|
|
135
|
+
sec.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));
|
|
136
|
+
sec.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));
|
|
137
|
+
|
|
138
|
+
while (true) {
|
|
139
|
+
NamedPipeServerStream srv;
|
|
140
|
+
try {
|
|
141
|
+
srv = new NamedPipeServerStream(PIPE, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, sec);
|
|
142
|
+
} catch {
|
|
143
|
+
Thread.Sleep(1000); continue;
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
srv.WaitForConnection();
|
|
147
|
+
var sr = new StreamReader(srv);
|
|
148
|
+
var sw = new StreamWriter(srv) { AutoFlush = true };
|
|
149
|
+
string line;
|
|
150
|
+
while ((line = sr.ReadLine()) != null) {
|
|
151
|
+
line = line.Trim();
|
|
152
|
+
if (line.Length == 0) continue;
|
|
153
|
+
if (line == "STATE") {
|
|
154
|
+
sw.WriteLine("DESKTOP " + ActiveDesktop());
|
|
155
|
+
} else if (line.StartsWith("TYPE ")) {
|
|
156
|
+
TypeText(line.Substring(5));
|
|
157
|
+
sw.WriteLine("OK");
|
|
158
|
+
} else if (line == "STOP") {
|
|
159
|
+
sw.WriteLine("OK");
|
|
160
|
+
// Non-zero exit so the scheduled task's restart-on-failure policy
|
|
161
|
+
// fires and re-launches the worker (with the freshly rebuilt exe).
|
|
162
|
+
try { srv.Dispose(); } catch { }
|
|
163
|
+
Environment.Exit(1);
|
|
164
|
+
} else {
|
|
165
|
+
sw.WriteLine("ERR unknown");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
} finally {
|
|
170
|
+
try { srv.Dispose(); } catch { }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Desktop elevate launcher (run as admin via UAC). Spawns desktop-bridge.exe as
|
|
2
|
+
// Local System inside the console session so it can SendInput into the Winlogon
|
|
3
|
+
// (login) desktop. Worker path defaults to the launcher's own directory.
|
|
4
|
+
// csc -nologo -target:winexe -out:desktop-elevate.exe desktop-elevate.cs
|
|
5
|
+
using System;
|
|
6
|
+
using System.Diagnostics;
|
|
7
|
+
using System.IO;
|
|
8
|
+
using System.Runtime.InteropServices;
|
|
9
|
+
|
|
10
|
+
class DesktopElevate {
|
|
11
|
+
[DllImport("kernel32.dll")] static extern uint WTSGetActiveConsoleSessionId();
|
|
12
|
+
[DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess();
|
|
13
|
+
[DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr OpenProcess(uint da, bool inh, int pid);
|
|
14
|
+
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr h);
|
|
15
|
+
[DllImport("advapi32.dll", SetLastError = true)] static extern bool OpenProcessToken(IntPtr h, uint da, out IntPtr t);
|
|
16
|
+
[DllImport("advapi32.dll", SetLastError = true)] static extern bool DuplicateTokenEx(IntPtr ex, uint da, IntPtr sa, int imp, int t, out IntPtr dup);
|
|
17
|
+
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool LookupPrivilegeValueW(string sys, string name, out LUID luid);
|
|
18
|
+
[DllImport("advapi32.dll", SetLastError = true)] static extern bool AdjustTokenPrivileges(IntPtr h, bool dis, ref TOKEN_PRIVILEGES np, int len, IntPtr p, IntPtr l);
|
|
19
|
+
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
|
20
|
+
static extern bool CreateProcessWithTokenW(IntPtr h, uint logonFlags, string app, string cmd, uint flags, IntPtr env, string dir, ref STARTUPINFO si, out PROCESS_INFORMATION pi);
|
|
21
|
+
[DllImport("userenv.dll", SetLastError = true)] static extern bool CreateEnvironmentBlock(out IntPtr env, IntPtr hToken, bool inherit);
|
|
22
|
+
[DllImport("userenv.dll")] static extern bool DestroyEnvironmentBlock(IntPtr env);
|
|
23
|
+
|
|
24
|
+
const uint TOKEN_QUERY = 0x0008;
|
|
25
|
+
const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
|
26
|
+
const uint TOKEN_DUPLICATE = 0x0002;
|
|
27
|
+
const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
|
|
28
|
+
const uint TOKEN_ALL_ACCESS = 0xF01FF;
|
|
29
|
+
const uint PROCESS_QUERY_INFORMATION = 0x0400;
|
|
30
|
+
const int SecurityImpersonation = 2;
|
|
31
|
+
const int TokenPrimary = 1;
|
|
32
|
+
const uint SE_PRIVILEGE_ENABLED = 0x00000002;
|
|
33
|
+
const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
|
34
|
+
|
|
35
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
36
|
+
struct LUID { public uint LowPart; public int HighPart; }
|
|
37
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
38
|
+
struct LUID_AND_ATTRIBUTES { public LUID Luid; public uint Attributes; }
|
|
39
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
40
|
+
struct TOKEN_PRIVILEGES { public uint PrivilegeCount; public LUID_AND_ATTRIBUTES Privileges; }
|
|
41
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
42
|
+
struct STARTUPINFO {
|
|
43
|
+
public int cb; public string lpReserved; public string lpDesktop; public string lpTitle;
|
|
44
|
+
public int dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
|
|
45
|
+
public short wShowWindow, cbReserved2; public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
|
|
46
|
+
}
|
|
47
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
48
|
+
struct PROCESS_INFORMATION { public IntPtr hProcess, hThread; public int dwProcessId, dwThreadId; }
|
|
49
|
+
|
|
50
|
+
static void EnablePriv(IntPtr h, string n) {
|
|
51
|
+
LUID l;
|
|
52
|
+
if (!LookupPrivilegeValueW(null, n, out l)) return;
|
|
53
|
+
TOKEN_PRIVILEGES tp;
|
|
54
|
+
tp.PrivilegeCount = 1;
|
|
55
|
+
tp.Privileges.Luid = l;
|
|
56
|
+
tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED;
|
|
57
|
+
AdjustTokenPrivileges(h, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static void Main() {
|
|
61
|
+
uint sid = WTSGetActiveConsoleSessionId();
|
|
62
|
+
|
|
63
|
+
IntPtr hSelf;
|
|
64
|
+
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hSelf)) {
|
|
65
|
+
EnablePriv(hSelf, "SeImpersonatePrivilege");
|
|
66
|
+
EnablePriv(hSelf, "SeAssignPrimaryTokenPrivilege");
|
|
67
|
+
EnablePriv(hSelf, "SeIncreaseQuotaPrivilege");
|
|
68
|
+
CloseHandle(hSelf);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
int wpid = -1;
|
|
72
|
+
foreach (var p in Process.GetProcesses()) {
|
|
73
|
+
if (p.ProcessName.Equals("winlogon", StringComparison.OrdinalIgnoreCase) && p.SessionId == sid) { wpid = p.Id; break; }
|
|
74
|
+
}
|
|
75
|
+
if (wpid < 0) return;
|
|
76
|
+
|
|
77
|
+
IntPtr hp = OpenProcess(PROCESS_QUERY_INFORMATION, false, wpid);
|
|
78
|
+
if (hp == IntPtr.Zero) return;
|
|
79
|
+
|
|
80
|
+
IntPtr ht;
|
|
81
|
+
if (!OpenProcessToken(hp, TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY, out ht)) { CloseHandle(hp); return; }
|
|
82
|
+
|
|
83
|
+
IntPtr dup;
|
|
84
|
+
if (!DuplicateTokenEx(ht, TOKEN_ALL_ACCESS, IntPtr.Zero, SecurityImpersonation, TokenPrimary, out dup)) { CloseHandle(ht); CloseHandle(hp); return; }
|
|
85
|
+
|
|
86
|
+
IntPtr env;
|
|
87
|
+
if (!CreateEnvironmentBlock(out env, dup, false)) { CloseHandle(dup); CloseHandle(ht); CloseHandle(hp); return; }
|
|
88
|
+
|
|
89
|
+
// Worker lives beside this launcher exe.
|
|
90
|
+
string app = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "desktop-bridge.exe");
|
|
91
|
+
if (!File.Exists(app)) { DestroyEnvironmentBlock(env); CloseHandle(dup); CloseHandle(ht); CloseHandle(hp); return; }
|
|
92
|
+
|
|
93
|
+
var si = new STARTUPINFO { cb = Marshal.SizeOf(typeof(STARTUPINFO)) };
|
|
94
|
+
si.lpDesktop = @"winsta0\default";
|
|
95
|
+
|
|
96
|
+
// CreateProcessWithTokenW: the launcher itself runs elevated in the console
|
|
97
|
+
// session (spawned via Start-Process -Verb RunAs from the agent), so the
|
|
98
|
+
// worker inherits the console session and SendInput reaches the user's
|
|
99
|
+
// Winlogon desktop. Same pattern as .docs/login/launcher.cs.
|
|
100
|
+
PROCESS_INFORMATION pi;
|
|
101
|
+
bool ok = CreateProcessWithTokenW(dup, 0, app, "\"" + app + "\"", CREATE_UNICODE_ENVIRONMENT, env, AppDomain.CurrentDomain.BaseDirectory, ref si, out pi);
|
|
102
|
+
|
|
103
|
+
DestroyEnvironmentBlock(env);
|
|
104
|
+
if (ok) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); }
|
|
105
|
+
CloseHandle(dup); CloseHandle(ht); CloseHandle(hp);
|
|
106
|
+
}
|
|
107
|
+
}
|