packetfu 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.document +4 -0
  2. data/CHANGES +36 -0
  3. data/INSTALL +40 -0
  4. data/LICENSE +28 -0
  5. data/README +25 -0
  6. data/TODO +25 -0
  7. data/examples/ackscan.rb +38 -0
  8. data/examples/arp.rb +60 -0
  9. data/examples/arphood.rb +56 -0
  10. data/examples/ethernet.rb +10 -0
  11. data/examples/examples.rb +3 -0
  12. data/examples/ids.rb +4 -0
  13. data/examples/idsv2.rb +6 -0
  14. data/examples/oui.txt +84177 -0
  15. data/examples/packetfu-shell.rb +111 -0
  16. data/examples/simple-stats.rb +42 -0
  17. data/examples/slammer.rb +33 -0
  18. data/examples/uniqpcap.rb +15 -0
  19. data/lib/packetfu.rb +108 -0
  20. data/lib/packetfu/arp.rb +239 -0
  21. data/lib/packetfu/capture.rb +169 -0
  22. data/lib/packetfu/config.rb +55 -0
  23. data/lib/packetfu/eth.rb +264 -0
  24. data/lib/packetfu/icmp.rb +153 -0
  25. data/lib/packetfu/inject.rb +65 -0
  26. data/lib/packetfu/invalid.rb +41 -0
  27. data/lib/packetfu/ip.rb +318 -0
  28. data/lib/packetfu/ipv6.rb +230 -0
  29. data/lib/packetfu/packet.rb +492 -0
  30. data/lib/packetfu/pcap.rb +502 -0
  31. data/lib/packetfu/structfu.rb +274 -0
  32. data/lib/packetfu/tcp.rb +1061 -0
  33. data/lib/packetfu/udp.rb +210 -0
  34. data/lib/packetfu/utils.rb +182 -0
  35. data/test/all_tests.rb +37 -0
  36. data/test/ptest.rb +10 -0
  37. data/test/sample.pcap +0 -0
  38. data/test/sample2.pcap +0 -0
  39. data/test/test_arp.rb +135 -0
  40. data/test/test_eth.rb +90 -0
  41. data/test/test_icmp.rb +54 -0
  42. data/test/test_inject.rb +33 -0
  43. data/test/test_invalid.rb +28 -0
  44. data/test/test_ip.rb +69 -0
  45. data/test/test_ip6.rb +68 -0
  46. data/test/test_octets.rb +37 -0
  47. data/test/test_packet.rb +41 -0
  48. data/test/test_pcap.rb +210 -0
  49. data/test/test_structfu.rb +112 -0
  50. data/test/test_tcp.rb +327 -0
  51. data/test/test_udp.rb +73 -0
  52. metadata +144 -0
