PoshCode Archive  Artifact [043ae85393]

Artifact 043ae85393c644648cffd9fe48307a86b329985ce99a39bb666e336a7d907835:

  • File Get-Packet.ps1 — part of check-in [996a191acb] at 2018-06-10 14:23:49 on branch trunk — This is an updated version of Get-Packet, an IP packet sniffer for Powershell. (user: Robbie Foust size: 15048)

# encoding: ascii
# api: powershell
# title: Get-Packet
# description: This is an updated version of Get-Packet, an IP packet sniffer for Powershell.
# version: 0.1
# type: script
# author: Robbie Foust 
# license: CC0
# x-poshcode-id: 764
# x-archived: 2017-05-03T10:58:25
# x-published: 2009-12-30T16:55:00
#
#
#
# get-packet.ps1
#
# Receives and displays all incoming IP packets.  NIC driver must support promiscuous mode.
#
# Usage: get-packet.ps1 [-LocalIP [<String>]] [-Protocol [<String>]] [[-Seconds] [<Int32>]] [-ResolveHosts] [-Statistics] [-Silent]
#
# Author: Robbie Foust (rfoust@duke.edu)
# Date: Nov 19, 2007
#
# Revised: Dec 30, 2008
#  - Added Version field
#  - Added support for resolving IPs (uses hashtable cache for improved performance)
#  - Flags now stored in an array
#  - ESC key will stop script cleanly
#  - Calculates stats when sniffing is finished with -Statistics
#  - Can suppress packet output using -Silent
#
# Stats logic obtained from Jeffery Hicks's analyze-packet script
# (http://blog.sapien.com/index.php/2008/08/14/analyze-packet-reloaded/)
#

param([string]$LocalIP = "NotSpecified", [string]$Protocol = "all", [int]$Seconds = 0, [switch]$ResolveHosts, [switch]$Statistics, [switch]$Silent)

$starttime = get-date
$byteIn = new-object byte[] 4
$byteOut = new-object byte[] 4
$byteData = new-object byte[] 4096  # size of data

$byteIn[0] = 1  # this enables promiscuous mode (ReceiveAll)
$byteIn[1-3] = 0
$byteOut[0-3] = 0

# TCP Control Bits
$TCPFIN = [byte]0x01
$TCPSYN = [byte]0x02
$TCPRST = [byte]0x04
$TCPPSH = [byte]0x08
$TCPACK = [byte]0x10
$TCPURG = [byte]0x20

# Takes a 2 byte array, switches it from big endian to little endian, and converts it to uint16.
function NetworkToHostUInt16 ($value)
	{
	[Array]::Reverse($value)
	[BitConverter]::ToUInt16($value,0)
	}

# Takes a 4 byte array, switches it from big endian to little endian, and converts it to uint32.
function NetworkToHostUInt32 ($value)
	{
	[Array]::Reverse($value)
	[BitConverter]::ToUInt32($value,0)
	}

# Takes a byte array, switches it from big endian to little endian, and converts it to a string.
function ByteToString ($value)
	{
	$AsciiEncoding = new-object system.text.asciiencoding
	$AsciiEncoding.GetString($value)
	}

$hostcache = @{}  # hashtable to cache hostnames to speed up ResolveIP()

function ResolveIP ($ip)
	{
	if ($data = $hostcache."$($ip.IPAddressToString)")
		{
		if ($ip.IPAddressToString -eq $data)
			{
			[system.net.ipaddress]$ip
			}
		else
			{
			$data
			}
		}
	else
		{
		$null,$null,$null,$data = nslookup $ip.IPAddressToString 2>$null

		$data = $data -match "Name:"

		if ($data -match "Name:")
			{
			$data = $data[0] -replace "Name:\s+",""
			$hostcache."$($ip.IPAddressToString)" = "$data"
			$data
			}
		else
			{
			$hostcache."$($ip.IPAddressToString)" = "$($ip.IPAddressToString)"
			$ip
			}
		}
	}

