packetgen-plugin-smb 0.3.0 → 0.6.2
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/.github/workflows/specs.yml +28 -0
- data/.rubocop.yml +8 -1
- data/Gemfile +15 -3
- data/README.md +59 -3
- data/Rakefile +10 -4
- data/examples/llmnr-responder +110 -0
- data/examples/smb-responder +233 -0
- data/lib/packetgen-plugin-smb.rb +5 -2
- data/lib/packetgen/plugin/gssapi.rb +11 -6
- data/lib/packetgen/plugin/llmnr.rb +58 -0
- data/lib/packetgen/plugin/netbios.rb +19 -0
- data/lib/packetgen/plugin/netbios/datagram.rb +108 -0
- data/lib/packetgen/plugin/netbios/name.rb +64 -0
- data/lib/packetgen/plugin/netbios/session.rb +72 -0
- data/lib/packetgen/plugin/ntlm.rb +211 -0
- data/lib/packetgen/plugin/ntlm/authenticate.rb +197 -0
- data/lib/packetgen/plugin/ntlm/av_pair.rb +115 -0
- data/lib/packetgen/plugin/ntlm/challenge.rb +140 -0
- data/lib/packetgen/plugin/ntlm/negotiate.rb +127 -0
- data/lib/packetgen/plugin/ntlm/ntlmv2_response.rb +59 -0
- data/lib/packetgen/plugin/smb.rb +27 -15
- data/lib/packetgen/plugin/smb/blocks.rb +2 -4
- data/lib/packetgen/plugin/smb/browser.rb +8 -8
- data/lib/packetgen/plugin/smb/browser/domain_announcement.rb +2 -7
- data/lib/packetgen/plugin/smb/browser/host_announcement.rb +10 -7
- data/lib/packetgen/plugin/smb/browser/local_master_announcement.rb +2 -7
- data/lib/packetgen/plugin/smb/close.rb +2 -2
- data/lib/packetgen/plugin/smb/close/request.rb +3 -3
- data/lib/packetgen/plugin/smb/close/response.rb +3 -3
- data/lib/packetgen/plugin/smb/filetime.rb +30 -3
- data/lib/packetgen/plugin/smb/negotiate.rb +20 -0
- data/lib/packetgen/plugin/smb/negotiate/dialect.rb +39 -0
- data/lib/packetgen/plugin/smb/negotiate/request.rb +35 -0
- data/lib/packetgen/plugin/smb/negotiate/response.rb +29 -0
- data/lib/packetgen/plugin/smb/nt_create_and_x.rb +2 -2
- data/lib/packetgen/plugin/smb/ntcreateandx/request.rb +5 -5
- data/lib/packetgen/plugin/smb/ntcreateandx/response.rb +3 -3
- data/lib/packetgen/plugin/smb/string.rb +60 -23
- data/lib/packetgen/plugin/smb/trans.rb +2 -2
- data/lib/packetgen/plugin/smb/trans/request.rb +4 -4
- data/lib/packetgen/plugin/smb/trans/response.rb +3 -3
- data/lib/packetgen/plugin/smb2.rb +20 -9
- data/lib/packetgen/plugin/smb2/base.rb +5 -7
- data/lib/packetgen/plugin/smb2/error.rb +3 -4
- data/lib/packetgen/plugin/smb2/guid.rb +6 -4
- data/lib/packetgen/plugin/smb2/negotiate.rb +2 -2
- data/lib/packetgen/plugin/smb2/negotiate/context.rb +28 -27
- data/lib/packetgen/plugin/smb2/negotiate/request.rb +16 -12
- data/lib/packetgen/plugin/smb2/negotiate/response.rb +25 -14
- data/lib/packetgen/plugin/smb2/session_setup.rb +2 -2
- data/lib/packetgen/plugin/smb2/session_setup/request.rb +12 -7
- data/lib/packetgen/plugin/smb2/session_setup/response.rb +13 -8
- data/lib/packetgen/plugin/smb_version.rb +3 -1
- data/packetgen-plugin-smb.gemspec +10 -15
- metadata +28 -81
- data/.travis.yml +0 -12
data/lib/packetgen-plugin-smb.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file is part of packetgen-plugin-smb.
|
2
4
|
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
3
5
|
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
4
6
|
# This program is published under MIT license.
|
5
7
|
|
6
|
-
# frozen_string_literal: true
|
7
|
-
|
8
8
|
require 'packetgen'
|
9
9
|
require_relative 'packetgen/plugin/smb_version'
|
10
10
|
require_relative 'packetgen/plugin/gssapi'
|
11
|
+
require_relative 'packetgen/plugin/netbios'
|
11
12
|
require_relative 'packetgen/plugin/smb'
|
12
13
|
require_relative 'packetgen/plugin/smb2'
|
14
|
+
require_relative 'packetgen/plugin/llmnr'
|
15
|
+
require_relative 'packetgen/plugin/ntlm'
|
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file is part of packetgen-plugin-smb.
|
2
4
|
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
3
5
|
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
4
6
|
# This program is published under MIT license.
|
5
7
|
|
6
|
-
# frozen_string_literal: true
|
7
|
-
|
8
8
|
require 'rasn1'
|
9
9
|
|
10
10
|
module PacketGen::Plugin
|
@@ -75,7 +75,7 @@ module PacketGen::Plugin
|
|
75
75
|
content: [sequence_of(:mech_types, RASN1::Types::ObjectId, explicit: 0, class: :context),
|
76
76
|
bit_string(:req_flags, explicit: 1, class: :context, constructed: true, optional: true),
|
77
77
|
octet_string(:mech_token, explicit: 2, class: :context, constructed: true, optional: true),
|
78
|
-
|
78
|
+
any(:mech_list_mic, explicit: 3, class: :context, constructed: true, optional: true)]
|
79
79
|
end
|
80
80
|
|
81
81
|
# GSS API Negotiation Token Response
|
@@ -86,7 +86,7 @@ module PacketGen::Plugin
|
|
86
86
|
'accept-incomplete' => 1,
|
87
87
|
'reject' => 2,
|
88
88
|
'request-mic' => 3
|
89
|
-
}
|
89
|
+
}.freeze
|
90
90
|
sequence :token, explicit: 1, class: :context, constructed: true,
|
91
91
|
content: [enumerated(:negstate, enum: NEG_STATES, explicit: 0, class: :context, constructed: true, optional: true),
|
92
92
|
objectid(:supported_mech, explicit: 1, class: :context, constructed: true, optional: true),
|
@@ -101,17 +101,20 @@ module PacketGen::Plugin
|
|
101
101
|
model(:token_resp, NegTokenResp)]
|
102
102
|
|
103
103
|
# @param [Hash] args
|
104
|
-
# @
|
104
|
+
# @option args [Symbol] :token +:init+ or +:response+ to force selection of
|
105
105
|
# token CHOICE.
|
106
106
|
def initialize(args={})
|
107
107
|
token = args.delete(:token)
|
108
108
|
super
|
109
|
-
self[:gssapi].chosen =
|
109
|
+
self[:gssapi].chosen = token == :init ? 0 : 1
|
110
110
|
end
|
111
|
+
|
111
112
|
# Populate Object from +str+
|
112
113
|
# @param [String] str
|
113
114
|
# @return [self]
|
114
115
|
def read(str)
|
116
|
+
return self if str.nil?
|
117
|
+
|
115
118
|
parse!(str, ber: true)
|
116
119
|
self
|
117
120
|
end
|
@@ -121,5 +124,7 @@ module PacketGen::Plugin
|
|
121
124
|
def sz
|
122
125
|
to_der.size
|
123
126
|
end
|
127
|
+
|
128
|
+
alias to_s to_der
|
124
129
|
end
|
125
130
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of packetgen-plugin-smb.
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Link-Local Multicast Name Resolution (LLMNR) header ({https://tools.ietf.org/html/rfc4795 RFC 4795}).
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
class LLMNR < PacketGen::Header::DNS
|
12
|
+
# UDP port number
|
13
|
+
UDP_PORT = 5355
|
14
|
+
# MAC address used with IPv4 multicast addresses
|
15
|
+
MAC_IPV4_MCAST = '01:00:5e:00:00:fc'
|
16
|
+
|
17
|
+
# @api private
|
18
|
+
# @note This method is used internally by PacketGen and should not be
|
19
|
+
# directly called
|
20
|
+
def added_to_packet(packet)
|
21
|
+
packet.instance_eval <<-END_OF_DEFINITION
|
22
|
+
def llmnrize(**kwargs)
|
23
|
+
llmnr = headers.find { |hdr| hdr.is_a? PacketGen::Plugin::LLMNR }
|
24
|
+
llmnr.llmnrize(**kwargs)
|
25
|
+
end
|
26
|
+
END_OF_DEFINITION
|
27
|
+
end
|
28
|
+
|
29
|
+
# Fixup IP header according to RFC 4795:
|
30
|
+
# * optionally set destination address,
|
31
|
+
# * set TTL to 1 if destination is a mcast address,
|
32
|
+
# * set MAC destination address to {MAC_IPV4_MCAST} if destination address is a mcast one.
|
33
|
+
# This method may be called as:
|
34
|
+
# # first way
|
35
|
+
# pkt.llmnr.llmnrize
|
36
|
+
# # second way
|
37
|
+
# pkt.llmnrize
|
38
|
+
# @param [String,nil] dst destination address. May be a dotted IP
|
39
|
+
# address (by example '224.0.0.252').
|
40
|
+
# @return [void]
|
41
|
+
def llmnrize(dst: nil)
|
42
|
+
ip = ip_header(self)
|
43
|
+
ip.dst = dst unless dst.nil?
|
44
|
+
ip.ttl = 1 if ip[:dst].mcast?
|
45
|
+
|
46
|
+
# rubocop:disable Lint/SuppressedException
|
47
|
+
begin
|
48
|
+
llh = ll_header(self)
|
49
|
+
llh.dst = MAC_IPV4_MCAST if ip[:dst].mcast?
|
50
|
+
rescue PacketGen::FormatError
|
51
|
+
end
|
52
|
+
# rubocop:enable Lint/SuppressedException
|
53
|
+
end
|
54
|
+
end
|
55
|
+
PacketGen::Header.add_class LLMNR
|
56
|
+
PacketGen::Header::UDP.bind LLMNR, sport: LLMNR::UDP_PORT
|
57
|
+
PacketGen::Header::UDP.bind LLMNR, dport: LLMNR::UDP_PORT
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
# frozen_string_literal: true
|
9
|
+
|
10
|
+
module PacketGen::Plugin
|
11
|
+
# Module to group all NetBIOS headers
|
12
|
+
# @author Sylvain Daubert
|
13
|
+
module NetBIOS
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative 'netbios/name'
|
18
|
+
require_relative 'netbios/session'
|
19
|
+
require_relative 'netbios/datagram'
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Datagram Service messages.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Datagram < PacketGen::Header::Base
|
15
|
+
# Give protocol name
|
16
|
+
# @return [String]
|
17
|
+
def self.protocol_name
|
18
|
+
'NetBIOS::Datagram'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Port number for NetBIOS Session Service over TCP
|
22
|
+
UDP_PORT = 138
|
23
|
+
|
24
|
+
# Datagram packet types
|
25
|
+
TYPES = {
|
26
|
+
'direct_unique' => 0x10,
|
27
|
+
'direct_group' => 0x11,
|
28
|
+
'broadcast' => 0x12,
|
29
|
+
'error' => 0x13,
|
30
|
+
'query_request' => 0x14,
|
31
|
+
'positive_query_resp' => 0x15,
|
32
|
+
'negative_query_resp' => 0x16,
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
# @!attribute type
|
36
|
+
# 8-bit session packet type
|
37
|
+
# @return [Integer]
|
38
|
+
define_field :type, PacketGen::Types::Int8Enum, enum: TYPES
|
39
|
+
# @!attribute flags
|
40
|
+
# 8-bit flags
|
41
|
+
# @return [Integer]
|
42
|
+
define_field :flags, PacketGen::Types::Int8
|
43
|
+
# @!attribute dgm_id
|
44
|
+
# 16-bit next transaction ID for datagrams
|
45
|
+
# @return [Integer]
|
46
|
+
define_field :dgm_id, PacketGen::Types::Int16
|
47
|
+
# @!attribute src_ip
|
48
|
+
# Source IP address
|
49
|
+
# @return [IP::Addr]
|
50
|
+
define_field :src_ip, PacketGen::Header::IP::Addr
|
51
|
+
# @!attribute src_port
|
52
|
+
# Source port
|
53
|
+
# @return [IP::Addr]
|
54
|
+
define_field :src_port, PacketGen::Types::Int16
|
55
|
+
# @!attribute dgm_length
|
56
|
+
# Length of data + second level of encoded names. Not present in error datagram.
|
57
|
+
# @return [Integer]
|
58
|
+
define_field :dgm_length, PacketGen::Types::Int16, optional: ->(h) { h.type != 0x13 }
|
59
|
+
# @!attribute packet_offset
|
60
|
+
# Not present in error datagram.
|
61
|
+
# @return [Integer]
|
62
|
+
define_field :packet_offset, PacketGen::Types::Int16, optional: ->(h) { h.type != 0x13 }
|
63
|
+
# @!attribute error_code
|
64
|
+
# Error code. Only present in error datagrams.
|
65
|
+
# @return [Integer]
|
66
|
+
define_field :error_code, PacketGen::Types::Int16, optional: ->(h) { h.type == 0x13 }
|
67
|
+
# @!attribute src_name
|
68
|
+
# NetBIOS source name. Only present in direct_unique, direct_group and broadcast datagrams.
|
69
|
+
# @return []
|
70
|
+
define_field :src_name, Name, default: '', optional: ->(h) { (h.type >= 0x10) && (h.type <= 0x12) }
|
71
|
+
# @!attribute dst_name
|
72
|
+
# NetBIOS destination name. Present in all but error datagrams.
|
73
|
+
# @return []
|
74
|
+
define_field :dst_name, Name, default: '', optional: ->(h) { h.type != 0x13 }
|
75
|
+
# @!attribute body
|
76
|
+
# User data. Ony present in direct_unique, direct_group and broadcast datagrams.
|
77
|
+
# @return [String]
|
78
|
+
define_field :body, PacketGen::Types::String, optional: ->(h) { (h.type >= 0x10) && (h.type <= 0x12) }
|
79
|
+
|
80
|
+
# @!attribute :rsv
|
81
|
+
# 4-bit rsv field. 4 upper bits of {#flags}
|
82
|
+
# @return [Integer]
|
83
|
+
# @!attribute :snt
|
84
|
+
# 2-bit SNT (Source end-Node Type) field from {#flags}.
|
85
|
+
# @return [Integer]
|
86
|
+
# @!attribute f
|
87
|
+
# First packet flag. If set then this is first
|
88
|
+
# (and possibly only) fragment of NetBIOS datagram.
|
89
|
+
# @return [Boolean]
|
90
|
+
# @!attribute m
|
91
|
+
# More flag. If set then more NetBIOS datagram
|
92
|
+
# fragments follow.
|
93
|
+
# @return [Boolean]
|
94
|
+
define_bit_fields_on :flags, :rsv, 4, :snt, 2, :f, :m
|
95
|
+
|
96
|
+
# Compute and set {#dgm_length} field
|
97
|
+
# @return [Integer] calculated length
|
98
|
+
def calc_length
|
99
|
+
length = self[:body].sz
|
100
|
+
length += self[:src_name].sz if present?(:src_name)
|
101
|
+
length += self[:dst_name].sz if present?(:dst_name)
|
102
|
+
self.dgm_length = length
|
103
|
+
end
|
104
|
+
end
|
105
|
+
PacketGen::Header.add_class Datagram
|
106
|
+
PacketGen::Header::UDP.bind Datagram, dport: Datagram::UDP_PORT, sport: Datagram::UDP_PORT
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Name.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Name < PacketGen::Header::DNS::Name
|
15
|
+
# Size, in bytes, of an encoded NetBIOS name
|
16
|
+
ENCODED_NAME_SIZE = 32
|
17
|
+
|
18
|
+
# Read a NetBIOS name from a string
|
19
|
+
# @param [String] str
|
20
|
+
# @return [Name] self
|
21
|
+
def from_human(str)
|
22
|
+
clear
|
23
|
+
return self if str.nil?
|
24
|
+
|
25
|
+
encoded_name = encode_name(str)
|
26
|
+
super(encoded_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get a human readable string
|
30
|
+
# @return [String]
|
31
|
+
def to_human
|
32
|
+
encoded_name = super
|
33
|
+
decode_name(encoded_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def encode_name(name)
|
39
|
+
basename, *scope_id = name.split('.')
|
40
|
+
basename = '' if basename.nil?
|
41
|
+
scope_id = scope_id.join('.')
|
42
|
+
encoded_name = +''
|
43
|
+
basename.each_byte do |byte|
|
44
|
+
a = (byte >> 4) + 0x41
|
45
|
+
b = (byte & 0xf) + 0x41
|
46
|
+
encoded_name << [a, b].pack('C2')
|
47
|
+
end
|
48
|
+
encoded_name << 'CA' * ((ENCODED_NAME_SIZE - encoded_name.size) / 2) if encoded_name.size < ENCODED_NAME_SIZE
|
49
|
+
encoded_name << ".#{scope_id}" if scope_id
|
50
|
+
encoded_name
|
51
|
+
end
|
52
|
+
|
53
|
+
def decode_name(encoded_name)
|
54
|
+
name = +''
|
55
|
+
encoded_name.partition('.').first.scan(/../).map do |duo|
|
56
|
+
a = (duo[0].ord - 0x41) & 0xf
|
57
|
+
b = (duo[1].ord - 0x41) & 0xf
|
58
|
+
name << (a << 4 | b).chr
|
59
|
+
end
|
60
|
+
name.strip
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of PacketGen
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Module to group all NetBIOS headers
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
module NetBIOS
|
12
|
+
# NetBIOS Session Service messages.
|
13
|
+
# @author Sylvain Daubert
|
14
|
+
class Session < PacketGen::Header::Base
|
15
|
+
# Give protocol name
|
16
|
+
# @return [String]
|
17
|
+
def self.protocol_name
|
18
|
+
'NetBIOS::Session'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Port number for NetBIOS Session Service over TCP
|
22
|
+
TCP_PORT = 139
|
23
|
+
# Port number for NetBIOS Session Service over TCP (mainly used yb {SMB2})
|
24
|
+
TCP_PORT2 = 445
|
25
|
+
|
26
|
+
# Session packet types
|
27
|
+
TYPES = {
|
28
|
+
'message' => 0,
|
29
|
+
'request' => 0x81,
|
30
|
+
'positive_response' => 0x82,
|
31
|
+
'negative_response' => 0x83,
|
32
|
+
'retarget_response' => 0x84,
|
33
|
+
'keep_alive' => 0x85,
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
# @!attribute type
|
37
|
+
# 8-bit session packet type
|
38
|
+
# @return [Integer]
|
39
|
+
define_field :type, PacketGen::Types::Int8Enum, enum: TYPES
|
40
|
+
# @!attribute length
|
41
|
+
# 17-bit session packet length
|
42
|
+
# @return [Integer]
|
43
|
+
define_field :length, PacketGen::Types::Int24
|
44
|
+
# @!attribute body
|
45
|
+
# @return [String]
|
46
|
+
define_field :body, PacketGen::Types::String
|
47
|
+
|
48
|
+
# Compute and set {#length} field
|
49
|
+
# @return [Integer] calculated length
|
50
|
+
def calc_length
|
51
|
+
PacketGen::Header::Base.calculate_and_set_length(self, header_in_size: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
# @note This method is used internally by PacketGen and should not be
|
56
|
+
# directly called
|
57
|
+
# @since 2.7.0 Set TCP sport according to bindings, only if sport is 0.
|
58
|
+
# Needed by new bind API.
|
59
|
+
def added_to_packet(packet)
|
60
|
+
return unless packet.is? 'TCP'
|
61
|
+
return unless packet.tcp.sport.zero?
|
62
|
+
|
63
|
+
packet.tcp.sport = TCP_PORT
|
64
|
+
end
|
65
|
+
end
|
66
|
+
PacketGen::Header.add_class Session
|
67
|
+
PacketGen::Header::TCP.bind Session, dport: Session::TCP_PORT
|
68
|
+
PacketGen::Header::TCP.bind Session, sport: Session::TCP_PORT
|
69
|
+
PacketGen::Header::TCP.bind Session, dport: Session::TCP_PORT2
|
70
|
+
PacketGen::Header::TCP.bind Session, sport: Session::TCP_PORT2
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file is part of packetgen-plugin-smb.
|
4
|
+
# See https://github.com/sdaubert/packetgen-plugin-smb for more informations
|
5
|
+
# Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
|
6
|
+
# This program is published under MIT license.
|
7
|
+
|
8
|
+
module PacketGen::Plugin
|
9
|
+
# Base class for NTLM authentication protocol.
|
10
|
+
# @author Sylvain Daubert
|
11
|
+
class NTLM < PacketGen::Types::Fields
|
12
|
+
# NTLM message types
|
13
|
+
TYPES = {
|
14
|
+
'negotiate' => 1,
|
15
|
+
'challenge' => 2,
|
16
|
+
'authenticate' => 3
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# NTLM signature
|
20
|
+
SIGNATURE = "NTLMSSP\0"
|
21
|
+
|
22
|
+
# void version
|
23
|
+
VOID_VERSION = [0].pack('q').freeze
|
24
|
+
VOID_CHALLENGE = VOID_VERSION
|
25
|
+
|
26
|
+
# @!attribute signature
|
27
|
+
# 8-byte NTLM signature
|
28
|
+
# @return [String]
|
29
|
+
define_field :signature, PacketGen::Types::String, static_length: 8, default: SIGNATURE
|
30
|
+
# @!attribute type
|
31
|
+
# 4-byte message type
|
32
|
+
# @return [Integer]
|
33
|
+
define_field :type, PacketGen::Types::Int32leEnum, enum: TYPES
|
34
|
+
# @!attribute payload
|
35
|
+
# @return [String]
|
36
|
+
define_field :payload, PacketGen::Types::String
|
37
|
+
|
38
|
+
class <<self
|
39
|
+
# @api private
|
40
|
+
# Return fields defined in payload one.
|
41
|
+
# @return [Hash]
|
42
|
+
attr_accessor :payload_fields
|
43
|
+
|
44
|
+
# Create a NTLM object from a binary string
|
45
|
+
# @param [String] str
|
46
|
+
# @return [NTLM]
|
47
|
+
def read(str)
|
48
|
+
ntlm = self.new.read(str)
|
49
|
+
type = TYPES.key(ntlm.type)
|
50
|
+
return ntlm if type.nil?
|
51
|
+
|
52
|
+
klass = NTLM.const_get(type.capitalize)
|
53
|
+
klass.new.read(str)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Define a flags field.
|
57
|
+
# @return [void]
|
58
|
+
def define_negotiate_flags
|
59
|
+
define_field_before :payload, :flags, PacketGen::Types::Int32le
|
60
|
+
define_bit_fields_on :flags, :flags_w, :flags_v, :flags_u, :flags_r13, 3,
|
61
|
+
:flags_t, :flags_r4, :flags_s, :flags_r,
|
62
|
+
:flags_r5, :flags_q, :flags_p, :flags_r6,
|
63
|
+
:flags_o, :flags_n, :flags_m, :flags_r7,
|
64
|
+
:flags_l, :flags_k, :flags_j, :flags_r8,
|
65
|
+
:flags_h, :flags_r9, :flags_g, :flags_f,
|
66
|
+
:flags_e, :flags_d, :flags_r10, :flags_c,
|
67
|
+
:flags_b, :flags_a
|
68
|
+
alias_method :nego56?, :flags_w?
|
69
|
+
alias_method :key_exch?, :flags_v?
|
70
|
+
alias_method :nego128?, :flags_u?
|
71
|
+
alias_method :version?, :flags_t?
|
72
|
+
alias_method :target_info?, :flags_s?
|
73
|
+
alias_method :non_nt_session_key?, :flags_r?
|
74
|
+
alias_method :identify?, :flags_q?
|
75
|
+
alias_method :ext_session_security?, :flags_p?
|
76
|
+
alias_method :target_type_server?, :flags_o?
|
77
|
+
alias_method :target_type_domain?, :flags_n?
|
78
|
+
alias_method :always_sign?, :flags_m?
|
79
|
+
alias_method :oem_workstation_supplied?, :flags_l?
|
80
|
+
alias_method :oem_domain_supplied?, :flags_k?
|
81
|
+
alias_method :anonymous?, :flags_j?
|
82
|
+
alias_method :ntlm?, :flags_h?
|
83
|
+
alias_method :lm_key?, :flags_g?
|
84
|
+
alias_method :datagram?, :flags_f?
|
85
|
+
alias_method :seal?, :flags_e?
|
86
|
+
alias_method :sign?, :flags_d?
|
87
|
+
alias_method :request_target?, :flags_c?
|
88
|
+
alias_method :oem?, :flags_b?
|
89
|
+
alias_method :unicode?, :flags_a?
|
90
|
+
alias_method :old_flags_a=, :flags_a=
|
91
|
+
alias_method :old_flags=, :flags=
|
92
|
+
|
93
|
+
class_eval do
|
94
|
+
def flags_a=(value)
|
95
|
+
self.old_flags_a = value
|
96
|
+
self.class.payload_fields.each do |name, _|
|
97
|
+
attr = send(name)
|
98
|
+
attr.unicode = value if attr.respond_to?(:unicode=)
|
99
|
+
end
|
100
|
+
|
101
|
+
value
|
102
|
+
end
|
103
|
+
|
104
|
+
def flags=(value)
|
105
|
+
self.old_flags = value
|
106
|
+
self.flags_a = value & 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Define a field in payload. Also add +name_len+, +name_maxlen+ and
|
112
|
+
# +name_offset+ fields.
|
113
|
+
# @param [Symbol] name name of field.
|
114
|
+
# @param [Class,nil] type type of +name+ field.
|
115
|
+
# @param [Hash] options type's options needed at build time
|
116
|
+
# @return [void]
|
117
|
+
def define_in_payload(name, type=SMB::String, options={})
|
118
|
+
@payload_fields ||= {}
|
119
|
+
@payload_fields[name] = [type, options]
|
120
|
+
|
121
|
+
define_field_before :payload, :"#{name}_len", PacketGen::Types::Int16le
|
122
|
+
define_field_before :payload, :"#{name}_maxlen", PacketGen::Types::Int16le
|
123
|
+
define_field_before :payload, :"#{name}_offset", PacketGen::Types::Int32le
|
124
|
+
|
125
|
+
attr_accessor name
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @abstract This method is meaningful for {NTLM} subclasses only.
|
130
|
+
def initialize(options={})
|
131
|
+
super
|
132
|
+
return if self.class.payload_fields.nil?
|
133
|
+
|
134
|
+
self.class.payload_fields.each do |name, type_and_opt|
|
135
|
+
type, options = type_and_opt
|
136
|
+
content = if type.new.respond_to?(:unicode?)
|
137
|
+
type.new(options.merge(unicode: unicode?))
|
138
|
+
else
|
139
|
+
type.new(options)
|
140
|
+
end
|
141
|
+
send(:"#{name}=", content)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
146
|
+
# Populate object from a binary string
|
147
|
+
# @param [String] str
|
148
|
+
# @return [self]
|
149
|
+
def read(str)
|
150
|
+
super
|
151
|
+
return self if self.class.payload_fields.nil?
|
152
|
+
|
153
|
+
self.class.payload_fields.each do |name, type_and_opt|
|
154
|
+
type, options = type_and_opt
|
155
|
+
offset_in_payload = send(:"#{name}_offset") - offset_of(:payload)
|
156
|
+
length = send(:"#{name}_len")
|
157
|
+
content = if type.new.respond_to?(:unicode?)
|
158
|
+
type.new(options.merge(unicode: unicode?))
|
159
|
+
else
|
160
|
+
type.new(options)
|
161
|
+
end
|
162
|
+
content.read(payload[offset_in_payload, length]) if length.positive?
|
163
|
+
send(:"#{name}=", content)
|
164
|
+
end
|
165
|
+
|
166
|
+
self
|
167
|
+
end
|
168
|
+
|
169
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
170
|
+
# Calculate and set +len+, +maxlen+ and +offset+ fields defined for
|
171
|
+
# fields in {#payload}.
|
172
|
+
# @return [void]
|
173
|
+
def calc_length
|
174
|
+
return self if self.class.payload_fields.nil?
|
175
|
+
|
176
|
+
previous_len = 0
|
177
|
+
self.class.payload_fields.each do |name, _type_and_opt|
|
178
|
+
send(:"#{name}_len=", 0)
|
179
|
+
send(:"#{name}_offset=", offset_of(:payload) + previous_len)
|
180
|
+
|
181
|
+
field = send(name)
|
182
|
+
next unless field && !field.empty?
|
183
|
+
|
184
|
+
length = field.respond_to?(:sz) ? field.sz : field.size
|
185
|
+
send(:"#{name}_len=", length)
|
186
|
+
send(:"#{name}_maxlen=", length)
|
187
|
+
previous_len = length
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# @abstract This class is meaningful for {NTLM} subclasses only.
|
192
|
+
# @return [String]
|
193
|
+
def to_s
|
194
|
+
s = super
|
195
|
+
return s if self.class.payload_fields.nil?
|
196
|
+
|
197
|
+
self.class.payload_fields.each do |name, _type_and_opt|
|
198
|
+
attr = send(name)
|
199
|
+
attr.unicode = unicode? if attr.respond_to?(:unicode=)
|
200
|
+
s << attr.to_s unless attr.nil? || send("#{name}_len").zero?
|
201
|
+
end
|
202
|
+
s
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
require_relative 'ntlm/av_pair'
|
208
|
+
require_relative 'ntlm/ntlmv2_response'
|
209
|
+
require_relative 'ntlm/negotiate'
|
210
|
+
require_relative 'ntlm/challenge'
|
211
|
+
require_relative 'ntlm/authenticate'
|