RDP, mstsc, shadow mode, 2 machines on different continents
Latest update:
On the diagram below, I'm on the right:
The goal is to "administer" the laptop on the left via the RDP
protocol in such a way that the person in Calgary (call him Bob) is
able to see what I'm doing. I beleive this is called "shadowing" in MS
parlance.
At first, I tried to use UltraVNC: installed it on the Calgary laptop
& connected to it via Remmina from a Fedora machine in Kyiv. The
results were pitiful: it was painfuly slow, even when cranking down
the colour scheme to 8-bit. UltraVNC works fine within a LAN, but when
the traffic goes through a VPN server--& especially when Bob uses his
phone as an internet gateway--it becomes almost unusable.
Then I switched on an RDP daemon on the Calgary laptop & tried to
connect to it using Remmina. It was performing noticeably better, but
the scheme was a no-go: regular Windows editions allow only 1 active
RDP session at a time, meaning I could interact with the remote
machine, but Bob would be automatically logged off, & vice-versa.
Most of all the articles & advices on this topic either assume you
have a Windows Terminal Server license or you're setting up machines
in a LAN that belong to a "domain" or both. None of that is my
case. The text assumes regular Windows 10/11 Pro editions.
"Server" (i.e., the Calgary laptop) setup
This should go without saying, but I want to remind the reader that
both machines must communicate with each other through a
VPN. Otherwise, the server will be vulnerable to being hacked through
its open RDP ports fairly quickly, regardless of how strong your
passwords may be.
Turn on Remote Desktop:
445 port must be open:
& a predefined Windows rule Remote Desktop - Shadow (TCP-In) is
turned on also:
In gpedit, set
Local Computer Policy
Administrative Templates
Windows Componets
Remote Desktop Services
Remote Desktop Session Host
Connections
Set rules for remote control of Remote Desktop Services user sessions
policy to "Full Control without user's permission":
Client (i.e., the Kyiv workstation)
To connect to the server, we need to know an active session number of
a user whose desktop we want to interact with. We can either ask Bob
to type
S> qwinsta
SESSIONNAME USERNAME ID STATE TYPE DEVICE
services 0 Disc
>console bob 1 Active
rdp-tcp 65536 Listen
in his terminal & tell us the number, or grab that value themselves,
for qwinsta can list sessions on a remote machine. The latter, of
course, will miscarry:
C> qwinsta /server:10.200.200.2
Error 5 getting sessionnames
Error [5]:Access is denied.
because we didn't supply any credentials for how to log into
10.200.200.2:
C> cmdkey /add:10.200.200.2 /user:bob /pass:abc123
Having the session id (say, 1),
C> mstsc /v:10.200.200.2 /shadow:1 /noConsentPrompt /control
should yield the final result.
Now, this implies we can only "shadow" active Bob's sessions. As soon
as he logs off or switches to another account, our connection to his
laptop gets terminated. This also means that if there are multiple
accouns on the laptop (say, bob
--an unpriviledged regular user &
root
--a local admin), we cannot use root
's cridentials to "shadow"
bob
's sessions.
To always allow specifying the admin cridentials regardless of which
user session we'd like to "shadow", modify the
LocalAccountTokenFilterPolicy
registry
entry on the server, & rerun
cmdkey:
C> cmdkey /add:10.200.200.2 /user:root /pass:abc123
This will override our previous bob
entry, for apparently Windows
Credential Manager doesn't support multiple entries for a single
host. (Why?)
Automation
The following PowerShell script retrieves an active user session id on
a server & connects to it:
C> ./shadow.ps1 10.200.200.2
It assumes you've already saved the appropriate server's credentials
on the client machine.
Add-Type -AssemblyName PresentationFramework
function errx($s) {
[void][System.Windows.MessageBox]::Show(
$s, $PSCommandPath,
[System.Windows.MessageBoxButton]::OK,
[System.Windows.MessageBoxImage]::Error
)
exit 1
}
if (!$args[0]) { errx "USAGE: shadow.ps1 192.168.1.1" }
$server = $args[0]
$sessions = qwinsta /server:$server 2>&1
if ($LastExitCode -ne 0) { errx $sessions }
$session = $sessions | findstr /r /c:"^ *console.*Active *$" | % { $_.split() } | where {$_}
if ($session) {
mstsc /v:$server "/shadow:"$session[2] /noConsentPrompt /control
} else {
errx "Failed to get a session id for ${server}"
}
Tags: ойті
Authors: ag