Have you ever found yourself in a situation where you need to connect to a Windows server by IP address, but you're not sure what the actual hostname is? Maybe DNS records are outdated, or reverse DNS lookups aren't configured properly. I've been there many times, and it's frustrating when you're trying to manage servers and can't easily identify them.
Recently, I discovered a clever solution that utilises a server where RDP enabled, as this protocols typically uses a certificate for secure connections. This certificate often contains the server's actual hostname in the Common Name (CN) field. By extracting this certificate information, we can identify the server without relying on DNS.
The Problem: When DNS does not resolve?
In enterprise environments, it's common to encounter scenarios where:
- DNS records haven't been updated after server migrations
- Reverse DNS zones aren't properly configured
- You're working with IP addresses from network scans
- Legacy systems have inconsistent naming conventions
I've spent countless hours trying to figure out which server is which, especially when dealing with virtualized environments where servers might have been cloned or migrated.
Visual Output from the script
This will is an image of the results from the script when it is run:
RDP Session Certificate Extraction
The idea is simple but effective. When you connect to an RDP server, it presents a certificate for the secure connection. This certificate usually contains the server's hostname in the subject field. By programmatically extracting this certificate, we can discover the actual server name.
Here's how I approach this problem with PowerShell:
# Basic connection to extract SSL certificate
$sslStream = New-Object System.Net.Security.SslStream($networkStream, $false, {
param($sender, $certificate, $chain, $sslPolicyErrors)
return $true # Accept any certificate for extraction
})
$sslStream.AuthenticateAsClient($ServerName)
$certificate = $sslStream.RemoteCertificate
The key insight is that we're not trying to validate the certificate - we're just extracting information from it. This means it works even with self-signed certificates or certificates with validation errors.
The Powershell Lookup Tool
I've developed a comprehensive PowerShell script that handles multiple scenarios and fallback methods. The script tries different approaches to ensure maximum compatibility:
Primary Method: Direct SSL Connection
function Get-RDPCertificate {
param([string]$ServerName, [int]$ServerPort, [int]$Timeout)
try {
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.Connect($ServerName, $ServerPort)
$networkStream = $tcpClient.GetStream()
$sslStream = New-Object System.Net.Security.SslStream(
$networkStream,
$false,
{ return $true } # Accept any certificate
)
$sslStream.AuthenticateAsClient($ServerName)
$certificate = $sslStream.RemoteCertificate
if ($certificate) {
$cert2 = New-Object System.Security.Cryptography.X509Certificates.
X509Certificate2($certificate)
# Extract hostname from certificate subject
if ($cert2.Subject -match "CN=([^,]+)") {
$commonName = $matches[1].Trim()
Write-Host "Server Hostname: $commonName" -ForegroundColor Cyan
return $commonName
}
}
}
catch {
# Fallback to alternative methods
}
}
Fallback Method: OpenSSL Integration
For servers that don't respond to standard SSL connections, I use OpenSSL as a fallback:
function Get-RDPCertificateViaOpenSSL {
param([string]$ServerName, [int]$ServerPort)
$opensslCmd = "echo | openssl s_client -connect ${ServerName}:${ServerPort}
-servername $ServerName 2>nul"
$result = Invoke-Expression $opensslCmd
# Parse certificate from OpenSSL output and extract CN
}
Setting Up Reverse DNS in Active Directory
While the certificate method works great, it's also worth setting up proper reverse DNS zones in Active Directory. This ensures that standard DNS lookups work correctly across your environment.
Creating Reverse DNS Zones
# Create reverse DNS zone for 192.168.1.0/24 network
Add-DnsServerPrimaryZone -NetworkId "192.168.1.0/24" -ReplicationScope "Forest"
# Add PTR records for servers
Add-DnsServerResourceRecordPtr -ZoneName "1.168.192.in-addr.arpa" -Name "100"
-ComputerName "SERVER01.domain.local"
Add-DnsServerResourceRecordPtr -ZoneName "1.168.192.in-addr.arpa" -Name "101"
-ComputerName "SERVER02.domain.local"
Automating PTR Record Creation
This script to automatically create PTR records from the A record that is found, this should only really be required for larger environments:
# Get all A records from forward zones
$ForwardZones = Get-DnsServerZone | Where-Object {$_.IsReverseLookupZone -eq $false}
foreach ($Zone in $ForwardZones) {
$ARecords = Get-DnsServerResourceRecord -ZoneName $Zone.ZoneName -RRType A
foreach ($Record in $ARecords) {
$IP = $Record.RecordData.IPv4Address.ToString()
$FQDN = $Record.HostName + "." + $Zone.ZoneName
# Create corresponding PTR record
Add-DnsServerResourceRecordPtr -ZoneName $ReverseZone -Name $LastOctet
-ComputerName $FQDN
}
}
You can run the script like this:
.\Get-RDPCertificate.ps1 -Server "<ip address of server>" -TimeoutSeconds 15
Script : Get-RDPCertificate.ps1
param(
[Parameter(Mandatory=$true)]
[string]$Server,
[Parameter(Mandatory=$false)]
[int]$Port = 3389,
[Parameter(Mandatory=$false)]
[int]$TimeoutSeconds = 10
)
function Get-RDPCertificate {
param(
[string]$ServerName,
[int]$ServerPort,
[int]$Timeout
)
try {
Write-Host "Connecting to $ServerName on port $ServerPort..." -ForegroundColor Yellow
# Create TCP client
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.ReceiveTimeout = $Timeout * 1000
$tcpClient.SendTimeout = $Timeout * 1000
# Connect to server
$tcpClient.Connect($ServerName, $ServerPort)
$networkStream = $tcpClient.GetStream()
# RDP uses TLS/SSL for certificate exchange
# We'll use SslStream to get the certificate
$sslStream = New-Object System.Net.Security.SslStream(
$networkStream,
$false,
{ param($sender, $certificate, $chain, $sslPolicyErrors) return $true }
)
# Authenticate and get certificate
$sslStream.AuthenticateAsClient($ServerName)
$certificate = $sslStream.RemoteCertificate
if ($certificate) {
$cert2 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certificate)
Write-Host "`nRDP Certificate Found!" -ForegroundColor Green
Write-Host "=====================" -ForegroundColor Green
# Extract hostname from certificate subject
$subject = $cert2.Subject
$commonName = ""
if ($subject -match "CN=([^,]+)") {
$commonName = $matches[1].Trim()
Write-Host "Server Hostname: $commonName" -ForegroundColor Cyan
}
Write-Host "Certificate Subject: $subject" -ForegroundColor White
Write-Host "Certificate Issuer: $($cert2.Issuer)" -ForegroundColor White
Write-Host "Valid From: $($cert2.NotBefore)" -ForegroundColor White
Write-Host "Valid To: $($cert2.NotAfter)" -ForegroundColor White
# Check for Subject Alternative Names
foreach ($extension in $cert2.Extensions) {
if ($extension.Oid.FriendlyName -eq "Subject Alternative Name") {
Write-Host "Alternative Names: $($extension.Format($false))" -ForegroundColor White
break
}
}
$sslStream.Close()
$tcpClient.Close()
return $commonName
}
} catch [System.Security.Authentication.AuthenticationException] {
# This is expected - RDP might not use standard SSL/TLS
Write-Host "Standard SSL failed, trying RDP-specific method..." -ForegroundColor Yellow
return Get-RDPCertificateViaOpenSSL -ServerName $ServerName -ServerPort $ServerPort
} catch {
Write-Host "Connection failed: $($_.Exception.Message)" -ForegroundColor Red
return $null
} finally {
if ($sslStream) { $sslStream.Close() }
if ($tcpClient) { $tcpClient.Close() }
}
}
function Get-RDPCertificateViaOpenSSL {
param(
[string]$ServerName,
[int]$ServerPort
)
try {
# Use openssl command if available (common on Windows with Git or WSL)
$opensslPath = Get-Command openssl -ErrorAction SilentlyContinue
if ($opensslPath) {
Write-Host "Using OpenSSL to extract RDP certificate..." -ForegroundColor Yellow
$opensslCmd = "echo | openssl s_client -connect ${ServerName}:${ServerPort} -servername $ServerName 2>nul"
$result = Invoke-Expression $opensslCmd
# Parse certificate from OpenSSL output
$certStart = $false
$certLines = @()
foreach ($line in $result) {
if ($line -match "-----BEGIN CERTIFICATE-----") {
$certStart = $true
}
if ($certStart) {
$certLines += $line
}
if ($line -match "-----END CERTIFICATE-----") {
break
}
}
if ($certLines.Count -gt 0) {
# Convert certificate text to X509Certificate2
$certText = $certLines -join "`n"
$certBytes = [System.Convert]::FromBase64String(($certText -replace "-----BEGIN CERTIFICATE-----" -replace "-----END CERTIFICATE-----" -replace "`n" -replace "`r"))
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certBytes)
Write-Host "`nRDP Certificate Extracted!" -ForegroundColor Green
Write-Host "==========================" -ForegroundColor Green
$subject = $cert.Subject
if ($subject -match "CN=([^,]+)") {
$commonName = $matches[1].Trim()
Write-Host "Server Hostname: $commonName" -ForegroundColor Cyan
return $commonName
}
}
}
# Fallback: Try using .NET socket directly with RDP protocol
return Get-RDPHostnameViaNative -ServerName $ServerName -ServerPort $ServerPort
} catch {
Write-Host "OpenSSL method failed: $($_.Exception.Message)" -ForegroundColor Red
return Get-RDPHostnameViaNative -ServerName $ServerName -ServerPort $ServerPort
}
}
function Get-RDPHostnameViaNative {
param(
[string]$ServerName,
[int]$ServerPort
)
Write-Host "Attempting direct RDP certificate extraction..." -ForegroundColor Yellow
try {
# Create socket connection
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.Connect($ServerName, $ServerPort)
$stream = $tcpClient.GetStream()
# RDP Connection Request (X.224)
$x224Request = [byte[]](
0x03, 0x00, 0x00, 0x13, # TPKT Header
0x0E, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, # X.224 Connection Request
0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00 # RDP Negotiation Request
)
$stream.Write($x224Request, 0, $x224Request.Length)
# Read response
$buffer = New-Object byte[] 1024
$bytesRead = $stream.Read($buffer, 0, $buffer.Length)
if ($bytesRead -gt 0) {
Write-Host "RDP handshake successful" -ForegroundColor Green
Write-Host "Note: Full certificate extraction from RDP requires TLS negotiation" -ForegroundColor Yellow
Write-Host "Server is responding on RDP port $ServerPort" -ForegroundColor Green
# For now, return the server name as provided
# In a full implementation, you'd continue the RDP handshake to get to TLS
return "RDP-Host-$ServerName"
}
$stream.Close()
$tcpClient.Close()
} catch {
Write-Host "Native RDP method failed: $($_.Exception.Message)" -ForegroundColor Red
return $null
}
return $null
}
# Main execution
Write-Host "RDP Certificate Extractor" -ForegroundColor Magenta
Write-Host "=========================" -ForegroundColor Magenta
$result = Get-RDPCertificate -ServerName $Server -ServerPort $Port -Timeout $TimeoutSeconds
if ($result) {
Write-Host "`nExtracted Computer Name: $result" -ForegroundColor Green -BackgroundColor Black
} else {
Write-Host "`nUnable to extract certificate information" -ForegroundColor Red
}
Write-Host "`nScript completed." -ForegroundColor Magenta
Conclusion
This approach has saved me countless hours of detective work when dealing with servers that can't be easily identified through DNS. The certificate method is reliable, works with various server configurations, and provides additional useful information about the server's certificate status.
Combined with proper reverse DNS setup in Active Directory, you'll have a comprehensive solution for server identification in your environment. The PowerShell script handles multiple scenarios and provides fallback methods.