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,206 @@
1
+ module PacketFu
2
+
3
+ # HSRPHeader is a complete HSRP struct, used in HSRPPacket. HSRP is typically used for
4
+ # fault-tolerant default gateway in IP routing environment.
5
+ #
6
+ # For more on HSRP packets, see http://www.networksorcery.com/enp/protocol/hsrp.htm
7
+ #
8
+ # Submitted by fropert@packetfault.org. Thanks, Francois!
9
+ #
10
+ # ==== Header Definition
11
+ #
12
+ # Int8 :hsrp_version Default: 0 # Version
13
+ # Int8 :hsrp_opcode # Opcode
14
+ # Int8 :hsrp_state # State
15
+ # Int8 :hsrp_hellotime Default: 3 # Hello Time
16
+ # Int8 :hsrp_holdtime Default: 10 # Hold Time
17
+ # Int8 :hsrp_priority # Priority
18
+ # Int8 :hsrp_group # Group
19
+ # Int8 :hsrp_reserved Default: 0 # Reserved
20
+ # String :hsrp_password # Authentication Data
21
+ # Octets :hsrp_vip # Virtual IP Address
22
+ # String :body
23
+ class HSRPHeader < Struct.new(:hsrp_version, :hsrp_opcode, :hsrp_state,
24
+ :hsrp_hellotime, :hsrp_holdtime,
25
+ :hsrp_priority, :hsrp_group,
26
+ :hsrp_reserved, :hsrp_password,
27
+ :hsrp_vip, :body)
28
+
29
+ include StructFu
30
+
31
+ def initialize(args={})
32
+ super(
33
+ Int8.new(args[:hsrp_version] || 0),
34
+ Int8.new(args[:hsrp_opcode]),
35
+ Int8.new(args[:hsrp_state]),
36
+ Int8.new(args[:hsrp_hellotime] || 3),
37
+ Int8.new(args[:hsrp_holdtime] || 10),
38
+ Int8.new(args[:hsrp_priority]),
39
+ Int8.new(args[:hsrp_group]),
40
+ Int8.new(args[:hsrp_reserved] || 0),
41
+ StructFu::String.new.read(args[:hsrp_password] || "cisco\x00\x00\x00"),
42
+ Octets.new.read(args[:hsrp_vip] || ("\x00" * 4)),
43
+ StructFu::String.new.read(args[:body])
44
+ )
45
+ end
46
+
47
+ # Returns the object in string form.
48
+ def to_s
49
+ self.to_a.map {|x| x.to_s}.join
50
+ end
51
+
52
+ # Reads a string to populate the object.
53
+ def read(str)
54
+ force_binary(str)
55
+ return self if str.nil?
56
+ self[:hsrp_version].read(str[0,1])
57
+ self[:hsrp_opcode].read(str[1,1])
58
+ self[:hsrp_state].read(str[2,1])
59
+ self[:hsrp_hellotime].read(str[3,1])
60
+ self[:hsrp_holdtime].read(str[4,1])
61
+ self[:hsrp_priority].read(str[5,1])
62
+ self[:hsrp_group].read(str[6,1])
63
+ self[:hsrp_reserved].read(str[7,1])
64
+ self[:hsrp_password].read(str[8,8])
65
+ self[:hsrp_vip].read(str[16,4])
66
+ self[:body].read(str[20,str.size]) if str.size > 20
67
+ self
68
+ end
69
+
70
+ # Setter for the type.
71
+ def hsrp_version=(i); typecast i; end
72
+ # Getter for the type.
73
+ def hsrp_version; self[:hsrp_version].to_i; end
74
+ # Setter for the type.
75
+ def hsrp_opcode=(i); typecast i; end
76
+ # Getter for the type.
77
+ def hsrp_opcode; self[:hsrp_opcode].to_i; end
78
+ # Setter for the type.
79
+ def hsrp_state=(i); typecast i; end
80
+ # Getter for the type.
81
+ def hsrp_state; self[:hsrp_state].to_i; end
82
+ # Setter for the type.
83
+ def hsrp_hellotime=(i); typecast i; end
84
+ # Getter for the type.
85
+ def hsrp_hellotime; self[:hsrp_hellotime].to_i; end
86
+ # Setter for the type.
87
+ def hsrp_holdtime=(i); typecast i; end
88
+ # Getter for the type.
89
+ def hsrp_holdtime; self[:hsrp_holdtime].to_i; end
90
+ # Setter for the type.
91
+ def hsrp_priority=(i); typecast i; end
92
+ # Getter for the type.
93
+ def hsrp_priority; self[:hsrp_priority].to_i; end
94
+ # Setter for the type.
95
+ def hsrp_group=(i); typecast i; end
96
+ # Getter for the type.
97
+ def hsrp_group; self[:hsrp_group].to_i; end
98
+ # Setter for the type.
99
+ def hsrp_reserved=(i); typecast i; end
100
+ # Getter for the type.
101
+ def hsrp_reserved; self[:hsrp_reserved].to_i; end
102
+
103
+ def hsrp_addr=(addr)
104
+ self[:hsrp_vip].read_quad(addr)
105
+ end
106
+
107
+ # Returns a more readable IP source address.
108
+ def hsrp_addr
109
+ self[:hsrp_vip].to_x
110
+ end
111
+
112
+ # Readability aliases
113
+
114
+ alias :hsrp_vip_readable :hsrp_addr
115
+
116
+ def hsrp_password_readable
117
+ hsrp_password.to_s.inspect
118
+ end
119
+
120
+ end
121
+
122
+ # HSRPPacket is used to construct HSRP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
123
+ #
124
+ # == Example
125
+ #
126
+ # hsrp_pkt.new
127
+ # hsrp_pkt.hsrp_opcode = 0
128
+ # hsrp_pkt.hsrp_state = 16
129
+ # hsrp_pkt.hsrp_priority = 254
130
+ # hsrp_pkt.hsrp_group = 1
131
+ # hsrp_pkt.hsrp_vip = 10.100.100.254
132
+ # hsrp_pkt.recalc
133
+ # hsrp_pkt.to_f('/tmp/hsrp.pcap')
134
+ #
135
+ # == Parameters
136
+ #
137
+ # :eth
138
+ # A pre-generated EthHeader object.
139
+ # :ip
140
+ # A pre-generated IPHeader object.
141
+ # :udp
142
+ # A pre-generated UDPHeader object.
143
+ # :flavor
144
+ # TODO: HSRP packets don't tend have any flavor.
145
+ # :config
146
+ # A hash of return address details, often the output of Utils.whoami?
147
+ class HSRPPacket < Packet
148
+
149
+ attr_accessor :eth_header, :ip_header, :udp_header, :hsrp_header
150
+
151
+ def self.can_parse?(str)
152
+ return false unless str.size >= 54
153
+ return false unless EthPacket.can_parse? str
154
+ return false unless IPPacket.can_parse? str
155
+ return false unless UDPPacket.can_parse? str
156
+ temp_packet = UDPPacket.new
157
+ temp_packet.read(str)
158
+ if temp_packet.ip_ttl == 1 and [temp_packet.udp_sport,temp_packet.udp_dport] == [1985,1985]
159
+ return true
160
+ else
161
+ return false
162
+ end
163
+ end
164
+
165
+ def read(str=nil, args={})
166
+ raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
167
+ @eth_header.read(str)
168
+ @ip_header.read(str[14,str.size])
169
+ @eth_header.body = @ip_header
170
+ @udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
171
+ @ip_header.body = @udp_header
172
+ @hsrp_header.read(str[14+(@ip_header.ip_hlen)+8,str.size])
173
+ @udp_header.body = @hsrp_header
174
+ super(args)
175
+ self
176
+ end
177
+
178
+ def initialize(args={})
179
+ @eth_header = EthHeader.new(args).read(args[:eth])
180
+ @ip_header = IPHeader.new(args).read(args[:ip])
181
+ @ip_header.ip_proto = 0x11
182
+ @udp_header = UDPHeader.new(args).read(args[:udp])
183
+ @hsrp_header = HSRPHeader.new(args).read(args[:hsrp])
184
+ @udp_header.body = @hsrp_header
185
+ @ip_header.body = @udp_header
186
+ @eth_header.body = @ip_header
187
+ @headers = [@eth_header, @ip_header, @udp_header, @hsrp_header]
188
+ super
189
+ end
190
+
191
+ # Peek provides summary data on packet contents.
192
+ def peek_format
193
+ peek_data = ["UH "]
194
+ peek_data << "%-5d" % self.to_s.size
195
+ peek_data << "%-16s" % self.hsrp_addr
196
+ peek_data << "%-4d" % self.hsrp_group
197
+ peek_data << "%-35s" % self.hsrp_password_readable
198
+ peek_data << "%-15s" % self.ip_saddr
199
+ peek_data.join
200
+ end
201
+
202
+ end
203
+
204
+ end
205
+
206
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
@@ -0,0 +1,179 @@
1
+ module PacketFu
2
+
3
+ # ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is
4
+ # typically used for network administration and connectivity testing.
5
+ #
6
+ # For more on ICMP packets, see
7
+ # http://www.networksorcery.com/enp/protocol/icmp.htm
8
+ #
9
+ # ==== Header Definition
10
+ #
11
+ # Int8 :icmp_type # Type
12
+ # Int8 :icmp_code # Code
13
+ # Int16 :icmp_sum Default: calculated # Checksum
14
+ # String :body
15
+ class ICMPHeader < Struct.new(:icmp_type, :icmp_code, :icmp_sum, :body)
16
+
17
+ include StructFu
18
+
19
+ def initialize(args={})
20
+ super(
21
+ Int8.new(args[:icmp_type]),
22
+ Int8.new(args[:icmp_code]),
23
+ Int16.new(args[:icmp_sum] || icmp_calc_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[:icmp_type].read(str[0,1])
38
+ self[:icmp_code].read(str[1,1])
39
+ self[:icmp_sum].read(str[2,2])
40
+ self[:body].read(str[4,str.size])
41
+ self
42
+ end
43
+
44
+ # Setter for the type.
45
+ def icmp_type=(i); typecast i; end
46
+ # Getter for the type.
47
+ def icmp_type; self[:icmp_type].to_i; end
48
+ # Setter for the code.
49
+ def icmp_code=(i); typecast i; end
50
+ # Getter for the code.
51
+ def icmp_code; self[:icmp_code].to_i; end
52
+ # Setter for the checksum. Note, this is calculated automatically with
53
+ # icmp_calc_sum.
54
+ def icmp_sum=(i); typecast i; end
55
+ # Getter for the checksum.
56
+ def icmp_sum; self[:icmp_sum].to_i; end
57
+
58
+ # Calculates and sets the checksum for the object.
59
+ def icmp_calc_sum
60
+ checksum = (icmp_type.to_i << 8) + icmp_code.to_i
61
+ chk_body = (body.to_s.size % 2 == 0 ? body.to_s : body.to_s + "\x00")
62
+ if 1.respond_to? :ord
63
+ chk_body.scan(/../).map { |x| (x[0].ord << 8) + x[1].ord }.each { |y| checksum += y }
64
+ else
65
+ chk_body.scan(/../).map { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y }
66
+ end
67
+ checksum = checksum % 0xffff
68
+ checksum = 0xffff - checksum
69
+ checksum == 0 ? 0xffff : checksum
70
+ end
71
+
72
+ # Recalculates the calculatable fields for ICMP.
73
+ def icmp_recalc(arg=:all)
74
+ # How silly is this, you can't intern a symbol in ruby 1.8.7pl72?
75
+ # I'm this close to monkey patching Symbol so you can force it...
76
+ arg = arg.intern if arg.respond_to? :intern
77
+ case arg
78
+ when :icmp_sum
79
+ self.icmp_sum=icmp_calc_sum
80
+ when :all
81
+ self.icmp_sum=icmp_calc_sum
82
+ else
83
+ raise ArgumentError, "No such field `#{arg}'"
84
+ end
85
+ end
86
+
87
+ # Readability aliases
88
+
89
+ def icmp_sum_readable
90
+ "0x%04x" % icmp_sum
91
+ end
92
+
93
+ end
94
+
95
+ # ICMPPacket is used to construct ICMP Packets. They contain an EthHeader, an IPHeader, and a ICMPHeader.
96
+ #
97
+ # == Example
98
+ #
99
+ # icmp_pkt.new
100
+ # icmp_pkt.icmp_type = 8
101
+ # icmp_pkt.icmp_code = 0
102
+ # icmp_pkt.payload = "ABC, easy as 123. As simple as do-re-mi. ABC, 123, baby, you and me!"
103
+ #
104
+ # icmp_pkt.ip_saddr="1.2.3.4"
105
+ # icmp_pkt.ip_daddr="5.6.7.8"
106
+ #
107
+ # icmp_pkt.recalc
108
+ # icmp_pkt.to_f('/tmp/icmp.pcap')
109
+ #
110
+ # == Parameters
111
+ #
112
+ # :eth
113
+ # A pre-generated EthHeader object.
114
+ # :ip
115
+ # A pre-generated IPHeader object.
116
+ # :flavor
117
+ # TODO: Sets the "flavor" of the ICMP packet. Pings, in particular, often betray their true
118
+ # OS.
119
+ # :config
120
+ # A hash of return address details, often the output of Utils.whoami?
121
+ class ICMPPacket < Packet
122
+
123
+ attr_accessor :eth_header, :ip_header, :icmp_header
124
+
125
+ def self.can_parse?(str)
126
+ return false unless str.size >= 54
127
+ return false unless EthPacket.can_parse? str
128
+ return false unless IPPacket.can_parse? str
129
+ return false unless str[23,1] == "\x01"
130
+ return true
131
+ end
132
+
133
+ def read(str=nil, args={})
134
+ raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
135
+ @eth_header.read(str)
136
+ @ip_header.read(str[14,str.size])
137
+ @eth_header.body = @ip_header
138
+ @icmp_header.read(str[14+(@ip_header.ip_hlen),str.size])
139
+ @ip_header.body = @icmp_header
140
+ super(args)
141
+ self
142
+ end
143
+
144
+ def initialize(args={})
145
+ @eth_header = EthHeader.new(args).read(args[:eth])
146
+ @ip_header = IPHeader.new(args).read(args[:ip])
147
+ @ip_header.ip_proto = 1
148
+ @icmp_header = ICMPHeader.new(args).read(args[:icmp])
149
+
150
+ @ip_header.body = @icmp_header
151
+ @eth_header.body = @ip_header
152
+
153
+ @headers = [@eth_header, @ip_header, @icmp_header]
154
+ super
155
+ end
156
+
157
+ # Peek provides summary data on packet contents.
158
+ def peek_format
159
+ peek_data = ["IC "] # I is taken by IP
160
+ peek_data << "%-5d" % self.to_s.size
161
+ type = case self.icmp_type.to_i
162
+ when 8
163
+ "ping"
164
+ when 0
165
+ "pong"
166
+ else
167
+ "%02x-%02x" % [self.icmp_type, self.icmp_code]
168
+ end
169
+ peek_data << "%-21s" % "#{self.ip_saddr}:#{type}"
170
+ peek_data << "->"
171
+ peek_data << "%21s" % "#{self.ip_daddr}"
172
+ peek_data << "%23s" % "I:"
173
+ peek_data << "%04x" % self.ip_id
174
+ peek_data.join
175
+ end
176
+
177
+ end
178
+
179
+ end
@@ -0,0 +1,55 @@
1
+ module PacketFu
2
+
3
+ # InvalidHeader catches all packets that we don't already have a Struct for,
4
+ # or for whatever reason, violates some basic packet rules for other packet
5
+ # types.
6
+ class InvalidHeader < Struct.new(:body)
7
+ include StructFu
8
+
9
+ def initialize(args={})
10
+ args[:body] ||= StructFu::String.new
11
+ super(args[:body])
12
+ end
13
+
14
+ # Returns the object in string form.
15
+ def to_s
16
+ self.to_a.map {|x| x.to_s}.join
17
+ end
18
+
19
+ # Reads a string to populate the object.
20
+ def read(str)
21
+ force_binary(str)
22
+ return self if str.nil?
23
+ self[:body].read str
24
+ self
25
+ end
26
+
27
+ end
28
+
29
+ # You probably don't want to write invalid packets on purpose.
30
+ class InvalidPacket < Packet
31
+ attr_accessor :invalid_header
32
+
33
+ # Any packet is potentially an invalid packet
34
+ def self.can_parse?(str)
35
+ true
36
+ end
37
+
38
+ def self.layer
39
+ 0
40
+ end
41
+
42
+ def read(str=nil,args={})
43
+ @invalid_header.read(str)
44
+ self
45
+ end
46
+
47
+ def initialize(args={})
48
+ @invalid_header = (args[:invalid] || InvalidHeader.new)
49
+ @headers = [@invalid_header]
50
+ end
51
+ end
52
+
53
+ end # module PacketFu
54
+
55
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
@@ -0,0 +1,378 @@
1
+ require 'ipaddr'
2
+ module PacketFu
3
+
4
+ # Octets implements the addressing scheme for IP.
5
+ #
6
+ # ==== Header Definition
7
+ #
8
+ # Int8 :o1
9
+ # Int8 :o2
10
+ # Int8 :o3
11
+ # Int8 :o4
12
+ class Octets < Struct.new(:o1, :o2, :o3, :o4)
13
+ include StructFu
14
+
15
+ def initialize(args={})
16
+ super(
17
+ Int8.new(args[:o1]),
18
+ Int8.new(args[:o2]),
19
+ Int8.new(args[:o3]),
20
+ Int8.new(args[:o4]))
21
+ end
22
+
23
+ # Returns the object in string form.
24
+ def to_s
25
+ self.to_a.map {|x| x.to_s}.join
26
+ end
27
+
28
+ # Reads a string to populate the object.
29
+ def read(str)
30
+ force_binary(str)
31
+ return self if str.nil?
32
+ self[:o1].read str[0,1]
33
+ self[:o2].read str[1,1]
34
+ self[:o3].read str[2,1]
35
+ self[:o4].read str[3,1]
36
+ self
37
+ end
38
+
39
+ # Returns an address in dotted-quad format.
40
+ def to_x
41
+ ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
42
+ IPAddr.new(ip_str).to_s
43
+ end
44
+
45
+ # Returns an address in numerical format.
46
+ def to_i
47
+ ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
48
+ IPAddr.new(ip_str).to_i
49
+ end
50
+
51
+ # Set the IP Address by reading a dotted-quad address.
52
+ def read_quad(str)
53
+ read([IPAddr.new(str).to_i].pack("N"))
54
+ end
55
+
56
+ end
57
+
58
+ # IPHeader is a complete IP struct, used in IPPacket. Most traffic on most networks today is IP-based.
59
+ #
60
+ # For more on IP packets, see http://www.networksorcery.com/enp/protocol/ip.htm
61
+ #
62
+ # ==== Header Definition
63
+ #
64
+ # Fixnum (4 bits) :ip_v, Default: 4
65
+ # Fixnum (4 bits) :ip_hl, Default: 5
66
+ # Int8 :ip_tos, Default: 0 # TODO: Break out the bits
67
+ # Int16 :ip_len, Default: calculated
68
+ # Int16 :ip_id, Default: calculated # IRL, hardly random.
69
+ # Int16 :ip_frag, Default: 0 # TODO: Break out the bits
70
+ # Int8 :ip_ttl, Default: 0xff # Changes per flavor
71
+ # Int8 :ip_proto, Default: 0x01 # TCP: 0x06, UDP 0x11, ICMP 0x01
72
+ # Int16 :ip_sum, Default: calculated
73
+ # Octets :ip_src
74
+ # Octets :ip_dst
75
+ # String :body
76
+ #
77
+ # Note that IPPackets will always be somewhat incorrect upon initalization,
78
+ # and want an IPHeader#recalc() to become correct before a
79
+ # Packet#to_f or Packet#to_w.
80
+ class IPHeader < Struct.new(:ip_v, :ip_hl, :ip_tos, :ip_len,
81
+ :ip_id, :ip_frag, :ip_ttl, :ip_proto,
82
+ :ip_sum, :ip_src, :ip_dst, :body)
83
+ include StructFu
84
+
85
+ def initialize(args={})
86
+ @random_id = rand(0xffff)
87
+ super(
88
+ (args[:ip_v] || 4),
89
+ (args[:ip_hl] || 5),
90
+ Int8.new(args[:ip_tos]),
91
+ Int16.new(args[:ip_len] || 20),
92
+ Int16.new(args[:ip_id] || ip_calc_id),
93
+ Int16.new(args[:ip_frag]),
94
+ Int8.new(args[:ip_ttl] || 32),
95
+ Int8.new(args[:ip_proto]),
96
+ Int16.new(args[:ip_sum] || ip_calc_sum),
97
+ Octets.new.read(args[:ip_src] || "\x00\x00\x00\x00"),
98
+ Octets.new.read(args[:ip_dst] || "\x00\x00\x00\x00"),
99
+ StructFu::String.new.read(args[:body])
100
+ )
101
+ end
102
+
103
+ # Returns the object in string form.
104
+ def to_s
105
+ byte_v_hl = [(self.ip_v << 4) + self.ip_hl].pack("C")
106
+ byte_v_hl + (self.to_a[2,10].map {|x| x.to_s}.join)
107
+ end
108
+
109
+ # Reads a string to populate the object.
110
+ def read(str)
111
+ force_binary(str)
112
+ return self if str.nil?
113
+ self[:ip_v] = str[0,1].unpack("C").first >> 4
114
+ self[:ip_hl] = str[0,1].unpack("C").first.to_i & 0x0f
115
+ self[:ip_tos].read(str[1,1])
116
+ self[:ip_len].read(str[2,2])
117
+ self[:ip_id].read(str[4,2])
118
+ self[:ip_frag].read(str[6,2])
119
+ self[:ip_ttl].read(str[8,1])
120
+ self[:ip_proto].read(str[9,1])
121
+ self[:ip_sum].read(str[10,2])
122
+ self[:ip_src].read(str[12,4])
123
+ self[:ip_dst].read(str[16,4])
124
+ self[:body].read(str[20,str.size]) if str.size > 20
125
+ self
126
+ end
127
+
128
+ # Setter for the version.
129
+ def ip_v=(i); self[:ip_v] = i.to_i; end
130
+ # Getter for the version.
131
+ def ip_v; self[:ip_v].to_i; end
132
+ # Setter for the header length (divide by 4)
133
+ def ip_hl=(i); self[:ip_hl] = i.to_i; end
134
+ # Getter for the header length (multiply by 4)
135
+ def ip_hl; self[:ip_hl].to_i; end
136
+ # Setter for the differentiated services
137
+ def ip_tos=(i); typecast i; end
138
+ # Getter for the differentiated services
139
+ def ip_tos; self[:ip_tos].to_i; end
140
+ # Setter for total length.
141
+ def ip_len=(i); typecast i; end
142
+ # Getter for total length.
143
+ def ip_len; self[:ip_len].to_i; end
144
+ # Setter for the identication number.
145
+ def ip_id=(i); typecast i; end
146
+ # Getter for the identication number.
147
+ def ip_id; self[:ip_id].to_i; end
148
+ # Setter for the fragmentation ID.
149
+ def ip_frag=(i); typecast i; end
150
+ # Getter for the fragmentation ID.
151
+ def ip_frag; self[:ip_frag].to_i; end
152
+ # Setter for the time to live.
153
+ def ip_ttl=(i); typecast i; end
154
+ # Getter for the time to live.
155
+ def ip_ttl; self[:ip_ttl].to_i; end
156
+ # Setter for the protocol number.
157
+ def ip_proto=(i); typecast i; end
158
+ # Getter for the protocol number.
159
+ def ip_proto; self[:ip_proto].to_i; end
160
+ # Setter for the checksum.
161
+ def ip_sum=(i); typecast i; end
162
+ # Getter for the checksum.
163
+ def ip_sum; self[:ip_sum].to_i; end
164
+ # Setter for the source IP address.
165
+ def ip_src=(i)
166
+ case i
167
+ when Numeric
168
+ self[:ip_src] = Octets.new.read([i].pack("N"))
169
+ when Octets
170
+ self[:ip_src] = i
171
+ else
172
+ typecast i
173
+ end
174
+ end
175
+ # Getter for the source IP address.
176
+ def ip_src; self[:ip_src].to_i; end
177
+ # Setter for the destination IP address.
178
+ def ip_dst=(i)
179
+ case i
180
+ when Numeric
181
+ self[:ip_dst] = Octets.new.read([i].pack("N"))
182
+ when Octets
183
+ self[:ip_dst] = i
184
+ else
185
+ typecast i
186
+ end
187
+ end
188
+ # Getter for the destination IP address.
189
+ def ip_dst; self[:ip_dst].to_i; end
190
+
191
+ # Calulcate the true length of the packet.
192
+ def ip_calc_len
193
+ (ip_hl * 4) + body.to_s.length
194
+ end
195
+
196
+ # Return the claimed header length
197
+ def ip_hlen
198
+ (ip_hl * 4)
199
+ end
200
+
201
+ # Calculate the true checksum of the packet.
202
+ # (Yes, this is the long way to do it, but it's e-z-2-read for mathtards like me.)
203
+ def ip_calc_sum
204
+ checksum = (((self.ip_v << 4) + self.ip_hl) << 8) + self.ip_tos
205
+ checksum += self.ip_len
206
+ checksum += self.ip_id
207
+ checksum += self.ip_frag
208
+ checksum += (self.ip_ttl << 8) + self.ip_proto
209
+ checksum += (self.ip_src >> 16)
210
+ checksum += (self.ip_src & 0xffff)
211
+ checksum += (self.ip_dst >> 16)
212
+ checksum += (self.ip_dst & 0xffff)
213
+ checksum = checksum % 0xffff
214
+ checksum = 0xffff - checksum
215
+ checksum == 0 ? 0xffff : checksum
216
+ end
217
+
218
+ # Retrieve the IP ID
219
+ def ip_calc_id
220
+ @random_id
221
+ end
222
+
223
+ # Sets a more readable IP address. If you wants to manipulate individual octets,
224
+ # (eg, for host scanning in one network), it would be better use ip_src.o1 through
225
+ # ip_src.o4 instead.
226
+ def ip_saddr=(addr)
227
+ self[:ip_src].read_quad(addr)
228
+ end
229
+
230
+ # Returns a more readable IP source address.
231
+ def ip_saddr
232
+ self[:ip_src].to_x
233
+ end
234
+
235
+ # Sets a more readable IP address.
236
+ def ip_daddr=(addr)
237
+ self[:ip_dst].read_quad(addr)
238
+ end
239
+
240
+ # Returns a more readable IP destination address.
241
+ def ip_daddr
242
+ self[:ip_dst].to_x
243
+ end
244
+
245
+ # Translate various formats of IPv4 Addresses to an array of digits.
246
+ def self.octet_array(addr)
247
+ if addr.class == String
248
+ oa = addr.split('.').collect {|x| x.to_i}
249
+ elsif addr.class == Fixnum
250
+ oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
251
+ elsif addr.class == Bignum
252
+ oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
253
+ elsif addr.class == Array
254
+ oa = addr
255
+ else
256
+ raise ArgumentError, "IP Address should be a dotted quad string, an array of ints, or a bignum"
257
+ end
258
+ end
259
+
260
+ # Recalculate the calculated IP fields. Valid arguments are:
261
+ # :all
262
+ # :ip_len
263
+ # :ip_sum
264
+ # :ip_id
265
+ def ip_recalc(arg=:all)
266
+ case arg
267
+ when :ip_len
268
+ self.ip_len=ip_calc_len
269
+ when :ip_sum
270
+ self.ip_sum=ip_calc_sum
271
+ when :ip_id
272
+ @random_id = rand(0xffff)
273
+ when :all
274
+ self.ip_id= ip_calc_id
275
+ self.ip_len= ip_calc_len
276
+ self.ip_sum= ip_calc_sum
277
+ else
278
+ raise ArgumentError, "No such field `#{arg}'"
279
+ end
280
+ end
281
+
282
+ # Readability aliases
283
+
284
+ alias :ip_src_readable :ip_saddr
285
+ alias :ip_dst_readable :ip_daddr
286
+
287
+ def ip_id_readable
288
+ "0x%04x" % ip_id
289
+ end
290
+
291
+ def ip_sum_readable
292
+ "0x%04x" % ip_sum
293
+ end
294
+
295
+ end
296
+
297
+ # IPPacket is used to construct IP packets. They contain an EthHeader, an IPHeader, and usually
298
+ # a transport-layer protocol such as UDPHeader, TCPHeader, or ICMPHeader.
299
+ #
300
+ # == Example
301
+ #
302
+ # require 'packetfu'
303
+ # ip_pkt = PacketFu::IPPacket.new
304
+ # ip_pkt.ip_saddr="10.20.30.40"
305
+ # ip_pkt.ip_daddr="192.168.1.1"
306
+ # ip_pkt.ip_proto=1
307
+ # ip_pkt.ip_ttl=64
308
+ # ip_pkt.ip_payload="\x00\x00\x12\x34\x00\x01\x00\x01"+
309
+ # "Lovingly hand-crafted echo responses delivered directly to your door."
310
+ # ip_pkt.recalc
311
+ # ip_pkt.to_f('/tmp/ip.pcap')
312
+ #
313
+ # == Parameters
314
+ #
315
+ # :eth
316
+ # A pre-generated EthHeader object.
317
+ # :ip
318
+ # A pre-generated IPHeader object.
319
+ # :flavor
320
+ # TODO: Sets the "flavor" of the IP packet. This might include known sets of IP options, and
321
+ # certainly known starting TTLs.
322
+ # :config
323
+ # A hash of return address details, often the output of Utils.whoami?
324
+ class IPPacket < Packet
325
+
326
+ attr_accessor :eth_header, :ip_header
327
+
328
+ def self.can_parse?(str)
329
+ return false unless str.size >= 34
330
+ return false unless EthPacket.can_parse? str
331
+ if str[12,2] == "\x08\x00"
332
+ if 1.respond_to? :ord
333
+ ipv = str[14,1][0].ord >> 4
334
+ else
335
+ ipv = str[14,1][0] >> 4
336
+ end
337
+ return true if ipv == 4
338
+ else
339
+ return false
340
+ end
341
+ end
342
+
343
+ def read(str=nil, args={})
344
+ raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
345
+ @eth_header.read(str)
346
+ @ip_header.read(str[14,str.size])
347
+ @eth_header.body = @ip_header
348
+ super(args)
349
+ self
350
+ end
351
+
352
+ # Creates a new IPPacket object.
353
+ def initialize(args={})
354
+ @eth_header = EthHeader.new(args).read(args[:eth])
355
+ @ip_header = IPHeader.new(args).read(args[:ip])
356
+ @eth_header.body=@ip_header
357
+
358
+ @headers = [@eth_header, @ip_header]
359
+ super
360
+ end
361
+
362
+ # Peek provides summary data on packet contents.
363
+ def peek_format
364
+ peek_data = ["I "]
365
+ peek_data << "%-5d" % to_s.size
366
+ peek_data << "%-21s" % "#{ip_saddr}"
367
+ peek_data << "->"
368
+ peek_data << "%21s" % "#{ip_daddr}"
369
+ peek_data << "%23s" % "I:"
370
+ peek_data << "%04x" % ip_id.to_i
371
+ peek_data.join
372
+ end
373
+
374
+ end
375
+
376
+ end
377
+
378
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby