packetfu 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/.gitignore +3 -0
  2. data/INSTALL.rdoc +40 -0
  3. data/LICENSE.txt +25 -0
  4. data/examples/100kpackets.rb +41 -0
  5. data/examples/ackscan.rb +38 -0
  6. data/examples/arp.rb +60 -0
  7. data/examples/arphood.rb +59 -0
  8. data/examples/dissect_thinger.rb +22 -0
  9. data/examples/ethernet.rb +10 -0
  10. data/examples/examples.rb +3 -0
  11. data/examples/ids.rb +4 -0
  12. data/examples/idsv2.rb +6 -0
  13. data/examples/new-simple-stats.rb +52 -0
  14. data/examples/oui.txt +84177 -0
  15. data/examples/packetfu-shell.rb +113 -0
  16. data/examples/simple-sniffer.rb +40 -0
  17. data/examples/simple-stats.rb +50 -0
  18. data/examples/slammer.rb +33 -0
  19. data/examples/uniqpcap.rb +15 -0
  20. data/lib/packetfu.rb +147 -0
  21. data/lib/packetfu/capture.rb +169 -0
  22. data/lib/packetfu/config.rb +58 -0
  23. data/lib/packetfu/inject.rb +65 -0
  24. data/lib/packetfu/packet.rb +533 -0
  25. data/lib/packetfu/pcap.rb +594 -0
  26. data/lib/packetfu/protos/arp.rb +268 -0
  27. data/lib/packetfu/protos/eth.rb +296 -0
  28. data/lib/packetfu/protos/hsrp.rb +206 -0
  29. data/lib/packetfu/protos/icmp.rb +179 -0
  30. data/lib/packetfu/protos/invalid.rb +55 -0
  31. data/lib/packetfu/protos/ip.rb +378 -0
  32. data/lib/packetfu/protos/ipv6.rb +250 -0
  33. data/lib/packetfu/protos/tcp.rb +1127 -0
  34. data/lib/packetfu/protos/udp.rb +240 -0
  35. data/lib/packetfu/structfu.rb +294 -0
  36. data/lib/packetfu/utils.rb +194 -0
  37. data/lib/packetfu/version.rb +50 -0
  38. data/packetfu.gemspec +21 -0
  39. data/setup.rb +1586 -0
  40. data/test/all_tests.rb +41 -0
  41. data/test/ethpacket_spec.rb +74 -0
  42. data/test/packet_spec.rb +73 -0
  43. data/test/packet_subclasses_spec.rb +13 -0
  44. data/test/packetfu_spec.rb +90 -0
  45. data/test/ptest.rb +16 -0
  46. data/test/sample-ipv6.pcap +0 -0
  47. data/test/sample.pcap +0 -0
  48. data/test/sample2.pcap +0 -0
  49. data/test/sample_hsrp_pcapr.cap +0 -0
  50. data/test/structfu_spec.rb +335 -0
  51. data/test/tcp_spec.rb +101 -0
  52. data/test/test_arp.rb +135 -0
  53. data/test/test_eth.rb +91 -0
  54. data/test/test_hsrp.rb +20 -0
  55. data/test/test_icmp.rb +54 -0
  56. data/test/test_inject.rb +31 -0
  57. data/test/test_invalid.rb +28 -0
  58. data/test/test_ip.rb +69 -0
  59. data/test/test_ip6.rb +68 -0
  60. data/test/test_octets.rb +37 -0
  61. data/test/test_packet.rb +174 -0
  62. data/test/test_pcap.rb +209 -0
  63. data/test/test_structfu.rb +112 -0
  64. data/test/test_tcp.rb +327 -0
  65. data/test/test_udp.rb +73 -0
  66. data/test/vlan-pcapr.cap +0 -0
  67. metadata +85 -6
