packetfu 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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