@@ -0,0 +1,169 @@
1
+ module PacketFu
2
+
3
+ # The Capture class is used to construct PcapRub objects in order to collect
4
+ # packets from an interface.
5
+ #
6
+ # This class requires PcapRub. In addition, you will need root (or root-like) privileges
7
+ # in order to capture from the interface.
8
+ #
9
+ # Note, on some wireless cards, setting :promisc => true will disable capturing.
10
+ #
11
+ # == Example
12
+ #
13
+ # # Typical use
14
+ # cap = PacketFu::Capture.new(:iface => 'eth0', :promisc => true)
15
+ # cap.start
16
+ # sleep 10
17
+ # cap.save
18
+ # first_packet = cap.array[0]
19
+ #
20
+ # # Tcpdump-like use
21
+ # cap = PacketFu::Capture.new(:start => true)
22
+ # cap.show_live(:save => true, :filter => 'tcp and not port 22')
23
+ #
24
+ # == See Also
25
+ #
26
+ # Read, Write
27
+ class Capture
28
+ attr_accessor :array, :stream # Leave these public and open.
29
+ attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
30
+
31
+ def initialize(args={})
32
+ @array = [] # Where the packet array goes.
33
+ @stream = [] # Where the stream goes.
34
+ @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
35
+ @snaplen = args[:snaplen] || 0xffff
36
+ @promisc = args[:promisc] || false # Sensible for some Intel wifi cards
37
+ @timeout = args[:timeout] || 1
38
+
39
+ setup_params(args)
40
+ end
41
+
42
+ # Used by new().
43
+ def setup_params(args={})
44
+ filter = args[:filter] # Not global; filter criteria can change.
45
+ start = args[:start] || false
46
+ capture if start
47
+ bpf(:filter=>filter) if filter
48
+ end
49
+
50
+ # capture() initializes the @stream varaible. Valid arguments are:
51
+ #
52
+ # :filter
53
+ # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
54
+ # :start
55
+ # When true, start capturing packets to the @stream variable. Defaults to true
56
+ def capture(args={})
57
+ if Process.euid.zero?
58
+ filter = args[:filter]
59
+ start = args[:start] || true
60
+ if start
61
+ begin
62
+ @stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
63
+ rescue RuntimeError
64
+ $stderr.print "Are you sure you're root? Error: "
65
+ raise
66
+ end
67
+ bpf(:filter=>filter) if filter
68
+ else
69
+ @stream = []
70
+ end
71
+ @stream
72
+ else
73
+ raise RuntimeError,"Not root, so can't capture packets. Error: "
74
+ end
75
+ end
76
+
77
+ # start() is equivalent to capture().
78
+ def start(args={})
79
+ capture(args)
80
+ end
81
+
82
+ # clear() clears the @stream and @array variables, essentially starting the
83
+ # capture session over. Valid arguments are:
84
+ #
85
+ # :array
86
+ # If true, the @array is cleared.
87
+ # :stream
88
+ # If true, the @stream is cleared.
89
+ def clear(args={})
90
+ array = args[:array] || true
91
+ stream = args[:stream] || true
92
+ @array = [] if array
93
+ @stream = [] if stream
94
+ end
95
+
96
+ # bpf() sets a bpf filter on a capture session. Valid arugments are:
97
+ #
98
+ # :filter
99
+ # Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
100
+ def bpf(args={})
101
+ filter = args[:filter]
102
+ capture if @stream.class == Array
103
+ @stream.setfilter(filter)
104
+ end
105
+
106
+ # wire_to_array() saves a packet stream as an array of binary strings. From here,
107
+ # packets may accessed by other functions. Note that the wire_to_array empties
108
+ # the stream, so multiple calls will append new packets to @array.
109
+ # Valid arguments are:
110
+ #
111
+ # :filter
112
+ # Provide a bpf filter to apply to packets moving from @stream to @array.
113
+ def wire_to_array(args={})
114
+ filter = args[:filter]
115
+ bpf(:filter=>filter) if filter
116
+
117
+ while this_pkt = @stream.next
118
+ @array << this_pkt
119
+ end
120
+ @array.size
121
+ end
122
+
123
+ # next() exposes the Stream object's next method to the outside world.
124
+ def next
125
+ return @stream.next
126
+ end
127
+
128
+ # w2a() is a equivalent to wire_to_array()
129
+ def w2a(args={})
130
+ wire_to_array(args)
131
+ end
132
+
133
+ # save() is a equivalent to wire_to_array()
134
+ def save(args={})
135
+ wire_to_array(args)
136
+ end
137
+
138
+ # show_live() is a method to capture packets and display peek() data to stdout. Valid arguments are:
139
+ #
140
+ # :filter
141
+ # Provide a bpf filter to captured packets.
142
+ # :save
143
+ # Save the capture in @array
144
+ # :verbose
145
+ # TODO: Not implemented yet; do more than just peek() at the packets.
146
+ # :quiet
147
+ # TODO: Not implemented yet; do less than peek() at the packets.
148
+ def show_live(args={})
149
+ filter = args[:filter]
150
+ save = args[:save]
151
+ verbose = args[:verbose] || args[:v] || false
152
+ quiet = args[:quiet] || args[:q] || false # Setting q and v doesn't make a lot of sense but hey.
153
+
154
+ # Ensure the capture's started.
155
+ if @stream.class == Array
156
+ capture
157
+ end
158
+
159
+ @stream.setfilter(filter) if filter
160
+ while true
161
+ @stream.each do |pkt|
162
+ puts Packet.parse(pkt).peek
163
+ @array << pkt if args[:save]
164
+ end
165
+ end
166
+ end
167
+
168
+ end
169
+ end
@@ -0,0 +1,55 @@
1
+ module PacketFu
2
+
3
+ # The Config class holds various bits of useful default information
4
+ # for packet creation. If initialized without arguments, @iface will be
5
+ # set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will
6
+ # be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
7
+ # this is a TODO.)
8
+ #
9
+ # Any number of instance variables can be passed in to the intialize function (as a
10
+ # hash), though only the expected network-related variables will be readable and
11
+ # writeable directly.
12
+ #
13
+ # == Examples
14
+ #
15
+ # PacketFu::Config.new(:ip_saddr => "1.2.3.4").ip_saddr #=> "1.2.3.4"
16
+ # PacketFu::Config.new(:foo=>"bar").foo #=> NomethodError: undefined method `foo'...
17
+ #
18
+ # The config() function, however, does provide access to custom variables:
19
+ #
20
+ # PacketFu::Config.new(:foo=>"bar").config[:foo] #=> "bar"
21
+ # obj = PacketFu::Config.new(:foo=>"bar")
22
+ # obj.config(:baz => "bat")
23
+ # obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"}
24
+ class Config
25
+ attr_accessor :eth_saddr, # The discovered eth_saddr
26
+ :eth_daddr, # The discovered eth_daddr (ie, the gateway)
27
+ :eth_src, # The discovered eth_src in binary form.
28
+ :eth_dst, # The discovered eth_dst (gateway) in binary form.
29
+ :ip_saddr, # The discovered ip_saddr
30
+ :ip_src, # The discovered ip_src in binary form.
31
+ :iface, # The declared interface.
32
+ :pcapfile # A declared default file to write to.
33
+
34
+ def initialize(args={})
35
+ if Process.euid.zero?
36
+ @iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
37
+ end
38
+ @pcapfile = "/tmp/out.pcap"
39
+ args.each_pair { |k,v| self.instance_variable_set(("@" + k.to_s).intern,v) }
40
+ end
41
+
42
+ # Returns all instance variables as a hash (including custom variables set at initialization).
43
+ def config(arg=nil)
44
+ if arg.nil?
45
+ config_hash = {}
46
+ self.instance_variables.each { |v| config_hash[v.delete("@").intern] = self.instance_variable_get(v) }
47
+ config_hash
48
+ else
49
+ arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,264 @@
1
+ module PacketFu
2
+
3
+ # EthOui is the Organizationally Unique Identifier portion of a MAC address, used in EthHeader.
4
+ #
5
+ # See the OUI list at http://standards.ieee.org/regauth/oui/oui.txt
6
+ #
7
+ # ==== Header Definition
8
+ #
9
+ # Fixnum :b0
10
+ # Fixnum :b1
11
+ # Fixnum :b2
12
+ # Fixnum :b3
13
+ # Fixnum :b4
14
+ # Fixnum :b5
15
+ # Fixnum :local
16
+ # Fixnum :multicast
17
+ # Int16 :oui, Default: 0x1ac5 :)
18
+ class EthOui < Struct.new(:b5, :b4, :b3, :b2, :b1, :b0, :local, :multicast, :oui)
19
+
20
+ # EthOui is unusual in that the bit values do not enjoy StructFu typing.
21
+ def initialize(args={})
22
+ args[:local] ||= 0
23
+ args[:oui] ||= 0x1ac # :)
24
+ args.each_pair {|k,v| args[k] = 0 unless v}
25
+ super(args[:b5], args[:b4], args[:b3], args[:b2],
26
+ args[:b1], args[:b0], args[:local], args[:multicast],
27
+ args[:oui])
28
+ end
29
+
30
+ # Returns the object in string form.
31
+ def to_s
32
+ byte = 0
33
+ byte += 0b10000000 if b5.to_i == 1
34
+ byte += 0b01000000 if b4.to_i == 1
35
+ byte += 0b00100000 if b3.to_i == 1
36
+ byte += 0b00010000 if b2.to_i == 1
37
+ byte += 0b00001000 if b1.to_i == 1
38
+ byte += 0b00000100 if b0.to_i == 1
39
+ byte += 0b00000010 if local.to_i == 1
40
+ byte += 0b00000001 if multicast.to_i == 1
41
+ [byte,oui].pack("Cn")
42
+ end
43
+
44
+ # Reads a string to populate the object.
45
+ def read(str)
46
+ force_binary(str)
47
+ return self if str.nil?
48
+ if 1.respond_to? :ord
49
+ byte = str[0].ord
50
+ else
51
+ byte = str[0]
52
+ end
53
+ self[:b5] = byte & 0b10000000 == 0b10000000 ? 1 : 0
54
+ self[:b4] = byte & 0b01000000 == 0b01000000 ? 1 : 0
55
+ self[:b3] = byte & 0b00100000 == 0b00100000 ? 1 : 0
56
+ self[:b2] = byte & 0b00010000 == 0b00010000 ? 1 : 0
57
+ self[:b1] = byte & 0b00001000 == 0b00001000 ? 1 : 0
58
+ self[:b0] = byte & 0b00000100 == 0b00000100 ? 1 : 0
59
+ self[:local] = byte & 0b00000010 == 0b00000010 ? 1 : 0
60
+ self[:multicast] = byte & 0b00000001 == 0b00000001 ? 1 : 0
61
+ self[:oui] = str[1,2].unpack("n").first
62
+ self
63
+ end
64
+
65
+ end
66
+
67
+ # EthNic is the Network Interface Controler portion of a MAC address, used in EthHeader.
68
+ #
69
+ # ==== Header Definition
70
+ #
71
+ # Fixnum :n1
72
+ # Fixnum :n2
73
+ # Fixnum :n3
74
+ #
75
+ class EthNic < Struct.new(:n0, :n1, :n2)
76
+
77
+ # EthNic does not enjoy StructFu typing.
78
+ def initialize(args={})
79
+ args.each_pair {|k,v| args[k] = 0 unless v}
80
+ super(args[:n0], args[:n1], args[:n2])
81
+ end
82
+
83
+ # Returns the object in string form.
84
+ def to_s
85
+ [n0,n1,n2].map {|x| x.to_i}.pack("C3")
86
+ end
87
+
88
+ # Reads a string to populate the object.
89
+ def read(str)
90
+ force_binary(str)
91
+ return self if str.nil?
92
+ self[:n0], self[:n1], self[:n2] = str[0,3].unpack("C3")
93
+ self
94
+ end
95
+
96
+ end
97
+
98
+ # EthMac is the combination of an EthOui and EthNic, used in EthHeader.
99
+ #
100
+ # ==== Header Definition
101
+ #
102
+ # EthOui :oui # See EthOui
103
+ # EthNic :nic # See EthNic
104
+ class EthMac < Struct.new(:oui, :nic)
105
+
106
+ def initialize(args={})
107
+ super(
108
+ EthOui.new.read(args[:oui]),
109
+ EthNic.new.read(args[:nic]))
110
+ end
111
+
112
+ # Returns the object in string form.
113
+ def to_s
114
+ "#{self[:oui]}#{self[:nic]}"
115
+ end
116
+
117
+ # Reads a string to populate the object.
118
+ def read(str)
119
+ force_binary(str)
120
+ return self if str.nil?
121
+ self.oui.read str[0,3]
122
+ self.nic.read str[3,3]
123
+ self
124
+ end
125
+
126
+ end
127
+
128
+ # EthHeader is a complete Ethernet struct, used in EthPacket.
129
+ # It's the base header for all other protocols, such as IPHeader,
130
+ # TCPHeader, etc.
131
+ #
132
+ # For more on the construction on MAC addresses, see
133
+ # http://en.wikipedia.org/wiki/MAC_address
134
+ #
135
+ # ==== Header Definition
136
+ #
137
+ # EthMac :eth_dst # See EthMac
138
+ # EthMac :eth_src # See EthMac
139
+ # Int16 :eth_proto, Default: 0x8000 # IP 0x0800, Arp 0x0806
140
+ # String :body
141
+ class EthHeader < Struct.new(:eth_dst, :eth_src, :eth_proto, :body)
142
+ include StructFu
143
+
144
+ def initialize(args={})
145
+ super(
146
+ EthMac.new.read(args[:eth_dst]),
147
+ EthMac.new.read(args[:eth_src]),
148
+ Int16.new(args[:eth_proto] || 0x0800),
149
+ StructFu::String.new.read(args[:body])
150
+ )
151
+ end
152
+
153
+ # Setter for the Ethernet destination address.
154
+ def eth_dst=(i); typecast(i); end
155
+ # Getter for the Ethernet destination address.
156
+ def eth_dst; self[:eth_dst].to_s; end
157
+ # Setter for the Ethernet source address.
158
+ def eth_src=(i); typecast(i); end
159
+ # Getter for the Ethernet source address.
160
+ def eth_src; self[:eth_src].to_s; end
161
+ # Setter for the Ethernet protocol number.
162
+ def eth_proto=(i); typecast(i); end
163
+ # Getter for the Ethernet protocol number.
164
+ def eth_proto; self[:eth_proto].to_i; end
165
+
166
+ # Returns the object in string form.
167
+ def to_s
168
+ self.to_a.map {|x| x.to_s}.join
169
+ end
170
+
171
+ # Reads a string to populate the object.
172
+ def read(str)
173
+ force_binary(str)
174
+ return self if str.nil?
175
+ self[:eth_dst].read str[0,6]
176
+ self[:eth_src].read str[6,6]
177
+ self[:eth_proto].read str[12,2]
178
+ self[:body].read str[14,str.size]
179
+ self
180
+ end
181
+
182
+ # Converts a readable MAC (11:22:33:44:55:66) to a binary string.
183
+ # Readable MAC's may be split on colons, dots, spaces, or underscores.
184
+ #
185
+ # irb> PacketFu::EthHeader.mac2str("11:22:33:44:55:66")
186
+ #
187
+ # #=> "\021\"3DUf"
188
+ def self.mac2str(mac)
189
+ if mac.split(/[:\x2d\x2e\x5f]+/).size == 6
190
+ ret = mac.split(/[:\x2d\x2e\x20\x5f]+/).collect {|x| x.to_i(16)}.pack("C6")
191
+ else
192
+ raise ArgumentError, "Unkown format for mac address."
193
+ end
194
+ return ret
195
+ end
196
+
197
+ # Converts a binary string to a readable MAC (11:22:33:44:55:66).
198
+ #
199
+ # irb> PacketFu::EthHeader.str2mac("\x11\x22\x33\x44\x55\x66")
200
+ #
201
+ # #=> "11:22:33:44:55:66"
202
+ def self.str2mac(mac='')
203
+ if mac.to_s.size == 6 && mac.kind_of?(::String)
204
+ ret = mac.unpack("C6").map {|x| sprintf("%02x",x)}.join(":")
205
+ end
206
+ end
207
+
208
+ # Sets the source MAC address in a more readable way.
209
+ def eth_saddr=(mac)
210
+ mac = EthHeader.mac2str(mac)
211
+ self[:eth_src].read mac
212
+ self[:eth_src]
213
+ end
214
+
215
+ # Gets the source MAC address in a more readable way.
216
+ def eth_saddr
217
+ EthHeader.str2mac(self[:eth_src].to_s)
218
+ end
219
+
220
+ # Set the destination MAC address in a more readable way.
221
+ def eth_daddr=(mac)
222
+ mac = EthHeader.mac2str(mac)
223
+ self[:eth_dst].read mac
224
+ self[:eth_dst]
225
+ end
226
+
227
+ # Gets the destination MAC address in a more readable way.
228
+ def eth_daddr
229
+ EthHeader.str2mac(self[:eth_dst].to_s)
230
+ end
231
+
232
+ end
233
+
234
+ # EthPacket is used to construct Ethernet packets. They contain an
235
+ # Ethernet header, and that's about it.
236
+ #
237
+ # == Example
238
+ #
239
+ # require 'packetfu'
240
+ # eth_pkt = PacketFu::EthPacket.new
241
+ # eth_pkt.eth_saddr="00:1c:23:44:55:66"
242
+ # eth_pkt.eth_daddr="00:1c:24:aa:bb:cc"
243
+ #
244
+ # eth_pkt.to_w('eth0') # Inject on the wire. (require root)
245
+ class EthPacket < Packet
246
+ attr_accessor :eth_header
247
+
248
+ def initialize(args={})
249
+ @eth_header = EthHeader.new(args).read(args[:eth])
250
+ @headers = [@eth_header]
251
+ super
252
+ end
253
+
254
+ # Does nothing, really, since there's no length or
255
+ # checksum to calculate for a straight Ethernet packet.
256
+ def recalc(args={})
257
+ @headers[0].inspect
258
+ end
259
+
260
+ end
261
+
262
+ end
263
+
264
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby