Information Technology Grimoire

Version .0.0.1

IT Notes from various projects because I forget, and hopefully they help you too.

Banner Grabs Powershell

You can use many tools that see headers, but here is a powershell script I wrote to check banners on multiple devices at once, it’s sort of fancy:

  • This script takes a simple 2 column CSV of IP and PORT and then scans them.
  • You specify in the source how many threads and how long of a pause between checks (to avoid detection/block)
  • It first figures out both the IP and HOST no matter you gave it
  • then checks with a ping to see if it’s online
  • if it is online, it does the port read to see if it’s open
  • if it is open on that port, it tries tag if port 80/443
  • it tries to get stream banner grab (anything) if any other port
  • Dumps all to nice CSV
<#
2 threads: about 2-5 results in 3 seconds (some responses take a while)
20 threads: about 25 results in 3 seconds (one response was 10 seconds old)
#>



# the name of your out report
$infile = "input-sample.csv"
$outfile = "output-results.csv"
$tmpfile = "investigate.tmp"	




# if ping fails, you want to scan anyway? 
# $ALSE = no
# $TRUE = yes
$TestAnyway = $FALSE


# how many threads?  5-20 suggested
# lower than 5 is to avoid scan detection alerts
$threads = 4


# milliseconds between scans
$slow = 500


# want to scroll your screen?
$debug = $TRUE


if ($debug) {write-host "DEBUG ON"} else {clear;}


# just in case you don't want to block your ip
$warning = Invoke-WebRequest -uri "https://ifconfig.me/ip"
write-host "YOUR SCAN FROM IP IS: $($warning.content)" -foregroundcolor yellow
write-host "YOU ARE USING $($threads) THREADS WITH $($slow)ms (ADDITIONAL) PAUSE" -foregroundcolor yellow
$confirmation = Read-Host "Are you sure you want to scan? (Y|N)"
if (($confirmation -eq 'N') -Or ($confirmation -eq 'n')){
	exit
}




# required for bypassing self signed error
if (-not("dummy" -as [type])) {
    add-type -TypeDefinition @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;


public static class Dummy {
    public static bool ReturnTrue(object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors) { return true; }


    public static RemoteCertificateValidationCallback GetDelegate() {
        return new RemoteCertificateValidationCallback(Dummy.ReturnTrue);
    }
}
"@
}


