-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGet-Packet.ps1
412 lines (338 loc) · 14 KB
/
Get-Packet.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
#
# 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 ([email protected])
# 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]$Statistics, [switch]$Silent, [switch]$ResolveHosts)
$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
}