# try to figure out which IP address to bind to by looking at the default route
if ($LocalIP -eq "NotSpecified") {
	route print 0* | % { 
		if ($_ -match "\s{2,}0\.0\.0\.0") { 
			$null,$null,$null,$LocalIP,$null = [regex]::replace($_.trimstart(" "),"\s{2,}",",").split(",")
			}
		}
	}

write-host "Using IPv4 Address: $LocalIP"
write-host


# open a socket -- Type should be Raw, and ProtocolType has to be IP for promiscuous mode, otherwise iocontrol will fail below.
$socket = new-object system.net.sockets.socket([Net.Sockets.AddressFamily]::InterNetwork,[Net.Sockets.SocketType]::Raw,[Net.Sockets.ProtocolType]::IP)

# this tells the socket to include the IP header
$socket.setsocketoption("IP","HeaderIncluded",$true)

# make the buffer big or we'll drop packets.
$socket.ReceiveBufferSize = 819200

$ipendpoint = new-object system.net.ipendpoint([net.ipaddress]"$localIP",0)
$socket.bind($ipendpoint)

# this enables promiscuous mode
[void]$socket.iocontrol([net.sockets.iocontrolcode]::ReceiveAll,$byteIn,$byteOut)

write-host "Press ESC to stop the packet sniffer ..." -fore yellow

$escKey = 27
$running = $true
$packets = @()  # this will hold all packets for later analysis

