PoshCode Logo PowerShell Code Repository

Get-Packet by rfoust 8 years ago
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/764"></script>download | new post

This is an updated version of Get-Packet, an IP packet sniffer for Powershell.

  1. #
  2. # get-packet.ps1
  3. #
  4. # Receives and displays all incoming IP packets.  NIC driver must support promiscuous mode.
  5. #
  6. # Usage: get-packet.ps1 [-LocalIP [<String>]] [-Protocol [<String>]] [[-Seconds] [<Int32>]] [-ResolveHosts] [-Statistics] [-Silent]
  7. #
  8. # Author: Robbie Foust (rfoust@duke.edu)
  9. # Date: Nov 19, 2007
  10. #
  11. # Revised: Dec 30, 2008
  12. #  - Added Version field
  13. #  - Added support for resolving IPs (uses hashtable cache for improved performance)
  14. #  - Flags now stored in an array
  15. #  - ESC key will stop script cleanly
  16. #  - Calculates stats when sniffing is finished with -Statistics
  17. #  - Can suppress packet output using -Silent
  18. #
  19. # Stats logic obtained from Jeffery Hicks's analyze-packet script
  20. # (http://blog.sapien.com/index.php/2008/08/14/analyze-packet-reloaded/)
  21. #
  22.  
  23. param([string]$LocalIP = "NotSpecified", [string]$Protocol = "all", [int]$Seconds = 0, [switch]$ResolveHosts, [switch]$Statistics, [switch]$Silent)
  24.  
  25. $starttime = get-date
  26. $byteIn = new-object byte[] 4
  27. $byteOut = new-object byte[] 4
  28. $byteData = new-object byte[] 4096  # size of data
  29.  
  30. $byteIn[0] = 1  # this enables promiscuous mode (ReceiveAll)
  31. $byteIn[1-3] = 0
  32. $byteOut[0-3] = 0
  33.  
  34. # TCP Control Bits
  35. $TCPFIN = [byte]0x01
  36. $TCPSYN = [byte]0x02
  37. $TCPRST = [byte]0x04
  38. $TCPPSH = [byte]0x08
  39. $TCPACK = [byte]0x10
  40. $TCPURG = [byte]0x20
  41.  
  42. # Takes a 2 byte array, switches it from big endian to little endian, and converts it to uint16.
  43. function NetworkToHostUInt16 ($value)
  44.         {
  45.         [Array]::Reverse($value)
  46.         [BitConverter]::ToUInt16($value,0)
  47.         }
  48.  
  49. # Takes a 4 byte array, switches it from big endian to little endian, and converts it to uint32.
  50. function NetworkToHostUInt32 ($value)
  51.         {
  52.         [Array]::Reverse($value)
  53.         [BitConverter]::ToUInt32($value,0)
  54.         }
  55.  
  56. # Takes a byte array, switches it from big endian to little endian, and converts it to a string.
  57. function ByteToString ($value)
  58.         {
  59.         $AsciiEncoding = new-object system.text.asciiencoding
  60.         $AsciiEncoding.GetString($value)
  61.         }
  62.  
  63. $hostcache = @{}  # hashtable to cache hostnames to speed up ResolveIP()
  64.  
  65. function ResolveIP ($ip)
  66.         {
  67.         if ($data = $hostcache."$($ip.IPAddressToString)")
  68.                 {
  69.                 if ($ip.IPAddressToString -eq $data)
  70.                         {
  71.                         [system.net.ipaddress]$ip
  72.                         }
  73.                 else
  74.                         {
  75.                         $data
  76.                         }
  77.                 }
  78.         else
  79.                 {
  80.                 $null,$null,$null,$data = nslookup $ip.IPAddressToString 2>$null
  81.  
  82.                 $data = $data -match "Name:"
  83.  
  84.                 if ($data -match "Name:")
  85.                         {
  86.                         $data = $data[0] -replace "Name:\s+",""
  87.                         $hostcache."$($ip.IPAddressToString)" = "$data"
  88.                         $data
  89.                         }
  90.                 else
  91.                         {
  92.                         $hostcache."$($ip.IPAddressToString)" = "$($ip.IPAddressToString)"
  93.                         $ip
  94.                         }
  95.                 }
  96.         }
  97.  
  98. # try to figure out which IP address to bind to by looking at the default route
  99. if ($LocalIP -eq "NotSpecified") {
  100.         route print 0* | % {
  101.                 if ($_ -match "\s{2,}0\.0\.0\.0") {
  102.                         $null,$null,$null,$LocalIP,$null = [regex]::replace($_.trimstart(" "),"\s{2,}",",").split(",")
  103.                         }
  104.                 }
  105.         }
  106.  
  107. write-host "Using IPv4 Address: $LocalIP"
  108. write-host
  109.  
  110.  
  111. # open a socket -- Type should be Raw, and ProtocolType has to be IP for promiscuous mode, otherwise iocontrol will fail below.
  112. $socket = new-object system.net.sockets.socket([Net.Sockets.AddressFamily]::InterNetwork,[Net.Sockets.SocketType]::Raw,[Net.Sockets.ProtocolType]::IP)
  113.  
  114. # this tells the socket to include the IP header
  115. $socket.setsocketoption("IP","HeaderIncluded",$true)
  116.  
  117. # make the buffer big or we'll drop packets.
  118. $socket.ReceiveBufferSize = 819200
  119.  
  120. $ipendpoint = new-object system.net.ipendpoint([net.ipaddress]"$localIP",0)
  121. $socket.bind($ipendpoint)
  122.  
  123. # this enables promiscuous mode
  124. [void]$socket.iocontrol([net.sockets.iocontrolcode]::ReceiveAll,$byteIn,$byteOut)
  125.  
  126. write-host "Press ESC to stop the packet sniffer ..." -fore yellow
  127.  
  128. $escKey = 27
  129. $running = $true
  130. $packets = @()  # this will hold all packets for later analysis
  131.  
  132. while ($running)
  133.         {
  134.         # check and see if ESC was pressed
  135.         if ($host.ui.RawUi.KeyAvailable)
  136.                 {
  137.                 $key = $host.ui.RawUI.ReadKey("NoEcho,IncludeKeyUp,IncludeKeyDown")
  138.  
  139.                 if ($key.VirtualKeyCode -eq $ESCkey)
  140.                         {
  141.                         $running = $false
  142.                         }
  143.                 }
  144.        
  145.         if ($Seconds -ne 0 -and ($([DateTime]::Now) -gt $starttime.addseconds($Seconds)))  # if user-specified timeout has expired
  146.                 {
  147.                 exit
  148.                 }
  149.  
  150.         if (-not $socket.Available)  # see if any packets are in the queue
  151.                 {
  152.                 start-sleep -milliseconds 500
  153.                 continue
  154.                 }
  155.        
  156.         # receive data
  157.         $rcv = $socket.receive($byteData,0,$byteData.length,[net.sockets.socketflags]::None)
  158.  
  159.         # decode the header (see RFC 791 or this will make no sense)
  160.         $MemoryStream = new-object System.IO.MemoryStream($byteData,0,$rcv)
  161.         $BinaryReader = new-object System.IO.BinaryReader($MemoryStream)
  162.  
  163.         # First 8 bits of IP header contain version & header length
  164.         $VersionAndHeaderLength = $BinaryReader.ReadByte()
  165.  
  166.         # Next 8 bits contain the TOS (type of service)
  167.         $TypeOfService= $BinaryReader.ReadByte()
  168.  
  169.         # total length of header and payload
  170.         $TotalLength = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  171.  
  172.         $Identification = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  173.         $FlagsAndOffset = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  174.         $TTL = $BinaryReader.ReadByte()
  175.         $ProtocolNumber = $BinaryReader.ReadByte()
  176.         $Checksum = [Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())
  177.  
  178.         $SourceIPAddress = $BinaryReader.ReadUInt32()
  179.         $SourceIPAddress = [System.Net.IPAddress]$SourceIPAddress
  180.         $DestinationIPAddress = $BinaryReader.ReadUInt32()
  181.         $DestinationIPAddress = [System.Net.IPAddress]$DestinationIPAddress
  182.  
  183.         # Get the IP version number from the "left side" of the Byte
  184.         $ipVersion = [int]"0x$(('{0:X}' -f $VersionAndHeaderLength)[0])"
  185.  
  186.         # Get the header length by getting right 4 bits (usually will be 5, as in 5 32 bit words)
  187.         # multiplying by 4 converts from words to octets which is what TotalLength is measured in
  188.         $HeaderLength = [int]"0x$(('{0:X}' -f $VersionAndHeaderLength)[1])" * 4
  189.  
  190.         if ($HeaderLength -gt 20)  # if header includes Options (is gt 5 octets long)
  191.                 {
  192.                 [void]$BinaryReader.ReadBytes($HeaderLength - 20)  # should probably do something with this later
  193.                 }
  194.        
  195.         $Data = ""
  196.         $TCPFlagsString = @()  # make this an array
  197.         $TCPWindow = ""
  198.         $SequenceNumber = ""
  199.        
  200.         switch ($ProtocolNumber)  # see http://www.iana.org/assignments/protocol-numbers
  201.                 {
  202.                 1 {  # ICMP
  203.                         $protocolDesc = "ICMP"
  204.  
  205.                         $sourcePort = [uint16]0
  206.                         $destPort = [uint16]0
  207.                         break
  208.                         }
  209.                 2 {  # IGMP
  210.                         $protocolDesc = "IGMP"
  211.                         $sourcePort = [uint16]0
  212.                         $destPort = [uint16]0
  213.                         $IGMPType = $BinaryReader.ReadByte()
  214.                         $IGMPMaxRespTime = $BinaryReader.ReadByte()
  215.                         $IGMPChecksum = [System.Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())
  216.                         $Data = ByteToString $BinaryReader.ReadBytes($TotalLength - ($HeaderLength - 32))
  217.                         }
  218.                 6 {  # TCP
  219.                         $protocolDesc = "TCP"
  220.                        
  221.                         $sourcePort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  222.                         $destPort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  223.                         $SequenceNumber = NetworkToHostUInt32 $BinaryReader.ReadBytes(4)
  224.                         $AckNumber = NetworkToHostUInt32 $BinaryReader.ReadBytes(4)
  225.                         $TCPHeaderLength = [int]"0x$(('{0:X}' -f $BinaryReader.ReadByte())[0])" * 4  # reads Data Offset + 4 bits of Reserve (ignored)
  226.                        
  227.                         $TCPFlags = $BinaryReader.ReadByte()  # this will also contain 2 bits of Reserve on the left, but we can just ignore them.
  228.  
  229.                         switch ($TCPFlags)
  230.                                 {
  231.                                 { $_ -band $TCPFIN } { $TCPFlagsString += "FIN" }
  232.                                 { $_ -band $TCPSYN } { $TCPFlagsString += "SYN" }
  233.                                 { $_ -band $TCPRST } { $TCPFlagsString += "RST" }
  234.                                 { $_ -band $TCPPSH } { $TCPFlagsString += "PSH" }
  235.                                 { $_ -band $TCPACK } { $TCPFlagsString += "ACK" }
  236.                                 { $_ -band $TCPURG } { $TCPFlagsString += "URG" }
  237.                                 }
  238.                        
  239.                         $TCPWindow = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  240.                         $TCPChecksum = [System.Net.IPAddress]::NetworkToHostOrder($BinaryReader.ReadInt16())
  241.                         $TCPUrgentPointer = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  242.  
  243.                         if ($TCPHeaderLength -gt 20)  # get to start of data
  244.                                 {
  245.                                 [void]$BinaryReader.ReadBytes($TCPHeaderLength - 20)
  246.                                 }
  247.  
  248.                         # if SYN flag is set, sequence number is initial sequence number, and therefore the first
  249.                         # octet of the data is ISN + 1.
  250.                         if ($TCPFlags -band $TCPSYN)
  251.                                 {
  252.                                 $ISN = $SequenceNumber
  253.                                 #$SequenceNumber = $BinaryReader.ReadBytes(1)
  254.                                 [void]$BinaryReader.ReadBytes(1)
  255.                                 }
  256.  
  257.                         $Data = ByteToString $BinaryReader.ReadBytes($TotalLength - ($HeaderLength + $TCPHeaderLength))
  258.                         break
  259.                         }
  260.                 17 {  # UDP
  261.                         $protocolDesc = "UDP"
  262.  
  263.                         $sourcePort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  264.                         $destPort = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  265.                         $UDPLength = NetworkToHostUInt16 $BinaryReader.ReadBytes(2)
  266.                         [void]$BinaryReader.ReadBytes(2)
  267.                         # subtract udp header length (2 octets) and convert octets to bytes.
  268.                         $Data = ByteToString $BinaryReader.ReadBytes(($UDPLength - 2) * 4)
  269.                         break
  270.                         }
  271.                 default {
  272.                         $protocolDesc = "Other ($_)"
  273.                         $sourcePort = 0
  274.                         $destPort = 0
  275.                         break
  276.                         }
  277.                 }
  278.        
  279.         $BinaryReader.Close()
  280.         $memorystream.Close()
  281.  
  282.         if ($ResolveHosts)  # resolve IP addresses to hostnames
  283.                 {
  284.                 # GetHostEntry is horribly slow on failed lookups, so I'm not using it
  285.                 # $DestinationHostName = ([System.Net.DNS]::GetHostEntry($DestinationIPAddress.IPAddressToString)).Hostname
  286.                 # $SourceHostName = ([System.Net.DNS]::GetHostEntry($SourceIPAddress.IPAddressToString)).Hostname
  287.  
  288.                 $DestinationHostName = ResolveIP($DestinationIPAddress)
  289.                 $SourceHostName = ResolveIP($SourceIPAddress)
  290.                 }
  291.  
  292.         # now throw the stuff we consider important into a psobject
  293.         # $ipObject = new-object psobject
  294.  
  295.         if ($Protocol -eq "all" -or $Protocol -eq $protocolDesc)
  296.                 {
  297.                 $packet = new-object psobject
  298.  
  299.                 $packet | add-member noteproperty Destination $DestinationIPAddress
  300.                 if ($ResolveHosts) { $packet | add-member noteproperty DestinationHostName $DestinationHostName }
  301.                 $packet | add-member noteproperty Source $SourceIPAddress
  302.                 if ($ResolveHosts) { $packet | add-member noteproperty SourceHostName $SourceHostName }
  303.                 $packet | add-member noteproperty Version $ipVersion
  304.                 $packet | add-member noteproperty Protocol $protocolDesc
  305.                 $packet | add-member noteproperty Sequence $SequenceNumber
  306.                 $packet | add-member noteproperty Window $TCPWindow
  307.                 $packet | add-member noteproperty DestPort $destPort
  308.                 $packet | add-member noteproperty SourcePort $sourcePort
  309.                 $packet | add-member noteproperty Flags $TCPFlagsString
  310.                 $packet | add-member noteproperty Data $Data
  311.                 $packet | add-member noteproperty Time (get-date)
  312.  
  313.                 $packets += $packet  # add this packet to the array
  314.  
  315.                 if (-not $Silent)
  316.                         {
  317.                         $packet
  318.                         }
  319.                 }
  320.         }
  321.  
  322. # calculate statistics
  323. if ($Statistics)
  324.         {
  325.         $activity = "Analyzing network trace"
  326.  
  327.         # calculate elapsed time
  328.         # Using this logic, the beginning time is when the first packet is received,
  329.         #  not when packet capturing is started. That may or may not be ideal depending
  330.         #  on what you're trying to measure.
  331.         write-progress $activity "Counting packets"
  332.         $elapsed = $packets[-1].time - $packets[0].time
  333.  
  334.         #calculate packets per second
  335.         write-progress $activity "Calculating elapsed time"
  336.         $pps = $packets.count/(($packets[-1].time -$packets[0].time).totalseconds)
  337.         $pps="{0:N4}" -f $pps
  338.  
  339.         # Calculating protocol distribution
  340.         write-progress $activity "Calculating protocol distribution"
  341.         $protocols = $packets | sort protocol | group protocol | sort count -descending | select Count,@{name="Protocol";Expression={$_.name}}
  342.  
  343.         # Calculating source port distribution
  344.         write-progress $activity "Calculating source port distribution"
  345.         $sourceport = $packets | sort sourceport | group sourceport | sort count -descending | select Count,@{name="Port";Expression={$_.name}}
  346.  
  347.         # Calculating destination distribution
  348.         write-progress $activity "Calculating destination distribution"
  349.         $destinationlist = $packets | sort Destination | select Destination
  350.  
  351.         # Calculating destination port distribution
  352.         write-progress $activity "Calculating destination port distribution"
  353.         $destinationport = $packets | sort destport | group destport | sort count -descending | select Count,@{name="Port";Expression={$_.name}}
  354.  
  355.         # Building source list
  356.         write-progress $activity "Building source list"
  357.         $sourcelist = $packets | sort source | select Source
  358.  
  359.         # Building source IP list
  360.         write-progress $activity "Building source IP list"
  361.         $ips = $sourcelist | group source | sort count -descending | select Count,@{Name="IP";Expression={$_.Name}}
  362.                
  363.         # Build destination IP list
  364.         write-progress $activity "Building destination IP list"
  365.         $ipd = $destinationlist | group destination | sort count -descending | select Count,@{Name="IP";Expression={$_.Name}}
  366.  
  367.         # Presenting data
  368.         write-progress $activity "Compiling results"
  369.         $protocols = $protocols | Select Count,Protocol,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}}
  370.  
  371.         $destinationport = $destinationport | select Count,Port,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}}
  372.  
  373.         $sourceport = $sourceport | Select Count,Port,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}}
  374.  
  375.         if ($ResolveHosts)
  376.                 {
  377.                 write-progress $activity "Resolving IPs"
  378.  
  379.                 # add hostnames to the new object(s)
  380.                 foreach ($destination in $ipd)
  381.                         {
  382.                         $destination | add-member noteproperty "Host" $(ResolveIP([system.net.ipaddress]$destination.IP))
  383.                         }
  384.                 foreach ($source in $ips)
  385.                         {
  386.                         $source | add-member noteproperty "Host" $(ResolveIP([system.net.ipaddress]$source.IP))
  387.                         }
  388.                 }
  389.  
  390.         write-progress $activity "Compiling results"
  391.         $destinations = $ipd | Select Count,IP,Host,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}}
  392.         $sources = $ips | Select Count,IP,Host,@{Name="Percentage";Expression={"{0:P4}" -f ($_.count/$packets.count)}}
  393.  
  394.         $global:stats = new-object psobject
  395.  
  396.         $stats | add-member noteproperty "TotalPackets" $packets.count
  397.         $stats | add-member noteproperty "Elapsedtime" $elapsed
  398.         $stats | add-member noteproperty "PacketsPerSec" $pps
  399.         $stats | add-member noteproperty "Protocols" $protocols
  400.         $stats | add-member noteproperty "Destinations" $destinations
  401.         $stats | add-member noteproperty "DestinationPorts" $destinationport
  402.         $stats | add-member noteproperty "Sources" $sources
  403.         $stats | add-member noteproperty "SourcePorts" $sourceport
  404.  
  405.         write-host
  406.         write-host " TotalPackets: " $stats.totalpackets
  407.         write-host "  ElapsedTime: " $stats.elapsedtime
  408.         write-host "PacketsPerSec: " $stats.packetspersec
  409.         write-host
  410.         write-host "More statistics can be accessed from the global `$stats variable." -fore cyan
  411.        
  412.         }

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me