Class: RubySMB::Client

Inherits:
Object
  • Object
show all
Includes:
Authentication, Negotiation, Signing, TreeConnect
Defined in:
lib/ruby_smb/client.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, 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 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)


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

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

#dispatcher ⇒ RubySMB::Dispatcher::Socket



27
28
29
# File 'lib/ruby_smb/client.rb', line 27

def dispatcher
  @dispatcher
end

#domain ⇒ String

Returns:

  • (String)


32
33
34
# File 'lib/ruby_smb/client.rb', line 32

def domain
  @domain
end

#local_workstation ⇒ String

Returns:

  • (String)


37
38
39
# File 'lib/ruby_smb/client.rb', line 37

def local_workstation
  @local_workstation
end

#ntlm_client ⇒ String

Returns:

  • (String)


42
43
44
# File 'lib/ruby_smb/client.rb', line 42

def ntlm_client
  @ntlm_client
end

#password ⇒ String

Returns:

  • (String)


47
48
49
# File 'lib/ruby_smb/client.rb', line 47

def password
  @password
end

#sequence_counter ⇒ Integer

Returns:

  • (Integer)


54
55
56
# File 'lib/ruby_smb/client.rb', line 54

def sequence_counter
  @sequence_counter
end

#session_id ⇒ Integer

Returns:

  • (Integer)


59
60
61
# File 'lib/ruby_smb/client.rb', line 59

def session_id
  @session_id
end

#signing_enabled ⇒ Boolean

Returns:

  • (Boolean)


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

attr_accessor :signing_required

#signing_required ⇒ Object

Whether or not the Server requires signing



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

def signing_required
  @signing_required
end

#smb1 ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
# File 'lib/ruby_smb/client.rb', line 69

def smb1
  @smb1
end

#smb2 ⇒ Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/ruby_smb/client.rb', line 74

def smb2
  @smb2
end

#smb2_message_id ⇒ Integer

Returns:

  • (Integer)


79
80
81
# File 'lib/ruby_smb/client.rb', line 79

def smb2_message_id
  @smb2_message_id
end

#user_id ⇒ String

Returns:

  • (String)


89
90
91
# File 'lib/ruby_smb/client.rb', line 89

def user_id
  @user_id
end

#username ⇒ String

Returns:

  • (String)


84
85
86
# File 'lib/ruby_smb/client.rb', line 84

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.



125
126
127
128
# File 'lib/ruby_smb/client.rb', line 125

def disconnect!
  logoff!
  dispatcher.tcp_socket.close
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:



136
137
138
139
140
141
142
# File 'lib/ruby_smb/client.rb', line 136

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.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ruby_smb/client.rb', line 146

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



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

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



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/ruby_smb/client.rb', line 185

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:



214
215
216
217
218
219
220
# File 'lib/ruby_smb/client.rb', line 214

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.



227
228
229
230
231
232
233
# File 'lib/ruby_smb/client.rb', line 227

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