while ($running)
	{
	# check and see if ESC was pressed
	if ($host.ui.RawUi.KeyAvailable)
		{
		$key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp,IncludeKeyDown")

		if ($key.VirtualKeyCode -eq $ESCkey)
			{
			$running = $false
			}
		}
	
	if ($Seconds -ne 0 -and ($([DateTime]::Now) -gt $starttime.addseconds($Seconds)))  # if user-specified timeout has expired
		{
		exit
		}

	if (-not $socket.Available)  # see if any packets are in the queue
		{
		start-sleep -milliseconds 500
		continue
		}
	
	# receive data
	$rcv = $socket.receive($byteData,0,$byteData.length,[net.sockets.socketflags]::None)

	# decode the header (see RFC 791 or this will make no sense)
	$MemoryStream = new-object System.IO.MemoryStream($byteData,0,$rcv)
	$BinaryReader = new-object System.IO.BinaryReader($MemoryStream)

	# First 8 bits of IP header contain version & header length
	$VersionAndHeaderLength = $BinaryReader.ReadByte()

	# Next 8 bits contain the TOS (type of service)
	$TypeOfService= $BinaryReader.ReadByte()

	# total length of header and payload
	$TotalLength = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)

	$Identification = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
	$FlagsAndOffset = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
	$TTL = $BinaryReader.ReadByte()
	$ProtocolNumber = $BinaryReader.ReadByte()
	$Checksum = [Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())

	$SourceIPAddress = $BinaryReader.ReadUInt32()
	$SourceIPAddress = [System.Net.IPAddress]$SourceIPAddress
	$DestinationIPAddress = $BinaryReader.ReadUInt32()
	$DestinationIPAddress = [System.Net.IPAddress]$DestinationIPAddress

	# Get the IP version number from the "left side" of the Byte
	$ipVersion = [int]"0x$(('{0:X}' -f $VersionAndHeaderLength)[0])"

	# Get the header length by getting right 4 bits (usually will be 5, as in 5 32 bit words)
	# multiplying by 4 converts from words to octets which is what TotalLength is measured in
	$HeaderLength = [int]"0x$(('{0:X}' -f $VersionAndHeaderLength)[1])" * 4

	if ($HeaderLength -gt 20)  # if header includes Options (is gt 5 octets long)
		{
		[void]$BinaryReader.ReadBytes($HeaderLength - 20)  # should probably do something with this later
		}
	
	$Data = ""
	$TCPFlagsString = @()  # make this an array
	$TCPWindow = ""
	$SequenceNumber = ""
	
	switch ($ProtocolNumber)  # see http://www.iana.org/assignments/protocol-numbers
		{
		1 {  # ICMP
			$protocolDesc = "ICMP"

			$sourcePort = [uint16]0
			$destPort = [uint16]0
			break
			}
		2 {  # IGMP
			$protocolDesc = "IGMP"
			$sourcePort = [uint16]0
			$destPort = [uint16]0
			$IGMPType = $BinaryReader.ReadByte()
			$IGMPMaxRespTime = $BinaryReader.ReadByte()
			$IGMPChecksum = [System.Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())
			$Data = ByteToString $BinaryReader.ReadBytes($TotalLength - ($HeaderLength - 32))
			}
		6 {  # TCP
			$protocolDesc = "TCP"
			
			$sourcePort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			$destPort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			$SequenceNumber = NetworkToHostUInt32 $BinaryReader.ReadBytes(4)
			$AckNumber = NetworkToHostUInt32 $BinaryReader.ReadBytes(4)
			$TCPHeaderLength = [int]"0x$(('{0:X}' -f $BinaryReader.ReadByte())[0])" * 4  # reads Data Offset + 4 bits of Reserve (ignored)
			
			$TCPFlags = $BinaryReader.ReadByte()  # this will also contain 2 bits of Reserve on the left, but we can just ignore them.

			switch ($TCPFlags)
				{
				{ $_ -band $TCPFIN } { $TCPFlagsString += "FIN" }
				{ $_ -band $TCPSYN } { $TCPFlagsString += "SYN" }
				{ $_ -band $TCPRST } { $TCPFlagsString += "RST" }
				{ $_ -band $TCPPSH } { $TCPFlagsString += "PSH" }
				{ $_ -band $TCPACK } { $TCPFlagsString += "ACK" }
				{ $_ -band $TCPURG } { $TCPFlagsString += "URG" }
				}
			
			$TCPWindow = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			$TCPChecksum = [System.Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())
			$TCPUrgentPointer = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)

			if ($TCPHeaderLength -gt 20)  # get to start of data
				{
				[void]$BinaryReader.ReadBytes($TCPHeaderLength - 20)
				}

			# if SYN flag is set, sequence number is initial sequence number, and therefore the first
			# octet of the data is ISN + 1.
			if ($TCPFlags -band $TCPSYN)
				{
				$ISN = $SequenceNumber
				#$SequenceNumber = $BinaryReader.ReadBytes(1)
				[void]$BinaryReader.ReadBytes(1)
				}

			$Data = ByteToString $BinaryReader.ReadBytes($TotalLength - ($HeaderLength + $TCPHeaderLength))
			break
			}
		17 {  # UDP
			$protocolDesc = "UDP"

			$sourcePort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			$destPort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			$UDPLength = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
			[void]$BinaryReader.ReadBytes(2)
			# subtract udp header length (2 octets) and convert octets to bytes.
			$Data = ByteToString $BinaryReader.ReadBytes(($UDPLength - 2) * 4)
			break
			}
		default {
			$protocolDesc = "Other ($_)"
			$sourcePort = 0
			$destPort = 0
			break
			}
		}
	
	$BinaryReader.Close()
	$memorystream.Close()

	if ($ResolveHosts)  # resolve IP addresses to hostnames
		{
		# GetHostEntry is horribly slow on failed lookups, so I'm not using it
		# $DestinationHostName = ([System.Net.DNS]::GetHostEntry($DestinationIPAddress.IPAddressToString)).Hostname
		# $SourceHostName = ([System.Net.DNS]::GetHostEntry($SourceIPAddress.IPAddressToString)).Hostname

		$DestinationHostName = ResolveIP($DestinationIPAddress)
		$SourceHostName = ResolveIP($SourceIPAddress)
		}

	# now throw the stuff we consider important into a psobject
	# $ipObject = new-object psobject

	if ($Protocol -eq "all" -or $Protocol -eq $protocolDesc)
		{
		$packet = new-object psobject

		$packet | add-member noteproperty Destination $DestinationIPAddress
		if ($ResolveHosts) { $packet | add-member noteproperty DestinationHostName $DestinationHostName }
		$packet | add-member noteproperty Source $SourceIPAddress
		if ($ResolveHosts) { $packet | add-member noteproperty SourceHostName $SourceHostName }
		$packet | add-member noteproperty Version $ipVersion
		$packet | add-member noteproperty Protocol $protocolDesc
		$packet | add-member noteproperty Sequence $SequenceNumber
		$packet | add-member noteproperty Window $TCPWindow
		$packet | add-member noteproperty DestPort $destPort
		$packet | add-member noteproperty SourcePort $sourcePort
		$packet | add-member noteproperty Flags $TCPFlagsString
		$packet | add-member noteproperty Data $Data
		$packet | add-member noteproperty Time (get-date)

		$packets += $packet  # add this packet to the array

		if (-not $Silent)
			{
			$packet
			}
		}
	}

