Class: RubySMB::Client

Inherits:
Object
  • Object
show all
Includes:
Authentication, Echo, Negotiation, Signing, TreeConnect
Defined in:
lib/ruby_smb/client.rb,
lib/ruby_smb/client/echo.rb,
lib/ruby_smb/client/signing.rb,
lib/ruby_smb/client/negotiation.rb,
lib/ruby_smb/client/tree_connect.rb,
lib/ruby_smb/client/authentication.rb

Overview

Represents an SMB client capable of talking to SMB1 or SMB2 servers and handling all end-user client functionality.

Defined Under Namespace

Modules: Authentication, Echo, Negotiation, Signing, TreeConnect

Constant Summary collapse

SMB1_DIALECT_SMB1_DEFAULT =

The Default SMB1 Dialect string used in an SMB1 Negotiate Request

"NT LM 0.12"
SMB1_DIALECT_SMB2_DEFAULT =

The Default SMB2 Dialect string used in an SMB1 Negotiate Request

"SMB 2.002"
SMB2_DIALECT_DEFAULT =

Dialect value for SMB2 Default (Version 2.02)

0x0202

Instance Attribute Summary collapse

Attributes included from Signing

#session_key

Instance Method Summary collapse

Methods included from Echo

#smb1_echo, #smb2_echo

Methods included from TreeConnect

#smb1_tree_connect, #smb1_tree_from_response, #smb2_tree_connect, #smb2_tree_from_response

Methods included from Signing

#smb1_sign, #smb2_sign

Methods included from Authentication

#authenticate, #smb1_authenticate, #smb1_ntlmssp_auth_packet, #smb1_ntlmssp_authenticate, #smb1_ntlmssp_challenge_packet, #smb1_ntlmssp_final_packet, #smb1_ntlmssp_negotiate, #smb1_ntlmssp_negotiate_packet, #smb1_type2_message, #smb2_authenticate, #smb2_ntlmssp_auth_packet, #smb2_ntlmssp_authenticate, #smb2_ntlmssp_challenge_packet, #smb2_ntlmssp_final_packet, #smb2_ntlmssp_negotiate, #smb2_ntlmssp_negotiate_packet, #smb2_type2_message

Methods included from Negotiation

#negotiate, #negotiate_request, #negotiate_response, #parse_negotiate_response, #smb1_negotiate_request, #smb2_negotiate_request

Constructor Details

#initialize(dispatcher, smb1: true, smb2: true, username:, password:, domain: '.', local_workstation: 'WORKSTATION') ⇒ Client

Returns a new instance of Client.

Parameters:

  • dispatcher (RubySMB::Dispacther::Socket)

    the packet dispatcher to use

  • smb1 (Boolean) (defaults to: true)

    whether or not to enable SMB1 support

  • smb2 (Boolean) (defaults to: true)

    whether or not to enable SMB2 support

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ruby_smb/client.rb', line 96

def initialize(dispatcher, smb1: true, smb2: true, username:,password:, domain:'.', local_workstation:'WORKSTATION')
  raise ArgumentError, 'No Dispatcher provided' unless dispatcher.kind_of? RubySMB::Dispatcher::Base
  if smb1 == false && smb2 == false
    raise ArgumentError, 'You must enable at least one Protocol'
  end
  @dispatcher        = dispatcher
  @domain            = domain
  @local_workstation = local_workstation
  @password          = password.encode("utf-8")
  @sequence_counter  = 0
  @session_id        = 0x00
  @session_key       = ''
  @signing_required  = false
  @smb1              = smb1
  @smb2              = smb2
  @username          = username.encode("utf-8")

  @ntlm_client = Net::NTLM::Client.new(
    @username,
    @password,
    workstation: @local_workstation,
    domain: @domain
  )

  @smb2_message_id = 0
end

Instance Attribute Details

#dispatcherRubySMB::Dispatcher::Socket



29
30
31
# File 'lib/ruby_smb/client.rb', line 29

def dispatcher
  @dispatcher
end

#domainString

Returns:

  • (String)


34
35
36
# File 'lib/ruby_smb/client.rb', line 34

def domain
  @domain
end

#local_workstationString

Returns:

  • (String)


39
40
41
# File 'lib/ruby_smb/client.rb', line 39

def local_workstation
  @local_workstation
end

#ntlm_clientString

Returns:

  • (String)


44
45
46
# File 'lib/ruby_smb/client.rb', line 44

def ntlm_client
  @ntlm_client
end

#passwordString

Returns:

  • (String)


49
50
51
# File 'lib/ruby_smb/client.rb', line 49

def password
  @password
end

#sequence_counterInteger

Returns:

  • (Integer)


56
57
58
# File 'lib/ruby_smb/client.rb', line 56

def sequence_counter
  @sequence_counter
end

#session_idInteger

Returns:

  • (Integer)


61
62
63
# File 'lib/ruby_smb/client.rb', line 61

def session_id
  @session_id
end

#signing_enabledBoolean

Returns:

  • (Boolean)


66
# File 'lib/ruby_smb/client.rb', line 66

attr_accessor :signing_required

#signing_requiredObject

Whether or not the Server requires signing



66
67
68
# File 'lib/ruby_smb/client.rb', line 66

def signing_required
  @signing_required
end