$TimeTaken = (Measure-Command {


	# load our file into memory as array of strings
	$Lines = Get-Content $infile
	$HowMany = $Lines.count
	
	$Skipped = 0
	Foreach ($Line in $Lines) {
		if($Line.StartsWith('#')) { $Skipped = $Skipped +1 }
	}
	
	$Lines | ForEach-Object -ThrottleLimit $threads -Parallel{
		# make a copy of object for use with parallel
		$Line = $_
		
		# skip any lines starting with #
		if($Line.StartsWith('#')) { continue }


		# break up our input line
		$Target,$_Port = $Line.split(",")
		
		# Determine Hostname
		try {
			# maybe they gave us a hostname, so look up IP
			$HostName = [System.Net.Dns]::GetHostByAddress($Target).Hostname
		} catch {
			# No... default to what it was
			$HostName = $Target
		}
		


		# Determine IP
		try {
			# Did they give us a hostname?  convert to IP if they did
			$IP = [System.Net.Dns]::GetHostByName($Target)
			$IP = $IP.AddressList[0].IPAddressToString
		} catch {
			# No... default to what it was
			$IP = 'NO REVERSE'
		}


		function QueryPort ([string]$HostName, [string]$_Port) {
			# Mandatory speed limit
			Start-Sleep -Milliseconds $($using:slow)
			
			[hashtable]$return = @{}
			if ($_Port -eq 80){
				$url = "http://$($HostName)"
				
				# if it's port 80, it's probably a web server
				try{
					$req = Invoke-WebRequest -uri $url
					
					# and if we get a status code 200, then
					$content = $req.content
					
					# we can massage data
					$content = [string]::join(" ",($content.split("`n")))
					$content = $content -replace '\s{2,}',' '
					
					# to extract title, for a clue
					$content -match '<title>([\s\S]*?)</title>'
					$title += $matches[1]
								
					$return.status = 'OPEN'
					$return.message = $title
				} catch{
					# or we'll get some other fail type code on http like 401,403,404
					$return.status = 'OPEN'
					$return.message = $_.Exception.Response.StatusCode.Value__
				}
			} elseif (($_Port -eq 443) -or ($_Port -eq 8080) -or ($_Port -eq 5986) -or ($_Port -eq 5985)){
				# fixme:  try tls connection as default and if that fails, read stream
				$url = "https://$($HostName):$($_Port)"


				# if it's port 443, it's probably a webserver
				try{
					# and even though a good result might work
					# it could have a self signed, so ignore that check
					[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [dummy]::GetDelegate()
					$req = Invoke-WebRequest -Uri $url
					$status = $req.StatusCode
					$content = $req.content
					
					# but then massage our data
					$content = [string]::join(" ",($content.split("`n")))
					$content = $content -replace '\s{2,}',' '
					
					# and see if there is a title
					$content -match '<title>([\s\S]*?)</title>'
					$title += $matches[1]
								
					$return.status = 'OPEN'
					$return.message = $title
				} catch{
					# or we'll get some other fail type code on https like 401,403,404
					$return.status = 'OPEN'
					$return.message = $_.Exception.Response.StatusCode.Value__
				}			
			} else {
				$Socket = New-Object System.Net.Sockets.TCPClient
				$Connected = ($Socket.BeginConnect( $HostName, $_Port, $Null, $Null)).AsyncWaitHandle.WaitOne(300)
				if ($Connected -eq "True"){
					$Stream = $Socket.getStream()
					Start-Sleep -m 500;
					$Text = ""
						
					# try to read banner
					while ($Stream.DataAvailable) { $Text += [char]$Stream.ReadByte() }
								
					# default in case nothing found
					if ($Text.Length -eq 0){ 
						$Text = "N/A"
					} 
					$Socket.Close()
						
					# Massage and return
					$Text = $Text -replace "`n"
					$Text = $Text -replace "`r"
					#$Text = $Text -replace "\W"


					$return.status = 'OPEN'
					$return.message = $Text
					
				} else {
					$return.status = 'CLOSED'
					$return.message = 'NOT TESTED'
				}
			}
			return $return
		}


		# if it responds to ping, then do scan
		# maybe pings are blocked though?
		# todo, or logic for scan anyway
		if ((Test-Connection -count 1 -comp $HostName -quiet) -or ($TestAnyway)) {
			
			$Details = [pscustomobject]@{
				Date	= get-date
				Online	= "ONLINE"
				IPv4	= $IP
				Host	= $HostName
				Port	= $_Port
				OpCl	= ""
				Banner  = ""
			}
			
			# for the ports given, check them
			try {
				# call function
				$result	= QueryPort $HostName $_Port
				$Details.OpCl	= $result.status
				$Details.Banner	= $result.message


				# why this had a "True" in everystring beats me, but this fixes it
				#$Details.Port = $Details.Port -replace "True",""
					
			} catch {
				# maybe ping works, but all ports closed?
			}
			
		} else {
			$Details = [pscustomobject]@{
				Date	= get-date
				Online	= "OFFLINE"
				IPv4	= $IP
				Host	= $HostName
				Port	= $_Port
				OpCl	= "N/A"
				Banner  = "N/A"
			}
		}


		# convert it to the output format we want, skipping redundant header
		
		$data = $Details| ConvertTo-Csv -NoTypeInformation | select-object -skip 1
		
		# append to our results file
		# we are using mutex for file locking
		$mtx = New-Object -TypeName System.Threading.Mutex($false, "mtx")
		If ($mtx.WaitOne(1000)) {
			$data | Out-File -append -FilePath .\$($using:tmpfile) -ErrorAction Stop 
			[void]$mtx.ReleaseMutex()
		} else {
			Throw "Timed out acquiring mutex lock"
		}
		# Let us know where we are
		$Date = get-date
		#write-host "$($Date): $($Target):$($_Port)" -ForegroundColor Yellow


		if ($($using:debug)) { write-host "DEBUG:$($data)" -foregroundcolor blue }
		$mtx.Dispose()


	}
	
})


# Add a Header Back
"DATE,ICMP,IPV4,HOSTNAME,PORT,STATUS,BANNER" | Out-File -FilePath .\$($outfile) -ErrorAction Stop
Get-Content .\$($tmpfile) | Out-File -append -FilePath .\$($outfile) -ErrorAction Stop


# delete our temp file
Remove-Item .\$($tmpfile)




# how long did it take?
$timed = [math]::Round($TimeTaken.TotalSeconds,1)
write-host "$HowMany Processed, $Skipped were skipped. $timed seconds. $threads Threads" -ForegroundColor Green