# calculate statistics
if ($Statistics)
	{
	$activity = "Analyzing network trace"

	# calculate elapsed time
	# Using this logic, the beginning time is when the first packet is received,
	#  not when packet capturing is started. That may or may not be ideal depending
	#  on what you're trying to measure.
	write-progress $activity "Counting packets"
	$elapsed = $packets[-1].time - $packets[0].time

	#calculate packets per second
	write-progress $activity "Calculating elapsed time"
	$pps = $packets.count/(($packets[-1].time -$packets[0].time).totalseconds)
	$pps="{0:N4}" -f $pps

	# Calculating protocol distribution
	write-progress $activity "Calculating protocol distribution"
	$protocols = $packets | sort protocol | group protocol | sort count -descending | select Count,@{name="Protocol";Expression={$_.name}} 

	# Calculating source port distribution
	write-progress $activity "Calculating source port distribution"
	$sourceport = $packets | sort sourceport | group sourceport | sort count -descending | select Count,@{name="Port";Expression={$_.name}}

	# Calculating destination distribution
	write-progress $activity "Calculating destination distribution"
	$destinationlist = $packets | sort Destination | select Destination

	# Calculating destination port distribution
	write-progress $activity "Calculating destination port distribution"
	$destinationport = $packets | sort destport | group destport | sort count -descending | select Count,@{name="Port";Expression={$_.name}}

	# Building source list
	write-progress $activity "Building source list"
	$sourcelist = $packets | sort source | select Source

	# Building source IP list
	write-progress $activity "Building source IP list"
	$ips = $sourcelist | group source | sort count -descending | select Count,@{Name="IP";Expression={$_.Name}}
		
	# Build destination IP list
	write-progress $activity "Building destination IP list"
	$ipd = $destinationlist | group destination | sort count -descending | select Count,@{Name="IP";Expression={$_.Name}}

	# Presenting data
	write-progress $activity "Compiling results"
	$protocols = $protocols | Select Count,Protocol,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}} 

	$destinationport = $destinationport | select Count,Port,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}} 

	$sourceport = $sourceport | Select Count,Port,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}} 

	if ($ResolveHosts)
		{
		write-progress $activity "Resolving IPs"

		# add hostnames to the new object(s)
		foreach ($destination in $ipd)
			{
			$destination | add-member noteproperty "Host" $(ResolveIP([system.net.ipaddress]$destination.IP))
			}
		foreach ($source in $ips)
			{
			$source | add-member noteproperty "Host" $(ResolveIP([system.net.ipaddress]$source.IP))
			}
		}

	write-progress $activity "Compiling results"
	$destinations = $ipd | Select Count,IP,Host,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}} 
	$sources = $ips | Select Count,IP,Host,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}} 

	$global:stats = new-object psobject

	$stats | add-member noteproperty "TotalPackets" $packets.count
	$stats | add-member noteproperty "Elapsedtime" $elapsed
	$stats | add-member noteproperty "PacketsPerSec" $pps
	$stats | add-member noteproperty "Protocols" $protocols
	$stats | add-member noteproperty "Destinations" $destinations
	$stats | add-member noteproperty "DestinationPorts" $destinationport
	$stats | add-member noteproperty "Sources" $sources
	$stats | add-member noteproperty "SourcePorts" $sourceport 

	write-host
	write-host " TotalPackets: " $stats.totalpackets
	write-host "  ElapsedTime: " $stats.elapsedtime
	write-host "PacketsPerSec: " $stats.packetspersec
	write-host
	write-host "More statistics can be accessed from the global `$stats variable." -fore cyan
	
	}