#smb1Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/ruby_smb/client.rb', line 71

def smb1
  @smb1
end

#smb2Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/ruby_smb/client.rb', line 76

def smb2
  @smb2
end

#smb2_message_idInteger

Returns:

  • (Integer)


81
82
83
# File 'lib/ruby_smb/client.rb', line 81

def smb2_message_id
  @smb2_message_id
end

#user_idString

Returns:

  • (String)


91
92
93
# File 'lib/ruby_smb/client.rb', line 91

def user_id
  @user_id
end

#usernameString

Returns:

  • (String)


86
87
88
# File 'lib/ruby_smb/client.rb', line 86

def username
  @username
end

Instance Method Details

#disconnect!void

This method returns an undefined value.

Logs off any currently open session on the server and closes the TCP socket connection.



127
128
129
130
131
132
133
134
# File 'lib/ruby_smb/client.rb', line 127

def disconnect!
  begin
    logoff!
  rescue
    wipe_state!
  end
  dispatcher.tcp_socket.close
end

#echo(count: 1, data: '') ⇒ WindowsError::ErrorCode

Sends an Echo request to the server and returns the NTStatus of the last response packet received.

Parameters:

  • echo (Integer)

    the number of times the server should echo (ignored in SMB2)

  • data (String) (defaults to: '')

    the data the server should echo back (ignored in SMB2)

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus of the last response received



142
143
144
145
146
147
148
149
# File 'lib/ruby_smb/client.rb', line 142

def echo(count: 1, data: '' )
  if smb2
    response = smb2_echo
  else
    response = smb1_echo(count:count, data:data)
  end
  response.status_code
end

#increment_smb_message_id(packet) ⇒ RubySMB::GenericPacket

Sets the message id field in an SMB2 packet's header to the one tracked by the client. It then increments the counter on the client.

Parameters:

Returns:



157
158
159
160
161
162
163
# File 'lib/ruby_smb/client.rb', line 157

def increment_smb_message_id(packet)
  if packet.smb2_header.message_id == 0 && self.smb2_message_id != 0
    packet.smb2_header.message_id = self.smb2_message_id
    self.smb2_message_id += 1
  end
  packet
end

#login(username: self.username, password: self.password, domain: self.domain, local_workstation: self.local_workstation) ⇒ Object

Performs protocol negotiation and session setup. It defaults to using the credentials supplied during initialization, but can take a new set of credentials if needed.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ruby_smb/client.rb', line 167

def (username: self.username, password: self.password, domain: self.domain, local_workstation: self.local_workstation )
  @domain            = domain
  @local_workstation = local_workstation
  @password          = password.encode("utf-8")
  @username          = username.encode("utf-8")

  @ntlm_client = Net::NTLM::Client.new(
    @username,
    @password,
    workstation: @local_workstation,
    domain: @domain
  )

  negotiate
  authenticate
end

#logoff!WindowsError::ErrorCode

Sends a LOGOFF command to the remote server to terminate the session

Returns:

  • (WindowsError::ErrorCode)

    the NTStatus of the response



187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/ruby_smb/client.rb', line 187

def logoff!
  if smb2
    request      = RubySMB::SMB2::Packet::LogoffRequest.new
    raw_response = send_recv(request)
    response     = RubySMB::SMB2::Packet::LogoffResponse.read(raw_response)
  else
    request      = RubySMB::SMB1::Packet::LogoffRequest.new
    raw_response = send_recv(request)
    response     = RubySMB::SMB1::Packet::LogoffResponse.read(raw_response)
  end
  wipe_state!
  response.status_code
end

#send_recv(packet) ⇒ String

Sends a packet and receives the raw response through the Dispatcher. It will also sign the packet if neccessary.

Parameters:

Returns:

  • (String)

    the raw response data received



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/ruby_smb/client.rb', line 206

def send_recv(packet)
  case packet.packet_smb_version
    when 'SMB1'
      if self.user_id
        packet.smb_header.uid = self.user_id
      end
      packet = smb1_sign(packet)
    when 'SMB2'
      packet = increment_smb_message_id(packet)
      packet.smb2_header.session_id = self.session_id
      unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
        packet = smb2_sign(packet)
      end
    else
      packet = packet
  end
  dispatcher.send_packet(packet)
  raw_response = dispatcher.recv_packet

  if self.signing_required && !self.session_key.empty?
    self.sequence_counter += 1
  end
  raw_response
end

#tree_connect(share) ⇒ RubySMB::SMB1::Tree, RubySMB::SMB2::Tree

Connects to the supplied share

Parameters:

  • share (String)

    the path to the share in \\server\share_name format

Returns:



236
237
238
239
240
241
242
# File 'lib/ruby_smb/client.rb', line 236

def tree_connect(share)
  if smb2
    smb2_tree_connect(share)
  else
    smb1_tree_connect(share)
  end
end

#wipe_state!void

This method returns an undefined value.

Resets all of the session state on the client, setting it back to scratch. Should only be called when a session is no longer valid.



249
250
251
252
253
254
255
# File 'lib/ruby_smb/client.rb', line 249

def wipe_state!
  self.session_id       = 0x00
  self.user_id          = 0x00
  self.session_key      = ''
  self.sequence_counter = 0
  self.smb2_message_id  = 0
end