Last active
December 13, 2024 13:30
-
-
Save JohnLBevan/b2e55d348462a49ef52bc195ebaca3ea to your computer and use it in GitHub Desktop.
Script to run whois queries to fetch the registrar for a given domain. Note: TCP code stolen from my SMTPS script (https://gist.github.com/JohnLBevan/c7974c2839e1486345d63ab6bd76523c) - hence some redundant code (not yet cleaned up)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Receive-TcpServerResponse { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[System.IO.StreamReader]$StreamReader | |
) | |
# useful notes on SMTP https://www.rfc-editor.org/rfc/rfc5321.html / http://www.tcpipguide.com/free/t_SMTPRepliesandReplyCodes-3.htm / FTP on https://www.w3.org/Protocols/rfc959/4_FileTransfer.html | |
$return = [PSCustomObject]@{PSTypeName='TcpResponse';Code=$null;Text=''} # don't need code, but no harm in leaving it (it was here from: ) | |
$hasMoreData = $true | |
#Write-Verbose ': Awaiting Server Response' | |
while ($hasMoreData -and ($StreamReader.EndOfStream -ne $true)) { # had odd issues where EndOfStream was null, despite being a non-nullable bool :/ - hence `-ne $true` | |
$response = $StreamReader.ReadLine(); | |
if ($null -eq $response){break;} # I don't think we'd get this... but doing defensive coding | |
Write-Verbose "<-[$response]" | |
if ($response -match '^(?<Code>\d{3})(?<Cont>[-\s])(?<Text>.*)$') { | |
if ($null -ne $return.Code) {$return} | |
#$return.Code = $Matches['Code'] # code from SMTPS monitor is not relevant here | |
$return.Text = $Matches['Text'] | |
$hasMoreData = $Matches['Cont'] -eq '-' | |
} else { | |
$return.Text += "`r`n$response" # maybe we should trim the space from the start... couldn't find documentation (it just says where the line begins with numbers use neutral text like space(s) to show it's not a command)... :/ | |
} | |
} | |
#Write-Verbose ": End of Server Response (end of stream = [$($StreamReader.EndOfStream)])" # There's always a delay when reading EndOfStream, and it often turns out to be null :S | |
$return | |
} | |
function Send-TcpClientMessage { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[System.IO.StreamWriter]$StreamWriter | |
, | |
[Parameter(Mandatory = $true)] | |
[AllowEmptyString()] | |
[string]$Message | |
) | |
Write-Verbose "->[$Message]" | |
$StreamWriter.WriteLine($Message) | |
} | |
# This is used for TCP scenarios where we setup a client, send a single message, get a single response, then can't reuse the client, like a burner phone | |
function Send-TcpBurnerMessage { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory = $true)] | |
[string]$ComputerName | |
, | |
[Parameter(Mandatory = $true)] | |
[int]$Port | |
, | |
[Parameter(Mandatory = $true)] | |
[AllowEmptyString()] | |
[string]$Message | |
, | |
[Parameter()] | |
[int]$TimeoutMS = 10000 | |
) | |
try { | |
$client = [System.Net.Sockets.TcpClient]::new($ComputerName, $Port) | |
$clientStream = $client.GetStream() | |
$clientStream.ReadTimeout = $TimeoutMS | |
$clientStream.WriteTimeout = $TimeoutMS | |
$r = [System.IO.StreamReader]::new($clientStream) | |
$w = [System.IO.StreamWriter]::new($clientStream) | |
$w.AutoFlush = $true | |
Send-TcpClientMessage -StreamWriter $w -Message $Message | |
Receive-TcpServerResponse -StreamReader $r | |
} finally { | |
if ($w.Disposee){ $w.Dispose() } | |
if ($r.Dispose){ $r.Dispose() } | |
if ($clientStream.Dispose){ $clientStream.Dispose() } | |
if ($client.Dispose){ $client.Dispose() } | |
} | |
} | |
function Get-TldFromDomain { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory)] | |
[string]$Domain | |
) | |
# regex heavily borrowed from https://stackoverflow.com/a/26987741/361842 | |
# note: here I assume the domain is valid / don't extract the domain from a longer fqdn, as that's not relevant to my use case & just adds overhead | |
$Domain -replace '^(?:(?!-))(?:xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.((?:xn--)?(?:[a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,}))$', '$1' | |
} | |
function Get-DomainRegistrar { | |
[CmdletBinding()] | |
Param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[string]$Domain | |
, | |
[Parameter()] | |
[string]$WhoisServer = 'whois.iana.org' | |
, | |
[Parameter()] | |
[int]$WhoIsPort = 43 | |
) | |
Process { | |
$tld = Get-TldFromDomain -Domain $Domain | |
$response = Send-TcpBurnerMessage -ComputerName $WhoIsServer -Port $WhoIsPort -Message $tld | |
$tldRegistrar = $response.Text -replace '(?ms).*\n[wW][hH][oO][iI][sS]:\s*(\S+).*', '$1' | |
write-verbose "TLD [$tld] registrar is [$tldRegisrar]. Now fetching [$Domain]" | |
$response = Send-TcpBurnerMessage -ComputerName $tldRegistrar -Port $WhoIsPort -Message $Domain | |
$registrar = (($response.Text + "`n.") -replace '(?ms).*^([^\S\r\n]*)[rR][eE][gG][iI][sS][tT][rR][aA][rR]((?:\:[^\S\r\n]*[^\r\n]+)|(?:\:?[\r\n]+\1[^\S\r\n]+[^\r\n]+)+).*', '$2') -replace '(?ms)^[:\s]+', '' # this solution is really hacky, but as the formatting of these records is pretty inconsistent it's the best I could come up with | |
([PSCustomObject][ordered]@{ | |
Domain = $Domain | |
Registrar = ($registrar.ToCharArray() | Where-Object{(-not [Char]::IsControl($_)) -or ($_ -in @("`t","`r","`n"))}) -join '' # found NUL chars in data | |
TLD = $tld # useful for debugging | |
TldRegisrar = $tldRegistrar # useful for debugging | |
WhoIs = $response # useful for debugging (leave any \0 chars in in case they give clues to the issue) | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
NOTE: Instead of using
WHOIS
consider usingRDAP
. More info: https://stackoverflow.com/a/79278467/361842