@@ -0,0 +1,240 @@
1
+ module PacketFu
2
+
3
+ # UDPHeader is a complete UDP struct, used in UDPPacket. Many Internet-critical protocols
4
+ # rely on UDP, such as DNS and World of Warcraft.
5
+ #
6
+ # For more on UDP packets, see http://www.networksorcery.com/enp/protocol/udp.htm
7
+ #
8
+ # ==== Header Definition
9
+ # Int16 :udp_src
10
+ # Int16 :udp_dst
11
+ # Int16 :udp_len Default: calculated
12
+ # Int16 :udp_sum Default: 0. Often calculated.
13
+ # String :body
14
+ class UDPHeader < Struct.new(:udp_src, :udp_dst, :udp_len, :udp_sum, :body)
15
+
16
+ include StructFu
17
+
18
+ def initialize(args={})
19
+ super(
20
+ Int16.new(args[:udp_src]),
21
+ Int16.new(args[:udp_dst]),
22
+ Int16.new(args[:udp_len] || udp_calc_len),
23
+ Int16.new(args[:udp_sum]),
24
+ StructFu::String.new.read(args[:body])
25
+ )
26
+ end
27
+
28
+ # Returns the object in string form.
29
+ def to_s
30
+ self.to_a.map {|x| x.to_s}.join
31
+ end
32
+
33
+ # Reads a string to populate the object.
34
+ def read(str)
35
+ force_binary(str)
36
+ return self if str.nil?
37
+ self[:udp_src].read(str[0,2])
38
+ self[:udp_dst].read(str[2,2])
39
+ self[:udp_len].read(str[4,2])
40
+ self[:udp_sum].read(str[6,2])
41
+ self[:body].read(str[8,str.size])
42
+ self
43
+ end
44
+
45
+ # Setter for the UDP source port.
46
+ def udp_src=(i); typecast i; end
47
+ # Getter for the UDP source port.
48
+ def udp_src; self[:udp_src].to_i; end
49
+ # Setter for the UDP destination port.
50
+ def udp_dst=(i); typecast i; end
51
+ # Getter for the UDP destination port.
52
+ def udp_dst; self[:udp_dst].to_i; end
53
+ # Setter for the length field. Usually should be recalc()'ed instead.
54
+ def udp_len=(i); typecast i; end
55
+ # Getter for the length field.
56
+ def udp_len; self[:udp_len].to_i; end
57
+ # Setter for the checksum. Usually should be recalc()'ed instad.
58
+ def udp_sum=(i); typecast i; end
59
+ # Getter for the checksum.
60
+ def udp_sum; self[:udp_sum].to_i; end
61
+
62
+ # Returns the true length of the UDP packet.
63
+ def udp_calc_len
64
+ body.to_s.size + 8
65
+ end
66
+
67
+ # Recalculates calculated fields for UDP.
68
+ def udp_recalc(args=:all)
69
+ arg = arg.intern if arg.respond_to? :intern
70
+ case args
71
+ when :udp_len
72
+ self.udp_len = udp_calc_len
73
+ when :all
74
+ self.udp_recalc(:udp_len)
75
+ else
76
+ raise ArgumentError, "No such field `#{arg}'"
77
+ end
78
+ end
79
+
80
+ # Equivalent to udp_src.to_i
81
+ def udp_sport
82
+ self.udp_src
83
+ end
84
+
85
+ # Equivalent to udp_src=
86
+ def udp_sport=(arg)
87
+ self.udp_src=(arg)
88
+ end
89
+
90
+ # Equivalent to udp_dst
91
+ def udp_dport
92
+ self.udp_dst
93
+ end
94
+
95
+ # Equivalent to udp_dst=
96
+ def udp_dport=(arg)
97
+ self.udp_dst=(arg)
98
+ end
99
+
100
+ # Readability aliases
101
+
102
+ def udp_sum_readable
103
+ "0x%04x" % udp_sum
104
+ end
105
+
106
+ end
107
+
108
+ # UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
109
+ #
110
+ # == Example
111
+ #
112
+ # udp_pkt = PacketFu::UDPPacket.new
113
+ # udp_pkt.udp_src=rand(0xffff-1024) + 1024
114
+ # udp_pkt.udp_dst=53
115
+ #
116
+ # udp_pkt.ip_saddr="1.2.3.4"
117
+ # udp_pkt.ip_daddr="10.20.30.40"
118
+ #
119
+ # udp_pkt.recalc
120
+ # udp_pkt.to_f('/tmp/udp.pcap')
121
+ #
122
+ # == Parameters
123
+ #
124
+ # :eth
125
+ # A pre-generated EthHeader object.
126
+ # :ip
127
+ # A pre-generated IPHeader object.
128
+ # :flavor
129
+ # TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
130
+ # flavor, but their underlying ip headers do.
131
+ # :config
132
+ # A hash of return address details, often the output of Utils.whoami?
133
+ class UDPPacket < Packet
134
+
135
+ attr_accessor :eth_header, :ip_header, :udp_header
136
+
137
+ def self.can_parse?(str)
138
+ return false unless str.size >= 28
139
+ return false unless EthPacket.can_parse? str
140
+ return false unless IPPacket.can_parse? str
141
+ return false unless str[23,1] == "\x11"
142
+ return true
143
+ end
144
+
145
+ def read(str=nil, args={})
146
+ raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
147
+ @eth_header.read(str)
148
+ @ip_header.read(str[14,str.size])
149
+ @eth_header.body = @ip_header
150
+ if args[:strip]
151
+ udp_len = str[16,2].unpack("n")[0] - 20
152
+ @udp_header.read(str[14+(@ip_header.ip_hlen),udp_len])
153
+ else
154
+ @udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
155
+ end
156
+ @ip_header.body = @udp_header
157
+ super(args)
158
+ self
159
+ end
160
+
161
+ def initialize(args={})
162
+ @eth_header = EthHeader.new(args).read(args[:eth])
163
+ @ip_header = IPHeader.new(args).read(args[:ip])
164
+ @ip_header.ip_proto=0x11
165
+ @udp_header = UDPHeader.new(args).read(args[:icmp])
166
+ @ip_header.body = @udp_header
167
+ @eth_header.body = @ip_header
168
+ @headers = [@eth_header, @ip_header, @udp_header]
169
+ super
170
+ udp_calc_sum
171
+ end
172
+
173
+ # udp_calc_sum() computes the UDP checksum, and is called upon intialization.
174
+ # It usually should be called just prior to dropping packets to a file or on the wire.
175
+ def udp_calc_sum
176
+ # This is /not/ delegated down to @udp_header since we need info
177
+ # from the IP header, too.
178
+ checksum = (ip_src.to_i >> 16)
179
+ checksum += (ip_src.to_i & 0xffff)
180
+ checksum += (ip_dst.to_i >> 16)
181
+ checksum += (ip_dst.to_i & 0xffff)
182
+ checksum += 0x11
183
+ checksum += udp_len.to_i
184
+ checksum += udp_src.to_i
185
+ checksum += udp_dst.to_i
186
+ checksum += udp_len.to_i
187
+ if udp_len.to_i >= 8
188
+ # For IP trailers. This isn't very reliable. :/
189
+ real_udp_payload = payload.to_s[0,(udp_len.to_i-8)]
190
+ else
191
+ # I'm not going to mess with this right now.
192
+ real_udp_payload = payload
193
+ end
194
+ chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
195
+ chk_payload.unpack("n*").each {|x| checksum = checksum+x}
196
+ checksum = checksum % 0xffff
197
+ checksum = 0xffff - checksum
198
+ checksum == 0 ? 0xffff : checksum
199
+ @udp_header.udp_sum = checksum
200
+ end
201
+
202
+ # udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
203
+ #
204
+ # :all
205
+ # Recomputes all calculated fields.
206
+ # :udp_sum
207
+ # Recomputes the UDP checksum.
208
+ # :udp_len
209
+ # Recomputes the UDP length.
210
+ def udp_recalc(args=:all)
211
+ case args
212
+ when :udp_len
213
+ @udp_header.udp_recalc
214
+ when :udp_sum
215
+ udp_calc_sum
216
+ when :all
217
+ @udp_header.udp_recalc
218
+ udp_calc_sum
219
+ else
220
+ raise ArgumentError, "No such field `#{arg}'"
221
+ end
222
+ end
223
+
224
+ # Peek provides summary data on packet contents.
225
+ def peek_format
226
+ peek_data = ["U "]
227
+ peek_data << "%-5d" % self.to_s.size
228
+ peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
229
+ peek_data << "->"
230
+ peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
231
+ peek_data << "%23s" % "I:"
232
+ peek_data << "%04x" % self.ip_id
233
+ peek_data.join
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
240
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
@@ -0,0 +1,294 @@
1
+ # StructFu, a nifty way to leverage Ruby's built in Struct class
2
+ # to create meaningful binary data.
3
+
4
+ module StructFu
5
+
6
+ # Normally, self.size and self.length will refer to the Struct
7
+ # size as an array. It's a hassle to redefine, so this introduces some
8
+ # shorthand to get at the size of the resultant string.
9
+ def sz
10
+ self.to_s.size
11
+ end
12
+
13
+ alias len sz
14
+
15
+ # Typecast is used mostly by packet header classes, such as IPHeader,
16
+ # TCPHeader, and the like. It takes an argument, and casts it to the
17
+ # expected type for that element.
18
+ def typecast(i)
19
+ c = caller[0].match(/.*`([^']+)='/)[1]
20
+ self[c.intern].read i
21
+ end
22
+
23
+ # Used like typecast(), but specifically for casting Strings to StructFu::Strings.
24
+ def body=(i)
25
+ if i.kind_of? ::String
26
+ typecast(i)
27
+ elsif i.kind_of? StructFu
28
+ self[:body] = i
29
+ elsif i.nil?
30
+ self[:body] = StructFu::String.new.read("")
31
+ else
32
+ raise ArgumentError, "Can't cram a #{i.class} into a StructFu :body"
33
+ end
34
+ end
35
+
36
+ # Handle deep copies correctly. Marshal in 1.9, re-read myself on 1.8
37
+ def clone
38
+ begin
39
+ Marshal.load(Marshal.dump(self))
40
+ rescue
41
+ self.class.new.read(self.to_s)
42
+ end
43
+ end
44
+
45
+ # Ints all have a value, an endianness, and a default value.
46
+ # Note that the signedness of Int values are implicit as
47
+ # far as the subclasses are concerned; to_i and to_f will
48
+ # return Integer/Float versions of the input value, instead
49
+ # of attempting to unpack the pack value. (This can be a useful
50
+ # hint to other functions).
51
+ #
52
+ # ==== Header Definition
53
+ #
54
+ # Fixnum :value
55
+ # Symbol :endian
56
+ # Fixnum :width
57
+ # Fixnum :default
58
+ class Int < Struct.new(:value, :endian, :width, :default)
59
+ alias :v= :value=
60
+ alias :v :value
61
+ alias :e= :endian=
62
+ alias :e :endian
63
+ alias :w= :width=
64
+ alias :w :width
65
+ alias :d= :default=
66
+ alias :d :default
67
+
68
+ # This is a parent class definition and should not be used directly.
69
+ def to_s
70
+ raise StandardError, "StructFu::Int#to_s accessed, must be redefined."
71
+ end
72
+
73
+ # Returns the Int as an Integer.
74
+ def to_i
75
+ (self.v || self.d).to_i
76
+ end
77
+
78
+ # Returns the Int as a Float.
79
+ def to_f
80
+ (self.v || self.d).to_f
81
+ end
82
+
83
+ def initialize(value=nil, endian=nil, width=nil, default=nil)
84
+ super(value,endian,width,default=0)
85
+ end
86
+
87
+ # Reads either an Integer or a packed string, and populates the value accordingly.
88
+ def read(i)
89
+ self.v = i.kind_of?(Integer) ? i.to_i : i.to_s.unpack(@packstr).first
90
+ self
91
+ end
92
+
93
+ end
94
+
95
+ # Int8 is a one byte value.
96
+ class Int8 < Int
97
+
98
+ def initialize(v=nil)
99
+ super(v,nil,w=1)
100
+ @packstr = "C"
101
+ end
102
+
103
+ # Returns a one byte value as a packed string.
104
+ def to_s
105
+ [(self.v || self.d)].pack("C")
106
+ end
107
+
108
+ end
109
+
110
+ # Int16 is a two byte value.
111
+ class Int16 < Int
112
+ def initialize(v=nil, e=:big)
113
+ super(v,e,w=2)
114
+ @packstr = (self.e == :big) ? "n" : "v"
115
+ end
116
+
117
+ # Returns a two byte value as a packed string.
118
+ def to_s
119
+ @packstr = (self.e == :big) ? "n" : "v"
120
+ [(self.v || self.d)].pack(@packstr)
121
+ end
122
+
123
+ end
124
+
125
+ # Int16be is a two byte value in big-endian format. The endianness cannot be altered.
126
+ class Int16be < Int16
127
+ undef :endian=
128
+ end
129
+
130
+ # Int16le is a two byte value in little-endian format. The endianness cannot be altered.
131
+ class Int16le < Int16
132
+ undef :endian=
133
+ def initialize(v=nil, e=:little)
134
+ super(v,e)
135
+ @packstr = (self.e == :big) ? "n" : "v"
136
+ end
137
+ end
138
+
139
+ # Int32 is a four byte value.
140
+ class Int32 < Int
141
+ def initialize(v=nil, e=:big)
142
+ super(v,e,w=4)
143
+ @packstr = (self.e == :big) ? "N" : "V"
144
+ end
145
+
146
+ # Returns a four byte value as a packed string.
147
+ def to_s
148
+ @packstr = (self.e == :big) ? "N" : "V"
149
+ [(self.v || self.d)].pack(@packstr)
150
+ end
151
+
152
+ end
153
+
154
+ # Int32be is a four byte value in big-endian format. The endianness cannot be altered.
155
+ class Int32be < Int32
156
+ undef :endian=
157
+ end
158
+
159
+ # Int32le is a four byte value in little-endian format. The endianness cannot be altered.
160
+ class Int32le < Int32
161
+ undef :endian=
162
+ def initialize(v=nil, e=:little)
163
+ super(v,e)
164
+ end
165
+ end
166
+
167
+ # Strings are just like regular strings, except it comes with a read() function
168
+ # so that it behaves like other StructFu elements.
169
+ class String < ::String
170
+ def read(str)
171
+ str = str.to_s
172
+ self.replace str
173
+ self
174
+ end
175
+ end
176
+
177
+ # Provides a primitive for creating strings, preceeded by
178
+ # an Int type of length. By default, a string of length zero with
179
+ # a one-byte length is presumed.
180
+ #
181
+ # Note that IntStrings aren't used for much, but it seemed like a good idea at the time.
182
+ class IntString < Struct.new(:int, :string, :mode)
183
+
184
+ def initialize(string='',int=Int8,mode=nil)
185
+ if int < Int
186
+ super(int.new,string,mode)
187
+ calc
188
+ else
189
+ raise "IntStrings need a StructFu::Int for a length."
190
+ end
191
+ end
192
+
193
+ # Calculates the size of a string, and sets it as the value.
194
+ def calc
195
+ int.v = string.to_s.size
196
+ self.to_s
197
+ end
198
+
199
+ # Returns the object as a string, depending on the mode set upon object creation.
200
+ def to_s
201
+ if mode == :parse
202
+ "#{int}" + [string].pack("a#{len}")
203
+ elsif mode == :fix
204
+ self.int.v = string.size
205
+ "#{int}#{string}"
206
+ else
207
+ "#{int}#{string}"
208
+ end
209
+ end
210
+
211
+ # By redefining #string=, we can ensure the correct value
212
+ # is calculated upon assignment. If you'd prefer to have
213
+ # an incorrect value, use the syntax, obj[:string]="value"
214
+ # instead. Note, by using the alternate form, you must
215
+ # #calc before you can trust the int's value. Think of the =
216
+ # assignment as "set to equal," while the []= assignment
217
+ # as "boxing in" the value. Maybe.
218
+ def string=(s)
219
+ self[:string] = s
220
+ calc
221
+ end
222
+
223
+ # Shorthand for querying a length. Note that the usual "length"
224
+ # and "size" refer to the number of elements of this struct.
225
+ def len
226
+ self[:int].value
227
+ end
228
+
229
+ # Override the size, if you must.
230
+ def len=(i)
231
+ self[:int].value=i
232
+ end
233
+
234
+ # Read takes a string, assumes an int width as previously
235
+ # defined upon initialization, but makes no guarantees
236
+ # the int value isn't lying. You're on your own to test
237
+ # for that (or use parse() with a :mode set).
238
+ def read(s)
239
+ unless s[0,int.width].size == int.width
240
+ raise StandardError, "String is too short for type #{int.class}"
241
+ else
242
+ int.read(s[0,int.width])
243
+ self[:string] = s[int.width,s.size]
244
+ end
245
+ self.to_s
246
+ end
247
+
248
+ # parse() is like read(), except that it interprets the string, either
249
+ # based on the declared length, or the actual length. Which strategy
250
+ # is used is dependant on which :mode is set (with self.mode).
251
+ #
252
+ # :parse : Read the length, and then read in that many bytes of the string.
253
+ # The string may be truncated or padded out with nulls, as dictated by the value.
254
+ #
255
+ # :fix : Skip the length, read the rest of the string, then set the length
256
+ # to what it ought to be.
257
+ #
258
+ # else : If neither of these modes are set, just perfom a normal read().
259
+ # This is the default.
260
+ def parse(s)
261
+ unless s[0,int.width].size == int.width
262
+ raise StandardError, "String is too short for type #{int.class}"
263
+ else
264
+ case mode
265
+ when :parse
266
+ int.read(s[0,int.width])
267
+ self[:string] = s[int.width,int.value]
268
+ if string.size < int.value
269
+ self[:string] += ("\x00" * (int.value - self[:string].size))
270
+ end
271
+ when :fix
272
+ self.string = s[int.width,s.size]
273
+ else
274
+ return read(s)
275
+ end
276
+ end
277
+ self.to_s
278
+ end
279
+
280
+ end
281
+
282
+ end
283
+
284
+ class Struct
285
+
286
+ # Monkeypatch for Struct to include some string safety -- anything that uses
287
+ # Struct is going to presume binary strings anyway.
288
+ def force_binary(str)
289
+ PacketFu.force_binary(str)
290
+ end
291
+
292
+ end
293
+
294
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby