packetgen 2.8.7 → 3.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +0 -1
- data/README.md +5 -4
- data/lib/packetgen.rb +6 -12
- data/lib/packetgen/capture.rb +43 -39
- data/lib/packetgen/config.rb +0 -1
- data/lib/packetgen/deprecation.rb +1 -1
- data/lib/packetgen/header.rb +9 -9
- data/lib/packetgen/header/asn1_base.rb +10 -10
- data/lib/packetgen/header/base.rb +42 -101
- data/lib/packetgen/header/dhcp/option.rb +5 -11
- data/lib/packetgen/header/dhcpv6/duid.rb +2 -0
- data/lib/packetgen/header/dhcpv6/option.rb +2 -19
- data/lib/packetgen/header/dhcpv6/options.rb +7 -0
- data/lib/packetgen/header/dns.rb +5 -23
- data/lib/packetgen/header/dns/name.rb +1 -0
- data/lib/packetgen/header/dns/qdsection.rb +1 -0
- data/lib/packetgen/header/dns/question.rb +3 -7
- data/lib/packetgen/header/dns/rr.rb +3 -0
- data/lib/packetgen/header/dns/rrsection.rb +1 -0
- data/lib/packetgen/header/dot11.rb +1 -17
- data/lib/packetgen/header/dot1x.rb +1 -0
- data/lib/packetgen/header/eap.rb +4 -7
- data/lib/packetgen/header/eth.rb +2 -0
- data/lib/packetgen/header/http/headers.rb +3 -0
- data/lib/packetgen/header/http/request.rb +5 -4
- data/lib/packetgen/header/http/response.rb +5 -4
- data/lib/packetgen/header/icmp.rb +6 -0
- data/lib/packetgen/header/icmpv6.rb +6 -0
- data/lib/packetgen/header/igmpv3/mq.rb +2 -0
- data/lib/packetgen/header/ip.rb +32 -30
- data/lib/packetgen/header/ip/addr.rb +1 -0
- data/lib/packetgen/header/ip/option.rb +23 -20
- data/lib/packetgen/header/ip/options.rb +11 -24
- data/lib/packetgen/header/ipv6.rb +45 -34
- data/lib/packetgen/header/ipv6/addr.rb +2 -0
- data/lib/packetgen/header/ipv6/hop_by_hop.rb +7 -31
- data/lib/packetgen/header/mdns.rb +1 -0
- data/lib/packetgen/header/mldv2/mlq.rb +2 -0
- data/lib/packetgen/header/ospfv2/lsa.rb +15 -25
- data/lib/packetgen/header/ospfv3/ipv6_prefix.rb +1 -1
- data/lib/packetgen/header/ospfv3/lsa.rb +8 -25
- data/lib/packetgen/header/snmp.rb +2 -0
- data/lib/packetgen/header/tcp.rb +23 -2
- data/lib/packetgen/header/tcp/option.rb +51 -52
- data/lib/packetgen/header/tcp/options.rb +17 -52
- data/lib/packetgen/header/tftp.rb +3 -0
- data/lib/packetgen/header/udp.rb +8 -0
- data/lib/packetgen/packet.rb +119 -102
- data/lib/packetgen/pcapng/block.rb +4 -10
- data/lib/packetgen/pcapng/epb.rb +4 -4
- data/lib/packetgen/pcapng/file.rb +7 -3
- data/lib/packetgen/pcapng/idb.rb +2 -2
- data/lib/packetgen/pcapng/shb.rb +3 -3
- data/lib/packetgen/pcapng/spb.rb +1 -8
- data/lib/packetgen/pcapng/unknown_block.rb +0 -7
- data/lib/packetgen/types.rb +1 -0
- data/lib/packetgen/types/array.rb +73 -71
- data/lib/packetgen/types/cstring.rb +1 -1
- data/lib/packetgen/types/enum.rb +3 -3
- data/lib/packetgen/types/fields.rb +66 -106
- data/lib/packetgen/types/int.rb +9 -5
- data/lib/packetgen/types/length_from.rb +45 -0
- data/lib/packetgen/types/oui.rb +2 -0
- data/lib/packetgen/types/string.rb +10 -16
- data/lib/packetgen/types/tlv.rb +7 -15
- data/lib/packetgen/utils.rb +8 -8
- data/lib/packetgen/utils/arp_spoofer.rb +1 -2
- data/lib/packetgen/version.rb +1 -1
- metadata +3 -21
- data/lib/packetgen/header/crypto.rb +0 -62
- data/lib/packetgen/header/esp.rb +0 -413
- data/lib/packetgen/header/ike.rb +0 -243
- data/lib/packetgen/header/ike/auth.rb +0 -165
- data/lib/packetgen/header/ike/cert.rb +0 -76
- data/lib/packetgen/header/ike/certreq.rb +0 -66
- data/lib/packetgen/header/ike/id.rb +0 -99
- data/lib/packetgen/header/ike/ke.rb +0 -79
- data/lib/packetgen/header/ike/nonce.rb +0 -40
- data/lib/packetgen/header/ike/notify.rb +0 -176
- data/lib/packetgen/header/ike/payload.rb +0 -315
- data/lib/packetgen/header/ike/sa.rb +0 -561
- data/lib/packetgen/header/ike/sk.rb +0 -261
- data/lib/packetgen/header/ike/ts.rb +0 -270
- data/lib/packetgen/header/ike/vendor_id.rb +0 -39
- data/lib/packetgen/header/netbios.rb +0 -20
- data/lib/packetgen/header/netbios/datagram.rb +0 -105
- data/lib/packetgen/header/netbios/name.rb +0 -67
- data/lib/packetgen/header/netbios/session.rb +0 -64
data/lib/packetgen/types/int.rb
CHANGED
@@ -38,23 +38,26 @@ module PacketGen
|
|
38
38
|
|
39
39
|
# @abstract
|
40
40
|
# Read an Int from a binary string or an integer
|
41
|
-
# @param [Integer,
|
41
|
+
# @param [Integer, #to_s] value
|
42
42
|
# @return [self]
|
43
|
+
# @raise [ParseError] when reading +#to_s+ objects with abstract Int class.
|
43
44
|
def read(value)
|
44
45
|
@value = if value.is_a?(Integer)
|
45
46
|
value.to_i
|
46
|
-
|
47
|
+
elsif defined? @packstr
|
47
48
|
value.to_s.unpack(@packstr[@endian]).first
|
49
|
+
else
|
50
|
+
raise ParseError, 'Int#read is abstract and cannot read'
|
48
51
|
end
|
49
52
|
self
|
50
53
|
end
|
51
54
|
|
52
55
|
# @abstract
|
53
56
|
# @return [::String]
|
54
|
-
# @raise [
|
57
|
+
# @raise [ParseError] This is an abstrat method and must be redefined
|
55
58
|
def to_s
|
56
59
|
unless defined? @packstr
|
57
|
-
raise
|
60
|
+
raise ParseError, 'PacketGen::Types::Int#to_s is an abstract method'
|
58
61
|
end
|
59
62
|
|
60
63
|
[to_i].pack(@packstr[@endian])
|
@@ -66,6 +69,7 @@ module PacketGen
|
|
66
69
|
@value || @default
|
67
70
|
end
|
68
71
|
alias to_human to_i
|
72
|
+
alias from_human value=
|
69
73
|
|
70
74
|
# Convert Int to Float
|
71
75
|
# @return [Float]
|
@@ -76,7 +80,7 @@ module PacketGen
|
|
76
80
|
# Give size in bytes of self
|
77
81
|
# @return [Integer]
|
78
82
|
def sz
|
79
|
-
|
83
|
+
width
|
80
84
|
end
|
81
85
|
end
|
82
86
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# This file is part of PacketGen
|
3
|
+
# See https://github.com/sdaubert/packetgen for more informations
|
4
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
5
|
+
# This program is published under MIT license.
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
module PacketGen
|
10
|
+
module Types
|
11
|
+
# This module is a mixin adding +length_from+ capacity to a type.
|
12
|
+
# +length_from+ capacity is the capacity, for a type, to gets its
|
13
|
+
# length from another object.
|
14
|
+
# @author Sylvain Daubert
|
15
|
+
# @since 3.0.0
|
16
|
+
module LengthFrom
|
17
|
+
# Initialize +length from+ capacity.
|
18
|
+
# Should be call by extensed object's initialize.
|
19
|
+
# @param [Hash] options
|
20
|
+
# @option options [Types::Int,Proc] :length_from object or proc from which
|
21
|
+
# takes length when reading
|
22
|
+
# @return [void]
|
23
|
+
def initialize_length_from(options)
|
24
|
+
@length_from = options[:length_from]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return a substring from +str+ of length given in another object.
|
28
|
+
# @param [#to_s] str
|
29
|
+
# @return [String]
|
30
|
+
def read_with_length_from(str)
|
31
|
+
s = PacketGen.force_binary(str.to_s)
|
32
|
+
str_end = case @length_from
|
33
|
+
when Types::Int
|
34
|
+
@length_from.to_i
|
35
|
+
when Proc
|
36
|
+
@length_from.call
|
37
|
+
else
|
38
|
+
s.size
|
39
|
+
end
|
40
|
+
str_end = 0 if str_end < 0
|
41
|
+
s[0, str_end]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/packetgen/types/oui.rb
CHANGED
@@ -29,8 +29,10 @@ module PacketGen
|
|
29
29
|
# @return [OUI] self
|
30
30
|
def from_human(str)
|
31
31
|
return self if str.nil?
|
32
|
+
|
32
33
|
bytes = str.split(/:/)
|
33
34
|
raise ArgumentError, 'not a OUI' unless bytes.size == 3
|
35
|
+
|
34
36
|
self[:b2].read(bytes[0].to_i(16))
|
35
37
|
self[:b1].read(bytes[1].to_i(16))
|
36
38
|
self[:b0].read(bytes[2].to_i(16))
|
@@ -12,39 +12,33 @@ module PacketGen
|
|
12
12
|
# to be compatible with others {Types}.
|
13
13
|
# @author Sylvain Daubert
|
14
14
|
class String < ::String
|
15
|
+
include LengthFrom
|
16
|
+
|
17
|
+
# @return [Integer]
|
18
|
+
attr_reader :static_length
|
19
|
+
|
15
20
|
# @param [Hash] options
|
16
21
|
# @option options [Types::Int,Proc] :length_from object or proc from which
|
17
22
|
# takes length when reading
|
18
23
|
# @option options [Integer] :static_length set a static length for this string
|
19
24
|
def initialize(options={})
|
20
25
|
super()
|
21
|
-
|
26
|
+
initialize_length_from(options)
|
22
27
|
@static_length = options[:static_length]
|
23
28
|
end
|
24
29
|
|
25
30
|
# @param [::String] str
|
26
31
|
# @return [String] self
|
27
32
|
def read(str)
|
28
|
-
s = str
|
29
|
-
|
30
|
-
|
31
|
-
@length_from.to_i
|
32
|
-
when Proc
|
33
|
-
@length_from.call
|
34
|
-
else
|
35
|
-
if @static_length.is_a? Integer
|
36
|
-
@static_length
|
37
|
-
else
|
38
|
-
s.size
|
39
|
-
end
|
40
|
-
end
|
41
|
-
str_end = 0 if str_end < 0
|
42
|
-
self.replace(s[0, str_end])
|
33
|
+
s = read_with_length_from(str)
|
34
|
+
s = s[0, static_length] if static_length
|
35
|
+
self.replace(s)
|
43
36
|
self
|
44
37
|
end
|
45
38
|
|
46
39
|
alias sz length
|
47
40
|
alias to_human to_s
|
41
|
+
alias from_human read
|
48
42
|
end
|
49
43
|
end
|
50
44
|
end
|
data/lib/packetgen/types/tlv.rb
CHANGED
@@ -75,6 +75,8 @@ module PacketGen
|
|
75
75
|
# @private
|
76
76
|
alias old_type= type=
|
77
77
|
|
78
|
+
undef type=, value=, value
|
79
|
+
|
78
80
|
# Set type
|
79
81
|
# @param [::String,Integer] val
|
80
82
|
# @return [Integer]
|
@@ -86,8 +88,10 @@ module PacketGen
|
|
86
88
|
self.old_type = val
|
87
89
|
else
|
88
90
|
raise TypeError, 'need an Integer' unless human_types?
|
91
|
+
|
89
92
|
new_val = self.class::TYPES.key(val.to_s)
|
90
93
|
raise ArgumentError, "unknown #{val} type" if new_val.nil?
|
94
|
+
|
91
95
|
self.old_type = new_val
|
92
96
|
end
|
93
97
|
end
|
@@ -96,27 +100,15 @@ module PacketGen
|
|
96
100
|
# @param [::String,Integer] val
|
97
101
|
# @return [::String,Integer]
|
98
102
|
def value=(val)
|
99
|
-
|
100
|
-
|
101
|
-
elsif self[:value].is_a? Types::Int
|
102
|
-
self[:value].value = val
|
103
|
-
else
|
104
|
-
self.length = val.length if val.is_a? ::String
|
105
|
-
self[:value].read val
|
106
|
-
end
|
103
|
+
self[:value].from_human val
|
104
|
+
self.length = self[:value].sz
|
107
105
|
val
|
108
106
|
end
|
109
107
|
|
110
108
|
# Get +value+
|
111
109
|
# @return [Object] depend on +value+ type
|
112
110
|
def value
|
113
|
-
|
114
|
-
self[:value].to_human
|
115
|
-
elsif self[:value].is_a? Types::Int
|
116
|
-
self[:value].to_i
|
117
|
-
else
|
118
|
-
self[:value]
|
119
|
-
end
|
111
|
+
self[:value].to_human
|
120
112
|
end
|
121
113
|
|
122
114
|
# Return human readable type, if TYPES is defined
|
data/lib/packetgen/utils.rb
CHANGED
@@ -42,6 +42,7 @@ module PacketGen
|
|
42
42
|
# @option options [Integer] :timeout timeout in seconds before stopping
|
43
43
|
# request. Default to 2.
|
44
44
|
# @return [String,nil]
|
45
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
45
46
|
def self.arp(ipaddr, options={})
|
46
47
|
unless options[:no_cache]
|
47
48
|
local_cache = self.arp_cache
|
@@ -65,6 +66,7 @@ module PacketGen
|
|
65
66
|
cap_thread.join
|
66
67
|
|
67
68
|
return if capture.packets.empty?
|
69
|
+
|
68
70
|
capture.packets.each do |pkt|
|
69
71
|
break pkt.arp.sha.to_s if pkt.arp.spa.to_s == ipaddr
|
70
72
|
end
|
@@ -85,6 +87,7 @@ module PacketGen
|
|
85
87
|
# @option options [String] :iface interface to use. Default to
|
86
88
|
# {PacketGen.default_iface}
|
87
89
|
# @return [void]
|
90
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
88
91
|
def self.arp_spoof(target_ip, spoofed_ip, options={})
|
89
92
|
interval = options[:interval] || 1.0
|
90
93
|
as = ARPSpoofer.new(timeout: options[:for_seconds], interval: interval,
|
@@ -119,6 +122,7 @@ module PacketGen
|
|
119
122
|
# pkt
|
120
123
|
# end
|
121
124
|
# @since 2.2.0
|
125
|
+
# @raise [RuntimeError] user don't have permission to capture packets on network device.
|
122
126
|
def self.mitm(target1, target2, options={})
|
123
127
|
options = { iface: PacketGen.default_iface }.merge(options)
|
124
128
|
|
@@ -144,14 +148,10 @@ module PacketGen
|
|
144
148
|
iph = modified_pkt.ip
|
145
149
|
l2 = modified_pkt.is?('Dot11') ? modified_pkt.dot11 : modified_pkt.eth
|
146
150
|
|
147
|
-
if (iph.
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
l2.dst = mac1
|
152
|
-
else
|
153
|
-
next
|
154
|
-
end
|
151
|
+
if (iph.src == target1) || (iph.dst == target2)
|
152
|
+
l2.dst = mac2
|
153
|
+
elsif (iph.src == target2) || (iph.dst == target1)
|
154
|
+
l2.dst = mac1
|
155
155
|
else
|
156
156
|
next
|
157
157
|
end
|
@@ -5,8 +5,6 @@
|
|
5
5
|
|
6
6
|
# frozen_string_literal: true
|
7
7
|
|
8
|
-
require 'thread'
|
9
|
-
|
10
8
|
module PacketGen
|
11
9
|
module Utils
|
12
10
|
# @note This class is provided for test purpose.
|
@@ -117,6 +115,7 @@ module PacketGen
|
|
117
115
|
# @return [Boolean,nil]
|
118
116
|
def active?(target_ip)
|
119
117
|
return unless @targets.key?(target_ip)
|
118
|
+
|
120
119
|
@targets[target_ip][:active]
|
121
120
|
end
|
122
121
|
|
data/lib/packetgen/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packetgen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sylvain Daubert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: interfacez
|
@@ -153,7 +153,6 @@ files:
|
|
153
153
|
- lib/packetgen/header/asn1_base.rb
|
154
154
|
- lib/packetgen/header/base.rb
|
155
155
|
- lib/packetgen/header/bootp.rb
|
156
|
-
- lib/packetgen/header/crypto.rb
|
157
156
|
- lib/packetgen/header/dhcp.rb
|
158
157
|
- lib/packetgen/header/dhcp/option.rb
|
159
158
|
- lib/packetgen/header/dhcp/options.rb
|
@@ -183,7 +182,6 @@ files:
|
|
183
182
|
- lib/packetgen/header/eap/md5.rb
|
184
183
|
- lib/packetgen/header/eap/tls.rb
|
185
184
|
- lib/packetgen/header/eap/ttls.rb
|
186
|
-
- lib/packetgen/header/esp.rb
|
187
185
|
- lib/packetgen/header/eth.rb
|
188
186
|
- lib/packetgen/header/gre.rb
|
189
187
|
- lib/packetgen/header/http.rb
|
@@ -198,19 +196,6 @@ files:
|
|
198
196
|
- lib/packetgen/header/igmpv3/group_record.rb
|
199
197
|
- lib/packetgen/header/igmpv3/mq.rb
|
200
198
|
- lib/packetgen/header/igmpv3/mr.rb
|
201
|
-
- lib/packetgen/header/ike.rb
|
202
|
-
- lib/packetgen/header/ike/auth.rb
|
203
|
-
- lib/packetgen/header/ike/cert.rb
|
204
|
-
- lib/packetgen/header/ike/certreq.rb
|
205
|
-
- lib/packetgen/header/ike/id.rb
|
206
|
-
- lib/packetgen/header/ike/ke.rb
|
207
|
-
- lib/packetgen/header/ike/nonce.rb
|
208
|
-
- lib/packetgen/header/ike/notify.rb
|
209
|
-
- lib/packetgen/header/ike/payload.rb
|
210
|
-
- lib/packetgen/header/ike/sa.rb
|
211
|
-
- lib/packetgen/header/ike/sk.rb
|
212
|
-
- lib/packetgen/header/ike/ts.rb
|
213
|
-
- lib/packetgen/header/ike/vendor_id.rb
|
214
199
|
- lib/packetgen/header/ip.rb
|
215
200
|
- lib/packetgen/header/ip/addr.rb
|
216
201
|
- lib/packetgen/header/ip/option.rb
|
@@ -226,10 +211,6 @@ files:
|
|
226
211
|
- lib/packetgen/header/mldv2/mcast_address_record.rb
|
227
212
|
- lib/packetgen/header/mldv2/mlq.rb
|
228
213
|
- lib/packetgen/header/mldv2/mlr.rb
|
229
|
-
- lib/packetgen/header/netbios.rb
|
230
|
-
- lib/packetgen/header/netbios/datagram.rb
|
231
|
-
- lib/packetgen/header/netbios/name.rb
|
232
|
-
- lib/packetgen/header/netbios/session.rb
|
233
214
|
- lib/packetgen/header/ospfv2.rb
|
234
215
|
- lib/packetgen/header/ospfv2/db_description.rb
|
235
216
|
- lib/packetgen/header/ospfv2/hello.rb
|
@@ -271,6 +252,7 @@ files:
|
|
271
252
|
- lib/packetgen/types/fields.rb
|
272
253
|
- lib/packetgen/types/int.rb
|
273
254
|
- lib/packetgen/types/int_string.rb
|
255
|
+
- lib/packetgen/types/length_from.rb
|
274
256
|
- lib/packetgen/types/oui.rb
|
275
257
|
- lib/packetgen/types/string.rb
|
276
258
|
- lib/packetgen/types/tlv.rb
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module PacketGen
|
4
|
-
module Header
|
5
|
-
# Mixin for cryptographic classes
|
6
|
-
# @api private
|
7
|
-
# @author Sylvain Daubert
|
8
|
-
module Crypto
|
9
|
-
# Cryptographic error
|
10
|
-
class Error < PacketGen::Error; end
|
11
|
-
|
12
|
-
# Register cryptographic modes
|
13
|
-
# @param [OpenSSL::Cipher] conf
|
14
|
-
# @param [OpenSSL::HMAC] intg
|
15
|
-
# @return [void]
|
16
|
-
def set_crypto(conf, intg)
|
17
|
-
@conf = conf
|
18
|
-
@intg = intg
|
19
|
-
return unless conf.authenticated?
|
20
|
-
# #auth_tag_len only supported from ruby 2.4.0
|
21
|
-
@conf.auth_tag_len = @trunc if @conf.respond_to? :auth_tag_len
|
22
|
-
end
|
23
|
-
|
24
|
-
# Get confidentiality mode name
|
25
|
-
# @return [String]
|
26
|
-
def confidentiality_mode
|
27
|
-
mode = @conf.name.match(/-([^-]*)$/)[1]
|
28
|
-
raise Error, 'unknown cipher mode' if mode.nil?
|
29
|
-
mode.downcase
|
30
|
-
end
|
31
|
-
|
32
|
-
# Say if crypto modes permit authentication
|
33
|
-
# @return [Boolean]
|
34
|
-
def authenticated?
|
35
|
-
@conf.authenticated? || !@intg.nil?
|
36
|
-
end
|
37
|
-
|
38
|
-
def authenticate!
|
39
|
-
@conf.final
|
40
|
-
if @intg
|
41
|
-
@intg.update @esn.to_s if @esn
|
42
|
-
@intg.digest[0, @icv_length] == @icv
|
43
|
-
else
|
44
|
-
true
|
45
|
-
end
|
46
|
-
rescue OpenSSL::Cipher::CipherError
|
47
|
-
false
|
48
|
-
end
|
49
|
-
|
50
|
-
def encipher(data)
|
51
|
-
enciphered_data = @conf.update(data)
|
52
|
-
@intg.update(enciphered_data) if @intg
|
53
|
-
enciphered_data
|
54
|
-
end
|
55
|
-
|
56
|
-
def decipher(data)
|
57
|
-
@intg.update(data) if @intg
|
58
|
-
@conf.update(data)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
data/lib/packetgen/header/esp.rb
DELETED
@@ -1,413 +0,0 @@
|
|
1
|
-
# This file is part of PacketGen
|
2
|
-
# See https://github.com/sdaubert/packetgen for more informations
|
3
|
-
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
4
|
-
# This program is published under MIT license.
|
5
|
-
|
6
|
-
# frozen_string_literal: true
|
7
|
-
|
8
|
-
module PacketGen
|
9
|
-
module Header
|
10
|
-
# A ESP header consists of:
|
11
|
-
# * a Security Parameters Index (#{spi}, {Types::Int32} type),
|
12
|
-
# * a Sequence Number ({#sn}, +Int32+ type),
|
13
|
-
# * a {#body} (variable length),
|
14
|
-
# * an optional TFC padding ({#tfc}, variable length),
|
15
|
-
# * an optional {#padding} (to align ESP on 32-bit boundary, variable length),
|
16
|
-
# * a {#pad_length} ({Types::Int8}),
|
17
|
-
# * a Next header field ({#next}, +Int8+),
|
18
|
-
# * and an optional Integrity Check Value ({#icv}, variable length).
|
19
|
-
#
|
20
|
-
# == Create an ESP header
|
21
|
-
# # standalone
|
22
|
-
# esp = PacketGen::Header::ESP.new
|
23
|
-
# # in a packet
|
24
|
-
# pkt = PacketGen.gen('IP').add('ESP')
|
25
|
-
# # access to ESP header
|
26
|
-
# pkt.esp # => PacketGen::Header::ESP
|
27
|
-
#
|
28
|
-
# == Examples
|
29
|
-
# === Create an enciphered UDP packet (ESP transport mode), using CBC mode
|
30
|
-
# icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
|
31
|
-
# add('ESP', spi: 0xff456e01, sn: 12345678).
|
32
|
-
# add('UDP', dport: 4567, sport: 45362, body 'abcdef')
|
33
|
-
# cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
34
|
-
# cipher.encrypt
|
35
|
-
# cipher.key = 16bytes_key
|
36
|
-
# iv = 16bytes_iv
|
37
|
-
# esp.esp.encrypt! cipher, iv
|
38
|
-
#
|
39
|
-
# === Create a ESP packet tunneling a UDP one, using GCM combined mode
|
40
|
-
# # create inner UDP packet
|
41
|
-
# icmp = PacketGen.gen('IP', src: '192.168.1.1', dst: '192.168.2.1').
|
42
|
-
# add('UDP', dport: 4567, sport: 45362, body 'abcdef')
|
43
|
-
#
|
44
|
-
# # create outer ESP packet
|
45
|
-
# esp = PacketGen.gen('IP', src '198.76.54.32', dst: '1.2.3.4').add('ESP')
|
46
|
-
# esp.esp.spi = 0x87654321
|
47
|
-
# esp.esp.sn = 0x123
|
48
|
-
# esp.esp.icv_length = 16
|
49
|
-
# # encapsulate ICMP packet in ESP one
|
50
|
-
# esp.encapsulate icmp
|
51
|
-
#
|
52
|
-
# # encrypt ESP payload
|
53
|
-
# cipher = OpenSSL::Cipher.new('aes-128-gcm')
|
54
|
-
# cipher.encrypt
|
55
|
-
# cipher.key = 16bytes_key
|
56
|
-
# iv = 8bytes_iv
|
57
|
-
# esp.esp.encrypt! cipher, iv, salt: 4bytes_gcm_salt
|
58
|
-
#
|
59
|
-
# === Decrypt a ESP packet using CBC mode and HMAC-SHA-256
|
60
|
-
# cipher = OpenSSL::Cipher.new('aes-128-cbc')
|
61
|
-
# cipher.decrypt
|
62
|
-
# cipher.key = 16bytes_key
|
63
|
-
#
|
64
|
-
# hmac = OpenSSL::HMAC.new(hmac_key, OpenSSL::Digest::SHA256.new)
|
65
|
-
#
|
66
|
-
# pkt.esp.decrypt! cipher, intmode: hmac # => true if ICV check OK
|
67
|
-
# @author Sylvain Daubert
|
68
|
-
# @since 1.2.0
|
69
|
-
class ESP < Base
|
70
|
-
include Crypto
|
71
|
-
|
72
|
-
# IP protocol number for ESP
|
73
|
-
IP_PROTOCOL = 50
|
74
|
-
|
75
|
-
# Well-known UDP port for ESP
|
76
|
-
UDP_PORT = 4500
|
77
|
-
|
78
|
-
# @!attribute spi
|
79
|
-
# 32-bit Security Parameter Index
|
80
|
-
# @return [Integer]
|
81
|
-
define_field :spi, Types::Int32
|
82
|
-
# @!attribute sn
|
83
|
-
# 32-bit Sequence Number
|
84
|
-
# @return [Integer]
|
85
|
-
define_field :sn, Types::Int32
|
86
|
-
# @!attribute body
|
87
|
-
# @return [Types::String,Header::Base]
|
88
|
-
define_field :body, Types::String
|
89
|
-
# @!attribute tfc
|
90
|
-
# Traffic Flow Confidentiality padding
|
91
|
-
# @return [Types::String,Header::Base]
|
92
|
-
define_field :tfc, Types::String
|
93
|
-
# @!attribute padding
|
94
|
-
# ESP padding
|
95
|
-
# @return [Types::String,Header::Base]
|
96
|
-
define_field :padding, Types::String
|
97
|
-
# @!attribute pad_length
|
98
|
-
# 8-bit padding length
|
99
|
-
# @return [Integer]
|
100
|
-
define_field :pad_length, Types::Int8
|
101
|
-
# @!attribute next
|
102
|
-
# 8-bit next protocol value
|
103
|
-
# @return [Integer]
|
104
|
-
define_field :next, Types::Int8
|
105
|
-
# @!attribute icv
|
106
|
-
# Integrity Check Value
|
107
|
-
# @return [Types::String,Header::Base]
|
108
|
-
define_field :icv, Types::String
|
109
|
-
|
110
|
-
# ICV (Integrity Check Value) length
|
111
|
-
# @return [Integer]
|
112
|
-
attr_accessor :icv_length
|
113
|
-
|
114
|
-
# @param [Hash] options
|
115
|
-
# @option options [Integer] :icv_length ICV length
|
116
|
-
# @option options [Integer] :spi Security Parameters Index
|
117
|
-
# @option options [Integer] :sn Sequence Number
|
118
|
-
# @option options [::String] :body ESP payload data
|
119
|
-
# @option options [::String] :tfc Traffic Flow Confidentiality, random padding
|
120
|
-
# up to MTU
|
121
|
-
# @option options [::String] :padding ESP padding to align ESP on 32-bit
|
122
|
-
# boundary
|
123
|
-
# @option options [Integer] :pad_length padding length
|
124
|
-
# @option options [Integer] :next Next Header field
|
125
|
-
# @option options [::String] :icv Integrity Check Value
|
126
|
-
def initialize(options={})
|
127
|
-
@icv_length = options[:icv_length] || 0
|
128
|
-
super
|
129
|
-
end
|
130
|
-
|
131
|
-
# Read a ESP packet from string.
|
132
|
-
#
|
133
|
-
# {#padding} and {#tfc} are not set as they are enciphered (impossible
|
134
|
-
# to guess their respective size). {#pad_length} and {#next} are also
|
135
|
-
# enciphered.
|
136
|
-
# @param [String] str
|
137
|
-
# @return [self]
|
138
|
-
def read(str)
|
139
|
-
return self if str.nil?
|
140
|
-
force_binary str
|
141
|
-
self[:spi].read str[0, 4]
|
142
|
-
self[:sn].read str[4, 4]
|
143
|
-
self[:body].read str[8...-@icv_length - 2]
|
144
|
-
self[:tfc].read ''
|
145
|
-
self[:padding].read ''
|
146
|
-
self[:pad_length].read str[-@icv_length - 2, 1]
|
147
|
-
self[:next].read str[-@icv_length - 1, 1]
|
148
|
-
self[:icv].read str[-@icv_length, @icv_length] if @icv_length
|
149
|
-
self
|
150
|
-
end
|
151
|
-
|
152
|
-
# Encrypt in-place ESP payload and trailer.
|
153
|
-
#
|
154
|
-
# This method removes all data from +tfc+ and +padding+ fields, as their
|
155
|
-
# enciphered values are concatenated into +body+.
|
156
|
-
#
|
157
|
-
# It also removes headers under ESP from packet, as they are enciphered in
|
158
|
-
# ESP body, and then are no more accessible.
|
159
|
-
# @param [OpenSSL::Cipher] cipher keyed cipher.
|
160
|
-
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
161
|
-
# cipher to add integrity, use +:intmode+ option.
|
162
|
-
# @param [String] iv full IV for encryption
|
163
|
-
# * CTR and GCM modes: +iv+ is 8-bytes long.
|
164
|
-
# @param [Hash] options
|
165
|
-
# @option options [String] :salt salt value for CTR and GCM modes
|
166
|
-
# @option options [Boolean] :tfc
|
167
|
-
# @option options [Fixnum] :tfc_size ESP body size used for TFC
|
168
|
-
# (default 1444, max size for a tunneled IPv4/ESP packet).
|
169
|
-
# This is the maximum size for ESP packet (without IP header
|
170
|
-
# nor Eth one).
|
171
|
-
# @option options [Fixnum] :esn 32 high-orber bits of ESN
|
172
|
-
# @option options [Fixnum] :pad_length set a padding length
|
173
|
-
# @option options [String] :padding set a padding. No check with
|
174
|
-
# +:pad_length+ is made. If +:pad_length+ is not set, +:padding+
|
175
|
-
# length is shortened to correct padding length
|
176
|
-
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
177
|
-
# confidentiality-only cipher. Only HMAC are supported.
|
178
|
-
# @return [self]
|
179
|
-
def encrypt!(cipher, iv, options={})
|
180
|
-
opt = { salt: '', tfc_size: 1444 }.merge(options)
|
181
|
-
|
182
|
-
set_crypto cipher, opt[:intmode]
|
183
|
-
|
184
|
-
real_iv = force_binary(opt[:salt]) + force_binary(iv)
|
185
|
-
real_iv += [1].pack('N') if confidentiality_mode == 'ctr'
|
186
|
-
cipher.iv = real_iv
|
187
|
-
|
188
|
-
authenticate_esp_header_if_needed options, iv
|
189
|
-
|
190
|
-
case confidentiality_mode
|
191
|
-
when 'cbc'
|
192
|
-
cipher_len = self.body.sz + 2
|
193
|
-
self.pad_length = (16 - (cipher_len % 16)) % 16
|
194
|
-
else
|
195
|
-
mod4 = to_s.size % 4
|
196
|
-
self.pad_length = 4 - mod4 if mod4 > 0
|
197
|
-
end
|
198
|
-
|
199
|
-
if opt[:pad_length]
|
200
|
-
self.pad_length = opt[:pad_length]
|
201
|
-
padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
|
202
|
-
self[:padding].read padding
|
203
|
-
else
|
204
|
-
padding = force_binary(opt[:padding] || (1..self.pad_length).to_a.pack('C*'))
|
205
|
-
self[:padding].read padding[0...self.pad_length]
|
206
|
-
end
|
207
|
-
|
208
|
-
tfc = ''
|
209
|
-
if opt[:tfc]
|
210
|
-
tfc_size = opt[:tfc_size] - body.sz
|
211
|
-
if tfc_size > 0
|
212
|
-
tfc_size = case confidentiality_mode
|
213
|
-
when 'cbc'
|
214
|
-
(tfc_size / 16) * 16
|
215
|
-
else
|
216
|
-
(tfc_size / 4) * 4
|
217
|
-
end
|
218
|
-
tfc = force_binary("\0" * tfc_size)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
msg = self.body.to_s + tfc
|
223
|
-
msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
|
224
|
-
enc_msg = encipher(msg)
|
225
|
-
# as padding is used to pad for CBC mode, this is unused
|
226
|
-
cipher.final
|
227
|
-
|
228
|
-
self[:body] = Types::String.new.read(iv) << enc_msg[0..-3]
|
229
|
-
self[:pad_length].read enc_msg[-2]
|
230
|
-
self[:next].read enc_msg[-1]
|
231
|
-
|
232
|
-
# reset padding field as it has no sense in encrypted ESP
|
233
|
-
self[:padding].read ''
|
234
|
-
|
235
|
-
set_esp_icv_if_needed
|
236
|
-
|
237
|
-
# Remove enciphered headers from packet
|
238
|
-
id = header_id(self)
|
239
|
-
if id < packet.headers.size - 1
|
240
|
-
(packet.headers.size - 1).downto(id + 1) do |index|
|
241
|
-
packet.headers.delete_at index
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
self
|
246
|
-
end
|
247
|
-
|
248
|
-
# Decrypt in-place ESP payload and trailer.
|
249
|
-
# @param [OpenSSL::Cipher] cipher keyed cipher
|
250
|
-
# This cipher is confidentiality-only one, or AEAD one. To use a second
|
251
|
-
# cipher to add integrity, use +:intmode+ option.
|
252
|
-
# @param [Hash] options
|
253
|
-
# @option options [Boolean] :parse parse deciphered payload to retrieve
|
254
|
-
# headers (default: +true+)
|
255
|
-
# @option options [Fixnum] :icv_length ICV length for captured packets,
|
256
|
-
# or read from PCapNG files
|
257
|
-
# @option options [String] :salt salt value for CTR and GCM modes
|
258
|
-
# @option options [Fixnum] :esn 32 high-orber bits of ESN
|
259
|
-
# @option options [OpenSSL::HMAC] :intmode integrity mode to use with a
|
260
|
-
# confidentiality-only cipher. Only HMAC are supported.
|
261
|
-
# @return [Boolean] +true+ if ESP packet is authenticated
|
262
|
-
def decrypt!(cipher, options={})
|
263
|
-
opt = { salt: '', parse: true }.merge(options)
|
264
|
-
|
265
|
-
set_crypto cipher, opt[:intmode]
|
266
|
-
|
267
|
-
case confidentiality_mode
|
268
|
-
when 'gcm'
|
269
|
-
iv = self.body.slice!(0, 8)
|
270
|
-
real_iv = opt[:salt] + iv
|
271
|
-
when 'cbc'
|
272
|
-
cipher.padding = 0
|
273
|
-
real_iv = iv = self.body.slice!(0, 16)
|
274
|
-
when 'ctr'
|
275
|
-
iv = self.body.slice!(0, 8)
|
276
|
-
real_iv = opt[:salt] + iv + [1].pack('N')
|
277
|
-
else
|
278
|
-
real_iv = iv = self.body.slice!(0, 16)
|
279
|
-
end
|
280
|
-
cipher.iv = real_iv
|
281
|
-
|
282
|
-
if authenticated? && (@icv_length.zero? || opt[:icv_length])
|
283
|
-
raise ParseError, 'unknown ICV size' unless opt[:icv_length]
|
284
|
-
@icv_length = opt[:icv_length].to_i
|
285
|
-
# reread ESP to handle new ICV size
|
286
|
-
msg = self.body.to_s + self[:pad_length].to_s
|
287
|
-
msg += self[:next].to_s
|
288
|
-
self[:icv].read msg.slice!(-@icv_length, @icv_length)
|
289
|
-
self[:body].read msg[0..-3]
|
290
|
-
self[:pad_length].read msg[-2]
|
291
|
-
self[:next].read msg[-1]
|
292
|
-
end
|
293
|
-
|
294
|
-
authenticate_esp_header_if_needed options, iv, self[:icv]
|
295
|
-
private_decrypt opt
|
296
|
-
end
|
297
|
-
|
298
|
-
private
|
299
|
-
|
300
|
-
def get_auth_data(opt)
|
301
|
-
ad = self[:spi].to_s
|
302
|
-
if opt[:esn]
|
303
|
-
@esn = Types::Int32.new(opt[:esn])
|
304
|
-
ad << @esn.to_s if @conf.authenticated?
|
305
|
-
end
|
306
|
-
ad << self[:sn].to_s
|
307
|
-
end
|
308
|
-
|
309
|
-
def authenticate_esp_header_if_needed(opt, iv, icv=nil)
|
310
|
-
if @conf.authenticated?
|
311
|
-
@conf.auth_tag = icv if icv
|
312
|
-
@conf.auth_data = get_auth_data(opt)
|
313
|
-
elsif @intg
|
314
|
-
@intg.reset
|
315
|
-
@intg.update get_auth_data(opt)
|
316
|
-
@intg.update iv
|
317
|
-
@icv = icv
|
318
|
-
else
|
319
|
-
@icv = nil
|
320
|
-
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def set_esp_icv_if_needed
|
324
|
-
return unless authenticated?
|
325
|
-
if @conf.authenticated?
|
326
|
-
self[:icv].read @conf.auth_tag[0, @icv_length]
|
327
|
-
else
|
328
|
-
self[:icv].read @intg.digest[0, @icv_length]
|
329
|
-
end
|
330
|
-
end
|
331
|
-
|
332
|
-
def private_decrypt(options)
|
333
|
-
# decrypt
|
334
|
-
msg = self.body.to_s
|
335
|
-
msg += self[:padding].to_s + self[:pad_length].to_s + self[:next].to_s
|
336
|
-
plain_msg = decipher(msg)
|
337
|
-
|
338
|
-
# check authentication tag
|
339
|
-
if authenticated?
|
340
|
-
return false unless authenticate!
|
341
|
-
end
|
342
|
-
|
343
|
-
# Set ESP fields
|
344
|
-
self[:body].read plain_msg[0..-3]
|
345
|
-
self[:pad_length].read plain_msg[-2]
|
346
|
-
self[:next].read plain_msg[-1]
|
347
|
-
|
348
|
-
# Set padding
|
349
|
-
if self.pad_length > 0
|
350
|
-
len = self.pad_length
|
351
|
-
self[:padding].read self.body.slice!(-len, len)
|
352
|
-
end
|
353
|
-
|
354
|
-
# Set TFC padding
|
355
|
-
encap_length = 0
|
356
|
-
pkt = nil
|
357
|
-
case self.next
|
358
|
-
when 4 # IPv4
|
359
|
-
pkt = Packet.parse(body, first_header: 'IP')
|
360
|
-
encap_length = pkt.ip.length
|
361
|
-
when 41 # IPv6
|
362
|
-
pkt = Packet.parse(body, first_header: 'IPv6')
|
363
|
-
encap_length = pkt.ipv6.length + pkt.ipv6.sz
|
364
|
-
when ICMP::IP_PROTOCOL
|
365
|
-
pkt = Packet.parse(body, first_header: 'ICMP')
|
366
|
-
# no size field. cannot recover TFC padding
|
367
|
-
encap_length = body.sz
|
368
|
-
when UDP::IP_PROTOCOL
|
369
|
-
pkt = Packet.parse(body, first_header: 'UDP')
|
370
|
-
encap_length = pkt.udp.length
|
371
|
-
when TCP::IP_PROTOCOL
|
372
|
-
# No length in TCP header, so TFC may not be used.
|
373
|
-
# Or underlayer protocol should have a size information...
|
374
|
-
pkt = Packet.parse(body, first_header: 'TCP')
|
375
|
-
encap_length = pkt.sz
|
376
|
-
when ICMPv6::IP_PROTOCOL
|
377
|
-
pkt = Packet.parse(body, first_header: 'ICMPv6')
|
378
|
-
# no size field. cannot recover TFC padding
|
379
|
-
encap_length = body.sz
|
380
|
-
else
|
381
|
-
# Unmanaged encapsulated protocol
|
382
|
-
encap_length = body.sz
|
383
|
-
end
|
384
|
-
|
385
|
-
if encap_length < body.sz
|
386
|
-
tfc_len = body.sz - encap_length
|
387
|
-
self[:tfc].read self.body.slice!(encap_length, tfc_len)
|
388
|
-
end
|
389
|
-
|
390
|
-
if options[:parse]
|
391
|
-
packet.encapsulate pkt unless pkt.nil?
|
392
|
-
end
|
393
|
-
|
394
|
-
true
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
self.add_class ESP
|
399
|
-
|
400
|
-
IP.bind ESP, protocol: ESP::IP_PROTOCOL
|
401
|
-
IPv6.bind ESP, next: ESP::IP_PROTOCOL
|
402
|
-
UDP.bind ESP, procs: [->(f) { f.dport = f.sport = ESP::UDP_PORT },
|
403
|
-
->(f) { (f.dport == ESP::UDP_PORT ||
|
404
|
-
f.sport == ESP::UDP_PORT) &&
|
405
|
-
Types::Int32.new.read(f.body[0..3]).to_i > 0 }]
|
406
|
-
ESP.bind IP, next: 4
|
407
|
-
ESP.bind IPv6, next: 41
|
408
|
-
ESP.bind TCP, next: TCP::IP_PROTOCOL
|
409
|
-
ESP.bind UDP, next: TCP::IP_PROTOCOL
|
410
|
-
ESP.bind ICMP, next: ICMP::IP_PROTOCOL
|
411
|
-
ESP.bind ICMPv6, next: ICMPv6::IP_PROTOCOL
|
412
|
-
end
|
413
|
-
end
|