Alexander Gromnitsky's Blog

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.

  1. Turn on Remote Desktop:

  2. 445 port must be open:

    & a predefined Windows rule Remote Desktop - Shadow (TCP-In) is turned on also:

  3. 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